Authentication
Authenticate API requests with project-scoped API keys, layered with grants, wallet bindings, and network controls.
SDP layers independent controls onto a single API key — grants, wallet bindings, and IP allowlist — so a key grants exactly the access its integration needs, and no more.
Authenticated API requests carry an API key in the Authorization header:
curl https://api.solana.com/v1/wallets \
-H "Authorization: Bearer sk_test_..."The Bearer prefix is optional — a raw key in the Authorization header is also accepted.
Key scope
Keys are issued per project and inherit that project's environment: sk_test_ keys belong to sandbox projects (devnet), sk_live_ keys to production projects (mainnet-beta). A key can only reach the wallets, tokens, and payments of the project it was issued for; there is no cross-project or organization-wide key.
Authorization layers
A request must clear every layer that is configured on its key:
| Layer | What it controls |
|---|---|
| Grants | The actions (module.verb) the key may perform. Each route needs exactly one; see Grants by module |
| Wallet bindings | Restrict which custody wallets the key can operate, each binding with its own permission list |
| IP allowlist | Optional IPv4/IPv6/CIDR ranges the key may be used from, set on the key; an organization-wide list under Settings applies to every key on top. Neither restricts signed-in dashboard members |
Errors
Authentication and authorization failures are always machine-distinguishable: the code says what kind of refusal it is, and a 403 names what it is missing in error.details.
| Condition | Status | Code | error.details |
|---|---|---|---|
| No API key on a keyed-only request | 401 | UNAUTHORIZED | |
| Malformed or unknown key | 401 | INVALID_API_KEY | |
| Key has been revoked, its organization was deleted or suspended, or its project was archived | 401 | REVOKED_API_KEY | |
| Key past its expiry or rotation deadline | 401 | EXPIRED_API_KEY | |
| Request origin not in the key's or the organization's IP allowlist | 403 | FORBIDDEN | reason: "ip_not_allowed" |
| Route only a signed-in dashboard member may use, such as managing keys, members or the organization | 403 | FORBIDDEN | reason: "human_session_required" |
| Key lacks the route's action, or the action a signature it asks for needs | 403 | INSUFFICIENT_PERMISSIONS | action names the missing action |
| SDP refused to sign for the request (see below) | 403 | FORBIDDEN | reason names why |
| Per-key rate limit exceeded | 429 | RATE_LIMITED |
Signing refusals
SDP decides again each time it signs on a key's behalf, so a change to the key or the organization applies to the next signature, including one later in the same request. Nothing is signed or sent on refusal. error.details.reason is one of:
reason | Why |
|---|---|
wallet_out_of_scope | The custody wallet is outside the key's wallet scope |
wallet_purpose | The custody wallet's purpose may not send payments: only a transfer wallet can be the source of a transfer, batch, recurring payment or offramp |
principal_not_live | The key was revoked, expired, or passed its rotation deadline during the request |
organization_inactive | The organization is suspended or deleted |
production_not_enabled | The project is a production project and the organization does not have production access |
project_not_found | The project no longer exists |
principal_missing | No credential is attached to the movement |
A key without the action a signature needs is refused with INSUFFICIENT_PERMISSIONS and error.details.action, exactly as at the route.
Requests are rate limited per key; the standard tier allows 100 requests per minute.
See Manage API Keys for creating, rotating, and revoking keys.