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" }
}
| Field | Always present? | Use |
|---|---|---|
message | yes | Display to humans only; do not parse. |
error_id | yes | Quote this when contacting support — it's the request's trace ID. |
error_code | yes | Machine-readable; safe to switch on. |
error_params | optional | Key-value context for client-side translation/UX. |
Standard error codes
error_code | HTTP | When |
|---|---|---|
bad_request | 400 | Malformed body or invalid parameter shape |
unauthorized | 401 | Missing, invalid, or expired bearer token |
forbidden | 403 | Valid token but the app isn't subscribed to this scope |
not_found | 404 | Resource doesn't exist (or you can't see it) |
conflict | 409 | State conflict (e.g. duplicate create) |
validation_error | 422 | Request fails server-side validation |
rate_limited | 429 | Too many requests; back off and retry |
internal_error | 500 | Unhandled server error |
upstream_error | 502 | A downstream provider returned something unexpected |
service_unavailable | 503 | Transient 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
429make your problem worse — they push you into longer back-off windows. Always jitter. - Don't reissue tokens on every request — cache for
expires_in - 60seconds. 404and403are intentionally indistinguishable for resources you can't access. Don't treat404as "doesn't exist anywhere."