npm package: @surajrasaili/shipengine
Description: A TypeScript-first Node.js client for the ShipEngine API with full type safety.
Author: Suraj Rasaili
Latest version: 1.0.1

# ShipEngine SDK for Node.js

A type-safe, production-ready Node.js SDK for the [ShipEngine API](https://www.shipengine.com/docs/) with a Stripe-like developer experience.

## Features

- 🔒 **Fully Type-Safe** - Built with TypeScript, all requests and responses are strictly typed
- 🎯 **Stripe-like DX** - Intuitive resource-based API (`shipengine.shipments.create()`)
- 🔄 **Automatic Retries** - Exponential backoff for rate limits (429) and server errors (5xx)
- 🔑 **Idempotency Support** - Prevent duplicate operations with idempotency keys
- ⚡ **Modern JavaScript** - Uses native `fetch`, no external HTTP dependencies
- 📝 **JSDoc Comments** - Full IntelliSense support in VS Code

## Installation

```bash
npm install @surajrasaili/shipengine
```

## Quick Start

```typescript
import { ShipEngine } from '@surajrasaili/shipengine';

const shipengine = new ShipEngine('your-api-key');

// Validate an address
const validated = await shipengine.addresses.validate([
  {
    name: 'John Doe',
    address_line1: '123 Main St',
    city_locality: 'Austin',
    state_province: 'TX',
    postal_code: '78701',
    country_code: 'US',
  },
]);

// Get shipping rates
const rates = await shipengine.rates.estimate({
  carrier_ids: ['se-123456'],
  from_postal_code: '78702',
  from_country_code: 'US',
  to_postal_code: '78701',
  to_country_code: 'US',
  weight: { value: 1, unit: 'pound' },
});

// Create a shipping label
const label = await shipengine.labels.create({
  shipment: {
    carrier_id: 'se-123456',
    service_code: 'usps_priority_mail',
    ship_to: {
      name: 'John Doe',
      address_line1: '123 Main St',
      city_locality: 'Austin',
      state_province: 'TX',
      postal_code: '78701',
      country_code: 'US',
    },
    ship_from: {
      name: 'Warehouse',
      address_line1: '456 Warehouse Ave',
      city_locality: 'Austin',
      state_province: 'TX',
      postal_code: '78702',
      country_code: 'US',
    },
    packages: [{ weight: { value: 1, unit: 'pound' } }],
  },
});

console.log(`Label created: ${label.label_id}`);
console.log(`Tracking: ${label.tracking_number}`);
```

## Configuration

```typescript
const shipengine = new ShipEngine('your-api-key', {
  baseUrl: 'https://api.shipengine.com', // API base URL (default)
  timeout: 30000,                         // Request timeout in ms (default: 30000)
  maxRetries: 3,                          // Max retry attempts (default: 3)
  retryDelay: 500,                        // Initial retry delay in ms (default: 500)
});
```

## Available Resources

| Resource | Description |
|----------|-------------|
| `account` | Account settings and images |
| `addresses` | Address validation and parsing |
| `batches` | Bulk label operations |
| `carriers` | Carrier management and connections |
| `documents` | Combined label documents |
| `insurance` | Shipsurance account management |
| `labels` | Shipping label creation and management |
| `manifests` | End-of-day manifests |
| `packages` | Custom package types |
| `pickups` | Carrier pickup scheduling |
| `rates` | Shipping rate calculation |
| `servicePoints` | PUDO locations |
| `shipments` | Shipment management |
| `tags` | Shipment tagging |
| `tracking` | Package tracking |
| `warehouses` | Warehouse/origin management |
| `webhooks` | Event webhooks |

## API Reference

### Addresses

```typescript
// Validate addresses
const results = await shipengine.addresses.validate([
  { name: 'John Doe', address_line1: '123 Main St', ... }
]);

// Parse address from text
const parsed = await shipengine.addresses.parse({
  text: 'Ship to John Doe at 123 Main St, Austin TX 78701',
});
```

### Shipments

```typescript
// List shipments
const shipments = await shipengine.shipments.list({
  page: 1,
  page_size: 25,
  shipment_status: 'pending',
});

// Create shipments
const created = await shipengine.shipments.create({
  shipments: [{ ... }],
});

// Get shipment by ID
const shipment = await shipengine.shipments.get('se-123456');

// Update shipment
await shipengine.shipments.update('se-123456', { ... });

// Cancel shipment
await shipengine.shipments.cancel('se-123456');

// Get shipment rates
const rates = await shipengine.shipments.listRates('se-123456');

// Add/remove tags
await shipengine.shipments.addTag('se-123456', 'priority');
await shipengine.shipments.removeTag('se-123456', 'priority');
```

### Labels

```typescript
// List labels
const labels = await shipengine.labels.list({
  label_status: 'completed',
  carrier_id: 'se-123456',
});

// Create label
const label = await shipengine.labels.create({
  shipment: { ... },
});

// Create label from rate
const label = await shipengine.labels.createFromRate('se-rate-123', {
  validate_address: 'validate_and_clean',
});

// Create label from shipment
const label = await shipengine.labels.createFromShipment('se-ship-123', {});

// Create return label
const returnLabel = await shipengine.labels.createReturnLabel('se-label-123', {});

// Void label
const voidResult = await shipengine.labels.void('se-label-123');

// Get tracking for label
const tracking = await shipengine.labels.getTracking('se-label-123');
```

### Rates

```typescript
// Calculate rates
const rates = await shipengine.rates.calculate({
  shipment: { ... },
  rate_options: { carrier_ids: ['se-123'] },
});

// Estimate rates (simplified)
const estimates = await shipengine.rates.estimate({
  carrier_ids: ['se-123'],
  from_postal_code: '78702',
  to_postal_code: '78701',
  from_country_code: 'US',
  to_country_code: 'US',
  weight: { value: 1, unit: 'pound' },
});

// Bulk rate comparison
const bulk = await shipengine.rates.compareBulk({
  shipments: [...],
  rate_options: {},
});

// Get rate by ID
const rate = await shipengine.rates.get('se-rate-123');
```

### Carriers

```typescript
// List carriers
const carriers = await shipengine.carriers.list();

// Get carrier by ID
const carrier = await shipengine.carriers.get('se-carrier-123');

// Get carrier services
const services = await shipengine.carriers.listServices('se-carrier-123');

// Get carrier package types
const packages = await shipengine.carriers.listPackageTypes('se-carrier-123');

// Add funds to carrier
const balance = await shipengine.carriers.addFunds('se-carrier-123', {
  amount: 100,
  currency: 'usd',
});

// Connect a carrier
const connected = await shipengine.carriers.connect('ups', {
  nickname: 'My UPS Account',
  account_number: '123456',
  ...
});
```

### Tracking

```typescript
// Get tracking info
const tracking = await shipengine.tracking.get({
  carrier_code: 'ups',
  tracking_number: '1Z999AA10123456784',
});

// Subscribe to tracking updates
await shipengine.tracking.startTracking({
  carrier_code: 'ups',
  tracking_number: '1Z999AA10123456784',
});

// Unsubscribe from tracking
await shipengine.tracking.stopTracking({
  carrier_code: 'ups',
  tracking_number: '1Z999AA10123456784',
});
```

### Warehouses

```typescript
// List warehouses
const warehouses = await shipengine.warehouses.list();

// Create warehouse
const warehouse = await shipengine.warehouses.create({
  name: 'East Coast Warehouse',
  origin_address: { ... },
});

// Update warehouse
await shipengine.warehouses.update('se-wh-123', { name: 'Updated Name' });

// Delete warehouse
await shipengine.warehouses.delete('se-wh-123');
```

### Webhooks

```typescript
// List webhooks
const webhooks = await shipengine.webhooks.list();

// Create webhook
const webhook = await shipengine.webhooks.create({
  url: 'https://example.com/webhook',
  event: 'track',
});

// Update webhook
await shipengine.webhooks.update('se-wh-123', {
  url: 'https://example.com/new-webhook',
});

// Delete webhook
await shipengine.webhooks.delete('se-wh-123');
```

## Idempotency

For POST/PUT requests, you can pass an idempotency key to prevent duplicate operations:

```typescript
const label = await shipengine.labels.create(
  { shipment: { ... } },
  { idempotencyKey: 'order-12345-label' }
);
```

## Error Handling

The SDK provides typed error classes for different error scenarios:

```typescript
import {
  ShipEngineError,
  ShipEngineApiError,
  ShipEngineAuthenticationError,
  ShipEngineInvalidRequestError,
  ShipEngineRateLimitError,
} from '@surajrasaili/shipengine';

try {
  await shipengine.labels.create({ ... });
} catch (error) {
  if (error instanceof ShipEngineAuthenticationError) {
    // 401 - Invalid API key
    console.error('Authentication failed:', error.message);
  } else if (error instanceof ShipEngineInvalidRequestError) {
    // 400/422 - Validation error
    console.error('Invalid request:', error.message);
    console.error('Field:', error.fieldName);
  } else if (error instanceof ShipEngineRateLimitError) {
    // 429 - Rate limited (auto-retried by default)
    console.error('Rate limited, retry after:', error.retryAfter);
  } else if (error instanceof ShipEngineApiError) {
    // Other API errors
    console.error('API error:', error.statusCode, error.message);
    console.error('Request ID:', error.requestId);
    console.error('Errors:', error.errors);
  } else if (error instanceof ShipEngineError) {
    // Network/timeout errors
    console.error('SDK error:', error.message);
  }
}
```

## Automatic Retries

The SDK automatically retries requests that fail due to:
- **429 Too Many Requests** - Uses `Retry-After` header or exponential backoff
- **5xx Server Errors** - Exponential backoff with jitter

Configure retry behavior:

```typescript
const shipengine = new ShipEngine('your-api-key', {
  maxRetries: 5,    // Max retry attempts (default: 3)
  retryDelay: 1000, // Initial delay in ms (default: 500)
});
```

Set `maxRetries: 0` to disable retries.

## TypeScript Support

All types are exported for use in your application:

```typescript
import type {
  components,
  operations,
  paths,
} from '@surajrasaili/shipengine';

// Use component schemas
type Address = components['schemas']['address'];
type Shipment = components['schemas']['shipment'];
type Label = components['schemas']['label'];

// Use operation types
type CreateShipmentBody = components['schemas']['create_shipments_request_body'];
type CreateShipmentResponse = components['schemas']['create_shipments_response_body'];
```

## Development

```bash
# Install dependencies
npm install

# Run tests
npm test

# Run tests in watch mode
npm run test:watch

# Type check
npm run build

# Regenerate types from OpenAPI spec
npm run generate:types
```

## License

ISC

## Links

- [ShipEngine API Documentation](https://www.shipengine.com/docs/)
- [ShipEngine Dashboard](https://app.shipengine.com/)
- [OpenAPI Specification](https://github.com/ShipEngine/shipengine-openapi)