Pinboard's own failures return a status and a JSON body with one human-readable error string. There are no error codes; match on status first, then on the exact string.
{ "error": "no live backends for namespace" }| Field | Type | Present | Meaning |
|---|---|---|---|
error | string | always | What failed. Strings ending in : … carry the offending value. |
reregister | true | 409 from worker routes | The session is gone. Register again. |
superseded | true | 409 from heartbeat | Another registration displaced this one. Stop; do not re-register with the same epoch. |
Exceptions to the JSON shape:
- Invalid ids on the data plane and proxy failures (
502,504) aretext/plain. /readyzand the502for a client that aborted before the forward have an empty body.- WebSocket upgrades that fail get a bare status line with no body; see Proxy a request.
- Responses from a worker pass through unchanged. A worker's own errors use its framework's shape (pinned:
{"detail": "..."}).
Retry-After
Every 503 Pinboard produces carries Retry-After in whole seconds, at least 1. /readyz is the one 503 without it.
Status codes
| Status | Meaning | Retry |
|---|---|---|
400 | Malformed request: bad JSON, body schema, field rule, id charset | No; fix the request |
401 | No credential where one is required, or a rejected credential | No; fix the credential |
403 | Credential valid but out of scope, or instance attestation failed | No |
404 | Unknown route or namespace with no registered workers | Maybe; workers may register later |
409 | Worker session conflict or gone | Re-register, unless superseded |
413 | Request body over the cap | No |
500 | Unhandled server error | Yes, with backoff |
502 | Worker unreachable or failed mid-request | Yes |
503 | Refusal: placement not possible right now | Yes, after Retry-After |
504 | Worker sent no response headers within forwards.timeoutMs (30 s) | Yes |
508 | Instance call chain deeper than 50 | No |
Error registry
Every error Pinboard returns, grouped by where it is raised. … stands for the interpolated value.
Any route
| Status | error | When |
|---|---|---|
404 | not found | Path outside basePath; unknown /registry/* path or wrong method; WebSocket upgrade to a path that is not /{namespace}/{id} |
500 | internal error | A handler threw |
Request bodies (POST /registry/*)
| Status | error | When |
|---|---|---|
400 | invalid json | Body is not JSON |
413 | body too large | Body over 1 MiB (8 MiB on register), by Content-Length or by bytes read |
400 | expected { … } | Body fails the route's schema. The string lists the expected fields. |
Registry authentication
| Status | error | When |
|---|---|---|
401 | unauthorized | No credential accepted, or a provider rejected it |
403 | forbidden | Missing the route's scope; malformed pinboard claim; account mismatch; target outside the claim scope |
503 | not the serving fleet | This node does not hold the fleet license or failed cluster membership. Not raised by /registry/health. |
Register
| Status | error | When |
|---|---|---|
400 | namespace must be '/' plus a single path segment: "…" | A namespaces entry is malformed |
400 | hostname is required in hostname-aware mode | Hostname-aware, hostname absent |
400 | invalid hostname: "…" | hostname is * or holds whitespace, / or : |
400 | invalid env: "…" | Same rule for env or an envs value |
400 | envs key is not a registered namespace: … | envs names a namespace not in namespaces |
400 | instances key is not a registered namespace: … | instances names a namespace not in namespaces |
400 | hostname, env, envs, takeover, and nonzero epoch require hostname-aware mode | Single-hostname mode, a hostname-aware field present |
400 | registration probe failed | The challenge probe to advertiseUrl failed |
403 | namespace not allowed: … | Namespace outside claims.pinboard.namespaces |
403 | backend host not allowed: … | advertiseUrl host outside allowedBackendHosts |
403 | advertiseUrl port not allowed: … | Port outside allowedPorts |
403 | hostname not allowed: … | Hostname-aware, outside hostnames |
403 | env not allowed: … | Hostname-aware, outside envs |
403 | advertiseUrl is not a valid URL | advertiseUrl does not parse |
403 | advertiseUrl scheme not allowed | Scheme is not http: or https: |
403 | advertiseUrl must not carry credentials | URL has userinfo |
409 | client belongs to another account | clientId was registered by another principal |
409 | hostname … is bound to env … at epoch …, not … — register with a higher epoch to displace | Hostname-aware binding conflict |
409 | env … agreed takeover … at epoch …, not … — register with a higher epoch to displace | Hostname-aware takeover conflict |
Heartbeat, release, tag, deregister
| Status | error | Extra field | When |
|---|---|---|---|
409 | unknown session | reregister | Session unknown or its lease expired |
409 | registration voided: challenge value mismatch on the advertise URL | reregister | Heartbeat probe answered another worker's value |
409 | registration superseded: … now bound to env … at epoch … | superseded | Heartbeat: a higher-epoch registration took the binding |
400 | invalid namespace | Release or tag: malformed namespace | |
400 | namespace not registered: … | Release or tag: the session did not register that namespace |
Operator routes
| Status | error | When |
|---|---|---|
400 | invalid namespace | Overview: malformed ns |
400 | empty tag | Overview: tag= with no value |
400 | namespace must be '/' plus a single path segment: "…" | Wind down: malformed namespace |
400 | env is required in hostname-aware mode | Wind down: hostname-aware, env absent |
400 | env requires hostname-aware mode | Wind down: single-hostname, env present |
400 | invalid env: "…" | Wind down: malformed env |
403 | namespace not allowed: … | Wind down: namespace outside the claim |
403 | env not allowed: … | Wind down: env outside the claim |
500 | metrics collection failed | /metrics snapshot threw |
Data plane: request checks
| Status | Body | When |
|---|---|---|
400 | id must be non-empty (text) | Empty id segment |
400 | id must be at most 256 characters (text) | Long id |
400 | id must contain only [A-Za-z0-9._~-] (text) | Other characters, including percent-encoded ones |
404 | unknown namespace | Path has no id segment, or no worker ever registered the namespace (for the request's hostname) |
400 | missing Host header | Hostname-aware, no Host |
401 | invalid credential | A provider rejected the credential |
401 | authentication required | Rule needs a credential, none accepted |
403 | namespace not allowed | No data-plane rule for the namespace |
403 | insufficient scope | Credential holds none of the rule's scopes |
Data plane: instance attestation
| Status | error | When |
|---|---|---|
403 | caller attestation requires session credentials | Pinboard-From without Pinboard-Session and Pinboard-Session-Secret |
403 | malformed subrequest depth | Pinboard-Subrequest-Depth is not 1–9 digits |
403 | unknown session | Session unknown or expired |
403 | invalid session secret | Secret does not match the session |
403 | malformed caller claim | Pinboard-From is not {namespace}/{id} with a valid id |
403 | caller placement mismatch | The claimed instance is not placed on that session |
403 | target outside caller scope | The caller's worker did not register the target namespace, or (hostname-aware) the target's hostname or env differs from its registration |
508 | subrequest depth exceeded | Depth over 50 |
Data plane: refusals
All 503 with Retry-After.
error | When | Retry-After |
|---|---|---|
not the serving fleet | Node not licensed to serve | 1 |
no live backends for namespace | Namespace registered, no placeable worker (all dead, draining, degraded or near lease end) | 1 |
registry cold start, retry | The id's bucket is inside its cold-start window (one lease after first use) | remaining window |
no live workers for hostname | Hostname-aware: no binding for (Host, namespace), or no placeable worker | 1 |
hostname binding displaced, retry | Hostname-aware: binding displaced within the last lease; unplaced ids wait | remaining window |
hostname bindings inconsistent, retry | Hostname-aware: env has no takeover binding | 1 |
takeover in progress, retry | Placed on another hostname, wind_down takeover fenced | until the fence |
placement held by another hostname | Placed on another hostname, takeover deny | 1 |
worker inflight cap exceeded, retry | Worker has forwards.maxInflight (1024) forwards awaiting headers | 1 |
worker refused activation, retry | Worker answered Pinned-Refusal: lease-unhealthy and re-placement was not possible | 1 |
Data plane: forwarding
| Status | Body | When |
|---|---|---|
502 | bad gateway (text) | Worker unreachable or the connection failed before headers |
502 | empty | Client aborted before the forward was sent |
504 | upstream timeout (text) | No response headers within forwards.timeoutMs |