Developer guide
Sign in with Orbio
People connect their Orbio account to your app and use their own balance in it, for models and tools. No keys to paste, and an optional fee on top of what they spend, paid to you.
Building with agent infrastructure?
Sandboxes, deployments, servers, databases, and email. Start with the MCP and SDK guide.
How it works
Sign in with Orbio lets the people who use your app connect their Orbio account to it in one click, and lets your app call Orbio's models and tools on their balance. Nobody pastes a key. It is OAuth 2.1 with PKCE, and OpenID Connect when you ask for openid.
- You register an app at /developers/apps and get a client ID (and a client secret, for a server app).
- Your app sends the person to Orbio. They sign in with email or a wallet and approve your app on one screen.
- Orbio sends them back with a code. Your server swaps it for an access token and a refresh token.
- You call the API with the access token exactly as you would with an API key. Every request spends the person's balance, plus your app fee if you set one.
A person who already approved your app, for everything it asks, is signed straight back in without seeing the screen again when your redirect URI is https. With a custom scheme or a localhost redirect they see the screen each time, because another app on their device could be listening there. They can disconnect your app at any time from their dashboard, which stops its tokens at once.
Register an app
Sign in at /developers/apps with any Orbio account and choose New app.
| Field | What it does |
|---|---|
| Name, logo, homepage | Shown on the consent screen beside the Orbio mark. |
| App type | A server app keeps a client secret on its backend and sends it with every token request. A public app (a web app with no backend, or a phone, desktop or terminal app) has no secret to keep and proves itself with PKCE alone. |
| Redirect URIs | Where Orbio may send the person back. Matched exactly, never by prefix. https, a custom scheme for a native app, or http://localhost for development. |
| App fee | A percentage on top of what Orbio charges for everything your app spends, from 0 to 100%. Paid to you in USDG. |
A server app's secret is shown once, when it is made. Two can be live at a time, so you can rotate without downtime: add a new one, deploy it, revoke the old one.
2. Handle the callback
Orbio sends the browser to your redirect URI with code, your state, and iss (Orbio's issuer, so you can tell providers apart). Check that state is the one you sent. A person who cancels comes back with error=access_denied instead of a code. A request Orbio cannot accept shows the person the reason with a link back to you carrying the error; it is never bounced to your redirect without a click.
3. Exchange the code
Within five minutes. A server app exchanges from its backend and authenticates with HTTP Basic (or client_id and client_secret in the body). A public app sends its client_id and no secret, from wherever it runs: the token endpoint answers browsers, so a web app with no backend can sign people in on its own.
curl https://www.orbio.so/api/oauth/token \
-u "$ORBIO_CLIENT_ID:$ORBIO_CLIENT_SECRET" \
-d grant_type=authorization_code \
-d code="$CODE" \
-d redirect_uri=https://yourapp.com/auth/orbio/callback \
-d code_verifier="$CODE_VERIFIER"{
"access_token": "orbio_at_…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "orbio_rt_…",
"scope": "openid profile email wallet inference",
"id_token": "eyJ…"
}4. Call the API
The access token works wherever an Orbio API key does: the OpenAI-compatible gateway at https://api.orbio.so/api/v1, the tools at https://api.orbio.so/api/v1/tools, and MCP. Each request spends the person's balance through your app's own connection.
import OpenAI from 'openai'
const orbio = new OpenAI({ baseURL: 'https://api.orbio.so/api/v1', apiKey: accessToken })
const answer = await orbio.chat.completions.create({
model: 'anthropic/claude-sonnet-4.5',
messages: [{ role: 'user', content: 'Hello' }],
})An Anthropic-shaped client (the Anthropic SDK, the Claude Agent SDK) takes https://api.orbio.so/api as its base, because it appends /v1/messages itself. The @orbiodotso/sdk package takes the access token as apiKey.
Call from your server, or straight from the page in a browser app: the gateway answers a browser for a person's Sign in with Orbio token. It never answers one for an Orbio API key, which belongs on a server. A server is still the stronger choice where you have one, because it keeps tokens off the page, out of reach of any script that runs there. In the browser, keep the tokens in memory rather than in localStorage where you can, and refresh when the access token expires.
5. Keep the person signed in
Access tokens last an hour. Swap the refresh token for a new pair before then. Refresh tokens are single use: every refresh returns a new one, and presenting an old one again is treated as theft and disconnects the person. Save the new refresh token before you use the new access token, and refresh from one place, not from several servers at once.
curl https://www.orbio.so/api/oauth/token \
-u "$ORBIO_CLIENT_ID:$ORBIO_CLIENT_SECRET" \
-d grant_type=refresh_token \
-d refresh_token="$REFRESH_TOKEN"To sign the person out of your app, post the refresh token to https://www.orbio.so/api/oauth/revoke. That disconnects your app from their account.
Who signed in
Ask for openid and the code exchange returns an id_token signed with ES256, checkable against https://www.orbio.so/api/oauth/jwks. The same claims come from https://www.orbio.so/api/oauth/userinfo with the access token as a Bearer token. Each claim is there only if its scope was approved and the account has it.
| Claim | Scope | |
|---|---|---|
sub | always | The person's id for you. Stable, and the same across all apps you own; a different developer sees a different one. |
preferred_username, name, picture | profile | From their linked X account, when they have one. |
account_kind | profile | email, wallet or mixed: how they sign in to Orbio. |
email, email_verified | email | Accounts that sign in with email or Google. |
wallet_address, chain_id | wallet | Accounts that sign in with a wallet: the first wallet the account linked, on Robinhood Chain. |
OpenID Connect libraries need only the issuer, https://www.orbio.so: the configuration is at https://www.orbio.so/.well-known/openid-configuration.
{
id: 'orbio',
name: 'Orbio',
type: 'oidc',
issuer: 'https://www.orbio.so',
clientId: process.env.ORBIO_CLIENT_ID,
clientSecret: process.env.ORBIO_CLIENT_SECRET,
authorization: { params: { scope: 'openid profile email wallet inference' } },
checks: ['pkce', 'state'],
}Scopes
The person approves everything you ask for or nothing. Ask for what you use: each scope is a line on the consent screen.
| Scope | Grants |
|---|---|
openid | An OpenID Connect id_token on the code exchange, signed ES256. Its sub is the person's id for your app. |
profile | preferred_username (X handle), name, picture, account_kind (email, wallet or mixed). |
email | email and email_verified, for an account that signs in with email or Google. Absent on a wallet account. |
wallet | wallet_address (the first wallet the account linked) and chain_id, for an account that signs in with a wallet. Absent on an email account. |
offline_access | Accepted for compatibility. Every grant includes a refresh token. |
balance | Read the balance and usage: GET /api/v1/key, orbio_get_balance over MCP. |
inference | Every model route on the gateway: chat completions, messages, responses, embeddings, images, audio and video. |
tools | Every read tool in /api/v1/tools: web search and scrape, X, Instagram and TikTok reads, chain reads. |
social | social.post, social.post.status, social.post.delete and social.accounts: publishing as the person, through the accounts they authorized in their dashboard. |
infra | Managed infrastructure in an isolated scope created automatically for this connection. Includes sandbox, deployment, server, database, and inbox actions within approved spending limits. Existing explicit assignments keep their permissions. Does not grant access to other connections or permission to issue keys. Never added to previously approved grants. |
keys | Local MCP clients only (a self-registered client signing in through a loopback or custom-scheme redirect): read, create and revoke the account's permanent API key, and set its Incognito policy, which is how an MCP client configures a gateway on its user's machine. Never granted to a registered app or to any client whose redirect is a web address: a key outlives the connection that read it. |
Infrastructure requires explicit infra consent. Orbio creates an isolated resource scope for each approved connection on first use; no manual product setup or provider login is needed. Existing owner assignments keep their permissions. Hosted MCP and SDK 0.2.0 support E2B sandboxes, Vercel deployments, Fly.io servers, Supabase databases, and AgentMail inboxes. Actions require infra.read plus their resource permission; mutations require a saved idempotency key and approved cost ceiling. Start with the infrastructure guide.
A request outside what was granted is refused with 403 and the code insufficient_scope. To ask for more, send the person through the authorize URL again with the larger scope; they approve it on the consent screen, and the connection then holds both.
Your app fee
Set a fee and every request your app makes adds it on top of what Orbio charges: a 10% fee on a request Orbio charges $0.0100 for takes $0.0110 from the person, and $0.0010 is yours. The fee is at most 100%.
- The person sees your fee on the consent screen and next to your app in their dashboard.
- A change applies from the next request, for everyone. A request already running keeps the rate it started with.
- Your own account pays no fee through your own app.
- The fee is charged from the person's spare balance, after the request's own cost. A request is never refused because of the fee.
What you earn shows on your app's page. Claim it in USDG on Robinhood Chain once it reaches $10: claims are reviewed and paid within 24 hours, and the transaction appears beside the claim.
Errors
| Where | What it means |
|---|---|
invalid_client (401) at the token endpoint | Wrong or revoked client secret, or a secret sent by a public app. |
invalid_grant at the token endpoint | The code was used, expired, or issued to another redirect URI; or the refresh token was used before (the connection is now revoked). |
401 from the API | The token expired or the person disconnected your app. Refresh, or sign them in again. |
403 insufficient_scope from the API | Your token was not granted what that route needs. |
402 from the API | The person's balance cannot cover the request. |
