Authentication
Money-moving Swap API routes are authenticated with a partner API key, sent as an x-api-key header. Read-only discovery routes stay open and need no key.
Keys are self-serve: you create them yourself at dashboard.stablecoin.xyz, and they are issued and revoked there. The Swap API validates against that key store on every protected request (with a short-lived cache, see Propagation).
Get a key
- Open dashboard.stablecoin.xyz and connect a wallet — MetaMask, WalletConnect, or Coinbase Wallet.
- Sign in. Authentication is SIWE: you sign a message to prove you control the address. There is no password and no email. Your keys are scoped to that wallet address, so keep control of it.
- Create a key, giving it a name (e.g.
Production App) and an optional description. Use one key per environment or per service, so you can revoke one without taking the others down.
The key looks like this:
sbc-4f3c9a1e77b2d0865fe1a4c93b8d2e50sbc- followed by 32 hex characters. It is shown in the dashboard whenever you're signed in, so you can retrieve it again later, but treat it like a password: it is a bearer credential with no scopes — anything holding it can call every protected route on your behalf.
Send it
One header on every protected request:
curl -X POST .../api/base/swap \
-H 'content-type: application/json' \
-H "x-api-key: $SBC_API_KEY" \
-d '{ "user": "0xYourAddress", "uiAmount": "25", ... }'const SWAP_API = 'https://swap.stablecoin.xyz'
const res = await fetch(`${SWAP_API}/api/base/swap`, {
method: 'POST',
headers: {
'content-type': 'application/json',
'x-api-key': process.env.SBC_API_KEY!,
},
body: JSON.stringify(payload),
})The header name is case-insensitive. There is no Authorization: Bearer form — that scheme is reserved for operator endpoints you should never call.
Verify it works
The cheapest authenticated round trip is a redeem quote:
curl -H "x-api-key: $SBC_API_KEY" \
"https://swap.stablecoin.xyz/api/base/redeem/quote?in=SBC&amount=1"A 200 with a quote body means the key is live. Under enforcement, a bad key returns:
{ "error": "missing or invalid x-api-key", "code": "UNAUTHORIZED" }with HTTP 401. During the soft launch you'll get the quote either way, so this check confirms the route, not yet the key.
Which routes need a key
Needs x-api-key | Open, no key |
|---|---|
POST /api/base/swap | GET /api/health |
POST /api/base/redeem | GET /api/capabilities |
GET /api/base/redeem/quote | GET /api/config |
GET /api/base/confirm | GET /api/readiness |
POST /api/sol/swap | GET /api/base/info |
POST /api/sol/redeem | GET /api/base/balance |
GET /api/sol/redeem/quote | GET /api/sol/info |
GET /api/sol/confirm | GET /api/sol/balance |
POST /api/xchain/sol-origin/open/build | GET /api/sol/recent |
POST /api/xchain/evm-origin/open/build | |
GET /api/xchain/status/:orderId |
The rule behind the split: anything that moves funds, is tied to a specific wallet, or prices a fill needs a key. Pure chain/config discovery does not.
Two protected routes, /api/reserve and /api/reserve/release, are inventory helpers for our own UI. They are not part of the partner surface — don't build against them.
Operator endpoints (/api/xchain/*/settle, /api/xchain/*/refund, /api/solver/heartbeat) are not covered by partner keys at all. They sit behind a separate operator bearer token and reject partner keys. See Cross-chain swaps for why you never need them.
Propagation
Verdicts are cached at the edge, so key changes are not instantaneous:
| Change | Takes effect within |
|---|---|
| New key created | ~30 seconds |
| Key deactivated or deleted | ~5 minutes |
If the key store is briefly unreachable, the Swap API keeps honoring the last known verdict for up to an hour rather than failing your traffic. A revocation is therefore not an instant kill switch — if a key leaks, rotate it and treat anything it could have done in the next few minutes as still possible.
Rotating and revoking
In the dashboard, each key can be deactivated (stops validating, stays visible in your list) or deleted outright. There is no in-place rotation, so rotate by overlap:
- Create the replacement key.
- Deploy it to your service.
- Confirm traffic is flowing on the new key.
- Deactivate the old one.
Because a deactivation takes up to five minutes to propagate, do step 4 last, not first.
Keeping the key safe
Send the key from your backend. It carries no scopes and no spend limit of its own, so a key in a shipped mobile app or a browser bundle is a key anyone can lift and use.
If your architecture puts the call in the browser, proxy it: your frontend calls your server, your server attaches x-api-key and forwards to the Swap API. That also keeps you free to rotate without shipping a new client build.
Size limits apply on every network regardless of your key — read them live (maxUsd / hardMaxUsd on a redeem quote, solver inventory from /api/base/info for par routes) rather than assuming a value. Keys do not currently carry per-partner rate limits; a RATE_LIMITED code today comes from generic abuse protection, not from your key's tier.
Next steps
- Quotes and token info: read endpoints and redemption pricing
- Same-chain swaps: sign an order and submit it
- Cross-chain swaps: Base ↔ Solana intents and status polling