Skip to main content

Error codes

Every non-2xx response from the Sealmetrics API uses the same envelope. Branch on error.code, which is stable — never on error.message, which is written for humans and may change.

{
"error": {
"code": "rate_limit_exceeded",
"message": "Too many requests. Please retry after 15 seconds."
},
"request_id": "ae817c5b-82df-430f-83e6-10c937e4e5e4"
}
FieldDescription
error.codeStable machine-readable identifier. Switch on this.
error.messageHuman-readable description. Do not parse it.
error.detailOptional. Structured context — field-level validation errors, quota information, and similar.
request_idCorrelation id, also returned in the X-Request-ID response header. Log it, and quote it in support requests.
One exception: OAuth

The /oauth/* endpoints use the RFC 6749 shape ({"error": "...", "error_description": "..."}) instead of this envelope, because the OAuth spec requires it. See OAuth 2.1.

Catalogue

Client errors

CodeHTTPMeaningRetry?
bad_request400Malformed request or an invalid parameter combination.No
invalid_domain400The domain is not a valid hostname.No
invalid_timezone400Not a recognised IANA timezone.No
unauthorized401Missing, malformed or unrecognised credentials.No — re-authenticate
invalid_credentials401Email or password rejected at login.No
token_expired401The JWT or token has passed its expiry.No — refresh first
token_invalid401Signature or format rejected.No
forbidden403Authenticated, but not permitted. Usually a missing scope or another organization's resource.No
email_not_verified403The account exists but the email has not been confirmed.No
two_factor_required403Login needs the TOTP step. See 2FA.No
not_found404The resource does not exist, or is not visible to these credentials.No
account_not_found404Unknown account_id. See the FAQ on account_id vs site_id.No
user_not_found404Unknown user.No
method_not_allowed405Wrong HTTP verb for this path.No
conflict409The request conflicts with current state.No
duplicate_entry409A resource with that identifier already exists.No
payload_too_large413Request body exceeds the size limit.No — split the request
validation_error422Schema validation failed. error.detail is an array of {field, message, type}.No
rate_limit_exceeded429Over the rate limit. Honour Retry-After.Yes
quota_exceeded429A plan quota is exhausted (not the per-minute rate limit).No — upgrade or wait for the period

Server errors

CodeHTTPMeaningRetry?
internal_error500Unhandled server error.Yes, with backoff
database_error500A query failed.Yes, with backoff
bad_gateway502Upstream dependency returned an invalid response.Yes, with backoff
database_unavailable503A database is temporarily unreachable. Sent with Retry-After: 30.Yes
service_unavailable503A dependency or a feature switch is unavailable.Depends — read the message
gateway_timeout504Upstream dependency timed out.Yes, with backoff — consider narrowing the date range

Endpoint-specific codes

Some endpoints add their own codes on top of the list above:

CodeHTTPWhereMeaning
provision_unavailable409POST /provisionThe account could not be created. Deliberately ambiguous — it does not reveal whether the email is already registered.
invalid_input400POST /provisionA provisioning field failed validation.
rule_is_live409Channel groupsA draft-only mutation (only_if_inactive=true) was attempted on a rule that has gone live.
dependency_failedBatchPer-query status: a query was skipped because a depends_on query failed.
csrf_failed403Cookie-authenticated requestsThe double-submit CSRF token is missing or does not match. Does not apply to X-API-Key or bearer-token requests.

Validation errors in detail

A 422 carries the offending fields:

{
"error": {
"code": "validation_error",
"message": "Request validation failed",
"detail": [
{"field": "query.period", "message": "Input should be a valid string", "type": "string_type"}
]
},
"request_id": "…"
}

field is the dotted path to the parameter, so you can map it straight back to your request.

Retry policy

StatusRetry?How
429YesWait Retry-After seconds + jitter
503YesWait Retry-After seconds
500, 502, 504YesExponential backoff, capped attempts
4xx (others)NoFix the request first
Writes are not idempotent yet

The API does not currently accept an Idempotency-Key header, so retrying a failed POST can create a duplicate resource. Only retry a write once you have confirmed the first attempt did not take effect. See For AI Agents.

import random, time
import requests

RETRYABLE = {429, 500, 502, 503, 504}

def get_with_retry(url, headers, max_attempts=5):
for attempt in range(max_attempts):
r = requests.get(url, headers=headers, timeout=30)
if r.status_code not in RETRYABLE:
return r
# Retry-After is authoritative when present (429 and 503 always send it)
wait = float(r.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait + random.uniform(0, 1))
r.raise_for_status()
return r