Errors & rate limits
Platform-wide conventions — code against the shapes, not the prose.
Error envelope
{ "error": "MACHINE_CODE", "message": "Human-readable explanation", …context }
Status codes you'll meet
| status | code | when |
| 401 | UNAUTHENTICATED / INVALID_JWT / TOKEN_EXPIRED | missing, invalid or expired bearer — refresh and retry once |
| 403 | FORBIDDEN / FIELD_ACCESS_DENIED / EMAIL_NOT_VERIFIED | RBAC, field ACLs, or an unverified account |
| 404 | NOT_FOUND | also returned for resources you may not see (no existence oracle — e.g. webhook hooks) |
| 409 | SLUG_TAKEN / ALREADY_INSTALLED | identity conflicts |
| 422 | CERTIFICATION_FAILED / validation errors | structured report or field list attached |
| 428 | PERMISSIONS_NOT_ACKNOWLEDGED / SANDBOX_FIRST | install-governance gates — satisfy the precondition and retry |
| 429 | BUDGET_EXCEEDED / rate limits | honor Retry-After; agents: the tenant's monthly token budget is spent |
| 502 | INSTALL_FAILED | a downstream provision failed — the response lists what was rolled back |
Rate limits
| surface | limit |
| Verification email resend | 3 / hour / account |
| Public form & hook intakes | per-IP throttles on the CRM side |
| Your connector's upstream | declare connection.sync.rateLimitPerMinute; always back off on 429 (use the SDK's rateLimitedFetch) |
Retry guidance
- Idempotency: workflow manual runs accept an
Idempotency-Key header; install/uninstall are conflict-guarded — safe to retry on network failure.
- Backoff: exponential with jitter, cap at 60s, respect
Retry-After when present.
- Don't retry: 4xx other than 408/429 — fix the request instead.