Errors

The error shape, what each status means, and how to tell a blocked message from a failed call.

Embed
Preview of the card
<a href="https://hermetica.15.204.158.166.sslip.io/guides/errors"><img src="https://hermetica.15.204.158.166.sslip.io/guides/_api/public/orgs/5fcd8b3c-a27c-4688-972f-de710125592f/spaces/9a3da731-ded5-4536-b84f-8c9a61420ff4/pages/07cef503-04c7-454d-a690-e3e8b95cc6ca/social-card.svg?v=3e7ae33eec8b" alt="Errors" width="600" height="315"></a>

Every error is JSON with a stable code:

{"error":{"code":"invalid","message":"body/email must match format \"email\"","details":{"issues":[{"instancePath":"/email","message":"..."}]}}}

Branch on code, not on message. Messages may change.

Status

code

Means

400

invalid

The request is wrong. details.issues[].instancePath names the field. Malformed JSON is also invalid.

401

unauthenticated

Missing, wrong or revoked key.

403

forbidden

The key lacks a scope (Missing scope x:y), or the route needs a signed-in user.

404

not_found

Unknown id or route.

409

conflict

A duplicate, such as a name already in use.

412

precondition_failed

Something must be set up first, such as working provider credentials.

422

idempotency_key_reused

The Idempotency-Key was already used for a different request. Use a new key. See idempotency.

429

rate_limited

Slow down. Wait for Retry-After seconds.

Blocked is not an error

A message that consent or suppression stops is not an error. The send answers 202. The message is recorded as blocked with a reason:

curl -s "$BASE/v1/messages/<jobId>" -H "Authorization: Bearer $HERMETICA_API_KEY"

The decisionLog shows every check the message passed or failed. The same goes for deferred (quiet hours) and render_error (a template variable was missing).

Unknown fields are refused

With an API key, a body field the route does not know answers 400 and names it, for example Unknown field: standard with details.unknownFields: ["standard"]. Nothing is silently ignored, so a misspelled field never looks like a successful PATCH. Check the field names against the API reference.

Send Content-Type: application/json only when there is a body. A DELETE with that header and no body fails.

Dates

Analytics take dates as YYYY-MM-DD. Most other fields take ISO 8601 times, such as 2026-11-04T15:00:00Z.

Retry

Retry 429 and 5xx with backoff. Do not retry 4xx without changing the request. Make retries safe with idempotency.

Updated