Errors & Rate Limits

All USBC APIs return a consistent error envelope and signal rate-limit pressure with standard headers. Handling both well is what separates a sturdy integration from a flaky one.

Error envelope

{
  "message":     "Human-readable description.",
  "error_id":    "trace-id-or-uuid",
  "error_code":  "not_found",
  "error_params": { "field": "chain" }
}
FieldAlways present?Use
messageyesDisplay to humans only; do not parse.
error_idyesQuote this when contacting support — it's the request's trace ID.
error_codeyesMachine-readable; safe to switch on.
error_paramsoptionalKey-value context for client-side translation/UX.

Standard error codes

error_codeHTTPWhen
bad_request400Malformed body or invalid parameter shape
unauthorized401Missing, invalid, or expired bearer token
forbidden403Valid token but the app isn't subscribed to this scope
not_found404Resource doesn't exist (or you can't see it)
conflict409State conflict (e.g. duplicate create)
validation_error422Request fails server-side validation
rate_limited429Too many requests; back off and retry
internal_error500Unhandled server error
upstream_error502A downstream provider returned something unexpected
service_unavailable503Transient outage — retry with exponential backoff

Rate limits

Sandbox today: 5 requests/second, 100 requests/minute, 1000 requests/hour per client IP. Hitting any of those returns 429 rate_limited and the response includes:

Retry-After: 12
X-RateLimit-Limit-Second: 5
X-RateLimit-Remaining-Second: 0
X-RateLimit-Reset-Second: 12

Handling 429

on 429:
    sleep(retry_after_seconds OR exp_backoff_with_jitter())
    retry()
on retry exhausted:
    surface error to caller; don't loop forever

Use the Retry-After header when present. If absent, use exponential backoff with jitter — never a tight retry loop.

Idempotency

Mutating endpoints (POST, PATCH, DELETE) accept an Idempotency-Key header. Send a unique UUID per logical operation, and replays return the original result instead of double-applying:

POST /v1/transactions HTTP/1.1
Idempotency-Key: 2a4f1d50-9b53-4e7c-8c4a-2bfa1d9b6d11

The key is honored for 24 hours.

Common pitfalls

Tip: Always log the error_id. It saves hours when triaging with the platform team.

  • Tight retry loops on 429 make your problem worse — they push you into longer back-off windows. Always jitter.
  • Don't reissue tokens on every request — cache for expires_in - 60 seconds.
  • 404 and 403 are intentionally indistinguishable for resources you can't access. Don't treat 404 as "doesn't exist anywhere."