Authentication

API keys, the Bearer header, environments baked into the key, and the four scopes that gate every endpoint.

Every request except GET /health authenticates with an API key, sent as a Bearer token. The key carries everything Acute needs: which organization you are, which environment you are in, and what you are allowed to do.

bash
curl https://sandbox.api.acute.network/v1/wallets \
  -H "Authorization: Bearer acuinf_test_aV6Rn2pQk8wXyZ3mB1cD4eF7"

A key is a prefix that names the environment, followed by an opaque secret:

text
acuinf_test_<secret>     ← sandbox
acuinf_live_<secret>     ← production

The secret is 24 random bytes, base64url-encoded. The environment is part of the key: there is no separate "mode" flag to forget. A test key only works on the sandbox host (https://sandbox.api.acute.network/v1); a live key only works on the live host (https://api.acute.network/v1). They are not interchangeable: send a key to the host for the other environment and it is rejected with 401 (API_KEY_ENVIRONMENT_MISMATCH), so a key minted for one environment cannot be pointed at the other.

The secret is shown once

Acute stores only a hash of your key, never the secret. When you mint a key in the Console, copy it immediately: it is displayed exactly once. Lose it and you rotate, you do not recover.

Send the full key in the Authorization header on every authenticated call:

-H "Authorization: Bearer acuinf_test_..."

A malformed, unknown, or revoked key returns 401 authentication_error with the granular code API_KEY_INVALID (a missing header is API_KEY_MISSING). A well-formed key sent to the host for the other environment (a live key on the sandbox host, or a test key on the live host) returns 401 authentication_error with API_KEY_ENVIRONMENT_MISMATCH. The envelope never leaks an attacker's secret, just the reason the key did not authenticate.

401 Unauthorized
json
{
  "success": false,
  "statusCode": 401,
  "error": {
    "type": "authentication_error",
    "code": "API_KEY_INVALID",
    "message": "Invalid API key",
    "details": {}
  },
  "meta": { "requestId": "req_a17c4e92bd05f3681c0a7e44" }
}

A key carries one or more of four scopes. Each endpoint requires exactly one; if your key lacks it, you get 403 authorization_error (API_KEY_SCOPE_FORBIDDEN). Grant a key only the scopes it needs.

ScopeGrants
walletcreate/list/get wallets, KYC, balance, and withdraw
paymentcreate/list/get payments, refund, and the in-app pay action
transferwallet → wallet transfers
payoutbatch disbursement

Withdraw rides the wallet scope

POST /wallets/:id/withdraw is gated by wallet, not a scope of its own; it is a wallet action. POST /wallets/:id/transfer needs transfer, and POST /wallets/:id/pay needs payment. The scope → endpoint map on the reference overview is exhaustive.

EndpointScope
POST /wallets, GET /wallets, GET /wallets/:idwallet
POST /wallets/:id/kyc, GET /wallets/:id/balancewallet
POST /wallets/:id/withdrawwallet
POST /wallets/:id/transfertransfer
POST /wallets/:id/paypayment
POST /payments, GET /payments, GET /payments/:idpayment
POST /payments/:id/refundpayment
POST /payouts, GET /payouts, GET /payouts/:idpayout
GET /healthnone

Rotation mints a replacement carrying the same environment and scopes, then revokes the old one, so you can deploy the new secret before retiring the old. The new secret is returned once, exactly like creation. Revocation is immediate and idempotent; a revoked key authenticates nothing.

Two keys, two jobs

Keep test and live keys in separate secrets and separate config. A mix-up is loud (a live key pointed at the sandbox host is rejected with API_KEY_ENVIRONMENT_MISMATCH, and vice versa), but the cleanest defence is to never let them share a variable name.