Idempotency and limits

Retry POSTs safely and handle the rate limit.

Idempotency

Networks fail. To retry a POST without duplicating its effect, send Idempotency-Key with a unique value per operation (a UUID works; 1 to 255 characters):

KEY=$(uuidgen)

curl -X POST $BACKEND_URL/v1/sellers \
  -H "Authorization: Bearer mk_your_api_key" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $KEY" \
  -d '{"name":"Acme Store","externalId":"customer-123"}'

Run the same command again with the same KEY and body: you get the same response (same status and body) with the header:

Idempotency-Replayed: true

Rules:

  • Only POST /v1/* honors the header. PATCH and DELETE ignore it: repeating a DELETE already done responds 404.
  • A replay returns the stored status (e.g. 201).
  • A key longer than 255 characters responds 400 VALIDATION_ERROR.
  • The same key with a different body responds 409 CONFLICT (no details). Use a new key for a different operation.
  • The key is kept for 24 hours. After that, the same value counts as a new key.
  • The key is scoped per account: two API keys of the same account share idempotency keys. Another account can use the same value without a clash.
  • Only success responses are stored. If it failed, you can retry with the same key.
  • Don't send two requests with the same key at the same time. Wait for the first response.

The 409 for a different body:

{
  "error": {
    "code": "CONFLICT",
    "message": "Idempotency-Key already used with a different body."
  }
}

Rate limit

The default is 300 requests per minute, per API key (configurable). The counter belongs to the API key, summed across all /v1 routes. Every authenticated request to /v1 counts, including 4xx errors and idempotency replays. 401 does not count: authentication runs before the limit, and without a valid API key there is no counter to add to.

Authenticated /v1 responses that are not 429 carry (401 does not):

HeaderWhat it is
X-RateLimit-Limitwindow limit
X-RateLimit-Remainingrequests still allowed
X-RateLimit-Resetseconds until the window restarts (not a date)

When you exceed it, the response is 429, with the Retry-After header and this body:

HTTP/1.1 429 Too Many Requests
Retry-After: 30
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded. Try again after the time given in Retry-After."
  }
}

How to handle it:

  1. Stop sending with that API key and wait for Retry-After before the next attempt.
  2. Don't retry right away: it only uses up the next window.
  3. If you often get close to the limit, watch X-RateLimit-Remaining and slow down before you hit a 429.

When retrying a POST after a 429, resend the same Idempotency-Key.

Error format in Errors.