Reference

Rate limits

Per-account, per-endpoint token-bucket limits. 429 responses always include a Retry-After header. Production integrations rarely hit them; they exist to prevent buggy clients from hammering carriers.

Default limits

Limits are token-bucket: each account gets a refill rate + a burst capacity. Quoting and reading is more permissive than buying (carriers themselves rate-limit us on label purchases, so we have to throttle on your behalf).

  • Reads (GET endpoints)

    100 / sec

    Burst capacity: 200

  • Quotes (POST /labels/quote)

    20 / sec

    Burst capacity: 50

  • Buys (POST /labels/create)

    10 / sec

    Burst capacity: 20

  • Address verify

    20 / sec

    Burst capacity: 50

Need higher? Email support@shiponline.app with your expected traffic shape and we can lift the buy bucket for high-volume accounts.

429 response

When you exceed a bucket, the next request returns HTTP 429 Too Many Requests with two headers + a structured body:

HTTP
HTTP/1.1 429 Too Many Requests
Retry-After: 4
X-RateLimit-Reset: 1735233700
Content-Type: application/json

{
  "code": "rate_limited",
  "error": "You've exceeded the buys-per-second limit. Retry in 4 seconds.",
  "requestId": "req_HHH..."
}
  • Retry-After is the number of seconds to wait before retrying.
  • X-RateLimit-Resetis the absolute Unix timestamp when the bucket refills. Useful if you're aggregating across multiple clients and want to schedule globally.

Backoff guidance

Don't retry immediately. Use exponential backoff with jitter; the carriers themselves throttle us in waves, so a thundering-herd retry will turn one 429 into a flurry of them.

JavaScript
async function withRetry(fn, { maxAttempts = 5 } = {}) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const res = await fn();
    if (res.status !== 429) return res;

    const retryAfter = Number(res.headers.get("Retry-After")) || 1;
    const jitter = Math.random() * 0.4 + 0.8; // 0.8 - 1.2
    const waitMs = retryAfter * 1000 * jitter * 2 ** (attempt - 1);
    await new Promise((r) => setTimeout(r, waitMs));
  }
  throw new Error("Rate-limited after " + maxAttempts + " attempts");
}

Idempotency keys + 429 = safe

A 429 means we never started processing the request, so retrying with the same idempotency key is always safe. The request will land cleanly once the bucket refills.
Stuck or shipping production?

We read every developer email.

API questions, OAuth quirks, custom-flow integration help — email support@shiponline.app and a human replies, usually within a few hours.

  • No card to start
  • Labels in 60 seconds
  • Up to 70% off retail