Skip to main content

Error body

Errors return a JSON body with a stable machine code in error and, where available, a human-readable detail naming the offending field:
Two code styles coexist across the API, both in the error field:
  • snake_case machine codes on the v1 and rate-limited endpoints: invalid_payload, invalid_json, payload_too_large, rate_limited, automations_disabled, geocoding_unavailable, geocoding_failed, internal_error.
  • Human sentences on the older endpoints: Unauthorized, No address found in payload, Too many requests, Failed to subscribe, Internal server error.
Match on the specific string you expect per endpoint (see each endpoint’s page and the Endpoints section of this reference) rather than assuming one global convention.

Authentication errors

status
Missing, invalid, revoked, or expired key. Never reveals whether a key exists.
status
The key is valid but resolves to no organization, or is missing the scope the operation requires.
status
The authentication provider was temporarily unreachable — your key was not checked and its validity is unknown. Body { "error": "auth_unavailable" } with a Retry-After header. Retry with backoff; do not treat this as a bad key.
On POST /api/v1/events the auth body is { "error": "unauthorized" }; elsewhere it’s { "error": "Unauthorized" }. Same meaning, different casing.

Rate limits

Limits are per-org, counted in a fixed window that resets on the boundary (not a sliding window). Over-limit responses carry a Retry-After header (seconds). The events limit and the trigger limit are separate buckets — using one doesn’t consume the other. A dry_run event costs the same quota as a live one. Separately, a per-IP limit of 60 requests / minute / IP is applied before authentication: a flood of bad-key requests is throttled at the edge before it ever reaches key verification, so abusive traffic can’t degrade auth for real tenants. This pre-auth 429 has body { "error": "rate_limited" } and a Retry-After header — back off and retry; it says nothing about your key.

Request size

Every write endpoint caps the request body and returns 413 payload_too_large — with a detail naming the byte ceiling — when the declared Content-Length or the actual body exceeds it. POST /api/v1/events caps at 256 KB; every other write endpoint (import/jobs, conversions, tracking/lead, webhooks/trigger, customers/webhook) caps at 1 MB. A 413 is terminal — trim the payload and retry.

Retryable vs terminal

Retry (transient — the request was not applied): 429 rate_limited / Too many requests, 503 automations_disabled, 503 geocoding_unavailable, 503 auth_unavailable (auth provider unreachable — the key was not checked), 502 geocoding_failed, 500, and 409 import_in_progress (the original request with your Idempotency-Key is still running — retry with the same key for its result). Honor Retry-After where present and back off. Because writes are idempotent, retrying is always safe.Do not retry unchanged (terminal — fix the request): 400, 401, 403, 413, 422, 404. Correct the payload, key, scope, or automation id first. One exception: on POST /api/v1/webhooks/trigger the platform kill switch returns 400 with error: "Automations are globally disabled". That one is transient — retry it rather than dropping the record. (POST /api/v1/events reports the same condition as a 503.)

Recovering from gaps

There is no reconciliation sweep — Mailr won’t pull missed records from your CRM. If deliveries failed or you suspect a gap, replay the affected events. Idempotency (dedup_id, deal_id, address) means replaying a whole time window is free: already-processed items come back as duplicate / idempotent and nothing is double-mailed.