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.

FieldWhat it does
Name, logo, homepageShown on the consent screen beside the Orbio mark.
App typeA 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 URIsWhere 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 feeA 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.

1. Send the person to Orbio

Make a random code_verifier and keep it with the person's session, then send the browser to the authorize URL with its SHA-256 hash as the code_challenge.

Authorize URL
https://www.orbio.so/oauth/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yourapp.com/auth/orbio/callback
  &scope=openid profile email wallet inference
  &state=RANDOM_STATE
  &code_challenge=BASE64URL_SHA256_OF_VERIFIER
  &code_challenge_method=S256
Parameter
client_id, redirect_uriRequired. The redirect URI must be one you registered, exactly.
response_typeRequired. Always code.
code_challenge, code_challenge_methodRequired, for every app. S256 only.
stateRecommended. Echoed back so you can tie the callback to the session that started it.
scopeWhat you ask for, space separated (see Scopes). Defaults to balance inference.
nonceOptional. Echoed in the id_token.
promptOptional. consent always shows the screen. none signs the person straight back in when it can and otherwise shows the screen, so it never ends in an error. Neither forces a new sign-in.

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
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"
Response
{
  "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.

TypeScript · OpenAI SDK
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
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.

ClaimScope
subalwaysThe person's id for you. Stable, and the same across all apps you own; a different developer sees a different one.
preferred_username, name, pictureprofileFrom their linked X account, when they have one.
account_kindprofileemail, wallet or mixed: how they sign in to Orbio.
email, email_verifiedemailAccounts that sign in with email or Google.
wallet_address, chain_idwalletAccounts 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.

Auth.js
{
  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.

ScopeGrants
openidAn OpenID Connect id_token on the code exchange, signed ES256. Its sub is the person's id for your app.
profilepreferred_username (X handle), name, picture, account_kind (email, wallet or mixed).
emailemail and email_verified, for an account that signs in with email or Google. Absent on a wallet account.
walletwallet_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_accessAccepted for compatibility. Every grant includes a refresh token.
balanceRead the balance and usage: GET /api/v1/key, orbio_get_balance over MCP.
inferenceEvery model route on the gateway: chat completions, messages, responses, embeddings, images, audio and video.
toolsEvery read tool in /api/v1/tools: web search and scrape, X, Instagram and TikTok reads, chain reads.
socialsocial.post, social.post.status, social.post.delete and social.accounts: publishing as the person, through the accounts they authorized in their dashboard.
infraManaged 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.
keysLocal 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

WhereWhat it means
invalid_client (401) at the token endpointWrong or revoked client secret, or a secret sent by a public app.
invalid_grant at the token endpointThe 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 APIThe token expired or the person disconnected your app. Refresh, or sign them in again.
403 insufficient_scope from the APIYour token was not granted what that route needs.
402 from the APIThe person's balance cannot cover the request.