Manage API Keys
Create, rotate, and revoke API keys with explicit grants by module, a signing wallet scope, and zero-downtime rotation.
API keys authenticate SDP API requests. Each key belongs to a project, inherits its environment, and holds an explicit list of grants plus optional controls. Authentication covers how SDP enforces them.
Keys are credentials, not administrators. Only a signed-in organization admin creates, changes, rotates, or revokes a key, in the dashboard. An API key can't manage keys, the organization, its members, or project members, whatever it's granted: it's refused with 403 FORBIDDEN and error.details.reason set to human_session_required.
SDP returns the full secret once, at creation and at rotation, and stores only a keyed hash of it. A lost key can't be recovered, only rotated or replaced. Every key records lastUsedAt, so you can find stale keys before you revoke them.
Key format
Keys use the shared Solana Foundation format, sf_<key id>_<secret><checksum>:
| Part | What it is |
|---|---|
<key id> | 16 hex characters. Not secret: safe to log, and shown in the dashboard to tell keys apart |
<secret> | 43 base62 characters (256 random bits) |
<checksum> | 6 base62 characters, so a mistyped key is refused at once and secret scanners can match a leaked one |
The format doesn't name an environment: a key reaches the network of the project it was created in (sandbox: devnet, production: mainnet-beta). Keys created before this format, prefixed sk_test_ or sk_live_, keep working until they are revoked.
Grants by module
Every API route performs exactly one action, named module.verb, and a key may call a route only when it holds that action. A key holds an explicit list of grants: there is no role and no wildcard.
| Verb | What it allows |
|---|---|
read | View the module's records |
manage | Create and edit records reversibly: drafts, pausing, canceling |
execute | Anything irreversible or that leaves SDP: onchain transactions, provider calls, moving money, and changing where money goes, such as a payout destination or a recurring amount |
manage and execute each include the same module's read. Nothing else implies another action: execute doesn't include manage, and a grant on one module gives nothing on another. Some modules add verbs of their own. These are the actions a key can be granted on this deployment:
| Module | Actions | Covers |
|---|---|---|
transfers | transfers.read, transfers.execute | Single transfers from a custody wallet |
transfer_batches | transfer_batches.read, transfer_batches.execute | Batches of transfers, and their estimates |
payment_requests | payment_requests.read, payment_requests.manage | Payment requests and their payment links |
counterparties | counterparties.read, counterparties.manage, counterparties.execute | Counterparties, their accounts, and provider requirements |
transactions | transactions.read | The unified transaction list; each row also needs the read of its own module |
recurring | recurring.read, recurring.manage, recurring.execute | Recurring payments and their collection attempts; on beta, also subscription plans and subscriptions |
wallets | wallets.read, wallets.manage, wallets.execute | Custody wallets and their balances |
projects | projects.read | Projects and provider availability |
earn | earn.read, earn.manage, earn.execute | Earn strategies, deposits, withdrawals and programs |
Some actions are never granted to a key, because keys are credentials, not administrators: key, member, organization, and project administration, and every provider, credential, or connection setup (configure). A create or update naming one is refused with 400. So is a grant on a module this deployment doesn't run; a key that already holds one keeps it when an update sends it back.
A request without the route's action is refused with 403 INSUFFICIENT_PERMISSIONS, and error.details.action names the missing action. Nothing runs or is recorded on refusal. Grants are read on every request, so a change applies to the key's next request.
You can grant a key only actions you hold yourself. Everything in the dashboard writes out every action available when the key is created; an action added to SDP later isn't granted to existing keys.
Wallet access
A key's wallet scope decides which custody wallets SDP may sign with on its behalf:
walletScope | Signs with |
|---|---|
all | Every custody wallet in the key's project |
selected | Only the wallets listed in walletIds. With none listed, the key signs with no custody wallet; SDP's fee sponsor is outside wallet scope |
walletIds are custody wallet IDs, the id (cwlt_…) that GET /v1/wallets returns, not a provider's wallet ID. A key keeps the exact wallets you listed: a wallet created later, even one reusing a provider ID, is never added.
SDP checks the scope each time it signs, together with the key's grants and whether it's still active. A narrowed scope or a revoked key stops the next signature, including one later in the same request. A signature outside the scope is refused with 403 FORBIDDEN and error.details.reason set to wallet_out_of_scope; nothing is signed or sent.
The scope narrows what a key may sign with, not what it may read. A selected key's wallet list (GET /v1/wallets) and wallet detail reads show only its wallets; every other read, such as transfers, batches, and recurring payments, covers the whole project. Give a separate project to an integration that must not see another's records.
For recurring payments the scope also bounds writes that sign nothing. A selected key may create, change, cancel, activate, collect, or resume only a recurring payment whose source wallet it lists, and may move one only to another listed wallet, because whoever activates a pending payment signs the terms it was last given. Anything else is refused with wallet_out_of_scope before it is recorded. Reading and listing recurring payments still cover the whole project.
The dashboard starts a key that holds any execute action on Selected wallets, so it can sign with nothing you didn't give it. Choose All wallets to widen it.
Recurring payments outlive the key
A recurring payment is collected on schedule as its own mandate, not as the key or member that activated it. SDP checks that the payment is active, that it pays from its own source wallet, that its counterparty and account are active in the project, and that the organization may move money. Revoking or narrowing the key that activated it doesn't stop it, so rotating a key never interrupts a customer's schedule.
Archiving the counterparty archives its active accounts too, and new transfers
and batches can't use them. Archival that commits before a new collection is
admitted prevents it from signing and pauses the recurring payment with
pausedReason: "destination_unavailable". A collection admitted earlier may
still finish or recover, and earlier requests still replay their recorded
outcomes.
The dashboard shows the pause reason and offers Cancel. Paused schedules can't be edited, so cancel the schedule and create a new one with an active recipient.
Create a key
-
Open API keys in the sidebar. You start with an empty list.

-
Click Create new API key. Once the project has keys, the button reads New API key. Fill in each step, then click Continue:
- Details: a Name (for example, "CI deploy key"), an optional Expiration, and optional Allowed IP addresses, one IPv4 or IPv6 address or CIDR range per line. An empty list allows any address. The Environment is the project's.
- Permissions: a checkbox per verb for each module (Read, Manage, Execute); ticking Manage or Execute includes Read. Everything ticks every offered action, and Clear all clears them. Grant at least one.
- Access: All wallets, or Selected wallets with at least one wallet picked.

-
On Review API key, check the summary and click Create key.

-
In the API key generated dialog, click Copy and save the key. It isn't shown again.
-
Click Dismiss. The key appears in the table with its prefix, permissions, wallet access, environment, and status.

To change a key's details, permissions, or wallets later, open its Actions menu and click Edit API key.
Rotate a key
Rotation replaces the secret without downtime. The new key is a fresh record with the old key's grants, IP allowlist, wallet scope, and expiry. The old key keeps working until its grace deadline, 24 hours after a dashboard rotation; after that, it fails with EXPIRED_API_KEY.
-
In the API keys table, open the key's Actions menu, click Rotate key (24h grace), then confirm with Rotate key.

-
Copy the new key from the API key generated dialog. It's shown once.
Revoke a key
Open the key's Actions menu and click Delete key. Type the key's name to confirm, then click Delete key. The key stops working on its next request and can't be restored. Deleting a rotated key during its grace period ends the overlap early: the replacement keeps working.
If the key may be compromised, tick Also cancel recurring payments this key started ("cancelRecurringPayments": true in the API). Otherwise its recurring payments keep collecting. The response lists canceled schedules and any you must cancel individually. A recurring payment answers to the key that activated or last resumed it, or last changed its source wallet, recipient, token, amount, or period. Metadata and next collection date changes don't change that control.