Rate Limits

The ShipOS API implements rate limiting to ensure fair usage and maintain service reliability for all users. This guide explains how rate limits work, the different tiers available, and best practices for handling rate limit responses.

How Rate Limiting Works

  • Window Duration: Rate limits are calculated per 60-second window
  • Per API Key: Each API key has its own rate limit counter
  • Shared Across Endpoints: The rate limit applies to all API endpoints collectively
  • Tier-Based: Different rate limit tiers are available based on your needs

Rate Limit Tiers

ShipOS offers six rate limit tiers to accommodate different usage patterns:

TierRequests per MinuteRequests per Second
Tier 1300.5
Tier 2601
Tier 31202
Tier 42404
Tier 560010
Tier 61,20020

Your API key's rate limit tier determines how many requests you can make. Contact support to upgrade your tier if needed.

Rate Limit Headers

Every API response includes headers that provide information about your current rate limit status:

X-RateLimit-Limit: 600
X-RateLimit-Remaining: 543
X-RateLimit-Reset: 1706012400

Header Descriptions

HeaderDescription
X-RateLimit-LimitTotal number of requests allowed in the current window
X-RateLimit-RemainingNumber of requests remaining in the current window
X-RateLimit-ResetUnix timestamp (in seconds) when the rate limit window resets

Rate Limit Errors

When you exceed your rate limit, the API returns a 429 Too Many Requests status with an error response:

{
  "type": "urn:cybership:error:rate-limit",
  "title": "Rate Limit Exceeded",
  "status": 429,
  "detail": "Rate limit exceeded. You have made too many requests in a short period of time. (Reference this ID for support: 8f7e6d5c4b3a2918)",
  "limit": 600,
  "remaining": 0,
  "reset_at": "2024-01-23T10:20:00Z",
  "retry_after": 45
}

The response also includes a Retry-After header indicating how many seconds to wait before retrying:

Retry-After: 45

Best Practices

1. Monitor Rate Limit Headers

Always check the rate limit headers in responses to track your usage:

const response = await fetch(`${API_BASE}/shipos/orders`, {
  headers: {
    'X-Access-Token': API_KEY,
  },
});

const remaining = response.headers.get('X-RateLimit-Remaining');
const reset_at = response.headers.get('X-RateLimit-Reset');

console.log(`Requests remaining: ${remaining}`);
console.log(`Reset at: ${new Date(reset_at * 1000).toISOString()}`);

2. Implement Exponential Backoff

When you receive a 429 error, implement exponential backoff with jitter:

async function makeRequestWithRetry(url, options, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const response = await fetch(url, options);
    
    if (response.status !== 429) {
      return response;
    }
    
    const retryAfter = parseInt(response.headers.get('Retry-After')) || 60;
    const jitter = Math.random() * 1000; // 0-1 second jitter
    const delay = (retryAfter * 1000) + jitter;
    
    console.log(`Rate limited. Waiting ${delay}ms before retry ${i + 1}/${maxRetries}`);
    await new Promise(resolve => setTimeout(resolve, delay));
  }
  
  throw new Error('Max retries exceeded');
}

3. Spread Requests Over Time

Instead of bursting requests, spread them evenly:

// Bad: Burst of requests
for (const order of orders) {
  await processOrder(order); // May hit rate limit
}

// Good: Spread requests with delays
for (const order of orders) {
  await processOrder(order);
  await new Promise(resolve => setTimeout(resolve, 500)); // 2 req/sec for Tier 1
}

4. Use Webhooks for Real-Time Updates

Instead of polling endpoints repeatedly, use webhooks to receive real-time updates about order status changes, inventory updates, and other events.

5. Batch Operations

Where possible, use batch endpoints to reduce the number of API calls:

  • Use bulk order retrieval instead of fetching orders one by one
  • Create multiple packages in a single fulfillment session
  • Update multiple inventory items in one request

Handling Rate Limits in Production

Example Rate Limit Handler

class ShipOSApiClient {
  constructor(apiKey, baseUrl) {
    this.apiKey = apiKey;
    this.baseUrl = baseUrl;
    this.requestQueue = [];
    this.processing = false;
  }

  async request(method, path, body = null) {
    return new Promise((resolve, reject) => {
      this.requestQueue.push({ method, path, body, resolve, reject });
      this.processQueue();
    });
  }

  async processQueue() {
    if (this.processing || this.requestQueue.length === 0) {
      return;
    }

    this.processing = true;

    while (this.requestQueue.length > 0) {
      const { method, path, body, resolve, reject } = this.requestQueue.shift();

      try {
        const response = await fetch(`${this.baseUrl}${path}`, {
          method,
          headers: {
            'X-Access-Token': this.apiKey,
            'Content-Type': 'application/json',
          },
          body: body ? JSON.stringify(body) : null,
        });

        if (response.status === 429) {
          // Put the request back in the queue
          this.requestQueue.unshift({ method, path, body, resolve, reject });
          
          // Wait for rate limit reset
          const retryAfter = parseInt(response.headers.get('Retry-After')) || 60;
          console.log(`Rate limited. Pausing queue for ${retryAfter} seconds`);
          
          await new Promise(res => setTimeout(res, retryAfter * 1000));
          continue;
        }

        const data = await response.json();
        
        if (!response.ok) {
          reject(new Error(data.detail || 'API request failed'));
        } else {
          resolve(data);
        }

        // Respect rate limits by adding delays based on remaining quota
        const remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));
        const limit = parseInt(response.headers.get('X-RateLimit-Limit'));
        
        if (remaining < limit * 0.2) {
          // Slow down when approaching limit
          await new Promise(res => setTimeout(res, 1000));
        }
        
      } catch (error) {
        reject(error);
      }
    }

    this.processing = false;
  }
}

Frequently Asked Questions

Need Higher Rate Limits?

If your use case requires higher rate limits than Tier 6 provides, please contact support to discuss enterprise options. We can accommodate high-volume integrations with custom rate limiting solutions.