Skip to main content

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."
}
FieldTypeDescription
codestringMachine-readable error identifier (SCREAMING_SNAKE_CASE). Stable across releases — safe for client switch/case logic.
messagestringHuman-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​

CodeHTTP statusMeaning
BAD_REQUEST400Malformed request that cannot be understood.
UNAUTHORIZED401Authentication is required or credentials are invalid.
FORBIDDEN403Authenticated but not permitted to perform this action.
NOT_FOUND404The requested resource does not exist.
CONFLICT409The resource already exists or is in a conflicting state.
VALIDATION_ERROR422Request body or parameters failed validation.
INTERNAL_ERROR500An unexpected server-side error occurred.
SERVICE_UNAVAILABLE503A downstream dependency is unavailable.

Domain-specific codes​

CodeHTTP statusMeaning
PAYMENT_REQUIRED402The plan quota for this billing period is exhausted.
SEND_CAP_REACHED402Your own configured monthly send cap has been reached — raise the cap in Billing rather than upgrading the plan.
TENANT_SUSPENDED403The workspace account has been suspended.
TENANT_DELETED403The workspace account has been closed.
FEATURE_NOT_AVAILABLE403The feature is not available on your current plan. Plan gates on a specific option (smart channel, optimal send time, A/B testing, digest batching) also carry feature (which one) and "upgrade_required": true.
CHANNEL_TYPE_NOT_PERMITTED403Creating a channel of a type your plan does not include. The body also carries allowed (the channel types your plan permits) and "upgrade_required": true.
IN_APP_NOT_PERMITTED403In-app messaging is not included in your plan. The body also carries "upgrade_required": true.
MANAGED_EMAIL_NOT_PERMITTED403Hober-managed email is not enabled for this workspace. Hober support turns it on; it is not tied to a plan, so there is no upgrade_required.
READ_ONLY_TOKEN403The token is read-only and cannot authorize write operations.
FILE_TOO_LARGE413An uploaded file exceeds the maximum allowed size.
TEMPLATE_NOT_FOUND422The template_id referenced in the request does not exist in this workspace.
QUOTA_EXCEEDED429The workspace has exceeded a plan quota.
RATE_LIMITED429Too many requests: the workspace's per-minute request limit, or a rate-limited surface such as account registration.

Rate limits​

Per-workspace request limit​

Requests authenticated with a dashboard session token or a server key (Bearer hober_srv_…) count against one per-workspace limit over a fixed one-minute window. The limit depends on your plan:

PlanRequests per minute
Free60
Starter300
Growth1,000
Scale3,000
Agency5,000
Enterprise10,000

The limit is being rolled out: requests over it are currently counted, not rejected. Once it is enforced, a request over the limit gets 429 with code RATE_LIMITED in the standard error envelope and a Retry-After header giving the seconds until the window resets. Retry-After is the only rate-limit header — there are no X-RateLimit-* headers.

Requests authenticated with a client SDK key come from your users' devices and are not counted against this 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 request501,000
Request body—500 KB (413 beyond)
Single event—32 KB
occurred_at backdating window48 hours90 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.

The monthly message quota counts messages handed to a provider: one per device, address or in-app recipient a send reached, including sends the provider rejected. Simulated sends and sends that fail before reaching a provider do not count. A notification is accepted while any quota remains and, once it starts sending, is delivered in full, so the last one in a period can take usage past the limit; the next one is refused. A scheduled notification is checked again when it is due: if the quota ran out in the meantime, it ends failed without sending.

Handling errors​

  • Branch on code, never on message.
  • On 429, honor the Retry-After header before retrying; add jittered exponential backoff for repeated rejections.
  • Treat 500 and 503 as transient and safe to retry with backoff; 4xx responses (other than 429) will not succeed on retry without changing the request.