# Inference — machine-paid confidential inference

Open models running in GPU TEEs, one request one payment, USDC over x402.
No signup, no API key needed: the paying wallet is the customer and the only
identity (a wallet CAN mint bearer keys on its prepaid balance — below).
Every model's hardware attestation is public and served beside its price.

## Models (live prices — reread before paying)

- `gpt-oss-20b` (GPT-OSS 20B) — input $0.06/M tokens, output $0.20/M tokens. Context 131,072 tokens; prompt up to 131,072 bytes, output ceiling up to 4,096 tokens (default 1,024). Attestation: `GET https://agentgates-backend.vercel.app/api/inference/attestation?model=gpt-oss-20b`.
- `gpt-oss-120b` (GPT-OSS 120B) — input $0.20/M tokens, output $0.78/M tokens. Context 131,072 tokens; prompt up to 131,072 bytes, output ceiling up to 4,096 tokens (default 1,024). Attestation: `GET https://agentgates-backend.vercel.app/api/inference/attestation?model=gpt-oss-120b`.
- `deepseek-v4-flash` (DeepSeek V4 Flash) — input $0.26/M tokens, output $0.52/M tokens. Context 1,048,576 tokens; prompt up to 131,072 bytes, output ceiling up to 4,096 tokens (default 1,024). Attestation: `GET https://agentgates-backend.vercel.app/api/inference/attestation?model=deepseek-v4-flash`.
- `qwen3-5-122b` (Qwen3.5 122B) — input $0.60/M tokens, output $4.79/M tokens. Context 262,144 tokens; prompt up to 131,072 bytes, output ceiling up to 4,096 tokens (default 1,024). Attestation: `GET https://agentgates-backend.vercel.app/api/inference/attestation?model=qwen3-5-122b`.
- `qwen3-5-397b` (Qwen3.5 397B) — input $0.72/M tokens, output $4.55/M tokens. Context 262,144 tokens; prompt up to 131,072 bytes, output ceiling up to 4,096 tokens (default 1,024). Attestation: `GET https://agentgates-backend.vercel.app/api/inference/attestation?model=qwen3-5-397b`.
- `glm-5-2` (GLM 5.2) — input $1.64/M tokens, output $3.90/M tokens. Context 1,048,576 tokens; prompt up to 131,072 bytes, output ceiling up to 4,096 tokens (default 1,024). Attestation: `GET https://agentgates-backend.vercel.app/api/inference/attestation?model=glm-5-2`.
- `kimi-k3` (Kimi K3) — input $3.90/M tokens, output $19.50/M tokens. Context 1,048,576 tokens; prompt up to 131,072 bytes, output ceiling up to 4,096 tokens (default 1,024). Attestation: `GET https://agentgates-backend.vercel.app/api/inference/attestation?model=kimi-k3`.

Prices are read from the catalog at request time: `GET https://agentgates-backend.vercel.app/api/inference/catalog`.

## How a request is priced (deterministic, before the model runs)

- INPUT is charged by BYTE at the input rate: one byte of your `messages`
  JSON counts as one token. A BPE token never encodes less than one byte, so
  the quote is a ceiling known before inference; typical English runs about
  4 bytes per token, which makes the input charge conservative. The byte
  count is `utf8ByteLength(JSON.stringify(messages))`.
- OUTPUT is charged at the CEILING you set: `max_tokens` (the model's row
  default if you omit it) at the output rate. Unused ceiling is not
  refunded — ask for what you need. Reasoning models spend part of the
  ceiling on a thinking pass before the answer; a ceiling too small can
  return `finishReason: "length"` with a `reasoning` field and little or
  no `content` — that is a served, billed completion, so give reasoning
  models room.
- ONE ON-CHAIN SETTLE is carried by every request, at the same margin as the
  tokens — from $0.001595 right now.
  It is read from live gas at the moment you ask, never a constant, so the
  quote moves with the chain. Paying us costs us a transaction; that cost is
  in the price instead of coming out of it. The settle costs a different amount on each chain, so THE PRICE IS PER
  NETWORK: `base` $0.001595, `polygon` $0.003465, `solana-devnet` $0.010000.
  What another chain's gas is doing never moves the price on yours.
- The total snaps up to the next $0.001 and floors at $0.005000 per
  request. You never need to compute any of this: the 402 quotes the exact
  amount for your exact body.

## Prepay once, then ask many times

🚨 THIS IS A PREPAID BALANCE. You send USDC FIRST and spend it down. Nothing
is loaned in either direction: the transfer settles on chain before your
balance moves, and an empty balance serves nothing.

Every request you pay for individually carries one on-chain settle. Top a
balance up instead and that settle happens ONCE, for the whole amount.

1. `POST https://agentgates-backend.vercel.app/api/inference/credits` with `{"amountUsd": 5}`
   (`1`, `5`, `25` or `100`). The `402` asks for the amount plus the one
   settle that delivers it; `GET /api/inference/catalog` prices every rung.
2. Pay it the same way you pay for a request (below). The reply carries your
   balance.
3. From then on, `POST /api/inference/chat` with an `X-Wallet-Auth` header
   and NO `X-PAYMENT`. The ticket is drawn from the balance and the answer
   comes back with no chain write at all.
4. `GET https://agentgates-backend.vercel.app/api/inference/credits` with `X-Wallet-Auth` reads the
   balance and the ledger.

A balance never expires and is not refundable. It belongs to the wallet that
paid for it and cannot be transferred. There is a ceiling on how much unspent
balance the lane will hold at once, and on how much it takes in a day: read
`sale.availableUsd` off the catalog or your own balance and pick a rung
inside it, rather than being refused after you have signed. A balance short of a request's ticket is
never partly drawn: you get the ordinary `402` and can pay for that one
request or top up. A request that fails to serve spends nothing, so there is
nothing to replay — ask again.

## API keys (optional, for software that cannot sign wallet messages)

The wallet stays the only account. A key is a bearer handle the wallet mints
on its own prepaid balance — for an OpenAI-style client, a cron job, or a
teammate's script that holds no wallet code. Mint one on
`https://agentgates-backend.vercel.app/inference` (connect the wallet, one
signature), or over the API:

- `POST https://agentgates-backend.vercel.app/api/inference/keys` with `X-Wallet-Auth` and
  `{"name": "my-agent"}` — mints a key; the answer carries the secret ONCE
  and it is never stored or shown again.
- `GET https://agentgates-backend.vercel.app/api/inference/keys` with `X-Wallet-Auth` — the wallet's
  active keys (names and prefixes, never secrets).
- `POST https://agentgates-backend.vercel.app/api/inference/keys` with `X-Wallet-Auth` and
  `{"revoke": "<key id>"}` — kills a key instantly.

Then any request is one header:

`POST /api/inference/chat` with `Authorization: Bearer ag_…` draws the
wallet's balance exactly like an owner-signed draw — no chain write, no
`X-PAYMENT`. `GET /api/inference/credits` with the same header reads the
balance the key spends. An empty balance answers the ordinary `402`.

Several keys per wallet are fine (one per agent, up to 20 active); each is
named, listed, and revocable on its own. A key can ONLY draw the balance its
wallet prepaid — it signs no payments and never touches the wallet, so the
unspent balance is the most a leaked key can ever spend.

## Pay in one pass

1. `POST https://agentgates-backend.vercel.app/api/inference/chat` with JSON
   `{"model": "<model id>", "messages": [{"role": "user", "content": "..."}], "max_tokens": <int, optional>}`
   (`temperature`, `top_p`, `stop` are passed through; `stream` and
   `tools` are not supported yet and refuse cleanly).
2. The reply is `402` with an `accepts` array (x402, scheme `exact`).
   Networks offered: `base` (USDC 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, pay to 0xC5d451ADf4bED53E36fa7D091f6d3C3Edba4127D), `polygon` (USDC 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359, pay to 0xC5d451ADf4bED53E36fa7D091f6d3C3Edba4127D), `solana-devnet` (USDC BCSqoz1RT7L3ZFcMzRXnXGKNKBP5uuFPKiL73nuXSEJH, pay to Cj8uWYMBwq4vg1u1NyWHKmHbfBwxf2rRbyXsWtfpqJtN).
3. Sign an EIP-3009 `TransferWithAuthorization` for `maxAmountRequired`
   USDC to `payTo` (EIP-712 domain in `extra`), and retry the SAME body with
   `X-PAYMENT: base64({"x402Version":1,"scheme":"exact","network":"<network>","payload":{"signature":"0x…","authorization":{"from","to","value","validAfter","validBefore","nonce"}}})`.
   On a `solana` / `solana-devnet` leg, build a transaction of exactly
   [ComputeBudget SetComputeUnitLimit, ComputeBudget SetComputeUnitPrice,
   spl-token `TransferChecked` of `maxAmountRequired` USDC (`asset`, 6
   decimals) to `payTo`'s associated token account, optional Memo], set the
   fee payer to `extra.feePayer` (you pay no SOL — we co-sign that seat and
   broadcast), sign it as the token owner, and retry with
   `X-PAYMENT: base64({"x402Version":1,"scheme":"exact","network":"solana","payload":{"transaction":"<base64 partially-signed tx>"}})`.
   Your own wallet must never be the fee payer, and the fee payer may not
   appear in any instruction's accounts. Sign against a FRESH blockhash —
   the payment is only good for ~60 seconds.
   The price is recomputed from your body and must match the quote, so do
   not change the body between the two calls.
4. Settlement is confirmed on-chain, then the model runs. The reply is
   `200 {"id", "model", "content", "finishReason", "usage": {"inTokens", "outTokens"}, "charged": {"amountUsd", "amountMicro", "chainId", "txHash"}, "attestation", "docs"}`.
   `usage` is the provider-reported actual token count — an accounting
   fact; the charge was fixed before the model ran.

## If you lose the answer

A retry with the SAME `X-PAYMENT` answers `409 payment_already_spent`, never a
fresh price: you cannot be charged twice for one authorization. The completion
itself is gone (no prompt and no answer is stored, ever), so recovery means the
receipt, not the text. Find it with the owner call below. A new question needs
a new authorization.

If we settled and the model did NOT serve, you get `serve_failed` with an
`invoiceId`: replay that invoice free, above. Nothing is owed twice.

## Wallet-signed calls (no payment)

Reads and replays are wallet-signed, no session. Header
`X-Wallet-Auth: base64({"address","timestamp","nonce","signature"})` where
`signature` is an EIP-191 personal_sign over the exact string
`METHOD\npath\nnonce\ntimestamp` (e.g. `GET\n/api/inference/invoices\n<nonce>\n<unix seconds>`).
A Solana wallet signs the SAME string with its ed25519 key and sends
`signature` as base64 of the raw 64-byte signature, `address` base58.
Nonces are single-use; timestamps must be within the server skew.

- `GET https://agentgates-backend.vercel.app/api/inference/invoices` — your settled requests: sizes,
  actual usage, amounts, transaction hashes.
- REPLAY: if a paid request answers `502` with `{"code": "serve_failed",
  "invoiceId": "..."}`, the ticket stands and the delivery is on us —
  re-send the SAME request body plus `"replayInvoice": "<invoiceId>"` under
  `X-Wallet-Auth` (signed by the paying wallet) to the same endpoint. No
  new payment. A served invoice never replays.

## Verify the hardware

Every model's attestation is public:
`GET https://agentgates-backend.vercel.app/api/inference/attestation?model=<model id>` re-serves the
GPU TEE attestation report exactly as the serving infrastructure publishes
it (TDX workload keyset, receipt signing keys), plus this lane's own live
read of the model's confidential status. The report is re-served verbatim
and names its own origin — check it against what it claims.

## What we can see (stated plainly)

Your prompt and the completion transit this service in memory over TLS on
the way to the GPU TEE. They are not stored and not logged; the billed byte
count and the provider's token counts are the only metering facts kept, on
your invoice. The chain sees the payment. The model runs inside attested
hardware at the inference provider — verify it yourself, above.

## Rules

Sanctioned wallets are refused at every settlement (fail-closed screen).

Two daily walls, both ahead of the payment gate, so nothing is ever charged
on a refusal. `503 at_capacity` means the lane has SERVED its requests for
this UTC day — drawn and paid alike, because either one is a call we make
for you. `503 sales_at_capacity` means it is not TAKING more money today; a
balance topped up earlier still serves, because those dollars were counted the
day they were paid. A top-up is one of the day's sales, so
`GET /api/inference/credits` publishes `sale.availableUsd` — how much the
lane will add to balances right now — before you pick a rung.

Payments are final: tickets buy an output ceiling, and a serve failure is
re-served free rather than refunded.
