> Solana Developer Platform docs, served as Markdown. The [docs index](https://platform-beta.solana.com/docs.md) lists every page and [llms.txt](https://platform-beta.solana.com/docs/ai/llms.txt) the key ones; [SKILL.md](https://platform-beta.solana.com/SKILL.md) has the rules for building on SDP.

# Authentication

Authenticate requests with a project-scoped API key, limited by its grants, wallet scope, and IP allowlist.

Send your API key in the `Authorization` header. The example reads it from `SDP_API_KEY`:

```bash
curl 'https://api.solana.com/v1/wallets' \
  -H "Authorization: Bearer $SDP_API_KEY"
```

The `Bearer` prefix is optional; the header also accepts the bare key.

Each key carries three independent controls: grants, a wallet scope, and an IP allowlist. Together they give the key exactly the access its integration needs.

## Key scope

A key belongs to one **project** and inherits that project's environment: a key from a sandbox project reaches devnet, one from a production project mainnet-beta. The key itself doesn't name its environment; the dashboard shows it next to each key. A key reaches only its own project's resources. 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](/docs/developing-with-sdp/manage-api-keys#grants-by-module) |
| **Wallet scope** | Which custody wallets SDP may sign with for the key: `all` in its project, or only the `selected` wallets in `walletIds`; see [Wallet access](/docs/developing-with-sdp/manage-api-keys#wallet-access) |
| **IP allowlist** | Optional IPv4 or IPv6 addresses and CIDR ranges the key may be used from. An organization-wide list under **Settings** > **API key access** 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 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 its project was archived | 401 | `REVOKED_API_KEY` | |
| Key is live but its organization is suspended; the key works again once SDP resumes it | 403 | `FORBIDDEN` | `reason: "organization_suspended"` |
| 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, transfer batch, or recurring payment |
| `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 |
| `movement_disabled` | SDP has disabled this kind of money movement for the project |

A key without the action a signature needs is refused with `INSUFFICIENT_PERMISSIONS` and `error.details.action`, exactly as at the route.

Each key is rate limited by its tier. [Rate limits](/docs/reference/conventions/rate-limits) lists the limits.

See [Manage API Keys](/docs/developing-with-sdp/manage-api-keys) for creating, rotating, and revoking keys.
