On this page
Error Codes
Every Artery error response carries a structured envelope plus an
X-Request-Id header. Branch your client code on error.code, not on
HTTP status — the status can change (e.g. validation_failed is 400
or 422 depending on layer) but the code is stable.
Envelope
jsonc{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded for tier \"free\" (10000/month)",
"requestId": "req_5a6604f3-7c32-42da-ba70-3df3ca864cfc"
}
}| Field | Meaning |
|---|---|
error.code | Stable Artery enum (see table below) — branch on this |
error.message | Human-readable summary, safe to surface to end-users |
error.requestId | Per-request id, mirrored on the X-Request-Id response header |
error.provider | (Upstream errors only) Which adapter raised the error |
error.upstream.status | (Upstream errors only) HTTP status returned by the provider |
error.upstream.body | (Upstream errors only) Raw provider error body |
Code reference
| Status | Code | Meaning | How to fix |
|---|---|---|---|
| 400 | bad_request | Generic validation failure (malformed JSON, bad query) | Inspect error.message — it carries the field path |
| 400/422 | validation_failed | Zod schema violation on body / query | Match the message's field.path to your payload and fix the offending field |
| 401 | unauthenticated | Missing or malformed Authorization header | Set Authorization: Bearer art_live_<key> or pass ?token=... on the WS upgrade |
| 403 | forbidden_scope | Key lacks the required scope | Mint a new key with the missing scope (read / stream / admin) |
| 404 | not_found | Event / wallet / webhook id does not exist | Verify the id; for events, call GET /v1/events first |
| 409 | conflict | Idempotency / uniqueness violation (e.g. duplicate wallet binding) | Re-read the resource — it likely already exists |
| 429 | rate_limited | Artery quota exceeded | Honor Retry-After; see limits for tier thresholds |
| 429 | upstream_rate_limited | Upstream provider rate-limited Artery (transient) | Artery already retries GETs with backoff — surface to user only after retry exhaustion |
| 401/403 | upstream_unauthorized | Provider rejected your provider credentials (planned) | Re-upload Polymarket / Kalshi / HL creds; check expiration |
| 500 | internal_error | Artery bug — please file with requestId | Retry after a short delay; if it persists, include X-Request-Id in your bug report |
| 502/503/504 | upstream_unavailable | Provider 5xx or timeout | Retry idempotent calls; for trades, the client decides |
Examples
401 unauthenticated
bashcurl -i "https://api.artery.questflow.ai/v1/events"httpHTTP/1.1 401 Unauthorized
Content-Type: application/json
X-Request-Id: req_5a6604f3-...
{ "error": { "code": "unauthenticated", "message": "Missing Authorization header", "requestId": "req_5a6604f3-..." } }403 forbidden_scope
bash# A `read`-only key trying to mint a webhook (admin scope required)
curl -i -H "Authorization: Bearer art_live_readonly..." \
-X POST "https://api.artery.questflow.ai/v1/webhooks" \
-d '{}'httpHTTP/1.1 403 Forbidden
{ "error": { "code": "forbidden_scope", "message": "Required scope: admin", "requestId": "req_..." } }429 rate_limited
httpHTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 10000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1780822653
{ "error": { "code": "rate_limited", "message": "Rate limit exceeded for tier \"free\" (10000/month)", "requestId": "req_..." } }Retry-After is in seconds, not milliseconds. Most HTTP clients honor it automatically
(e.g. axios-retry, Python requests with urllib3.Retry).
404 not_found
bashcurl -i -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/events/evt_does_not_exist"httpHTTP/1.1 404 Not Found
{ "error": { "code": "not_found", "message": "event evt_does_not_exist not found", "requestId": "req_..." } }502 upstream_unavailable
jsonc// Polymarket Polygon RPC returned 504
{
"error": {
"code": "upstream_unavailable",
"message": "Polymarket /book request failed after 5 attempts",
"provider": "polymarket",
"upstream": { "status": 504, "body": "gateway timeout" },
"requestId": "req_..."
}
}Request id propagation
Every response carries X-Request-Id. If you send your own
X-Request-Id header (non-empty string ≤128 chars) Artery echoes it
back unchanged — useful for stitching distributed traces:
bashcurl -i -H "Authorization: Bearer art_live_<your-key>" \
-H "X-Request-Id: my-trace-abc-123" \
"https://api.artery.questflow.ai/v1/events"
# → X-Request-Id: my-trace-abc-123Retry policy
Artery's transport layer retries idempotent calls (GET) on network
errors and 5xx automatically:
| Attempt | Delay |
|---|---|
| 1 | 0 ms |
| 2 | 100 ms ± 20% |
| 3 | 250 ms ± 20% |
| 4 | 500 ms ± 20% |
| 5 | 1000 ms ± 20% |
After attempt 5 the error surfaces with code: upstream_unavailable.
POST and DELETE are not retried — the caller decides
idempotency.
See also
- Error handling concept — upstream passthrough details
- Rate limits & quotas — full 429 reference
- Authentication — scope rules