Gateman
Documentation/Errors and responses

Errors and responses

Gateman does not impose one universal JSON response envelope. The response depends on the capability you call.

Gateman responses fall into four families:

  • Gateman-generated JSON responses.
  • Provider-native HTTP responses returned through provider relays.
  • Public client acquisition artifacts.
  • Webhook responses.

For application integrations, use the HTTP status, response headers, and the response contract for the selected capability. Do not assume that every successful response contains ok: true, or that every failure contains ok: false.

Shared HTTP behavior

Request correlation

Gateman generates X-Gateman-Request-ID for machine API requests. It is a 16-character lowercase hexadecimal identifier and is returned on normal responses, including authentication failures. It is useful when investigating a request and is distinct from any request identifier generated by a provider.

The caller cannot choose or override it. Keep it with application logs and support reports. Do not put credentials or secret values in those logs.

Headers and content types

Gateman-generated machine responses normally include:

Content-Type: application/json; charset=utf-8
Cache-Control: no-store
X-Gateman-Request-ID: <request identifier>

These headers are not a promise that every deployment component has an identical header set. Provider relays may replace the content type with the provider's content type, and client acquisition routes return downloadable artifacts.

Retry-After appears only where it applies, such as some concurrency, rate-limit, and provider-throttling responses. It is not guaranteed on every 429 or 503 response.

Gateman-generated JSON

A successful Gateman-generated response has a route-specific shape. For example, GET /health returns:

{
  "ok": true,
  "service": "gateman",
  "version": "1.0.0",
  "time": "2026-10-10T00:00:00+00:00"
}

A common Gateman-generated error shape is:

{
  "ok": false,
  "error": "Invalid JSON"
}

ok: false and an error string are common conventions, not a universal schema. Some routes add capability-specific fields such as provider information, usage limits, retry information, or an AWS error code. There is no universal Gateman error-code namespace. Provider-native errors may use a completely different body structure.

HTTP status codes

These are statuses currently generated by Gateman itself:

StatusCurrent use
200Successful Gateman operations and acknowledgements.
400Invalid input or capability-specific request rejection.
401Authentication rejection.
403Monthly operation allowance exhausted or an email sending policy rejection.
404Unknown route or missing resource.
409Credential replay, idempotency conflict, or indeterminate send outcome.
410Retired endpoint.
429Gateman concurrency/rate limiting or a provider rate-limit result translated by a capability.
500Internal capability, webhook, or credential-store failure.
502Provider or capability-boundary failure.
503Unavailable authentication, infrastructure, provider, admission, or accounting dependency.

Provider relays may return other statuses directly from the provider, including 201, 204, 422, and provider-specific 4xx or 5xx statuses. A wrong method/path combination generally reaches Gateman's unmatched-route response and returns 404; do not assume a universal 405 response.

The status is authoritative. A received HTTP error response is different from a transport failure where no HTTP response was received.

Authentication and authorization errors

Deterministic authentication rejection returns 401. Examples include:

  • Missing or invalid application authentication.
  • Invalid signature.
  • Expired, future, or malformed timestamp.
  • Unknown or inactive application.
  • Missing required scope for routes other than the email sending policy errors described below.
  • Source-IP rejection.
  • Deterministically invalid stored authentication material.

Genuine inability to complete authentication because the application registry or authentication KMS/database dependency is unavailable returns 503:

{
  "ok": false,
  "error": "Authentication unavailable"
}

Incorrect credentials are not the same as authentication infrastructure failure. Do not automatically retry invalid signatures, missing scopes, or other deterministic authentication rejections.

See Authentication and signing and Contract v1 for the authentication contract.

Email sending policy errors

Email routes use stable 403 errors for the application-wide sending policy:

  • scope_not_allowed โ€” the email.send capability is not granted.
  • email_sending_suspended โ€” Sending Protection has deliberately suspended email sending.
  • email_sending_limit_exceeded โ€” the current request would exceed the configured automatic sending limit; the request is rejected and sending is suspended.

Applications should stop automatic retries for these errors. Check Sending Protection and use an authorised recovery path before investigating provider credentials, networking, or provider outages.

Request validation

Malformed or unsupported requests commonly return 400, including invalid JSON, missing fields, incorrect field types, unsupported environments, and invalid capability parameters. Exact validation rules and error strings are capability-specific; there is no universal schema-validation envelope.

Protected routes authenticate before route-specific body validation. A credential-disclosure route also validates its credential-request identifier before processing the body.

Provider-native responses

The Stripe, Square, PayPal, Twilio, and Telnyx relays can preserve provider responses. Where applicable, Gateman retains:

  • The original provider HTTP status.
  • The provider response body bytes.
  • The selected provider content type.
  • Selected provider request or diagnostic headers.

The selected headers differ by adapter. Gateman does not forward every upstream header, and a provider body is not necessarily JSON.

A provider response means the provider returned an HTTP response that Gateman can relay. A Gateman-generated failure means Gateman could not complete part of the request and returned its own controlled response. A provider 400 or 500 is therefore not automatically a Gateman 502.

PayPal obtains an access token before making the requested provider call. Some token acquisition failures are translated into Gateman-controlled responses, so no provider operation response exists to relay in those cases.

Email responses

SES, Postmark, and Resend return Gateman-generated email responses.

In production, a successful accepted send includes fields such as:

{
  "ok": true,
  "caller": "application-id",
  "provider": "resend",
  "message_id": "provider-message-id",
  "error": null
}

Provider acceptance is not proof of final recipient delivery.

In staging and development, Gateman captures the message in its local mailbox instead of sending through the external provider. A successful capture indicates local capture, not delivery.

Email failures include request validation, missing configuration, definite provider rejection, provider throttling, and ambiguous provider outcomes. When Gateman cannot establish whether the provider accepted the message, it returns 409 with mail_send_indeterminate. The provider may have accepted the message. Do not blindly resend an indeterminate operation.

Email idempotency is optional and capability-specific. A completed successful replay may return its retained result. Reusing a key with a different payload returns 409 with idempotency_key_payload_conflict.

SMS responses

In production, Twilio and Telnyx responses may preserve provider-native statuses and bodies. AWS SMS returns Gateman-generated JSON for successful operations and controlled Gateman errors for failures.

In staging and development, SMS is captured locally. The following is illustrative; the capture ID is not stable:

{
  "ok": true,
  "staged": true,
  "sent": false,
  "provider_accepted": false,
  "delivery_status": "not_sent",
  "message_id": null,
  "capture_id": "example-capture-id",
  "provider": "twilio",
  "environment": "staging"
}

When Gateman cannot establish whether an external send was accepted, it returns 409 with sms_send_indeterminate where the route's idempotency behavior applies. This does not establish that the SMS was rejected or never sent. Do not automatically retry an uncertain send.

Credential retrieval responses

/secrets/get

POST /secrets/get is environment-aware and returns requested values only:

{
  "ok": true,
  "secrets": {
    "EXAMPLE_NAME": "value"
  }
}

Unknown, unconfigured, invalid, and retrieval-disabled names are omitted rather than returned as per-entry errors. Custom-secret lookup uses the selected environment slot; it does not disclose both environment slots in one response.

Credential-request replay conflicts return 409. Credential-store or KMS failures after request acceptance may return 500.

/db/get

POST /db/get is environment-independent and retrieves one named database password. A successful response contains the requested name and includes password only when a value is available; an unknown or unavailable named password returns 200 without that property. Credential-request replay conflicts return 409; credential-store failures may return 500.

Both routes require the single-use X-GM-Credential-Request-ID defined by Contract v1. A legitimate subsequent retrieval attempt requires a fresh identifier. Reusing an already-claimed identifier is not a valid retry mechanism.

Other response families

  • /config/get returns Gateman-generated public configuration JSON and is non-billable, with its own request rate limit.
  • /turnstile/get and /recaptcha/get return Gateman-generated credential bundles; Gateman is not in the visitor verification request path.
  • /s3/presign and /r2/presign return JSON containing a presigned URL, method, expiry, and required headers.
  • /client/get returns app-scoped application/client context JSON.
  • /client/php, /client/node, /client/python, and /client/go return downloadable source or archive artifacts rather than ordinary JSON API responses.

Idempotency and replay errors

These are separate mechanisms:

Authentication timestamp freshness

Signed requests are accepted only within the Contract v1 freshness window. Freshness does not make every ordinary signed request single-use.

Credential-disclosure protection

/secrets/get and /db/get require single-use credential-request identifiers. Reusing a claimed identifier returns 409.

Operation idempotency

Email and SMS have optional, capability-specific idempotency stores. Completed successful operations may replay a retained result. A different payload under the same key produces an idempotency conflict. Ambiguous sends require special care because external acceptance may be unknown.

Payment-provider idempotency remains provider- and capability-specific. Gateman does not provide universal idempotency for every route.

Concurrency and rate limiting

Gateman applies shared per-application concurrent-request admission. Exhausted application capacity can return 429 with Retry-After: 1; admission infrastructure failure can return 503.

Some routes also have endpoint-specific limits, such as public configuration. Providers may impose independent rate limits, which can be returned as provider-native responses or translated by the capability. There is no universal retry interval or rate-limit header set.

Operation accounting and failed requests

HTTP success or failure alone does not determine whether an operation counts toward Gateman usage.

Authentication rejection, request validation, and some concurrency or admission failures occur before operation reservation. Other failures occur after an operation has been reserved, including provider attempts and capability-specific failures. Staging and development captures remain subject to applicable operation accounting.

Do not treat every 4xx or 5xx as free. Once a billable operation has been accepted into the accounting lifecycle, it consumes one unit whether execution succeeds or fails; rejection before reservation does not consume an operation. See Operation Accounting for the complete rule.

Canonical-client response handling

The four official v1.0.0 clients expose the underlying HTTP result rather than turning every non-2xx status into a language-level exception. Each exposes, using language-appropriate types:

  • HTTP status.
  • Response headers.
  • Raw response body.
  • Parsed JSON when possible.
  • X-Gateman-Request-ID when valid.
  • Retry-After when present and parseable.

Network, configuration, timeout, and transport failures are separate from a received HTTP response. A malformed or binary body may be preserved as raw data while JSON parsing reports a decode problem.

See the PHP client, Node.js client, Python client, and Go client guides for language-specific response types.

Handling responses in an application

  • Inspect the HTTP status first.
  • Preserve the response body even when it is not JSON.
  • Read Gateman ok and error fields only when that response actually uses the Gateman-generated JSON convention.
  • Handle provider-native bodies according to the provider's contract.
  • Distinguish a received HTTP response from a network or timeout failure.
  • Retain X-Gateman-Request-ID for diagnosis.
  • Respect Retry-After when it is provided.
  • Avoid blind retries when provider acceptance may be ambiguous.
  • Never log credentials, secrets, authorization headers, or sensitive provider payloads.

Do not assume every 502 or 503 is safe to retry. The capability contract and whether external acceptance is known determine the next action.