Soba Docs

Get started

Quickstart

Point your OpenAI client at Soba, prove it with one request, then let your users connect the AI they already pay for.

Or let your agent do all of this

npx skills add https://soba.so/skill teaches the coding agent you already have open how to integrate Soba, and the MCP server lets it read this app's checklist and mint its own key. The steps below are what it does.

1. Get a key#

From your dashboard. It is shaped sk_soba_<id>.<secret>, shown once, and it is the only credential your server needs.

Terminal
export SOBA_KEY="sk_soba_…"

2. Change the base URL#

JavaScript
import OpenAI from "openai";

const soba = new OpenAI({
  baseURL: "https://api.soba.so/v1", // the only line that changes
  apiKey: process.env.SOBA_KEY,
});

Everything else stays: your prompts, your tools, your streaming, your error handling.

3. Pass the user's id#

JavaScript
const response = await soba.chat.completions.create({
  messages: [{ role: "user", content: "What is Soba?" }],
  model: "auto",
  user: session.user.id,   // ← the one addition
});

user is a hint in the OpenAI API and load-bearing here. It is how a run is attributed to a person, and therefore how Soba knows whose machine it may reach.

4. Prove it#

Terminal
curl https://api.soba.so/v1/chat/completions \
  -H "Authorization: Bearer $SOBA_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "model": "soba/echo", "user": "usr_123",
        "messages": [{ "role": "user", "content": "ping" }] }'

soba/echo is answered by Soba itself. No machine, no provider key, nothing billed, and it still proves the three things that actually break: the key, the base URL and the user field.

You are done with the required part

Every run is now metered per user and bounded by a spend ceiling. A user with nothing connected is handed back to the client you already have, so nothing changes for them yet. Everything below is optional.

5. Let users bring their own AI#

JavaScript
<SobaProvider userId={session.user.id}>
  <ConnectCompute />
</SobaProvider>

Drop that in your settings page. It is the connect step for someone with nothing paired, and the status of their machines once they have: asleep, offline or serving a run. To place those two apart, pass showStatus={false} and render <ComputeStatus /> yourself. A local model or a key of their own starts serving runs immediately, with no change at your call site. Their Claude or ChatGPT plan is served by Claude Code or Codex, which chat/completions cannot drive; that is what step 6 is for. See Components and Pairing.

6. Reach Claude Code and Codex#

chat/completions is stateless and the caller owns the loop, so it reaches models. Reaching a full agent runtime means letting Soba own the loop:

JavaScript
import { Soba } from "@soba-so/sdk";

const soba = new Soba({ apiKey: process.env.SOBA_KEY });

const run = await soba.run({
  user: session.user.id,
  model: "auto",
  prompt: "Summarise everything I saved this week",
  tools: [listNotes, readNote],   // your code, executed in your app
});

for await (const event of run) {
  if (event.type === "delta") process.stdout.write(event.text);
  if (event.type === "done") console.log(event.usage);
}

See Runs and the SDK.

7. Charge for it#

A plan sets the price, the allowance, what happens past it, which compute it may use, and what a user earns for connecting their own. It compiles to a route on every run, so pricing and routing stay one decision.

Next#

© 2026 Soba resolved = machine grant ∩ broker request