Auth for LLMs

Never worry about billing your users for AI again

One connect step gives every user their own AI access: a subscription, a local model or a key. Soba meters what each one uses and charges them on your plan, at your price.

Users who connect their own access cost you nothing to serve. Anyone who does not falls through to a metered tier.

What your user runs, once
❯ npx @soba/worker login --broker https://your-app.com

What your users
connect.

Their plan
Their machine
Their key
Metered tier

Soba Components

One flow, whatever they bring

The friction is not the same for everyone: an OAuth popup for a ChatGPT plan, install-then-approve for a Claude daemon on their own machine. Soba absorbs that difference, so your app has one integration and one state to render.

See exactly what ships today

Components in design

Every user brings whatever AI access they already have, in one step.

<ConnectCompute />in design

Connect your AI

Use access you already have. Nothing here is charged to you.

Continue with Claude last used
Continue with ChatGPT
Use a model on this machine
or
sk-ant-••••••••••••••••••••

Stays on this machine. Your app never receives it.

Continue
Secured by Soba
<ConnectButton />in design
Connect compute

Nothing connected yet · opens the modal

Connect compute

On your own surface · inherits your ground

Claude · MacBook Pro

Once connected · the same button carries the state

One control: it opens the modal, then reports what it opened
<SobaPlans />in design

Bring your own

Runs on the AI you connect

$0/ month

Current plan

Runs on the machine you paired

Your Claude or ChatGPT plan

Local models included

Hosted

Nothing to connect, nothing to install

$19/ month

Billed annually

Get started

Everything in Bring your own

500 runs a month included

Metered per run after that

Prices, allowances and copy are yours. Soba renders them.
<SobaCheckout />in design
Checkout ×
Hosted plan$19.00per month
Runs included500
Total due todayUSD $19.00

Payment source

Visa ··· 4242
Pay $19.00
or

Add a payment method

Add a card
More payment methods
Soba drives the rail. Payouts land in your account.
<SobaProvider />in design
<SobaProvider userId={session.user.id}>
  <SobaPlans />
  <ConnectCompute />
  <ComputeStatus />
</SobaProvider>
useSoba()in design
const { run, plan, compute, connect } = useSoba();

// the same four things the components render
await run({
  prompt,
  route: { allow: ["user-subscription"] }
});
<ComputeStatus />in design
MacBook Pro

claude-code · signed in

ready
home-vps

always on · 1 run in flight

busy
work-laptop

asleep since 18:40

offline
Their machine, their state, rendered in your app

A hosted fallback page covers Vue, Svelte, Rails and Django by redirect, the same trick a checkout uses.

Connect what you have

Four things a user can bring

Every user brings AI access they already hold, and your app asks for a class of compute rather than a machine, so the integration is the same whichever one they choose. Only the last option reaches your account.

Explore the protocol

They bring the compute. You never hold a credential or model a token bill.

A wrong route cannot bill you. The worker checks allow, not just the broker.

Where the inference bill lands 3 of 4 never reach you
their plan Claude, ChatGPT their machine laptop or box their key provider bills them your hosted plan the safety net paid by your user three of the four paid by you one of the four Every run your users make One tier you fund

Native agent support

Want to support Claude Code natively? Ship this.

One integration, whichever agent they already run.

Your app

One call, carrying your prompt and your tool schemas.

soba.run()

Their machinebackground service

Soba drives the agent CLI they installed and signed into themselves.

  • claude-code
  • codex
  • gemini-cli
  • ollama
  • Runs whether or not anyone is watching.
  • Their hardware, their plan, their account.
  • Soba never holds the login.

Back in your UI

  • Web
  • Native
  • Mobile

Tokens arrive the same way on every surface you ship.

Soba 0.1.0
Soba
Applications
Drag it in once: it registers a login item, pairs in the browser and picks up the agents already installed on the machine. The same worker installs today with npx @soba/worker install. in design

Their install, their account

The worker drives the CLI the user already installed and logged into, in place, on their own hardware. Nothing is copied off the machine, re-hosted or resold, and a paired machine only ever runs for the person who paired it.

Dials out, never listens

It holds a single outbound socket to the broker. No port is opened on the laptop, so there is nothing on it to scan, and a dropped socket simply cancels its runs.

Asks on the machine it affects

Nothing is askable until the owner opts in. The prompt lands here, a timeout or an unreachable approver reads as deny, and every decision is appended to the audit log.

The split

You own the tools. Soba owns the loop.

An agentic chat is only two things: calls to a model, and calls to your own code. Soba drives the loop and puts every model call on compute the user already pays for. Your app defines the tools and runs them against your data.

What you ship

An agentic chat

A model that reads, decides and acts inside your product.

Yours

Your tools

You define them and you run them, under your auth, against your database.

app.notes.create
Soba's

The LLM calls

Every turn of the loop, on compute the user already pays for.

costMicros=0

That is the whole integration. You never buy a token, and Soba never touches your database.

Soba does

Chooses the compute, drives the agent loop, enforces the permission policy, meters what is metered, renders the pricing and connect UI, and takes the payment.

You do

Define the tools, execute them, and own everything they touch. Nothing about your data model, your auth or your database has to leave your side of the line.

Connecting a machine

The shape gh auth login uses

It replaces "open the app, find settings, mint a token, copy it, paste it into a terminal", which is four steps, one of them a bearer credential pasted straight into shell history. Here the credential is never displayed and never pasted, and the person approves in a browser where they are already signed in to your app.

The pairing page must be https

The broker chooses that URL, and the broker is the component we ask people to trust least. An unchecked value is a phishing page one compromised control plane away.

The browser never opens unasked

The URL is printed first. A URL someone has read and agreed to is a different thing from one a server opened on their machine.

It works headless

--no-browser prints the URL and polls, so a box with no display pairs the same way a laptop does.

On their machineRFC 8628 device grant
 npx @soba/worker login --broker https://your-app.com

  Your code:  WXYZ-1234
  Approve at: https://your-app.com/link

  Press Enter to open that in your browser (or n to skip)…
[soba] waiting for you to approve…
[soba] paired. The token is in ~/.soba/worker.json (0600).
        It was never shown or pasted.
[soba] starting.

Billing, handled

Two meters, one integration

What the compute costs you, and what you charge your users for it. Your app declares which cost classes it accepts and stops thinking about the first; the plan you sell sits on top of it, priced at your rate rather than ours.

What the compute costs you

Cost classWho already paidMetered by the broker
user-hardware The user's own silicon: a local model on a machine they bought Not metered
user-subscription A plan they already pay for: Claude Pro or Max, ChatGPT Plus or Pro Not metered
open-weights An open model on GPU, bought wholesale Metered
frontier A frontier provider API Metered, unless the key is theirs

What you charge your users in design

The plan sits on top, at your price

A plan is a stored object (price, unit, allowance, expiry, route) with a per-user ledger priced at your rate rather than at our cost. Codes grant a plan for a period, with redemption caps. Payments run as a rail Soba drives rather than an integration you build: onboarding, checkout, entitlement, payouts, refunds, dunning, tax.

Today's metering is Soba's own cost accounting, the number above. The layer that bills your users is specified and not built, and the Status section says so in as many words.

Signed out is withheld

Finding the binary is not being able to run it

A signed-out CLI is kept out of the advertised set and reported to its owner with the fix, instead of being routed to and failing at spawn on every run.

No default class

A row without one is skipped

Guessing whose money this is, is exactly the mistake the field exists to prevent. Only the machine's owner can declare it.

Announced first

The tier arrives before the output

It is stamped on the opening status event and on usage, so a downgrade is something your interface can show rather than something a user has to feel.

You cannot beat free with a 20% discount.

Every other router competes on price per token, so its supply has to be paid for and its floor is somebody's margin. Soba's supply was bought by the person asking for the run. Routers earn on tokens flowing through them; this one earns on tokens that never flow at all.

Installlaunchd · systemd
 npm i -g @soba/worker && soba-worker install
[soba] launchd agent written to ~/Library/LaunchAgents
[soba] Restart=always · env from ~/.soba/service.env (0600)

 soba-worker --status --verify
  service     installed, running
  claude-code authenticated   user-subscription
  ollama      3 models         user-hardware
  codex       signed out       run `codex login`

Keeping it running

A laptop sleeps. A $5 box does not.

Pairing is one command. The step that loses people is the next one. A worker is a foreground process, so it dies with the terminal and never comes back after a reboot. "Leave this window open forever" is the real friction, on a laptop and on a VPS alike.

No credential in a service file

A systemd unit is world-readable by design, so the token stays in a 0600 file the worker loads itself. Only locations go in the unit, and they are not secrets.

An npx cache path is never recorded

npm prunes those directories, so a service pointing at one works today and fails weeks later for no visible reason. What gets written is the pinned command.

A version-managed Node is flagged

nvm, fnm, volta and asdf install per-version binaries that stop existing the day you upgrade. Install says so and carries on.

Connect once, then stop thinking about it

Metered only on the tier someone has to buy. Pair a machine, send a run, and watch the usage event come back at zero.