# 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:

```http
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:

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

A common Gateman-generated error shape is:

```json
{
  "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:

| Status | Current use |
| ---: | --- |
| `200` | Successful Gateman operations and acknowledgements. |
| `400` | Invalid input or capability-specific request rejection. |
| `401` | Authentication rejection. |
| `403` | Monthly operation allowance exhausted or an email sending policy rejection. |
| `404` | Unknown route or missing resource. |
| `409` | Credential replay, idempotency conflict, or indeterminate send outcome. |
| `410` | Retired endpoint. |
| `429` | Gateman concurrency/rate limiting or a provider rate-limit result translated by a capability. |
| `500` | Internal capability, webhook, or credential-store failure. |
| `502` | Provider or capability-boundary failure. |
| `503` | Unavailable 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`:

```json
{
  "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](https://docs.getgateman.com/authentication/) and [Contract v1](https://docs.getgateman.com/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:

```json
{
  "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:

```json
{
  "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:

```json
{
  "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](https://docs.getgateman.com/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](https://docs.getgateman.com/canonical-client-php/), [Node.js client](https://docs.getgateman.com/canonical-client-node/), [Python client](https://docs.getgateman.com/canonical-client-python/), and [Go client](https://docs.getgateman.com/canonical-client-go/) 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.

## Related references

- [Contract v1](https://docs.getgateman.com/contract-v1/)
- [Authentication and signing](https://docs.getgateman.com/authentication/)
- [Environments](https://docs.getgateman.com/environments/)
- [PHP client](https://docs.getgateman.com/canonical-client-php/)
- [Node.js client](https://docs.getgateman.com/canonical-client-node/)
- [Python client](https://docs.getgateman.com/canonical-client-python/)
- [Go client](https://docs.getgateman.com/canonical-client-go/)
