The rules
- Every non-2xx response ships the standard error envelope.
codeis stable. Match oncode, never onerror(that string is for humans).- Every response carries
X-Request-Id. Log it on every error path so support can trace the call.
Pattern by status
400 (validation)
The request body failed schema validation. Theissues[] array
lists every offending path. Do not retry; fix and re-post.
401 (auth)
Missing or bad key, or the key was revoked. Do not retry. Rotate the secret in your config and alert the on-call team.402 (payment)
Two distinct causes, distinguished bycode:
TIER_DOWNGRADED: organization dropped below Growth. Surface a billing alert; the key is still valid once the org upgrades.QUOTA_EXCEEDED: monthly quota exhausted. Wait for the reset on the first of the next month or upgrade tier.
422 (semantic)
The request was well-formed but cannot be executed. Common cases:MISSING_HS_CODE: call/v1/classifyfirst, then re-post.UNSUPPORTED_DESTINATION: the country is outside coverage. Surface to a human.DOMESTIC_SHIPMENT: origin equals destination. Filter these out upstream.
429 (rate limit)
ReadRetry-After, sleep, retry once. If the retry also 429s, back
off exponentially. See Rate limits for the full
pattern. The SDKs implement this by default.
500 (internal)
Retry with exponential backoff. Two attempts total is sufficient. If both fail, log therequest_id and page support. Never retry more
than three times; the engine has already retried its own downstream
calls before returning 500.
A production-grade retry wrapper
Idempotency and retries
Every mutating endpoint (calc, calc/bulk, classify, compare)
accepts an Idempotency-Key header. Set it to a UUID v4 per logical
operation. Re-sending the same key within 24 hours returns the
original response, not a new calculation. This makes retries safe:
409 IDEMPOTENCY_KEY_REUSED.
Do not reuse keys across distinct requests.
Bulk partial-failure pattern
/v1/calc/bulk returns 200 even when some rows fail. Inspect
aggregate.error_rows and errors[]; do not treat 200 as blanket
success.
Timeouts
Recommend a 30-second client timeout on/v1/calc and /v1/classify,
60 seconds on /v1/calc/bulk and /v1/compare. The server enforces
its own upper bound, but you want the client to give up first so
your queue does not stall.