# Inferit for agents

> Inferit is a market for LLM inference on Whitechain Sepolia (testnet). One OpenAI-compatible API sends each request to the cheapest healthy seller on Inferit. An agent pays per request with x402 v2: no account and no API key.

Testnet demo on Whitechain Sepolia. Prices settle in Inferit Test Credit (ITC), a test token with no monetary value. The contracts are not audited.

**What answers today.** The live model is Qwen 2.5 14B (`qwen/qwen-2.5-14b-instruct`), served by team sellers only. A labelled demo seller returns canned responses, and each of its replies says it is a mock completion. The founder's Mac serves real completions while it is listed and online. Today you cannot tell before you pay which of the two will answer: see [Is the best offer a demo seller?](#is-the-best-offer-a-demo-seller).

**Numbers.** Amounts are integer µ (1 µ = 0.000001 ITC). Prose groups thousands with a thin space (10 000 µ); the machine values are the plain integers in code (`"10000"`), and JSON carries them as decimal strings.

## In one minute

1. Claim test ITC: `POST https://api-production-c74b9.up.railway.app/v1/faucet` with `{"address":"0x..."}`. It is free and needs no gas.
2. Call `POST https://api-production-c74b9.up.railway.app/v1/chat/completions` with no key. You get `402 Payment Required` with a quote of at least 10 000 µ (0.01 ITC; `amount` ≥ `"10000"`).
3. Sign the quote with an x402 v2 client (the official `@x402/fetch` works unmodified) and resend the same body.
4. You get the answer. The request is charged its exact µ cost; the rest of the payment stays your escrow balance.

## Two ways to pay

| | x402 (no account) | API key |
| --- | --- | --- |
| Setup | None: any EVM key, even one with zero WBT | Sign in with the wallet (an off-chain signature), mint a key |
| Per request | One x402 payment of at least 10 000 µ, credited to your escrow balance | Nothing extra: the request spends your balance |
| Charged | The exact µ cost of the request | The exact µ cost of the request |
| Header | `PAYMENT-SIGNATURE` (answering the 402) | `Authorization: Bearer ik_...` |

## Where to go

| What | Where |
| --- | --- |
| API base (OpenAI-compatible; a `GET` on it returns a JSON index) | `https://api-production-c74b9.up.railway.app/v1` |
| Chat completions (streaming supported) | `POST https://api-production-c74b9.up.railway.app/v1/chat/completions` |
| x402 discovery | `GET https://api-production-c74b9.up.railway.app/.well-known/x402` |
| Inferit's ITC faucet (testnet) | `POST https://api-production-c74b9.up.railway.app/v1/faucet` |
| Models, list prices and the best live price | `GET https://api-production-c74b9.up.railway.app/v1/models` |
| One model's order book (price levels, with offer and healthy-offer counts) | `GET https://api-production-c74b9.up.railway.app/api/markets/<url-encoded model id>` |
| This guide | https://inferit.cc/agents.md (also served at https://inferit.app/agents.md) |
| The same guide for people | https://inferit.cc/agents |
| Every page of the site as markdown | https://inferit.cc/llms.txt (index), https://inferit.cc/llms-full.txt (full text) |
| Full API reference | https://inferit.cc/docs/api-reference.md |
| Selling inference, including the API-only path | https://inferit.cc/docs/sellers.md |
| The app, for people with a browser wallet | https://inferit.app |

## Two test tokens, two jobs

- **ITC** (Inferit Test Credit) is what you pay for inference with. It has 6 decimals, so 1 µ = 0.000001 ITC. Inferit's ITC faucet mints 1,000 ITC per address, once per 24 h, at no charge. It is sponsored: the claim needs no gas.
- **WBT** is Whitechain's native gas token. You need it only for on-chain transactions you send yourself: a deposit, a seller registration, a withdrawal. An agent that pays with x402 sends none, so it needs no WBT until it withdraws.

## Prices in µ

- 1 µ = one millionth of an ITC (0.000001 ITC) on the testnet; a micro-dollar at mainnet (planned).
- API prices are in µ per 1M tokens, input and output, all-in: the seller's price plus the 5% platform fee. 84 000 µ per 1M = 0.084 µ/token = 0.084 ITC per 1M tokens.
- `GET /v1/models` lists the whole catalog, including list-price-only rows (`best_price: null`). Closed models (`open_weight: false`) are reference rows that no seller can list on Inferit, so a request for one answers `503 no_available_offers`. A row with a `best_price` has a live offer.
- `GET /v1/models` carries each price twice. `pricing.prompt` and `pricing.completion` are decimal strings in ITC (test dollars) per token (`"0.0000001"`), as OpenRouter writes them; `input_per_m`, `output_per_m` and `cache_read_per_m` are integer µ per 1M tokens (`"100000"`). `pricing` is the list price, `best_price` the best live all-in price (or `null`), and `best_price.discount_pct` compares that all-in price with list.
- What a request costs, at the serving seller's own price (before the fee): `sellerCost = ceil((uncachedIn × inputPerM + cachedIn × cacheReadPerM + out × outputPerM) / 1000000)`, then `fee = ceil(sellerCost × feeBps / 10000)`; you pay `sellerCost + fee`. Illustrative, at the founder's Mac's listing of 2026-10-04 (`70000` / `140000` µ per 1M before the fee): 1,000 input and 500 output tokens cost 140 µ + 7 µ = 147 µ.
- The `x-inferit-buyer-cost-micro` response header is the authoritative cost of each request.

## Pay per request with x402: a prepaid meter

x402 amounts are in token base units, so x402 can express 1 µ. But every x402 payment is an on-chain transaction, and each one costs gas. So Inferit works as a prepaid meter:

1. Call `POST https://api-production-c74b9.up.railway.app/v1/chat/completions` with no `Authorization` header. It answers `402 Payment Required`. The `PAYMENT-REQUIRED` header is base64 JSON, and the body carries the same document: x402 v2, scheme `exact`, network `eip155:1874`, asset = ITC, `payTo` = the `MarketEscrow` contract (not Inferit), `maxTimeoutSeconds` 120 and `extra` = the token's EIP-712 name and version.
2. The amount is the worst-case cost of this exact request body at the top-ranked offer: the input estimate plus the output limit, all-in, and never less than 10 000 µ (0.01 ITC). The output limit is `min(max_tokens, max_completion_tokens, context_length)`, or 1,024 tokens when you send neither (times `n` when you ask for several choices); `context_length` is the model's, from `GET /v1/models`. The quote is pinned for 120 s.
3. Sign an EIP-3009 `TransferWithAuthorization` for that amount to the escrow, and resend the same body with the `PAYMENT-SIGNATURE` header. `X-PAYMENT`, the v1 name, is still accepted.
4. Inferit's facilitator verifies the payment and settles it first: it calls `depositWithAuthorization` on the escrow and pays the gas. The payment is credited to your own escrow balance.
5. The request is served (streaming included) and charged its exact µ cost against that balance. The response carries `PAYMENT-RESPONSE` (base64: the deposit transaction, the network and the payer) and `x-inferit-buyer-cost-micro`.
6. What the request does not use stays your escrow balance. Every call without a key gets its own 402 and its own payment. To spend the balance without paying again, sign in with the same wallet and use an API key (below), or withdraw it.

Settlement to sellers is batched on chain: one transaction pays for many requests.

### TypeScript, with the official x402 fetch client

A brand-new key with zero WBT works: the agent never sends a transaction itself.

```ts
// npm i @x402/fetch @x402/evm viem    then: npx tsx agent.mts    (Node 18+, @x402 2.28 or newer)
import { decodePaymentResponseHeader, wrapFetchWithPayment, x402Client } from '@x402/fetch';
import { ExactEvmScheme } from '@x402/evm/exact/client';
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';

const API = 'https://api-production-c74b9.up.railway.app';
// Any key works, including a brand-new one with zero WBT: the agent never sends a transaction itself.
const account = privateKeyToAccount((process.env.AGENT_KEY as `0x${string}` | undefined) ?? generatePrivateKey());

// 1. Testnet only: Inferit's sponsored ITC faucet (test credit, not WBT) mints 1,000 test ITC to this address (Inferit pays the gas).
const faucet = await fetch(`${API}/v1/faucet`, {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ address: account.address }),
});
console.log('faucet', faucet.status, await faucet.text());

// 2. The standard x402 v2 client, unmodified. It only pays in tokens it was told about, so name ITC,
//    and cap one payment at 1 ITC (1000000 base units, 6 decimals), whatever the server quotes.
const ITC = { network: 'eip155:1874', asset: '0x2E672dFE33EA977FD064E01aDe7d8c73B3Be7fBB' } as const;
const client = new x402Client()
  .register('eip155:*', new ExactEvmScheme(account))
  .setSpendControls({ allowedAssets: [{ ...ITC, maxAmountPerPayment: '1000000' }] });
const fetchWithPayment = wrapFetchWithPayment(fetch, client);

// 3. Call the API with no key: 402 -> sign -> paid retry, all inside fetchWithPayment.
const res = await fetchWithPayment(`${API}/v1/chat/completions`, {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({
    model: 'qwen/qwen-2.5-14b-instruct',
    messages: [{ role: 'user', content: 'Say hello in five words.' }],
    max_tokens: 64, // the quote is the worst case for this body, so a small max_tokens keeps it small
  }),
});
const body = await res.json();
console.log(res.status, body.choices?.[0]?.message?.content ?? body);

// The settlement receipt: the escrow deposit transaction on Whitechain, and who paid.
const receipt = res.headers.get('PAYMENT-RESPONSE');
if (receipt) console.log(decodePaymentResponseHeader(receipt));
console.log('cost', res.headers.get('x-inferit-buyer-cost-micro'), 'µ (micro-ITC); the rest stays in your escrow balance');
```

The `setSpendControls` line is configuration of the standard client, not a patch: the client only pays in tokens it knows, and ITC is not on its built-in list. Its `maxAmountPerPayment` (`"1000000"`, 1 ITC) is the client's maximum per payment: a setting in your client, not a rule the contract enforces.

### Python, the v2 exchange by hand

No x402 package: `eth-account` signs the EIP-3009 authorization, so every field is visible.

```python
# pip install "eth-account>=0.13" requests
import base64, json, os, time
import requests
from eth_account import Account
from eth_account.messages import encode_typed_data

API = "https://api-production-c74b9.up.railway.app"
acct = Account.from_key(os.environ["AGENT_KEY"]) if "AGENT_KEY" in os.environ else Account.create()
b64 = lambda o: base64.b64encode(json.dumps(o).encode()).decode()
unb64 = lambda s: json.loads(base64.b64decode(s))

requests.post(f"{API}/v1/faucet", json={"address": acct.address})  # testnet only

body = {"model": "qwen/qwen-2.5-14b-instruct", "messages": [{"role": "user", "content": "Say hello in five words."}], "max_tokens": 64}
r = requests.post(f"{API}/v1/chat/completions", json=body)
assert r.status_code == 402, r.text
required = unb64(r.headers["PAYMENT-REQUIRED"])
req = required["accepts"][0]                      # scheme "exact", network "eip155:<chainId>"
assert (req["network"], req["asset"].lower()) == ("eip155:1874", "0x2e672dfe33ea977fd064e01ade7d8c73b3be7fbb"), req  # ITC only
assert int(req["amount"]) <= 1_000_000, req       # never sign more than 1 ITC for one request

nonce = os.urandom(32)                             # random bytes32, never reused
auth = {"from": acct.address, "to": req["payTo"], "value": req["amount"], "validAfter": "0",
        "validBefore": str(int(time.time()) + req["maxTimeoutSeconds"]), "nonce": "0x" + nonce.hex()}
typed = {
    "types": {
        "EIP712Domain": [{"name": "name", "type": "string"}, {"name": "version", "type": "string"},
                         {"name": "chainId", "type": "uint256"}, {"name": "verifyingContract", "type": "address"}],
        "TransferWithAuthorization": [{"name": "from", "type": "address"}, {"name": "to", "type": "address"},
            {"name": "value", "type": "uint256"}, {"name": "validAfter", "type": "uint256"},
            {"name": "validBefore", "type": "uint256"}, {"name": "nonce", "type": "bytes32"}],
    },
    "primaryType": "TransferWithAuthorization",
    "domain": {"name": req["extra"]["name"], "version": req["extra"]["version"],
               "chainId": int(req["network"].split(":")[1]), "verifyingContract": req["asset"]},
    "message": {**auth, "value": int(auth["value"]), "validAfter": 0, "validBefore": int(auth["validBefore"]), "nonce": nonce},
}
sig = Account.sign_message(encode_typed_data(full_message=typed), acct.key).signature
payment = {"x402Version": 2, "resource": required.get("resource"), "accepted": req,
           "payload": {"authorization": auth, "signature": "0x" + bytes(sig).hex()}}

r = requests.post(f"{API}/v1/chat/completions", json=body, headers={"PAYMENT-SIGNATURE": b64(payment)})  # same body
print(r.status_code, r.json()["choices"][0]["message"]["content"])
print(unb64(r.headers["PAYMENT-RESPONSE"]))
```

### curl: see the 402 without paying

```sh
curl -sS -D - https://api-production-c74b9.up.railway.app/v1/chat/completions \
  -H 'content-type: application/json' \
  -d '{"model":"qwen/qwen-2.5-14b-instruct","messages":[{"role":"user","content":"Hi"}],"max_tokens":64}'
# HTTP/2 402
# payment-required: eyJ4NDAyVmVyc2lvbiI6Mi...   (base64 JSON; the body carries the same document)

curl -s https://api-production-c74b9.up.railway.app/.well-known/x402 | jq     # scheme, network, asset, payTo, extra (EIP-712 name and version)
```

### Is the best offer a demo seller?

The labelled demo seller answers with canned text. Today the API cannot tell you before you pay which seller will answer: since 2026-10-05 both team sellers ask the same price, so the best price does not tell them apart either. A flag for the demo seller in the API is planned, not deployed yet.

What you can check before you pay:

- `GET https://api-production-c74b9.up.railway.app/v1/models`: the model's `best_price` (all-in, per 1M tokens) and its `discount_pct` under the list price.
- `GET https://api-production-c74b9.up.railway.app/api/markets/<url-encoded model id>`: each price level carries `offers` and `healthyOffers`. No healthy offer means no seller is serving that model right now (the API answers 503 `no_available_offers`).

If you need a real completion, read the reply: the demo seller's text says it is a mock.

## Or use an API key

The same wallet signs in with an off-chain text signature (EIP-4361; it moves no tokens), then mints a key. The key spends your escrow balance: what x402 payments credited, or what you deposited yourself with `depositAndSetCap` (that deposit is a transaction you send, so it needs WBT).

1. `POST https://api-production-c74b9.up.railway.app/v1/auth/evm/challenge` with `{"address":"0x..."}` returns `{"message","nonce","expiresAt"}`.
2. Sign `message` (EIP-191 `personal_sign`), then `POST https://api-production-c74b9.up.railway.app/v1/auth/evm/verify` with `{"message","signature"}`. It returns a session token.
3. `POST https://api-production-c74b9.up.railway.app/v1/keys` with `Authorization: Bearer <session token>` and `{"name":"my-agent"}` returns `{"key":"ik_..."}`, shown once. Optional fields: `spendLimitMicro`, `minDiscountPct`.
4. Call the API with `Authorization: Bearer ik_...`.

```ts
// Same wallet as the x402 payer: sign in (EIP-4361, an off-chain text signature; it moves no tokens) and mint an API key.
const post = (path: string, body: unknown, token?: string) =>
  fetch(`https://api-production-c74b9.up.railway.app${path}`, {
    method: 'POST',
    headers: { 'content-type': 'application/json', ...(token ? { authorization: `Bearer ${token}` } : {}) },
    body: JSON.stringify(body),
  }).then((r) => r.json());

const { message } = await post('/v1/auth/evm/challenge', { address: account.address });
const { token } = await post('/v1/auth/evm/verify', { message, signature: await account.signMessage({ message }) });
const { key } = await post('/v1/keys', { name: 'agent' }, token);   // shown once; ik_…
// From here on: Authorization: Bearer <key>, spending what is left of your escrow balance. No 402 round trip.
```

```sh
curl https://api-production-c74b9.up.railway.app/v1/chat/completions \
  -H "Authorization: Bearer $INFERIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen/qwen-2.5-14b-instruct",
    "stream": true,
    "messages": [{"role": "user", "content": "Say hello in five words."}]
  }'
```

Every OpenAI-compatible client works with the base URL `https://api-production-c74b9.up.railway.app/v1`. To require a minimum discount against the list price, call `/min{N}/v1/chat/completions` or send `x-min-discount: N`.

## Inferit's ITC faucet (testnet)

```sh
curl -s -X POST https://api-production-c74b9.up.railway.app/v1/faucet -H 'content-type: application/json' -d '{"address":"0xYourAddress"}'
```

- It mints 1,000 test ITC to the address, at most once per 24 h per address (the token enforces it), and at most 10 calls an hour per IP. The server also caps sponsored mints at 120 an hour in total (`429 rate_limit_exceeded`).
- Inferit pays the gas, so a fresh address with zero WBT can claim and then pay with x402.
- It is Inferit's ITC faucet, not a WBT faucet. It runs on testnets only.
- `503 faucet_pending` (with `retry-after: 15`): the mint was sent but is not confirmed yet. Check the balance before you claim again: a claim after it lands gets `429 faucet_cooldown`.

A successful claim returns:

```json
{
  "address": "0xYourAddress",
  "amountMicro": "1000000000",
  "token": { "address": "0x2E672dFE33EA977FD064E01aDe7d8c73B3Be7fBB", "symbol": "ITC", "decimals": 6 },
  "network": "eip155:1874",
  "transaction": "0x…",
  "explorerUrl": "https://explorer.testnet.whitechain.io/tx/0x…",
  "next": "Pay for inference with x402: POST https://api-production-c74b9.up.railway.app/v1/chat/completions without credentials …"
}
```

## Discovery

`GET https://api-production-c74b9.up.railway.app/.well-known/x402` lists the paid resources and the requirement a client signs for: scheme, network, asset, `payTo`, the token's EIP-712 name and version, and the minimum payment. Pin the network and the asset in your own code rather than trusting the server that is being paid. On the testnet today:

| Field | Value |
| --- | --- |
| Network | Whitechain Sepolia, chain id 1874 (`eip155:1874`) |
| Asset (ITC, 6 decimals; TestCredit contract rev. 2) | `0x2E672dFE33EA977FD064E01aDe7d8c73B3Be7fBB` |
| `extra` (the token's EIP-712 domain) | `name: "Inferit Test Credit"`, `version: "1"` (the domain version, not the contract revision) |
| `payTo` (MarketEscrow v0.3) | `0x705dD9eCffa888FbcF2849eAE2D3b136eb78B7e0` |
| Minimum amount | `"10000"` (10 000 µ = 0.01 ITC) |
| Explorer | https://explorer.testnet.whitechain.io |

## Limits

| What | Default | Why |
| --- | --- | --- |
| Minimum payment | 10 000 µ (0.01 ITC) | Each payment is an on-chain deposit whose gas the facilitator pays. x402 itself could express 1 µ; the request is still charged its exact µ cost |
| Signature validity | 120 s (`maxTimeoutSeconds`) | The client sets `validBefore` = now + this; the API refuses authorizations close to expiry |
| Quote pin | 120 s, per request (path, routing headers and JSON body) | The paid retry verifies against the quote it was shown; a changed request gets a new 402 |
| Signer | A plain key (EOA), 65-byte signature, x402 version 2 | Smart-wallet signatures (ERC-1271, ERC-6492) and the Permit2 transfer method are not accepted |
| One payment per authorization | Each nonce is accepted once | A replayed payment is refused |
| General rate limit | 1,200 requests a minute per IP (hosted default) | Every route; `429 rate_limit_exceeded` with `retry-after`. Responses carry no RateLimit headers |
| Sign-in | 30 requests a minute per IP | `/v1/auth/*` |
| x402 payments | 30 paid attempts a minute per paying address, 60 per IP | On top of the general limit |
| ITC faucet | 1,000 ITC per address per 24 h, 10 calls an hour per IP, 120 sponsored mints an hour per server | Testnets only |
| Credit expiry | Each x402 credit extends your cap's expiry to at least 30 days from the payment | Money paid through x402 stays spendable |
| Escrow paused | No new payments | Pausing blocks deposits, including x402; withdrawals keep working |

If inference fails after the payment settled, the payment stays in your escrow balance and the error says so.

## Errors

Errors are OpenAI-shaped: `{"error":{"message","type","code"}}`.

| Status | Code | Meaning |
| --- | --- | --- |
| 402 | (the x402 challenge) | No credentials: the body is the x402 `PaymentRequired` document |
| 402 | (a refused payment) | The payment did not verify; the reason is in `error` |
| 400 | `invalid_request` | The body does not parse or a field is invalid; the message names it. Its `type` is `invalid_request_error` |
| 400 | `invalid_address` | `POST /v1/faucet` (or sign-in) with an address that is not 0x-prefixed 20-byte hex |
| 401 | `missing_credentials`, `invalid_api_key` | An account route without a token, or a key that is wrong or revoked. Inference without any key answers 402 instead |
| 402 | `insufficient_funds` | An API-key request costs more than your balance or ceiling allows |
| 404 | `model_not_found` | The model is not in the catalog (`GET /v1/models` lists it) |
| 404 | `faucet_disabled` | This server runs no sponsored faucet (not a testnet) |
| 429 | `rate_limit_exceeded` | Too many requests from this IP or paying address, or the faucet's hourly server budget is spent; honour `retry-after` |
| 429 | `faucet_cooldown` | This address claimed less than 24 h ago; `retry-after` says when the next claim works |
| 501 | `not_implemented` | `/anthropic/v1/messages` and legacy `/v1/completions`; use `/v1/chat/completions` |
| 503 | `no_available_offers` | No healthy seller for that model right now; honour `retry-after` |
| 503 | `faucet_unavailable` | The faucet cannot mint right now; retry after 60 s |
| 503 | `faucet_pending` | The mint was sent but is not confirmed yet; check the balance after `retry-after` (15 s) before claiming again |
| 503 | `x402_settlement_pending`, `x402_unavailable` | The payment path failed; the error says whether anything was charged |

Every response carries `x-request-id`, `x-inferit-network` and `x-inferit-testnet: 1` on the testnet.

## Who runs what

- Inferit's API settles x402 payments with its own x402 v2 facilitator on Whitechain Sepolia, self-hosted and operated by Inferit (key `0xc8c8E79E2547ba1D81FAC12c84867B1DbD83e602`): the party that sells the request also submits the payment. Its key pays gas only, and it can credit a payment only to the wallet that signed it.
- The seller that serves a request sees it. The API stores token counts and amounts, never prompts or responses.
- whitechain-x402-facilitator, the first public x402 v2 facilitator for Whitechain, is a separate open-source project (Apache-2.0) for other Whitechain merchants, live on Whitechain Sepolia (testnet): https://github.com/OGcryptonaut/whitechain-x402-facilitator. Inferit operates a public instance of it; it does not settle Inferit's own payments.

---

Canonical page: https://inferit.cc/agents
All pages as markdown: https://inferit.cc/llms.txt
Testnet demo: test tokens, no monetary value.
