Errors
The complete Proxio API error catalog. Every error returns a stable UPPER_SNAKE code, an HTTP status, a request_id, and a doc_url that deep-links to the matching section here. Includes retry guidance per code family.
Every error the API itself raises uses one envelope. There is no
header-encoded error, no bare string, and no HTML page, always this JSON
object under an error key, calling a real path with a verb it doesn't
support is no exception: it answers
METHOD_NOT_ALLOWED in the same envelope, with an
Allow header naming the verbs that path does support.
{
"error": {
"code": "VALIDATION_ERROR",
"message": "quantity_gb must be an integer >= 1.",
"doc_url": "https://docs.proxio.net/docs/api/errors#validation_error",
"request_id": "req_8Ke2jP4mQ",
"details": [{ "field": "quantity_gb", "issue": "too_small", "min": 1 }]
}
}codeis a stable, machine-readableUPPER_SNAKEvalue from the closed catalog below. Branch on this, never on the message.messageis a human-readable English string, safe to log. It may change wording between releases.doc_urldeep-links to this page. The anchor is always the lowercased code, soINVALID_CURSORlinks to#invalid_cursor.request_idmirrors theX-Request-Idresponse header. Quote it when you contact support, it pins the exact request in our logs.detailsis an optional array with structured context (for example the offending fields on a validation error). It is absent when not applicable.
Adding a new code in a later release is a backward-compatible change, so treat
any unrecognized code as a generic failure of its HTTP status class.
Catalog
The Retry column tells you whether resending the same request can succeed: retry codes are transient, non-retry codes need you to change something first.
| Code | HTTP | Retry | Meaning |
|---|---|---|---|
UNAUTHENTICATED | 401 | No | No credentials presented. |
INVALID_API_KEY | 401 | No | Malformed key or no matching key. |
EXPIRED_API_KEY | 401 | No | Key is past its expires_at. |
REVOKED_API_KEY | 401 | No | Key was revoked or deactivated. |
INSUFFICIENT_SCOPE | 403 | No | Valid key lacks the required scope. |
IP_NOT_ALLOWED | 403 | No | Caller IP is outside the key's allowlist. |
ACCOUNT_SUSPENDED | 403 | No | The owning account is suspended. |
FORBIDDEN | 403 | No | Generic authorization failure. Reserved (not currently returned by any endpoint). |
NOT_FOUND | 404 | No | Resource doesn't exist, or isn't owned by the caller. |
METHOD_NOT_ALLOWED | 405 | No | HTTP verb not supported on this path. Allow header (and details[0].allow) names the verbs that are. |
VALIDATION_ERROR | 400 | No | Body or query failed schema validation. details[] present. |
MALFORMED_JSON | 400 | No | Request body isn't valid JSON. |
MISSING_PARAMETER | 400 | No | A required query, path, or header parameter is absent. |
INVALID_CURSOR | 400 | No | Pagination cursor is unparseable or tampered. |
UNSUPPORTED_OPERATION | 400 | No | Operation is invalid for this resource's state. |
INVALID_IP | 400 | No | Whitelist IP is not a public, routable address, or is malformed. |
CONFLICT | 409 | No | Mostly an idempotency dead end: the key can't be reused. Retry with a new key. |
DUPLICATE_RESOURCE | 409 | No | A unique constraint was violated. |
IP_ALREADY_BOUND | 409 | No | Whitelist IP is already on one of your own credentials. details:[{credential_id}]. |
IP_UNAVAILABLE | 409 | No | Whitelist IP is unavailable. Choose a different IP. |
LIMIT_REACHED | 409 | No | A per-resource cap was hit. details:[{limit}]. |
IDEMPOTENCY_KEY_REUSED | 409 | No | Same Idempotency-Key, different request body. |
IDEMPOTENCY_IN_PROGRESS | 409 | Yes | A request with this key is still being processed. |
INSUFFICIENT_BALANCE | 402 | No | Wallet can't cover the order or renewal. |
QUOTA_EXCEEDED | 402 | No | A quota-gated write was blocked. Reserved (not currently returned by any endpoint). |
RATE_LIMITED | 429 | Yes | Per-key rate limit exceeded. Retry-After is set. |
UPSTREAM_ERROR | 502 | Yes | An upstream network dependency failed. |
SERVICE_UNAVAILABLE | 503 | Yes | A dependency is temporarily down. |
INTERNAL_ERROR | 500 | Yes | Unhandled server error. Quote the request_id. |
Authentication (401)
These mean the key itself was rejected. Fix the credential; retrying the same request won't help. See Authentication.
UNAUTHENTICATED
No Authorization header was sent. Add Authorization: Bearer pxo_….
INVALID_API_KEY
The header wasn't a well-formed Bearer pxo_… value, or no key matches the hash.
A malformed key and an unknown key deliberately return the same code (the
message text differs slightly between the two, but neither one says which
case you hit), so the API never confirms whether a given prefix exists.
Re-copy the key.
EXPIRED_API_KEY
The key is past its expires_at. Create a new key (or one without an expiry) and
swap it in.
REVOKED_API_KEY
The key was revoked or deactivated. Revocation is permanent; mint a replacement.
Authorization (403)
The key is valid but not permitted to do this. Change the key or the caller, not the request payload.
INSUFFICIENT_SCOPE
The key doesn't carry the scope the endpoint requires. details names the
missing scope, for example [{ "required": "purchase" }]. Create a key with the
right scope.
IP_NOT_ALLOWED
The caller's IP is outside the key's IP allowlist. Call from an allowed IP, or update the allowlist on the key.
ACCOUNT_SUSPENDED
The account that owns the key is suspended. Contact support, no key change will lift it.
FORBIDDEN
A generic authorization failure that isn't covered by a more specific 403 code. Reserved: it is defined in the catalog but not currently returned by any endpoint.
Not found (404)
NOT_FOUND
The resource doesn't exist, or it exists but isn't owned by your account.
Ownership failures return 404, never 403, so the API never confirms the
existence of resources you can't see. Check the id.
Method (405)
METHOD_NOT_ALLOWED
The path exists but doesn't support this HTTP verb, DELETE /locations for
example, the geo catalog has no DELETE. It's the ordinary error envelope
like every other code on this page, plus an Allow header naming the verbs
that path actually does support:
HTTP/1.1 405 Method Not Allowed
Allow: GET, HEAD, OPTIONS{
"error": {
"code": "METHOD_NOT_ALLOWED",
"message": "The DELETE method is not supported on this endpoint.",
"doc_url": "https://docs.proxio.net/docs/api/errors#method_not_allowed",
"request_id": "req_8Ke2jP4mQ",
"details": [{ "method": "DELETE", "allow": "GET, HEAD, OPTIONS" }]
}
}details[0].allow is the same string as the Allow header, in the body too
in case your client doesn't surface response headers as conveniently as it
surfaces JSON. HEAD is listed automatically whenever GET is supported, and
OPTIONS always is. Check the method against the
endpoint's reference page rather than guessing from this
response, Allow tells you what's supported on this path, not what the
verb you meant to use is called elsewhere in the API. This check runs before
authentication, so a wrong-verb call answers 405 even with no Authorization
header, or an invalid one, it never spends a rate-limit unit either.
Validation (400)
The request was understood but rejected. Correct the request and resend.
VALIDATION_ERROR
The body or query failed schema validation. details[] lists each offending
field with its issue (and constraints like min). Fix the named fields.
MALFORMED_JSON
The request body isn't valid JSON. Check for trailing commas, unquoted keys, or
a wrong Content-Type.
MISSING_PARAMETER
A required query, path, or header parameter is absent. For a money endpoint
called without an idempotency key, details is [{ "header": "Idempotency-Key" }].
Supply the missing parameter.
INVALID_CURSOR
The pagination cursor couldn't be decoded, it was truncated or altered. Never
build cursors yourself; only pass back the exact meta.next_cursor you received.
See Pagination.
UNSUPPORTED_OPERATION
The operation is invalid for this resource's current state, for example creating a credential on a static IP (ISP/datacenter) service, or topping up an unlimited package. The message explains the specific reason.
INVALID_IP
Only a public, routable address can be whitelisted. details[0].reason is one
of two values:
| Reason | Meaning |
|---|---|
INVALID_IP | The value isn't a parseable IP address. |
PRIVATE_OR_RESERVED | The address parses but isn't eligible: private, reserved, loopback, link-local, CGNAT, or otherwise not usable for whitelisting. |
The API deliberately does not distinguish further, so don't build logic on why a specific address was refused. Submit an address you control from the public internet. See Whitelist.
Conflict (409)
CONFLICT
In practice this is the idempotency layer telling you a key is spent and cannot be reused. It arrives in two situations, and the message distinguishes them:
| Situation | What happened |
|---|---|
| A previous request with this key did not complete | The original attempt was abandoned and stayed unfinished for 300 seconds. A committed side effect can't be ruled out, so it is never re-run. |
| A previous request with this key failed after it started | An earlier attempt began executing and then errored in a way that may have left a partial effect. |
Both mean the same thing for your code: the operation is not retryable under
this key. Generate a new Idempotency-Key and send the request again.
Reusing the same key will keep returning CONFLICT until the 24-hour record
expires.
Before you retry, check whether the original operation actually landed. For an
order, list GET /orders and look for a PAID order
matching what you intended; retrying blindly with a new key is what turns one
purchase into two.
IDEMPOTENCY_IN_PROGRESS becomes CONFLICT
A key held by a still-running request returns the retryable
IDEMPOTENCY_IN_PROGRESS. If that request never
finishes, the same key flips to CONFLICT once 300 seconds pass,
and that answer is permanent. So a poll loop on IDEMPOTENCY_IN_PROGRESS must
handle CONFLICT as its terminal case: stop retrying that key, reconcile, and
start over with a new one.
CONFLICT is also reserved as the generic 409 for any future write conflict not
covered by a more specific code, so branch on the code and read the message.
DUPLICATE_RESOURCE
A uniqueness constraint was violated, for example a webhook URL you've already registered. Reuse the existing resource or choose a different value.
IP_ALREADY_BOUND
The whitelist IP is already bound to one of your own credentials.
details carries [{ "credential_id": ... }] naming that credential. Remove
the existing binding first, or bind a different IP.
IP_UNAVAILABLE
The IP can't be whitelisted. The API gives no reason by design, and the response says nothing about who, if anyone, holds it. Choose a different IP. See Whitelist.
LIMIT_REACHED
A per-resource cap was hit: 20 credentials per service, 50 whitelist entries per
credential, or 20 webhooks per account. details carries the limit. Delete
something, or use a different parent resource.
IDEMPOTENCY_KEY_REUSED
You reused an Idempotency-Key with a different request body. Keys are bound
to their first request for 24 hours. Use a fresh key for a genuinely new request.
See Idempotency.
IDEMPOTENCY_IN_PROGRESS
A previous request with this idempotency key is still being processed. This is
retryable: wait briefly and retry with the same key to get the stored result.
It is not retryable forever, though. If the original request never completes,
the key turns into a permanent CONFLICT after 300 seconds, so cap
your poll loop and handle that outcome.
Payment (402)
INSUFFICIENT_BALANCE
Your wallet can't cover the order or renewal total. Top up and retry. See Orders.
QUOTA_EXCEEDED
A quota-gated write was blocked because a quota is exhausted. Raise or reset the relevant quota, then retry. Reserved: it is defined in the catalog but not currently returned by any endpoint.
Rate limit (429)
RATE_LIMITED
You exceeded the per-key rate limit. The response carries Retry-After (seconds)
and the X-RateLimit-* headers. Retryable: back off for Retry-After
seconds and resend. See Rate Limits.
Server (5xx)
These are transient by nature. Retry with exponential backoff and jitter.
UPSTREAM_ERROR
An upstream network dependency failed. Retryable, usually a fresh attempt succeeds.
SERVICE_UNAVAILABLE
A dependency the endpoint needs is temporarily down and it couldn't degrade. Retryable after a short wait.
INTERNAL_ERROR
An unhandled server error. Retryable with backoff; if it persists, contact
support and quote the request_id from the body.
Related pages
Authentication
Authenticate to the Proxio API with a single Bearer key. Learn the pxo_ key format, the read / write / purchase scopes, per-key IP allowlists, expiry, and how to rotate keys safely.
Rate Limits
Proxio API rate limits are documented and per-key. 120 requests per minute by default, a per-key override, and 60 per minute for unauthenticated discovery. Every response carries X-RateLimit-* headers, and 429s include Retry-After.

