The proxy returns its own status codes for queue and validation errors, and passes backend codes through for successful responses. Understanding which codes originate from the proxy vs. the backend helps diagnose issues quickly.
TL;DR
429— the proxy itself is at capacity (queue full, all circuits open, no active hosts).412— the request waited too long in the queue and its TTL expired before being sent.503— no host was attempted, or name-resolution/connection failures exhausted the available hosts.
These codes are generated by the proxy itself, never passed from a backend.
| Code | Name | Proxy cause |
|---|---|---|
400 |
Bad Request | Malformed S7PTTL header value (InvalidTTL) |
403 |
Forbidden | App ID validation failed (DisallowedAppID); inbound key validation failed (DisallowedKey); user not found in profile or suspended (UnknownProfile) |
408 |
Request Timeout | IO exception or task cancellation while communicating with backend |
412 |
Precondition Failed | Request TTL expired while waiting in queue (TTLExpired) — ExpiresAt passed before dispatch |
417 |
Expectation Failed | Required header missing (IncompleteHeaders); header validation rule failed (InvalidHeader) |
429 |
Too Many Requests | Queue full; backend circuit check failed; no active hosts; or max undrained events exceeded |
500 |
Internal Server Error | Unhandled exception; request body too large to buffer (OOM → ContentTooLarge) |
502 |
Bad Gateway | Backend attempts failed with different status codes, or an unclassified transport error occurred |
503 |
Service Unavailable | No host was attempted, or backend name-resolution/connection failures exhausted the available hosts |
Note
Proxy-originated 429 responses include a Retry-After header: the configured poll interval when no active hosts are available, otherwise 500 (ms). Probe requests are exempt from every admission check that produces these codes.
Any status code the proxy receives from a backend that is in AcceptableStatusCodes is forwarded directly to the client without triggering a retry.
Default AcceptableStatusCodes: 200, 202, 401, 403, 404, 408, 410, 412, 417, 400
Codes not in this list (for example, 500, 502, or 503) cause the proxy to advance to the next backend. Any code in the list, including 404, is returned directly without retry or circuit recording.
Tip
To pass 503 through from a backend instead of retrying, add it to AcceptableStatusCodes.
When a backend returns 429:
S7PREQUEUE header on response |
Proxy behaviour |
|---|---|
true |
Record the request as eligible for delayed requeue, then continue trying available hosts |
absent / not true |
Proxy tries the next backend host |
After host attempts are exhausted, the proxy requeues if at least one backend returned 429 with S7PREQUEUE: true. It uses the shortest eligible delay from retry-after-ms (milliseconds) or Retry-After (seconds), defaulting to 1000 ms.
| Header | Description |
|---|---|
S7PDEBUG |
Set to true to enable per-request debug tracing in logs |
S7PPriorityKey |
Looked up in PriorityKeys; matching entry sets the request priority from PriorityValues. Header name is configurable via PriorityKeyHeader |
S7PTTL |
Time-to-live for the request (seconds). Expired requests return 412. Default TTL is DefaultTTLSecs (300 s) |
S7PTimeout |
Per-request timeout override (ms). Header name is configurable via TimeoutHeader |
S7P-Iterator |
Per-request iteration mode override: SinglePass or MultiPass (case-insensitive). Missing or invalid values use the current IterationMode default |
S7PREQUEUE |
Set by a backend on a 429 response to trigger requeue with retry-after logic |
These headers are injected by the proxy on every successful proxied response.
| Header | Description |
|---|---|
Request-Queue-Duration |
Milliseconds the request spent in the priority queue |
Request-Process-Duration |
Milliseconds spent in the proxy worker (dequeue → response write) |
BackendHost |
Hostname of the backend that served the response |
Total-Latency |
Total milliseconds from enqueue to response (queue + process) |
Attempts |
Backend attempts made during the current dispatch cycle |
Lifetime-Attempts |
Cumulative backend attempts across all requeue cycles |
Note
Exhausted-host error responses use x-Request-Queue-Duration, x-Total-Latency, x-ProxyHost, and x-MID. Async 202 responses include x-Data-Blob-URI and x-Header-Blob-URI.
Note
Health probe endpoints are served on the main application port by default. Port 9000 is only used when the optional sidecar is enabled (see HEALTH_CHECKING.md).
| Path | Returns |
|---|---|
/liveness |
200 when the process is running; never returns 503 |
/readiness |
200 when startup, active-host, event-backlog, and blob-queue criteria pass; 503 otherwise |
/startup |
200 when initialization and readiness health criteria pass; 503 otherwise |
Client request arrives
│
├── TTL already expired? ──► 412 Precondition Failed
├── Validation failed? ──► 403 Forbidden / 417 Expectation Failed
├── Queue full / CB open / no hosts? ──► 429 Too Many Requests
│
└── Enqueued → Worker picks up
│
├── TTL expired in queue? ──► 412
│
└── Send to backend (try each active host)
│
├── Backend: AcceptableStatusCode ────────────► pass-through to client
├── Backend: non-acceptable status ───────────► skip, try next host
├── Backend: 429 + S7PREQUEUE=true ───────────► collect; requeue after attempts exhaust
├── Backend: 429 (no requeue) ────────────────► skip, try next host
└── Attempts exhausted ───────────────────────► 502 or 503 by failure type
- CIRCUIT_BREAKER.md — When and why circuit breakers trip (429)
- CONFIGURATION_SETTINGS.md —
AcceptableStatusCodes,MaxQueueLength,DefaultTTLSecs - HEALTH_CHECKING.md — Probe endpoint internals
- REQUEST_VALIDATION.md — Header validation rules (403 / 417)