Authentication
Every credential,
and when it needs you.
One balance, three ways to spend it. Two need a person once. A wallet key needs nobody: the wallet signs one line, the CREDIT activated to that wallet is the balance, and the signature is the key.
Three credentials, one balance
| Credential | Looks like | Who it is for | How you get one | A person involved |
|---|---|---|---|---|
| Account key | sk-orbio-… | Your own server, script or agent | The dashboard, or orbio_create_key over MCP | Once, to sign in |
| Wallet key | sk-orb-<epoch>-… | An agent with its own wallet | The wallet signs a fixed line. Nothing is issued | Never |
| App token | orbio_at_… | An app other people sign in to | Sign in with Orbio, OAuth 2.1 | Each person approves your app |
All three are sent the same way and work on every model, tool and infrastructure route, and on MCP. Each spends one account's balance; none of them can move tokens, sign a transaction or act on a launchpad vault.
Account keys
Every Orbio account has one permanent key. Sign in at orbio.so/dashboard to copy it, or call orbio_create_key from an MCP client connected over OAuth, which returns the same key rather than making another.
- Asking for the key again returns the live one. It is never rotated by being shown.
- Revoking stops it at once; the next request for a key makes a new one. The balance is untouched either way.
- It works beside a wallet key on the same account. Making one never retires the other.
Wallet keys: no sign-in
A wallet key is the wallet's own signature of one fixed line, Orbio API key · chain 4663 · epoch 0, presented as sk-orb-<epoch>-<signature in base64>. Orbio issues nothing and stores no secret: the gateway rebuilds the line from the epoch in the key and recovers the wallet that signed it. The balance is whatever CREDIT has been activated to that wallet, and its first activation gives it an account of its own.
- A plain EOA and an EIP-7702 delegated wallet sign with their own key, and both work.
- A contract wallet (a Safe, an ERC-4337 smart account) has no key to recover, so it cannot make a wallet key. Sign with an EOA and activate to it, or use an account key.
- Signing in at orbio.so later with only this wallet, as a new login, opens this same account and its balance.
1. Sign the key
With the wallet the agent already has, starting at epoch 0. The text must match character for character, middle dots included, and the signature is base64 of its 65 bytes, not hex. A key that cannot be read is refused with a message naming the line it should have signed.
import { privateKeyToAccount } from 'viem/accounts'
const wallet = privateKeyToAccount(process.env.ORBIO_PRIVATE_KEY as `0x${string}`)
const epoch = 0
const signature = await wallet.signMessage({ message: `Orbio API key · chain 4663 · epoch ${epoch}` })
const ORBIO_API_KEY = `sk-orb-${epoch}-${Buffer.from(signature.slice(2), 'hex').toString('base64')}`import { createOrbio } from '@orbiodotso/sdk'
// ORBIO_PRIVATE_KEY and ORBIO_RPC_URL set, and no ORBIO_API_KEY:
// the client signs its own wallet key from that wallet.
const orbio = await createOrbio()
// Buy CREDIT with USDG, activate it to this wallet, and wait for the balance.
await orbio.topUp({ token: 'USDG', amount: '10' })import { walletKey } from '@orbiodotso/sdk'
// For another client, such as the OpenAI SDK.
const ORBIO_API_KEY = await walletKey(process.env.ORBIO_PRIVATE_KEY as `0x${string}`)import base64, os
from eth_account import Account
from eth_account.messages import encode_defunct
epoch = 0
line = f"Orbio API key · chain 4663 · epoch {epoch}"
signed = Account.sign_message(encode_defunct(text=line), private_key=os.environ["ORBIO_PRIVATE_KEY"])
ORBIO_API_KEY = f"sk-orb-{epoch}-" + base64.b64encode(signed.signature).decode()2. Fund the wallet
Activate CREDIT to the wallet that signed. Nobody signs in: the activation is the account. CREDIT for agents covers buying it on the order book or through Uniswap, and /api/v1/swap already takes the wallet key at this point, before anything is activated, so an agent holding only USDG, ETH or ORBIO can buy its first CREDIT. With the SDK, orbio.topUp() buys, activates and waits for the balance in one call, as above.
// From the wallet that signed the key:
credit.activate(amount);
// From any wallet, to the one that signed it:
credit.activate(amount, bytes32(uint256(uint160(agentWallet))));
// Or buy from the order book and activate in one call, after approving USDG:
exchange.buyAndActivate(usdgIn, minCreditOut, bytes32(uint256(uint160(agentWallet))), maxFills);Amounts have 6 decimals, and previewActivation(amount) says what an activation will credit. CREDIT bridged to Ethereum activates there the same way, to the same balance.
3. Confirm the activation
The gateway learns about an activation from its indexer, a few seconds after the receipt. Poll GET https://www.orbio.so/api/protocol/tx/{hash} (add ?chain=1 for Ethereum) until status is applied. From then on the key works, and GET /api/v1/key shows the balance and the wallet it belongs to.
curl https://www.orbio.so/api/protocol/tx/$ACTIVATION_TX
# {"status":"applied","blockNumber":"…","events":[…,"Activated"]}
curl https://api.orbio.so/api/v1/key -H "Authorization: Bearer $ORBIO_API_KEY"
# {"object":"key","key":{"kind":"wallet","prefix":"sk-orb-0","label":null,"created_at":null,"address":"0x…"},"balance":{"currency":"USD","available":"25",…},…}Until the activation is applied, every route that spends answers 402 wallet_not_activated with the recovered address. If that is not the wallet you funded, the key was signed by a different wallet or over different text.
4. Use it, and rotate it
Use it exactly as an account key: the same base URLs, headers and MCP endpoint, below.
Rotate by signing a higher epoch. The first request that uses it retires every lower epoch for that wallet, which then answers key_rotated. Only the private key can raise the epoch, so this is also how a leaked wallet key is stopped.
App tokens
An app that other people use never asks them for a key. It sends them to Orbio to sign in and approve it, and spends their balance with the token it gets back, within the scopes they approved. Sign in with Orbio is the full guide.
Sending a credential
As Authorization: Bearer <credential>; the gateway also takes it as x-api-key, and MCP only as Authorization. Call the gateway base directly: a redirect, such as orbio.so to www, drops the header on the way.
| Client | Base URL |
|---|---|
| OpenAI-compatible SDKs and HTTP | https://api.orbio.so/api/v1 |
| Anthropic SDK, Claude Agent SDK, Claude Code | https://api.orbio.so/api, because those clients add /v1/messages themselves |
| MCP | https://www.orbio.so/api/mcp |
import OpenAI from 'openai'
const client = new OpenAI({ apiKey: process.env.ORBIO_API_KEY, baseURL: 'https://api.orbio.so/api/v1' })claude mcp add --transport http orbio https://www.orbio.so/api/mcp \
--header "Authorization: Bearer $ORBIO_API_KEY"An account key and a wallet key are server credentials. The gateway answers a browser page only for an app token, so a key in browser code can be stolen and cannot even be used there.
Reading the balance
GET /api/v1/key returns the balance, lifetime usage, rate limits and, for a wallet key, the wallet it recovered to. GET /api/v1/auth/key answers in OpenRouter's shape for clients that already read limit_remaining. Every gateway response also carries X-Orbio-Balance, the balance the request started from. Money is a decimal string beside an exact integer of micro-dollars.
Refusals
Models, tools, swap, MCP and Incognito refuse a credential in one shape, { "error": { "message", "type", "code", "param", "docs_url" } }, with address added when the refusal is about a wallet. Infrastructure routes refuse an account key or a wallet key with the same code and words in their own { "error": { "code", "message", "retryable" } }, and use their own codes for anything else, such as unauthorized for no credential at all. Branch on code; the message is written to be shown to whoever has to act.
| Status | Code | When | What to do |
|---|---|---|---|
| 401 | missing_api_key | No key arrived. A redirect drops the Authorization header, so calling orbio.so instead of the gateway base looks like this too. | Send Authorization: Bearer <key> (or x-api-key) to the gateway base itself. |
| 401 | invalid_api_key | The key is not one Orbio issued, or it was revoked. | Use the account's live key. A revoked key never comes back; asking for a key makes a new one. |
| 401 | invalid_api_key | The key starts with sk-orb- but is not a 65-byte signature in base64. The message says which line should have been signed. | Encode the signature in base64, not hex, and check the signed text character for character. |
| 401 | key_rotated | An account key was replaced, or a wallet key was signed with a lower epoch than that wallet has since used. | Use the new key, or sign the key message again with a higher epoch. |
| 402 | wallet_not_activated | A wallet key whose wallet has no account yet, because nothing has been activated to it. address is the wallet the signature recovered to. | Activate CREDIT to address, wait until the activation is indexed, and send the same key again. If address is not your wallet, the signed text was wrong. |
| 402 | insufficient_quota | The account's balance is empty. On a tool call the same refusal is ORB03, and ORB02 when the balance cannot hold the call's quote; a private request keeps insufficient_quota in Incognito's own shape. | Activate more CREDIT to the wallet, or top up the account. Nothing was charged. |
| 403 | insufficient_scope | An app's token without the scope this route needs. | Ask the person to connect the app again with that scope. |
| 503 | server_error | The credential could not be checked. | Retry. Nothing was charged. |
