Errors & Rate Limits
Error envelope
All HTTP error responses use a single canonical JSON envelope:
{
"code": "ERROR_CODE",
"message": "Human-readable description safe to display to clients."
}
| Field | Type | Description |
|---|---|---|
code | string | Machine-readable error identifier (SCREAMING_SNAKE_CASE). Stable across releases — safe for client switch/case logic. |
message | string | Human-readable description. May change between releases; do not match on this field programmatically. |
Some validation failures additionally include a fields object mapping field
names to per-field messages.
Standard codes
| Code | HTTP status | Meaning |
|---|---|---|
BAD_REQUEST | 400 | Malformed request that cannot be understood. |
UNAUTHORIZED | 401 | Authentication is required or credentials are invalid. |
FORBIDDEN | 403 | Authenticated but not permitted to perform this action. |
NOT_FOUND | 404 | The requested resource does not exist. |
CONFLICT | 409 | The resource already exists or is in a conflicting state. |
VALIDATION_ERROR | 422 | Request body or parameters failed validation. |
INTERNAL_ERROR | 500 | An unexpected server-side error occurred. |
SERVICE_UNAVAILABLE | 503 | A downstream dependency is unavailable. |
Domain-specific codes
| Code | HTTP status | Meaning |
|---|---|---|
PAYMENT_REQUIRED | 402 | The plan quota for this billing period is exhausted. |
SEND_CAP_REACHED | 402 | Your own configured monthly send cap has been reached — raise the cap in Billing rather than upgrading the plan. |
TENANT_SUSPENDED | 403 | The workspace account has been suspended. |
TENANT_DELETED | 403 | The workspace account has been closed. |
FEATURE_NOT_AVAILABLE | 403 | The feature is not available on your current plan. |
READ_ONLY_TOKEN | 403 | The token is read-only and cannot authorize write operations. |
FILE_TOO_LARGE | 413 | An uploaded file exceeds the maximum allowed size. |
TEMPLATE_NOT_FOUND | 422 | The template_id referenced in the request does not exist in this workspace. |
QUOTA_EXCEEDED | 429 | The workspace has exceeded a plan quota. |
RATE_LIMITED | 429 | Too many attempts on a rate-limited surface (currently account registration). |
Rate limits
Per-workspace request limit
Requests authenticated with a dashboard session token are rate limited per workspace over a fixed one-minute window. The limit depends on your plan:
| Plan | Requests per minute |
|---|---|
| Free | 60 |
| Starter | 300 |
| Pro | 1,000 |
| Enterprise | 10,000 |
When the limit is exceeded, the API responds 429 with a Retry-After
header giving the number of seconds until the window resets. Retry-After
is the only rate-limit header — there are no X-RateLimit-* headers. Note
that this particular response uses a different body shape from the standard
envelope:
{ "error": "rate limit exceeded", "retry_after": 42 }
Requests authenticated with a client SDK key or server key are not counted against this per-minute limit; the event ingestion endpoint instead enforces per-tier payload caps, below.
Event ingestion caps
POST /v1/events applies fixed caps that depend on the credential tier:
Client (X-SDK-Key) | Server (Bearer hober_srv_…) | |
|---|---|---|
| Events per request | 50 | 1,000 |
| Request body | — | 500 KB (413 beyond) |
| Single event | — | 32 KB |
occurred_at backdating window | 48 hours | 90 days |
Cap violations return 422 with code VALIDATION_ERROR (oversized
server-tier bodies return 413). See Events for the full
endpoint reference.
Registration limit
Creating a new account is limited to 5 registrations per IP address per hour
and 10 per email domain per day. Exceeding either limit returns 429 with
code RATE_LIMITED and a Retry-After header.
Quotas are not rate limits
QUOTA_EXCEEDED (429), PAYMENT_REQUIRED (402), and SEND_CAP_REACHED
(402) signal plan or billing limits, not request-frequency limits — retrying
will not succeed until the quota resets, the plan changes, or the cap is
raised.
Handling errors
- Branch on
code, never onmessage. - On
429, honor theRetry-Afterheader before retrying; add jittered exponential backoff for repeated rejections. - Treat
500and503as transient and safe to retry with backoff; 4xx responses (other than429) will not succeed on retry without changing the request.