SolidPeer

Error reference

All errors carry a stable reason string in the response body, plus an HTTP status that approximates the same semantics. Branch on reason for programmatic handling; use the status for generic HTTP middleware.

Wire shape

HTTP

{
  "error":  "<human-readable message>",
  "reason": "<stable_machine_string>"
}

X-RateLimit-Reason header echoes the same reason for 4xx responses. The standard Retry-After header (integer seconds, RFC 9110) is set on 429 rate and 429 concurrent responses. A sibling retry-after-ms header carries the same hint in milliseconds — useful when the bucket refill is sub-second (any rps_cap > 1); the standard header would round to 0 / 1 and mislead. balance and token_budget_exceeded 429s ship neither: they don't clear on a predictable schedule.

WebSocket

JSON-RPC error frame:

{
  "jsonrpc": "2.0",
  "id":      <request id>,
  "error": {
    "code":    429,
    "message": "<human-readable>",
    "data": {
      "reason":       "<stable_machine_string>",
      "http_status":  429,
      "retry_after_ms": <number>
    }
  }
}

For rate-limit and balance classes, error.code is 429. Other error classes use JSON-RPC server-defined codes in -32000..-32099.

Reason catalog

401 Unauthorized

reason meaning
invalid_token The token in the URL doesn't exist or has been revoked.
signature_invalid The signed-message signature didn't recover to the claimed account pubkey.
missing_headers A signed endpoint was called without X-Solidpeer-{Account,Timestamp,Signature}.
timestamp_skew The X-Solidpeer-Timestamp is more than 300 s from server clock.
replay_detected Same signature was already used inside the timestamp window.
token_expired The token has a hard expiry and that time has passed.

403 Forbidden

reason meaning
method_not_in_allowlist The token's method allowlist excludes this RPC method.
system_not_allowed The token isn't scoped to this system (bchn/fulcrum/chaingraph).
network_not_allowed The token isn't scoped to this network.
origin_denied The request's Origin header isn't on the token's origin allowlist.
account_suspended The account is operator-flagged.
account_expired Cycle ended without renewal. Renew to restore service.
requires_paid_tier The action (e.g. minting a token) requires a paid tier.
token_count_limit Account is at its tier's max active-token cap.

429 Too Many Requests

reason meaning
rate The per-account or per-token bucket is empty. Retry-After carries the suggested wait in seconds.
concurrent The per-account in-flight HTTP request cap is full. Try again as in-flight calls return.
balance Account CC balance is too low for the call's reservation. Top up or upgrade.
token_budget_exceeded The token's per-token CC budget cap has been hit. Use a different token.

400 / 500 / 502 / 503

reason meaning
parse_error Request body wasn't valid JSON-RPC / GraphQL.
preflight The request's parameters failed shape validation before forwarding.
upstream_error The upstream node returned an error. Body contains the upstream message verbatim.
no_upstream No healthy backend currently registered for the requested (system, network). Status endpoint shows live availability.
subscriptions_unsupported Tried to open a subscription on a system that doesn't support them (bchn).

Retry strategy

  • 429 rate — honor Retry-After. Exponential back-off isn't necessary; the bucket refills at a predictable rate.
  • 429 balance / 429 token_budget_exceeded — don't retry. Top up or rotate.
  • 503 no_upstream — short retry (≥1 s) is reasonable; upstream may recover.
  • 502 upstream_error — usually a transient upstream issue. One retry is fine; more is unlikely to help.
  • 401 and 403 — don't retry. Fix the credential or scope.