error.code is the
machine-readable key to everything below.
Auth & access
| Symptom | Likely cause / fix |
|---|---|
401 invalid_api_key | Key revoked, expired, or mistyped. Check GET /keys from the dashboard; create a fresh key if needed. |
401 unauthenticated | No credential at all: missing Authorization header and no session cookie. |
401 session_expired | The 24 h session TTL passed, or a password change invalidated it. Sign in again. |
403 session_required | You called a session-only endpoint with an API key. Do it from the dashboard or a cookie-authenticated call. |
403 account_disabled | The account was deactivated by an admin. Contact your Aro Media admin. |
401 invalid_credentials on every login | Wrong email/password, or the account is locked after 5 failures (wait 15 min). The response deliberately doesn’t distinguish. |
429 rate_limited | You exceeded a rate limit. Back off with jitter. |
Requests
| Symptom | Likely cause / fix |
|---|---|
415 unsupported_media_type | Write request without Content-Type: application/json. |
400 invalid_json | Body isn’t parseable JSON. |
400 validation_error | Schema violation; error.details lists { path, message } per field. |
400 invalid_amount | Amount must be a positive decimal string in human units ("25.50"). |
404 not_found | The ID doesn’t exist in your account; cross-account IDs read as 404 by design. |
Money movement
Money-movementPOSTs return 201 with a QUEUED transaction; a background
worker executes it. Problems with the request fail the POST; problems during
execution show up later as status: "FAILED" with errorCode on the
transaction (full list in
Errors).
A FAILED transaction never moved funds.
| Symptom | Likely cause / fix |
|---|---|
404 not_found from POST /fund | You’re calling production (Mainnet), where the faucet doesn’t exist. Use the sandbox (dev.aropay.aro.media) for faucet funding; fund production wallets with a real deposit. |
400 faucet_cap_exceeded | Rolling 24 h faucet cap reached (queued and pending requests count). Wait for older requests to age out. |
400 insufficient_stable (backed-mode mint) | Your custodial wallet doesn’t hold enough public stablecoin; fund it first and wait for the funding transaction to confirm. |
400 insufficient_balance (public transfer) | Not enough public stablecoin in the sending wallet. |
400 wallet_not_custodial | Spending requires a custodial wallet; external wallets are watch-only. |
400 invalid_recipient | to is not a valid address, or is the sending wallet itself. |
Transaction stays QUEUED for more than ~5 min | The worker is delayed or down. Your request is safe and resumes automatically when it recovers; report the transaction ID if it persists. |
Transaction stays PENDING | The transaction is broadcast and the worker is waiting for the receipt (it re-broadcasts if the network drops it). The chain can be slow; keep polling until CONFIRMED or FAILED. |
FAILED / insufficient_confidential_balance | The wallet’s decrypted cAROUSD (minus other in-flight debits) can’t cover the amount. Mint first, or wait for a pending mint to confirm before resubmitting. |
FAILED / faucet_failed | The platform operator wallet is out of stablecoin or ETH. Platform-side; report it. |
FAILED / mint_failed mentioning ROLE_MINTER | Float-mode deployment where the operator lacks the minter role. Platform-side. |
FAILED / mint_failed mentioning allow-listing | Backed-mode deployment where the stablecoin isn’t allow-listed on the commitment. Platform-side. |
FAILED / transfer_failed or redeem_failed | The on-chain call reverted; error has specifics. Often gas on the sending wallet (fund it) or balance on the relevant rail. |
FAILED / job_exhausted | Repeated RPC or relayer outages exhausted the worker’s retries before anything was broadcast. Safe to resubmit; report it if it recurs. |
FAILED / wallet_unavailable or wallet_key_unreadable | The wallet disappeared or its key can’t be decrypted. The former means the wallet was removed; the latter is platform-side, report it. |
Redeem stuck in SETTLING | The burn is confirmed and the payout is pending. In backed mode the worker waits for the public decryption to be published (re-checked every 30 s); if it persists for a long time, the operator may be out of ETH or liquidity (platform-side). Your cAROUSD is burned and the payout will follow; the row is never failed at this stage. |
Confidential operations
| Symptom | Likely cause / fix |
|---|---|
First balance read after a deploy is very slow, or the first confidential transaction after a worker restart takes longer to leave QUEUED | FHE keys are fetched on first use per process (web and worker separately). Subsequent calls are faster; don’t tune timeouts to the first call. |
External wallet shows confidential.amount: null | Expected: AroPay can’t decrypt balances it doesn’t hold keys for. encrypted: true confirms it. |
503 fhe_credentials_missing | The production deployment has no FHE relayer credentials configured yet; confidential operations are disabled until the platform sets them. Platform-side; report it. |
503 naming a configuration variable | The deployment is missing a required environment setting. Platform-side; report the message text. |
Aro Mail
| Symptom | Likely cause / fix |
|---|---|
400 invalid_postal_address | The address breaks the destination’s rules: US needs a two-letter state and a ZIP/ZIP+4 with lines of at most 64 characters; elsewhere region or postal must be present; line2 + landmark must fit the second-line limit. At mailing time it means Lob rejected the address; the message carries Lob’s reason. |
400 country_not_supported | The destination is blocked or not allow-listed on this deployment. Platform-side lists; ask your Aro Media admin. |
Letter stays DRAFT with a PENDING escrow | The escrow receipt is not in yet. Keep polling GET /transfer/letter/{id}; it flips to ESCROWED once the receipt lands and the amount verifies, or to FAILED if the transfer reverted or clamped. |
Letter FAILED right after creation | Escrow reverted or clamped to zero (400 insufficient_confidential_balance / 502 transfer_failed). Nothing left your wallet; mint more cAROUSD or check gas, then create a new letter. |
502 escrow_unsettled from create | The escrow transfer was broadcast (hash in the message) but could not be recorded fully. Value has left your wallet and the letter exists as DRAFT with error set. Do not create it again: find it in GET /transfer/letter, then poll GET /transfer/letter/{id}; reconciliation flips it to ESCROWED and returns the claim code. |
409 letter_not_printable | You asked for the PDF of a DRAFT or FAILED letter. Wait for ESCROWED. |
400 delivery_method_unavailable | courier is not offered; use standard or tracked. |
400 claim_origin_not_public | The letter’s claim origin (fixed at creation) is not https:; typical on local or misconfigured deployments. Set AROPAY_PUBLIC_URL and create a new letter. Self-printing still works. |
402 postage_unavailable | Lob is unavailable or rejected the letter for a non-address reason. The letter is still ESCROWED; retry /mail later or print it yourself. |
503 mail_provider_not_configured | The print provider key is missing or invalid, or the operator’s return address is incomplete. Platform-side; report it. GET /transfer/letter/rules shows mailing.available. |
mail.testMode: true and nothing arrives | The deployment uses a Lob test key: letters are validated and priced but never printed or mailed. Expected on the sandbox. |
mail.status / tracking never updates | Lob status refreshes on owner detail reads at most every 15 minutes, and only for MAILED letters; the list endpoint never calls Lob. Read GET /transfer/letter/{id}. |
400 void_requires_acknowledgement | Voiding a MAILED letter needs { "acknowledgeMailed": true }, confirming the paper letter will stop working. |
400 letter_not_escrowed / letter_already_mailed | /mail or /void called in a state that does not allow it (DRAFT, CLAIMING, or a terminal state; /mail twice). Read the letter first. |
400 invalid_claim_code | The code was mistyped or truncated. It is 28 characters in seven groups of four; O/0 and I/L/1 are corrected automatically, everything else must match. |
404 letter_not_found from the claim page | No letter matches the code (or, for owners, the id is not in your account). |
409 already_claimed | Someone else already claimed this letter. The same recipient claiming again simply gets the existing claim back. |
409 letter_not_claimable | The letter was voided by the sender (or is DRAFT/FAILED). Contact the sender. |
410 letter_expired | Claimed after expiresAt; the sender has been (or is being) refunded. |
400 cannot_claim_own_letter | You are the sender, or the chosen wallet is the sending address. Void the letter instead. |
502 claim_failed | The release transfer reverted; the letter is claimable again, retry. |
502 claim_unsettled | The release transfer was broadcast but could not be recorded fully. Not a revert: the letter stays CLAIMING for you and the payout is on its way. Claim again with the same account (or refresh the claim page); reconciliation returns the completed claim. |
503 escrow_vault_underfunded | The platform vault cannot cover the payout right now; the letter stays claimable. Platform-side; report it. |
429 letter_velocity_limit | Too many letters in flight for your account (default 25) or that destination country (default 250). Wait for letters to be claimed, voided or expired. |
needsReview: true on a letter | A leg could not be verified automatically (event missing, decryption failed, or a submission whose outcome is unknown). Money is safe: nothing moves until an operator reviews the error text. Report the letter id. |
Refund FAILED after void or expiry | The refund transfer reverted or the vault was short. The letter stays VOIDED/EXPIRED; the scheduled sweep retries the refund. |
Receipts & files
| Symptom | Likely cause / fix |
|---|---|
409 receipt_unavailable from GET /transactions/{id}/nota | The transaction is still QUEUED (no hash yet); details.retryable is true. Retry once it is PENDING or terminal. |
400 receipt_unavailable from GET /transactions/{id}/nota | The transaction FAILED before anything was submitted; there is nothing to notarize and no receipt will ever exist. |
400 invalid_nota registering a wallet | The file isn’t a valid Notareum address card; re-export it from the source wallet. |
Still stuck?
Grab the failing request’serror.code and error.message, the transaction
or letter ID if there is one, and a timestamp, then contact your Aro Media
admin. Admins can reset passwords, clear 2FA, re-activate accounts, check
operator health, and review Aro Mail letters flagged needsReview.