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.
The Proxio API uses one authentication scheme: a Bearer API key sent in the
Authorization header. There is no second token, no cookie, and no separate
auth domain.
Authorization: Bearer pxo_9fJ2kQ7xR4mN8pL1dW6vB3cH5tZ0aYqS7dK2mN9xEvery endpoint requires a valid key except the two public discovery routes: a
GET on the base URL itself and GET /openapi.json.
Key format
A key looks like pxo_ followed by 40 base62 characters. It is generated from
30 cryptographically random bytes, so it carries roughly 240 bits of entropy.
- The full key is shown once, at creation. It is stored only as a SHA-256 hash, so Proxio can never show it to you again.
- Only a short prefix (the first 12 characters, e.g.
pxo_9fJ2kQ7x) is retained for display, so you can tell keys apart in the dashboard. - Lost keys cannot be recovered. Revoke and recreate instead.
Store keys as secrets
Keep keys out of source control, browsers, and logs. Inject them from a secret manager or environment variable at runtime. If a key leaks, revoke it in Settings → API Keys immediately.
Managing keys
Keys are created and managed in the dashboard under Settings → API keys, or programmatically through the API Keys endpoints themselves, so a pipeline can mint and revoke its own credentials without a human opening the dashboard. Each key carries a name, its scopes, an optional expiry, an optional list of allowed IPs, and an optional per-key rate-limit override. The override is self-service in one direction only: you can set a key below the default of 120 req/min, but raising a key above the default is done by Proxio support. The dashboard table also shows each key's prefix, when it expires, its allowed IPs, and a Last used timestamp so you can spot idle or stale keys.
A key can only mint a weaker-or-equal key
Minting through the API carries one more rule the dashboard doesn't need to enforce on itself: a key can only create a key whose scopes, expiry, IP allowlist, and rate limit are no broader than its own. See API Keys for the exact inheritance and narrowing rules.
Scopes
Every key carries one or more scopes. Each endpoint declares exactly one scope
it requires, and the key must literally contain it, there is no implicit
elevation from write to read.
| Scope | Grants |
|---|---|
read | Every GET: account, products, services, usage, credentials, sessions, whitelist, locations, wallet (including top-ups and transactions), orders, webhooks (including deliveries and the event log), and API keys. Also POST /orders/quote, it prices without spending, so it's scoped like a read. |
write | Mutations that don't spend money: credential create / update / delete / rotate, whitelist add / remove (including batch add), session rotate / delete, webhook management (including redeliver), toggling a service's auto-renewal, and API key create / revoke. |
purchase | Wallet spend, and opening a wallet top-up: POST /orders, POST /services/{id}/renew, and POST /wallet/topups. |
The dashboard offers three presets when you create a key:
- Read-only =
read - Automation =
read,write - Full =
read,write,purchase
If a key is missing the scope an endpoint needs, the request fails with
INSUFFICIENT_SCOPE (403) and a
details array naming the required scope:
{
"error": {
"code": "INSUFFICIENT_SCOPE",
"message": "This key is missing the required scope.",
"doc_url": "https://docs.proxio.net/docs/api/errors#insufficient_scope",
"request_id": "req_8Ke2jP4mQ",
"details": [{ "required": "purchase" }]
}
}IP allowlists
Each key can pin the source IPs that are allowed to use it. Store a list of
CIDR ranges on the key; a call from an IP outside every range fails with
IP_NOT_ALLOWED (403). Leave the list empty
to allow any IP.
The IP that is checked is the public source address Proxio sees for the request. Allowlists pair well with server-to-server integrations that run from a stable set of egress IPs.
Expiry
A key can carry an expires_at timestamp. After it passes, the key stops
working and calls fail with
EXPIRED_API_KEY (401). Short-lived keys
are a good fit for time-boxed jobs and contractors. Leave expires_at unset for
a key that never expires on its own.
How a request is authenticated
On every call the API runs these checks in order and returns the first failure:
| Check | Failure code | HTTP |
|---|---|---|
Authorization header present | UNAUTHENTICATED | 401 |
Header is a well-formed Bearer pxo_… and the key hash matches | INVALID_API_KEY | 401 |
| Key is not revoked or deactivated | REVOKED_API_KEY | 401 |
Key is not past its expires_at | EXPIRED_API_KEY | 401 |
| Owning account is not suspended | ACCOUNT_SUSPENDED | 403 |
| Caller IP is within the key's allowlist | IP_NOT_ALLOWED | 403 |
| Key carries the endpoint's required scope | INSUFFICIENT_SCOPE | 403 |
A malformed key and an unknown key both return INVALID_API_KEY with the same
HTTP status and error code (the message text itself differs slightly, "the
provided API key is malformed" versus "...is invalid"), so the API never
reveals whether a given prefix exists.
Rotating keys
Because a key is scoped and independent, rotation is a clean, zero-downtime swap:
Create the replacement
Mint a new key with the same scopes (and IP allowlist, if any) in
Settings → API Keys. Copy the full pxo_… value once.
Deploy it
Roll the new key out to your services or secret manager. Because each key has its own rate-limit budget, the old and new keys run side by side without interfering.
Revoke the old key
Once traffic has fully moved, revoke the old key. Revocation is a soft delete:
the key stays listed as revoked for audit, and any further call with it returns
REVOKED_API_KEY.
Related pages
Getting Started
Create an API key in the Proxio dashboard, verify it with GET /account, and generate your first proxy list, with copy-paste examples in cURL, Python, and Node.js.
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.

