Skip to content
MailHaap
[03]Reference

Error codes

Errors are RFC 9457 problem documents with a stable machine code, a request id and a documentation URL. Read the code, not the HTTP status or the human-readable title — codes are a permanent contract and are never changed, only added.

422 Unprocessable Content
{
  "type": "https://docs.mailhaap.com/errors/domain_not_verified",
  "title": "Domain not verified",
  "status": 422,
  "code": "domain_not_verified",
  "detail": "The domain abcemlak.com has 2 of 4 required DNS records in place.",
  "request_id": "req_01J8XQ7M2K9P",
  "docs_url": "https://docs.mailhaap.com/domains/verification",
  "missing_records": [
    { "kind": "dkim", "record_type": "CNAME", "host": "mh1._domainkey.abcemlak.com" }
  ]
}
Error codes by status
StatusCodeWhat to do
400invalid_requestA field failed validation. The detail names it.
400invalid_jsonThe body was not parseable JSON.
400unsupported_content_typeSend application/json.
401missing_api_keyNo Authorization header.
401invalid_api_keyThe key does not exist or the hash did not match.
401revoked_api_keyThe key was revoked. Create a new one.
403insufficient_scopeThe key lacks the scope this endpoint needs.
403ip_not_allowedThe request came from outside the key's IP allowlist.
403tenant_suspendedThe workspace is suspended. Contact support.
404not_foundAlso returned for cross-workspace access — we never confirm existence.
409idempotency_conflictThe same key was reused with a different body.
422domain_not_verifiedPublish the missing DNS records listed in the response.
422identity_not_ownedThe From address is not on a verified domain of this workspace.
422recipient_suppressedThe address is on your suppression list. Remove it deliberately or not at all.
422attachment_too_large20 MB per attachment, 25 MB per message after encoding.
422invalid_recipientThe address failed syntax or MX validation.
422template_render_failedA merge tag could not resolve. The detail names it.
422missing_unsubscribeA campaign-plane message with no unsubscribe.
429rate_limitedHonour Retry-After. Retry with jitter, never in a tight loop.
451content_blockedThe abuse filter rejected the content.
500internal_errorRetry with backoff. Quote the request id to support.
503provider_unavailableRetry with backoff. Queued work is unaffected.

Why cross-workspace access returns 404, not 403

A 403 confirms the object exists. Returning 404 for anything outside your workspace means an attacker with a valid key learns nothing by probing ids. There is an automated test asserting this for every resource type, and it blocks merges.