Error codes
Every error response the Phylax API returns, what causes it, and whether to retry.
Response shape
Errors return a JSON body with a detail field:
{ "detail": "rate limit exceeded" }Unhandled server errors additionally carry a request id:
{ "detail": "internal error", "request_id": "a1b2c3d4" }Every response, successful or not, carries an x-request-id header. Quote it when reporting a
problem; it is what correlates to the server side log.
Status codes
| Status | Meaning | Retry |
|---|---|---|
| 400 | Malformed request | No, fix the request |
| 401 | Authentication failed | No, see below |
| 403 | Authenticated but not permitted | No |
| 404 | Resource not found | No |
| 411 | Content length required on POST, PUT, PATCH | No, set the header |
| 413 | Request body too large | No, reduce the body |
| 422 | Request failed validation | No, fix the parameters |
| 429 | Rate limited | Yes, honour Retry-After |
| 500 | Unhandled server error | Yes, with backoff |
| 502 | Upstream dependency unavailable | Yes, with backoff |
| 503 | Service temporarily unavailable | Yes, with backoff |
| 504 | Handler timed out | Yes, with backoff |
Public read endpoints
| Status | Detail | Cause |
|---|---|---|
| 404 | round not found | Unknown or orphaned round id |
| 404 | agent not found | The hotkey has no agent row |
| 422 | Validation error | A parameter is out of bounds, for example days above 180 or limit above 200 |
| 429 | rate limit exceeded | Global per IP limit, or a per endpoint limit |
The attestations endpoint returns an empty result rather than an error when the round id or
miner hotkey fails its format check, or when the round is not closed. Check whether tasks
is empty and round_status is blank rather than relying on a status code.
Transport level
These come from middleware and apply to every endpoint.
| Status | Detail | Cause |
|---|---|---|
| 411 | length required | A POST, PUT or PATCH arrived without Content-Length |
| 413 | Body too large | The body exceeded the configured maximum, 64 MiB by default |
| 429 | rate limit exceeded | The global per IP ceiling, 300 per minute by default |
| 500 | internal error | Unhandled exception. The request_id identifies the log entry |
| 504 | request handler timed out | The handler exceeded the timeout, 60 seconds by default |
GET /v1/health is exempt from the global rate limit.
Authentication errors
Only relevant if you hold an API key. No public read endpoint uses these.
| Status | Detail | Cause |
|---|---|---|
| 401 | malformed api key | The token does not match phyx_<8 hex>_<48 hex> |
| 401 | invalid or revoked api key | Unknown or revoked key |
| 403 | api key lacks '<scope>' scope | Valid key without the required scope |
| 400 | cannot revoke the key you are currently using | Self revocation attempt |
| 404 | key not found | Unknown, already revoked, or owned by another account |
Signed endpoint errors
You will not hit these unless you are running a validator or submitting agents, but they are listed because they appear in logs.
| Status | Detail | Cause |
|---|---|---|
| 401 | signature timestamp outside freshness window | Clock skew beyond the window, 120 seconds by default |
| 401 | signature already used (replay) | The same hotkey, timestamp and signature triple was reused |
| 403 | hotkey not registered on netuid 76 | Not on the metagraph |
| 403 | hotkey does not hold a validator permit on netuid 76 | Registered without a permit |
| 403 | validator access has been revoked | The validator record is revoked or suspended |
| 403 | source IP … is not registered in the validator allowlist | Host not allowlisted |
| 403 | metagraph unavailable - cannot verify validator permit | Chain read failed and the cached snapshot is too stale |
| 429 | too many signature failures | Signature failure rate limit per source IP |
Retry guidance
For 429, read Retry-After and wait at least that long. For 500, 502, 503 and 504, use
exponential backoff with jitter starting around one second.
Do not retry 4xx other than 429. They indicate a request that will fail identically every time.
Because most read endpoints are cached server side for 30 to 600 seconds, aggressive retrying returns the same cached body while consuming your rate limit. See rate limits.