Error Code Reference
Every error code the Vault can return, what it means, and what to do about it.
How errors look
Every error response from the Vault is JSON with a human-readable
error message and a machine-readable code:
{
"status": "error",
"error": "Would exceed daily limit",
"code": "EXCEEDS_DAILY_LIMIT"
}
The HTTP status of the response tells you the kind of failure; the code tells you the exact cause. As a rule of thumb:
| HTTP status | What it means | What to do |
|---|---|---|
| 400 | The request itself is wrong | Fix the request before retrying |
| 401 | Authentication failed | Check credentials and signature |
| 403 | Understood, but refused | Retrying unchanged will not help |
| 404 | The thing referenced does not exist | Check the identifier |
| 409 | Conflicts with current state | Start over with a fresh request |
| 429 | A limit that clears on its own | Back off and retry later |
| 500 | Something failed on the Vault's side | The request was fine; retrying is reasonable |
| 503 | Temporarily unavailable | Often user-fixable in the app (unlock wallet, reconnect device); retry after |
Agent API
Codes returned by the agent-facing API (default port 4663): the
/sign, /trade, /callback,
/mandate and status endpoints.
400 — Malformed request
| Code | Meaning |
|---|---|
INVALID_JSON | The request body is not valid JSON |
INVALID_CONTENT_LENGTH | The Content-Length header is missing or invalid |
MISSING_AGENT_ID | No agent_id in the request |
MISSING_SIGNATURE | No signature in the request |
MISSING_TRADE | /trade called without a trade object |
MISSING_REQUEST_ID / INVALID_REQUEST_ID | Status poll without a request id, or with a malformed one |
MISSING_TRANSACTION_ID / INVALID_TRANSACTION_ID | Transaction lookup without a transaction id, or with a malformed one |
MISSING_EVENT / INVALID_EVENT | /callback without an event, or with one that is not a recognized value |
MISSING_TX_HASH / INVALID_TX_HASH | Settlement callback without an on-chain transaction hash, or with a malformed one |
MISSING_X402_DATA | Payment request without the x402 payment data |
INVALID_X402_DATA / INVALID_X402_FORMAT | The x402 payment data does not parse or is not in a supported format |
INVALID_X402_RESPONSE / INVALID_RESPONSE_STATUS | Reserved for x402 response validation (not currently emitted) |
INVALID_PAYMENT_REQUIRED | The 402 Payment Required data forwarded by the agent is invalid |
INVALID_PAYMENT_DATA | Payment fields fail validation |
FIELD_NOT_PERMITTED | The trade includes a field the agent may not set (agent_id, wallet_address, recipient) — the Vault takes these from the authenticated credentials |
BAD_REQUEST | The trade object fails shape validation |
INVALID_REQUEST | Generic malformed request |
ERROR | Generic fallback when no more specific code applies |
401 — Authentication failed
AUTH_FAILED | The bearer token or HMAC signature did not verify. Check the token, the signed message shape, and clock skew (HMAC timestamps have a replay window) |
403 — Refused by policy or configuration
Retrying the same request will not help; either the request must change or the user must change the policy.
| Code | Meaning |
|---|---|
UNAUTHORIZED | The agent is not allowed to access this resource (e.g. another agent's transaction) |
AGENT_SUSPENDED | The user has suspended this agent |
AGENT_NOT_COMMISSIONED | The agent is registered but not commissioned with a policy and address |
ADDRESS_NOT_COMMISSIONED | The wallet address the agent is tied to is no longer commissioned |
NETWORK_DISABLED | The requested chain is disabled in Vault settings |
NETWORK_NOT_ALLOWED_BY_POLICY | The requested chain is excluded by the agent's policy |
X402_DISABLED | x402 payments are switched off in the agent's policy |
DOMAIN_NOT_ALLOWED | The payee's domain is not on the policy's allowlist |
DOMAIN_URL_REQUIRED | The policy restricts payees by domain, but the request carried no URL to check |
UNSUPPORTED_ASSET | The payment asset is not one the Vault supports |
EXCEEDS_PER_REQUEST_MAX | The amount is over the policy's per-request maximum. Permanent for this request — split it into smaller ones |
POLICY_REJECTED | A trade was refused by the policy at approval or execution time |
LEDGER_REJECTED | The user declined the signature on their hardware wallet |
FOREIGN_HOST_REJECTED | The request came from another machine; the API serves localhost only (unless --allow-lan) |
BROWSER_ORIGIN_REJECTED | The request came from a web page; browser calls are refused |
404 — Not found
AGENT_NOT_FOUND / UNKNOWN_AGENT | No agent with that id (it may have been deleted) |
POLICY_NOT_FOUND | The agent's policy no longer exists |
AGENT_OR_POLICY_MISSING | The agent or its policy has been removed since the request was made |
TRANSACTION_NOT_FOUND | No transaction with that id |
REQUEST_NOT_FOUND | No pending request with that id. Note that payment and trade requests live on different status endpoints; polling the wrong one returns this |
ADDRESS_NOT_FOUND | The referenced wallet address does not exist |
NOT_FOUND | The URL path does not match any endpoint, or the resource does not exist |
405 / 413 — Transport-level rejections
METHOD_NOT_ALLOWED | Wrong HTTP method for this endpoint (the Allow header lists valid ones) |
PAYLOAD_TOO_LARGE | Request body over the 1 MB limit |
409 — Conflicts with current state
The request was fine when made, but the world has moved. Start over with a fresh request rather than retrying this one.
REQUEST_ALREADY_PROCESSED | This approval request was already approved or rejected |
PAYMENT_ALREADY_SETTLED | This payment already settled on-chain; use an idempotency_key for a fresh payment |
REQUEST_EXPIRED | The approval window closed before the user acted; submit a new request |
PRICE_MOVED | The market moved past the slippage tolerance while the trade awaited approval; re-quote and resubmit |
TRADE_NO_LONGER_VALID | The trade could not be revalidated at approval time |
429 — Limits that clear on their own
Nothing about the request is wrong; waiting is the remedy.
RATE_LIMIT_EXCEEDED | Too many requests in a short window |
EXCEEDS_DAILY_LIMIT / LIMIT_REACHED | The policy's daily spending limit is used up; it renews at local midnight, once at least 20 hours have passed since the last renewal |
TOO_MANY_PENDING | Too many requests from this agent already await user approval |
500 — Failed on the Vault's side
The request was acceptable; something went wrong executing it. Resending unchanged is reasonable.
SIGNING_ERROR | Signing failed unexpectedly |
EXECUTION_ERROR | A trade failed during on-chain execution |
REQUOTE_FAILED | The Vault could not re-quote the pool to validate the trade |
NO_WALLET_PROVIDER / NO_WALLET | No wallet is loaded in the Vault |
NO_WALLET_ADDRESS | The wallet has no address to trade or sign from |
SDK_NOT_FOUND | A required signing dependency is not installed |
LEDGER_ERROR | The hardware wallet returned an unexpected error |
LEDGER_NO_SIGNATURE | The hardware wallet returned no signature |
INTERNAL_ERROR | Unexpected internal failure; details are in the Vault log |
503 — Temporarily unavailable
Usually fixable by the user in the Vault app; retry once fixed.
SERVICE_NOT_READY | The Vault is starting up or the service is not running |
WALLET_LOCKED | The wallet is locked; the user must unlock it in the app |
WALLET_ADDRESS_NOT_FOUND | The agent's address is not in the currently unlocked wallet; the user likely has the wrong wallet open |
LEDGER_DISCONNECTED | The hardware wallet is unplugged or asleep |
LEDGER_SIGN_NOT_AVAILABLE | Hardware-wallet signing needs the GUI, which this Vault instance is running without |
Codes on successful responses
Two codes ride along on non-error responses to add detail:
APPROVAL_REQUIRED (202) | Accompanies status pending: the user must approve in the app; poll the status endpoint |
ALREADY_SIGNED (200) | Accompanies status success: the payment was signed earlier (the header is no longer cached) |
Admin API
Codes specific to the admin API (default port 4664), used by the CLI and the Vault window itself. Agents never see these.
| Code | Meaning |
|---|---|
GUI_ONLY_MODE (403) | The admin API only accepts commands from the Vault window. Allow other local processes with primer-vault config set admin-api open or --admin-open |
UNLOCK_FAILED (401) | Wrong wallet password |
TOO_MANY_ATTEMPTS (429) | Too many failed unlock attempts; wait out the cooldown |
MISSING_NAME / MISSING_FIELDS (400) | A required field was not provided |
UNKNOWN_SETTING (400) | No setting by that name |
NOT_COMMISSIONED (400) | The operation needs a commissioned agent |
APPROVAL_FAILED (400) | Approving or rejecting the pending request failed |
TRADE_FAILED (400) | The approved trade failed to execute |
CREATE_FAILED / IMPORT_FAILED / DELETE_FAILED / DERIVE_FAILED / RENAME_FAILED (400) | The wallet, seed, or address operation failed; the message says why |
UPDATE_FAILED (400) | Editing the agent failed |
MANDATE_FAILED / SET_MANDATE_FAILED / UPLOAD_FAILED (400) | Generating, setting, or uploading the agent's mandate failed |
WALLET_LOCKED (409) | The operation needs the wallet unlocked |
The admin API also uses NOT_FOUND, INVALID_JSON,
BAD_REQUEST, INTERNAL_ERROR,
FOREIGN_HOST_REJECTED and BROWSER_ORIGIN_REJECTED
with the same meanings as above.
Client-side codes
The bundled Python client (CoreClient) raises errors
carrying a code. Two are synthesized by the client itself and never sent
by the server:
HTTP_ERROR | The server returned an error the client could not parse as JSON |
ERROR | Default when no code is available |
The authoritative machine-readable list is
ERROR_CODE_TO_HTTP_STATUS in
src/primer_vault/services/server.py in the Vault repository.
If a code you encounter is not on this page, that map is the place to
check first.