- name
- gateway
- description
- Use gateway.bitplan.dev, an OpenAI-compatible inference API paid in BSV with no API keys. Use when asked to call a model through the gateway, mint a gateway token, deposit BSV to the gateway, check a gateway balance or usage, pick a model by price, or explain how gateway.bitplan.dev works.
- metadata
- {"version":"0.1.2"}
# gateway.bitplan.dev
An OpenAI chat completions endpoint in front of every model on Vercel AI
Gateway and OpenRouter. Identity is a Bitcoin key. Deposits arrive over HTTP
402. Each call is deducted from the balance at provider cost plus a markup that
falls with volume, with a small per-call floor. The numbers live in one
place, the operator's price list: read `tiers`, `min_charge_sats` and
`byok_fee_bps` from `GET /v1/models` (or `/.well-known/x402-info`) rather
than assuming them (see Prices).
Base URL: `https://gateway.bitplan.dev`. Discovery: `GET /.well-known/x402-info`.
| Endpoint | Auth | Purpose |
| --- | --- | --- |
| `GET /v1/models` | none | model list with prices |
| `GET /v1/rate` | none | BSV/USD rate and `min_deposit_sats` |
| `POST /v1/chat/completions` | bearer | OpenAI chat completions, JSON or `stream: true` |
| `POST /v1/messages` | bearer | Anthropic Messages API over the same metering: `ANTHROPIC_BASE_URL=https://gateway.bitplan.dev ANTHROPIC_AUTH_TOKEN=<bearer>` makes Claude Code and Anthropic SDKs work; bare `claude-*` ids map to `anthropic/<id>` when listed |
| `POST /v1/messages/count_tokens` | none | Anthropic token count, a free bytes/4 estimate |
| `POST /v1/responses` | bearer | OpenAI Responses API over the same metering, for Codex CLI: `[model_providers.bitplan]` with `base_url = "https://gateway.bitplan.dev/v1"` and `env_key = "BITPLAN_TOKEN"`; stateless (`previous_response_id` is a 400), function tools only |
| `POST /v1/images/generations` | bearer | OpenAI image generation; `"model": "auto"` picks by Image Arena Elo |
| `GET /v1/batches` | bearer | your recent batches |
| `POST /v1/batches` | bearer | up to 500 requests within 24 hours at the batch price |
| `GET /v1/batches/{id}` | bearer | batch status, and `results` once finished |
| `POST /v1/evaluate` | bearer | typed classify/score/boolean questions over a `state` |
| `GET /v1/account` | bearer | credits, tier, paymail and BYOK status |
| `POST /v1/deposit` `{"sats": N}` | bearer | top up; always answers 402 until paid |
| `GET /v1/credits/packs` | none | card credit packs |
| `POST /v1/credits/checkout` `{"usd": 5}` | bearer | buy credits by card: returns a Stripe Checkout URL for a person to open; poll `GET /v1/credits/checkout/:id` until `credited` |
| `GET /v1/credits/checkout/:id` | bearer | status of one card purchase |
| `GET /v1/account/usage` | bearer | recent requests and ledger entries (`?limit=` up to 1000, default 50) |
| `GET /v1/account/dashboard` | bearer | usage dashboard data (the site's usage page) |
| `PUT /v1/account/paymail` `{"handle": "name"}` | bearer | claim a human paymail handle at `@bitplan.dev` (409 when taken) |
| `DELETE /v1/account/paymail` | bearer | release the handle; the automatic three-word alias remains |
| `PUT /v1/account/byok` | bearer | store your own key for one provider, encrypted |
| `DELETE /v1/account/byok` | bearer | forget the stored key for one provider |
Model ids: `vendor/model` routes to Vercel AI Gateway (for example
`anthropic/claude-opus-4.8`, `spacexai/grok-4.6`); `openrouter/vendor/model`
routes to OpenRouter; `cloudflare/@cf/author/model` routes to Cloudflare
Workers AI (listed on `/v1/models` when the operator has it configured); `opencode/<model>` and `opencode-go/<model>`
route to OpenCode Zen and OpenCode Go and need your own key (see Bring your
own key). Only ids returned by `/v1/models` are accepted. A request with no
`model` runs on the Jev Model Router (`bitplan/auto`), which picks the model
per request; name a model only when you need a specific one.
## Quick start for an agent
Use gateway.bitplan.dev for AI inference. It is an OpenAI-compatible API paid in BSV. There are no API keys.
1. Get a token from the wallet, in this order, and stop at the first that works:
- `bitplan gateway token` (the `bitplan` CLI, if installed, asks the connected BRC-100 wallet to sign a 24-hour bearer; nothing to configure).
- The `BITPLAN_GATEWAY_TOKEN` environment variable, if set.
- With a WIF in `$WIF` only: `bun "$SKILL_DIR/scripts/token.ts" --wif "$WIF"` (see below).
Pass the token as the `token` argument to MCP tools or as `Authorization: Bearer <token>` on HTTP.
2. Add the MCP server https://mcp.bitplan.dev (Streamable HTTP, no auth). If you cannot use MCP, call POST https://gateway.bitplan.dev/v1/chat/completions with an OpenAI-format body.
3. Call list_models (or GET /v1/models) and pick a starred model. Prices are per million tokens in BSV.
4. Call chat with the token. Leave `max_tokens` at the default (8192) or send 4096 or more: you pay only the tokens used, and reasoning models spend the first part of the cap thinking before any text appears. If the result says payment_required, follow "When a call answers 402" below: fund the account, then call chat again with the same arguments.
5. Report the model's answer and the settled cost from the chat result (`charge_sats`) or GET /v1/account/usage.
## When a call answers 402 (payment_required)
A 402 means the account's credits do not cover the call's reserve. Nothing
was charged, and billing is never a reason to stop: the job is to get the
account funded and run the same call again. Rules:
- Never treat a 402 as the task failing, and never say the task is done.
Never work around it: do not pick a cheaper model, cut the prompt, lower
`max_tokens`, skip the step or quietly downgrade anything. Fund, then
repeat the request exactly as it was.
- Never fail silently. Every 402 ends in either a paid retry or a message to
the person that says what is short and how to fund it.
Do these in order and stop at the first that works:
1. Pay it yourself when a wallet is available: `bitplan gateway deposit <sats>`
(the connected BRC-100 wallet pays; the wallet asks the person to
approve) or, with a WIF, pay `accepts[0].amount` sats to `payTo` and
repeat the request with the `PAYMENT-SIGNATURE` header. The 402's
`shortfall_sats` is the least that unblocks the call.
2. If there is no wallet, or the wallet cannot pay (its own insufficient
funds error), tell the person exactly, using the 402's own words
(`error.message`, `balance_sats`, `shortfall_sats`, `fund.paymail`,
`fund.card`): "This call needs N more sats. Send at least N sats to
`<fund.paymail>` from any BSV wallet (HandCash, Yours, RelayX, ...), or
buy credits by card at `<fund.card>` signed in with this key, or add BSV
to the wallet and I will pay from it." Then wait: poll `GET /v1/account`
every 15 to 30 seconds until `balance_sats` covers the reserve (a large
deposit may sit in `pending_confirmation_sats` for one block). Then
repeat the original request unchanged.
3. An anonymous call (no bearer) has no account to fund by paymail: pay the
challenge from a wallet, or get a bearer first (step 1 of the quick
start) and retry so the account can be funded by paymail or card.
Unused reserve always comes back as credits, so paying the exact shortfall
is never money lost.
## Identity: a self-signed bearer token
There is nothing to register. The client signs a token with its own key and
sends `Authorization: Bearer <token>`. Token format is the `bitcoin-auth`
format `pubkey|scheme|timestamp|requestPath|signature`.
- **Session token**: `requestPath` is the literal
`https://gateway.bitplan.dev/v1`; valid 24 hours for every `/v1/*` route.
This is the "API key" for tools that only accept a static bearer.
- **Per-request token**: `requestPath` is the request path (for example
`/v1/account`); valid 5 minutes; the body hash is bound when a body is sent.
Schemes: `brc77` (signature with a raw private key) or `brc100` (a BRC-100
wallet's `createSignature` with `protocolID [1, "bitcoin auth"]`,
`keyID = timestamp`, `counterparty "anyone"`; no key export).
Prefer the wallet: `bitplan gateway token` (npm `bitplan`) signs the token
with the connected BRC-100 wallet and prints it. Only when you hold a raw WIF
and no wallet, mint one with the bundled script. Install its two dependencies
once, then run it from anywhere:
```sh
(cd "$SKILL_DIR/scripts" && bun install --silent)
TOKEN=$(bun "$SKILL_DIR/scripts/token.ts" --wif "$WIF")
```
Never print, log, or send the WIF anywhere. The token is safe to hold for its
lifetime; anyone holding it can spend the balance until it expires.
## Deposit over HTTP 402
Any authenticated call that the balance cannot cover, and every call to
`POST /v1/deposit`, answers `402` with a challenge:
```json
{
"error": { "type": "payment_required", "message": "..." },
"challenge": {
"version": "bsv-tx-v1",
"challenge_id": "<uuid>",
"amount_sats": 25000000,
"payee_locking_script_hex": "76a914...88ac",
"payee_address": "1...",
"expires_at": "2026-09-16T12:34:56.000Z",
"pay_url": "https://gateway.bitplan.dev/v1/deposit"
}
}
```
The deposit floor is live: `min_deposit_sats` on `GET /v1/rate` and
`/.well-known/x402-info`. `POST /v1/deposit` with no `sats` asks for that
floor; a larger `sats` value is accepted. A 402 asks for exactly the
shortfall (at least `min_charge_sats` from `/v1/models`); send
`x-gateway-deposit: minimum` to be asked for a top-up of at least the
minimum deposit instead. Pay `amount_sats` to `payee_address` in a
transaction the gateway will broadcast, then send the **identical** request
again with the x402 header `PAYMENT-SIGNATURE: base64({"x402Version":2,"accepted":<the accepts[0] object from the 402>,"payload":{"transaction":"<signed raw tx, base64>"}})`.
The 402 is x402 protocol version 2: the `PAYMENT-REQUIRED` response header
(and the body) carry `{"x402Version":2,"resource":{"url"},"accepts":[{"scheme":"exact","network":"bip122:000000000019d6689c085ae165831e93","amount":"<sats>","asset":"BSV","payTo":"<address>","maxTimeoutSeconds","extra":{"challengeId","lockingScript"}}]}`,
and a paid response carries `PAYMENT-RESPONSE` with `{"success":true,"transaction":"<txid>","network","payer","amount"}`.
The legacy header `X402-Proof: base64url({"version":"bsv-tx-v1","challenge_id","rawtx_base64","txid"})`
still works. The challenge is bound to that exact request (method, path,
body) and to the signing key, and expires after 15 minutes.
With a WIF, the bundled script builds the transaction from the key's UTXOs and
prints the header value without broadcasting:
```sh
# N is at least min_deposit_sats from GET /v1/rate (or omit sats to be asked for that floor)
CHALLENGE=$(curl -s https://gateway.bitplan.dev/v1/deposit \
-H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \
-d "{\"sats\":$N}")
# --x402 needs the full 402 body (accepts[0]); challenge-only is the legacy proof
SIG=$(printf '%s' "$CHALLENGE" | bun "$SKILL_DIR/scripts/pay.ts" --wif "$WIF" --x402)
curl -s https://gateway.bitplan.dev/v1/deposit \
-H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \
-H "PAYMENT-SIGNATURE: $SIG" -d "{\"sats\":$N}"
```
The response reports `credited: true` when the balance is usable now, or
`false` when the deposit waits for one block confirmation (the gateway caps
how much unconfirmed value it credits at once; check `GET /v1/account` for
`pending_confirmation_sats`).
With a BRC-100 wallet and the `bsv-mcp` x402 tools: call `x402_request` with
the gateway URL, the bearer in `headers`, and `auth: "none"`; it returns a
quote for the `bsv-tx-v1` challenge; approve it with `x402_payQuote`.
Do not pay a challenge twice. If a paid request fails, check
`GET /v1/account/usage` and the txid before doing anything else.
## Batch: about half price, within 24 hours
`POST /v1/batches` runs up to 500 requests within 24 hours at the model's
batch price, about half of list (`pricing.batch` on `GET /v1/models` says
which models and how much). Offer it when the person has many prompts and
no rush. Send `endpoint` (`/v1/chat/completions`, `/v1/responses` or
`/v1/messages`), `model` and `requests` as `{custom_id, body}` items
inline: this is OpenRouter's inline batch shape, not OpenAI's file-based
batches (no `input_file_id` or JSONL upload).
```sh
curl -X POST https://gateway.bitplan.dev/v1/batches \
-H "Authorization: Bearer $TOKEN" -H "content-type: application/json" \
-d '{"endpoint":"/v1/chat/completions","model":"openai/gpt-5.6-luna",
"requests":[{"custom_id":"a","body":{"messages":[{"role":"user","content":"..."}]}}]}'
```
The answer is `202` with the batch (`id`, `status`, `request_counts`,
`hold_sats`). The whole batch is held once for its worst case, each
request is capped at the output tokens it was held for, and the batch
settles once on the usage upstream reports. A `402` here is paid like any
other call: pay it and repeat the request with `PAYMENT-SIGNATURE`.
`GET /v1/batches` lists your batches; poll `GET /v1/batches/<id>` (every
few minutes; a batch normally finishes well under 24 hours): when
`status` is `completed`, `results` lists
`{custom_id, response: {status_code, body}, error}` per request and
`charge_sats` is what it settled at. Text only: no images, audio or files.
## Fund by paymail
Every account is also a paymail address, so a person (or any paymail
wallet: HandCash, Yours, RelayX, Panda, ...) can add credits without
touching the API. `GET /v1/account` reports it:
```json
{ "paymail": "satchmo@bitplan.dev",
"paymail_auto": "word-word-word@bitplan.dev", ... }
```
`paymail_auto` is the automatic alias, three words at `@bitplan.dev`;
`paymail` is the claimed handle when there is one (`name@bitplan.dev`),
else the alias. A 402 for an authenticated caller also carries
`fund: { paymail, note }`. The flow for an agent that is out of credits:
1. Read `paymail` from `GET /v1/account` (or `fund.paymail` from the 402)
and tell the person: "send at least N sats to `<paymail>`".
2. Poll `GET /v1/account` until `balance_sats` covers the call (or
`pending_confirmation_sats` shows the deposit waiting for a block: a
large deposit is credited after one confirmation).
3. Retry the original request with the same bearer.
A P2P send (the wallet delivers the transaction to the gateway) is credited
within seconds; a basic paymail send is found by the gateway's on-chain scan
within about ten minutes. No proof header is involved: paymail deposits
credit the account directly.
Claim a human handle once (3 to 20 characters of `a-z`, `0-9` and hyphen,
View on GitHub