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>:

PartWhat 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.

VerbWhat it allows
readView the module's records
manageCreate and edit records reversibly: drafts, pausing, canceling
executeAnything 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:

ModuleActionsCovers
transferstransfers.read, transfers.executeSingle transfers from a custody wallet
transfer_batchestransfer_batches.read, transfer_batches.executeBatches of transfers, and their estimates
payment_requestspayment_requests.read, payment_requests.managePayment requests and their payment links
counterpartiescounterparties.read, counterparties.manage, counterparties.executeCounterparties, their accounts, and provider requirements
transactionstransactions.readThe unified transaction list; each row also needs the read of its own module
recurringrecurring.read, recurring.manage, recurring.executeRecurring payments and their collection attempts; on beta, also subscription plans and subscriptions
walletswallets.read, wallets.manage, wallets.executeCustody wallets and their balances
projectsprojects.readProjects and provider availability
earnearn.read, earn.manage, earn.executeEarn 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:

walletScopeSigns with
allEvery custody wallet in the key's project
selectedOnly 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

  1. Open API keys in the sidebar. You start with an empty list.

    API keys page with no keys yet

  2. 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.

    Key details step of the new API key page

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

    Review API key step

  4. In the API key generated dialog, click Copy and save the key. It isn't shown again.

    API key generated dialog with the full key redacted
  5. Click Dismiss. The key appears in the table with its prefix, permissions, wallet access, environment, and status.

    API keys list with one active key

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.

  1. In the API keys table, open the key's Actions menu, click Rotate key (24h grace), then confirm with Rotate key.

    Actions menu with Edit API key, Rotate key (24h grace) and Delete key

  2. 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.