Errors
Public API errors use application/problem+json and the RFC 9457 Problem shape:
{
"type": "urn:phishinghermes:problem:unsupported_media_type",
"title": "Unsupported media type",
"status": 415,
"detail": "Content-Type must be message/rfc822.",
"instance": "urn:phishinghermes:request:req_synthetic_0001",
"code": "unsupported_media_type",
"request_id": "req_synthetic_0001"
}
Every public response carries Cache-Control: no-store and X-Request-ID. The body request_id matches the header. A Problem can include errors only for safe field-level validation details.
The read-only API Reference contains a complete, schema-valid, sanitized RFC 9457 example for every public code listed below.
Complete catalog
| Status | Public codes and common causes | Retry? | Idempotency key | Recommended action |
|---|---|---|---|---|
400 | invalid_request, invalid_content_length, idempotency_key_required, invalid_idempotency_key: malformed framing, length, or operation key | No | Keep it while diagnosing | Correct the request before sending it again |
401 | invalid_credentials: missing or non-authenticating credential; deliberately generic | No | Unchanged | Correct or rotate the credential; do not probe credential states |
403 | insufficient_capability, credential_environment_mismatch, credential_not_customer_bound, service_not_enabled, entitlement_required: authenticated scope is not authorized | No | Unchanged | Use the credential issued for this customer/environment or contact Hermes |
404 | operation_not_found: unknown public path, including a non-canonical trailing-slash path | No | Unchanged | Use exactly /v1/email-analyses |
405 | method_not_allowed: a method other than POST was used | No | Unchanged | Use POST; the response also sends Allow: POST |
408 | request_body_timeout: the complete body did not arrive before the body-read deadline | Yes, bounded | Same key and bytes | Check upload/network behavior, then retry with backoff |
409 | idempotency_conflict: the same key was already used with different bytes | No | Recover original bytes; otherwise new key only for a genuinely new operation | Never bypass the conflict automatically |
413 | payload_too_large: body exceeds 10 MiB | No | Unchanged | Do not truncate evidence silently; use an approved alternative workflow |
415 | unsupported_media_type, unsupported_content_encoding: body is not raw message/rfc822 or is compressed | No | Unchanged | Correct headers and send the original bytes |
422 | unusable_email, email_too_complex: empty/NUL/headerless/bodyless or over structural limits | No | Unchanged | Re-export the source or use an approved alternative workflow |
429 | rate_limit_exceeded, usage_quota_exceeded, usage_quota_temporarily_reserved, analysis_concurrency_limited | Yes, bounded | Same key and bytes | Honor Retry-After when present, add jitter, reduce concurrency |
500 | internal_error: unexpected failure before a safe public result | Yes, bounded | Same key and bytes | Back off; do not assume retry will resolve it |
503 | Temporary authentication/rate/processing/persistence/accounting unavailability, timeout, still-processing idempotency, or uncertain outcome | Yes, bounded | Same key and bytes | Honor Retry-After when present; never create a new key to bypass uncertainty |
Public 503 codes are:
authentication_unavailable
rate_limit_unavailable
service_deadline_exceeded
email_parsing_unavailable
email_parsing_timeout
idempotency_unavailable
idempotency_in_progress
analysis_outcome_unknown
analysis_timeout
analysis_unavailable
idempotency_ownership_lost
analysis_persistence_unavailable
analysis_tenant_binding_failed
usage_accounting_unavailable
idempotency_in_progress and analysis_outcome_unknown require special care: retry the original operation with the same key and byte buffer. Do not generate another key. Hermes sends Retry-After where the specific path can calculate a useful interval; the header is not guaranteed for every 429 or 503.
The facade does not return 402.
Safe error handling
content_type = response.headers.get("content-type", "")
if response.status_code >= 400:
if "application/problem+json" not in content_type:
response.raise_for_status()
problem = response.json()
raise RuntimeError(
f"Hermes {problem['status']}: {problem['code']} "
f"(request_id={problem['request_id']})"
)
Log only stable status, code, request ID, and approximate UTC time. Never log the API key, EML, attachments, or sensitive headers.
When to contact support
Stop bounded retries and contact info@phishinghermes.com when a retryable failure persists, an uncertain outcome remains unresolved, or a permanent response is unclear. Provide the latest request_id, status, code, UTC time, client/library version, and whether every retry used the same key and bytes.