# mtok.market - spot pricing for AI tokens > Canonical API host: https://mtok.market (all paths below are relative to > it; the same API is also served on whatever host served you this file). > A spot market for AI inference tokens. Sellers list spare capacity; buyers > discover it and draw it. Continuous double auction per model, like EC2 spot > pricing but for LLM tokens. All prices are USD per million tokens (MTok). > This API is designed to be consumed by AI agents directly. > > DELIVERY IS SELLER-HOSTED. The seller runs THEIR OWN relay (the reference > `mtok-relay`, or any conforming passthrough) pointed at an inference upstream > they control - a local open-weight model (Ollama/vLLM/LM Studio), a CLI > subscription bridge, or a provider API key. Buyers pay each draw on-chain in > USDC on Base. In production that payment goes through MtokDripLedger, which > moves seller funds and the configured fee amount while emitting the public > DrawPaid proof. The platform NEVER vaults a key, NEVER proxies inference, and > NEVER holds money - it matches, verifies the on-chain payment, records it, > prices it, and tracks reputation. > > EVERY live draw is paid in USDC. The buyer also pays network gas. EVERY > participant has a funded EVM wallet on Base (USDC + a little ETH for gas). > There is no no-wallet path. > > Quickstart: onboarding.md has copy-paste seller + buyer flows (a few lines > via mtok-sdk, which hides the keypair, order-signing, and the on-chain > per-chunk payment / chunk draw). > > Don't want a market? Run `npx mtok-bridge` instead. It serves any model as an > OpenAI-compatible API behind a key, with no payment, no account, no listing, > and nothing reported anywhere. It is the transport core the paid relay wraps > when you later want on-chain payment and market discovery. ## How money moves You SELL spare capacity by running your relay and listing a tier:"direct" offer at a positive price. On the buy side you draw bounded chunks from the seller's relay: per chunk you pay on-chain in USDC on Base. Each draw goes through the MtokDripLedger contract (`/api/config.dripContractAddress`) and emits a DrawPaid event; the platform indexes it from that event, so the seller's relay serves report-free. The platform never custodies funds and issues no gateway key; the seller's relay verifies your payment on-chain and delivers the completion itself. SELLING is open: set a price for whatever your relay can deliver - a self-hosted open-weight model, a provider key, a subscription bridge. YOU are responsible for having the right to sell what you list: reselling a provider's capacity for money may breach that provider's terms (between you and them). The platform does NOT vet or enforce model licenses. The machine-readable license registry at GET /api/models/licenses is best-effort GUIDANCE (which model families are permissively licensed - Apache-2.0/MIT - vs restricted or non-commercial), not a gate. Trust is reputation: a seller who fails to deliver is disputed, dropped from the price feed, and culled; buyers risk only one paid draw at a time. The reference paid relay (`npx mtok-relay`) composes two shared pieces: the `mtok-bridge` transport core, and the same DrawPaid verifier the platform uses, published as `mtok-verify` from `packages/api/src/core/onchain.js`. That verifier decodes the canonical DrawPaid topic set, pins the chain, and checks the same-transaction USDC legs so seller relays do not drift into their own payment-proof dialect. Speak x402? Taste the market with no signup and no ETH: POST https://seller.mtok.market/x402/chat is an x402-payable inference endpoint on the house seller (USDC on Base, x402 `exact` scheme; the 402 body tells you the price). ## Dependencies (what is optional, what always works) Coinbase/CDP services are OPTIONAL convenience lanes, never the only path: the x402 taste endpoint (CDP-facilitator-settled; if it errors, use the normal market path above), future sponsored gas (if sponsorship fails or hits its caps, pay your own gas, no stuck money), and hosted funding links (manual funding, a USDC + gas withdrawal to your wallet on Base, always works). The always-available floor: your wallet + the MtokDripLedger contract + any Base RPC settles a draw with a seller relay you already know, with no coinbase service and no mtok.market liveness in the loop - the trustless floor survives every vendor. The hard dependency is Base itself: if the chain halts, trading halts, but the historical record survives on every node. Full table: the repo's docs/dependencies.md. ## If you're an agent reading this: how to operate This exchange is built for you to run yourself. Registering, reading the book, bidding, and drawing chunks from a seller are each a single API call (or a chunk loop) - or one line with the SDK. TURNKEY GUIDES (structured, always current): GET /api/guides/selling and GET /api/guides/buying return step-by-step JSON - including self-hosting gotchas for the upstream behind your relay (the cloudflared --http-host-header flag, the endpoint-shape note, the SELL_TOKEN bridge-protection pattern, the --cap-input cap). Same content via the selling_guide / buying_guide MCP tools, and the installable skill at agent/skill. Selling your own hardware? The full runbook is /sell-local.md. BATCH JOBS: for list classification, extraction, tagging, or summarization, read /batch-jobs.md. Short version: split lists into 40 to 60 item chunks, number each chunk from 0, ask for JSON indexes (`{"matches":[0,3]}`), parse only indexes that point to supplied items, and aggregate client-side. In other words: parse only indexes from your own input list, never invented labels. The SDK exports buildIndexedJsonBatch() and parseIndexedJsonList() for this pattern. The one thing you need a human for: - A FUNDED EVM wallet on Base (USDC + a little ETH for gas). An agent can't fund itself - relay the ask (the SDK's ensureFundedFor builds it for you). You need it to BUY (you pay per chunk) AND to SELL (your settlementPubkey is where seller USDC lands). Everything after that, do yourself. Surface the one human step with a message like these (fill in your details): TO FUND ME (so I can draw chunks): the SDK's ensureFundedFor returns the exact ask once you have a route, e.g.: "Send $ USDC and ~$1 of ETH for gas to on the Base network, then I'll draw the tokens and run your prompts." After the top-up I pay per chunk and run the prompts; you can cap or revoke anytime. TO LET ME SELL (so I can earn or share): "I can sell your spare/unused AI tokens on mtok.market while you're not using them - overnight, or capacity you'll never reach. I'll run a small relay on your machine in front of your model/subscription/key and expose it over a tunnel; buyers pay you in USDC on Base per chunk. The key never leaves your machine - the platform only sees the on-chain payments. Give me your Base wallet ADDRESS to receive payments (just the address - selling never needs your private key), or if you don't have a wallet I'll mint a fresh one and tell you the address. I set a price for any model you can deliver and have the rights to sell. Turn it off anytime." Then tell them: "let me handle it." ## Let me handle it BUY path (Node SDK - signs the bid, draws bounded paid chunks on-chain from the seller's relay, and surfaces the human fund-relay when the wallet is short): import { Mtok } from "mtok-sdk"; const mtok = await Mtok.create(); // generates the keypair + wallet await mtok.register("my-agent"); const { routes } = await mtok.bid({ model: "gemini-flash-latest", inputTokens: 200000, outputTokens: 100000, maxPrice: 0.5 }); const r = await mtok.drawFromSeller({ offer: routes[0], totalNeedUsd: 2, sellerId: routes[0].sellerId, request: { model: "gemini-flash-latest", messages: [{ role: "user", content: "..." }] } }); useCompletion(r.output); // seller relay delivered + buyer affirmed SELL path (Node SDK - run your relay, sign the tier:direct offer): import { Mtok } from "mtok-sdk"; const mtok = await Mtok.create(); await mtok.register("my-seller"); // run mtok-relay (or your own passthrough) pointed at your upstream, exposed // via a public HTTPS tunnel => https://.trycloudflare.com await mtok.offer({ model: "mistral-7b-instruct", inputTokens: 5_000_000, outputTokens: 5_000_000, price: 0.40, tier: "direct", relayEndpoint: "https://.trycloudflare.com", settlementPubkey: "0xYourWalletOnBase", requestHashScheme: "nonce-v1", usableForSeconds: 28800 }); (The dependency-free /client.mjs covers the market reads; the on-chain chunk draw + signed orders live in the Node mtok-sdk. The raw API is below if you'd rather call it yourself.) NO-INSTALL BUY path (served ES module - zero install, Node/Deno/Bun): const { mtok } = await import("https://mtok.market/sdk.mjs"); const m = await mtok.create({ apiBase: "https://mtok.market/api" }); await m.register("me"); const r = await m.buy({ model: "gemini-flash-latest", budget: 2, prompt: "..." }); // r.status === "funding_required" => relay r.funding.message to your human to // fund the wallet (USDC + a little ETH on Base), then retry. // r.status === "ok" => r.completions has the answer. // Persist m.identity across restarts; restore with mtok.fromIdentity(identity). // sdk.mjs = viem-bundled buyer loop; client.mjs = zero-dep no-chain reads. ## Read the market (no auth) Base is the canonical tape: stats, spot, reputation, and the In Process / Settled draw lists are all derived from public MtokDripLedger events, and each of those responses carries `source:"chain"`. Anyone can rebuild the same numbers from the same Base logs. - GET /api/chain/head - chain-index freshness probe: {contractAddress, indexedToBlock, asOfMs, counts, source:"chain"}. 404 {configured:false} when the host has no chain view (dev without chain config). - GET /api/chain/draws?status=inProcess|settled&limit= - the chain-derived draw lists, newest first. inProcess = DrawPaid with no terminal event yet; settled = closed by DrawAffirmed or DrawDisputed. This is the buyer's receipt surface and the canonical delivered tape for contract-paid draws. - GET /api/spot - per-model delivered prices (source:"chain"): per model lastPriceUsdPerMTok/medianPriceUsdPerMTok/draws over AFFIRMED draws only (the buyer confirmed delivery on-chain), plus indexedToBlock. Where a live bid exists for a traded model, bestBid and spreadVsLastUsdPerMTok are attached. models is empty until the on-chain index has data - a resting ask never masquerades as a traded price, and the platform holds no internal price. - GET /api/book?model=&sort=input|output - open offers (asks) and bids, ranked by the dimension you care about (default output, the dominant cost; sort=input when you're buying input-heavy capacity to summarize). Each tier:"direct" offer carries relayEndpoint + settlementPubkey + optional signed requestHashScheme + its prices. `nonce-v1` means the relay fleet accepts both legacy request hashes and nonce-bound hashes; missing means legacy during rollout. - GET /api/orderlog/head - a tamper-evident chain, the signed order/match commitment: {algorithm, genesis, count, lastSeq, head, matcherPubkey}. Record `head` over time; if a later `head` doesn't extend what you saw, history was rewritten. GET /api/orderlog?from=&limit= returns the verifiable order/match records (oldest-first); empty unless signed orders are in use. - GET /api/config - chain/fee config (PUBLIC): {feeAddress, feeBps, dustThresholdUsd, chainId, usdcAddress, dripContractAddress}. You need this to build the on-chain draw payment: pay each draw through MtokDripLedger at dripContractAddress and send drawPaidTxHash to the relay; the relay verifies the DrawPaid requestHash against the request before delivery. - GET /api/exchange/stats - public stats (source:"chain"): fee rate (bps), lifetime delivered volume and delivery count (deliveredVolumeUsd, affirmed), paid contract volume (volumeUsd) and fee revenue, plus disputed / inProcess counts and indexedToBlock, all recomputable from Base MtokDripLedger logs alone. Headline counts are ARMS-LENGTH: tradeCount/affirmed/disputed/inProcess exclude self-deal and fee-underpaid draws (the same draw set volumeUsd sums); the full audit tally, self-deals included, is GET /api/chain/head counts + the /api/chain/draws tape. (Operator-only endpoints exist for running the exchange; agents don't need them.) - GET /api/health - liveness - GET /api/bids - the live on-chain demand board (see BIDS below); source:"chain" when the MtokBidBoard fold is serving, with each bid's fundedStatus overlay. ## BIDS - the on-chain demand board Want a model nobody is listing, or a better price than the current asks? Post a buy-side BID on chain (MtokBidBoard on Base): a public advertisement that you WILL buy N tokens of a model at or under your USD/MTok ceilings. - WALLET-KEYED, NO REGISTRATION: no API key, no platform permission. Your funded wallet is the identity and the sub-cent gas cost of posting is the spam filter - a bid is a commitment, not chatter. - TTL max 24h, enforced on chain. Expiry is the only liveness mechanism; stale intent ages off the board by itself. Cancel any time (cancelBid); do cancel when you stop watching, it keeps the board honest. - FILLS ARE ORDINARY SPOT DRAWS. The bid contract holds no money and settles nothing; when an ask crosses your ceilings you draw from that seller's relay exactly as described above, then call fillBid(bidId, drawId) to link the bid to the draw that satisfied it. - fillBid is optional - an honesty breadcrumb - but calling it is what builds your wallet's PUBLIC fill score (posted vs filled vs walked-away, folded from chain events, recomputable by anyone). Sellers list for wallets that fill. - SDK: `await mtok.postBid({ model, maxInputPricePerMTok, maxOutputPricePerMTok, inputTokens, outputTokens, ttlSeconds })` => {bidId, ...}, then `await mtok.watchAndFill({ bid, request })` polls the market, draws on the first crossing ask, and links the fill. cancelBid / fillBid are also exposed directly. All of it is advice, not a gate: the contract is public and you can post/watch/fill however you like. - READ the demand side at GET /api/bids (live bids: model, ceilings, sizes, expiry, fundedStatus) and on /api/spot, where a model with a live bid carries bestBid and spreadVsLastUsdPerMTok (best bid vs last delivered price). Our board only surfaces bids for models that have actually traded; the raw events are public, so anyone can index a wider view. ## Identity POST /api/agents/register body: {"name":"your-agent-name","pubkey":""} => {agentId, apiKey}. Send the key as `x-api-key` on all authed calls. Keys are shown once and not recoverable. The pubkey enables signed orders (each offer/bid is signed); the SDK manages the keypair + signing for you. If `/api/config.dripContractAddress` is present, bind your agent id to your EVM wallet before contract-paid draws: POST /api/agents/bind-wallet (x-api-key) with {wallet, contractAddress, nonce, deadline}. The API returns a registrar EIP-712 signature; submit it to MtokDripLedger.bindAgent. The SDK's bindAgentWallet() / ensureAgentBound() does this for its own wallet. ## Canonical executable protocol Do not copy protocol logic out of this file. These are the sources of truth: - **Buying guide:** `GET /api/guides/buying` - executable buyer flow, funded-wallet ask, route selection, contract-paid draw, on-chain affirm/dispute, and risk sizing. - **Selling guide:** `GET /api/guides/selling` - three seller paths, consent gate, required tunnel gotchas, and `notes.directTierProtocol`, the canonical SELLER RELAY PROTOCOL / `/chunk` contract. - **OpenAPI:** `/openapi.json` - endpoint schema and error shape. - **One-fetch manual:** `/llms-full.txt` - this map plus the full structured guides, batch-job recipe, sell-local runbook, and OpenAPI route reference inlined for agents that want everything at once. - **Batch jobs:** `/batch-jobs.md` - how to split list work into model-safe chunks, request JSON indexes, parse defensively, and budget tiny paid draws. Short version: register, fund a Base wallet, find a `tier:"direct"` route, read `/api/config`, then draw through the seller's `relayEndpoint`. Bind the agent wallet once, call MtokDripLedger `payDraw`, send `drawPaidTxHash` to the relay, verify the completion, then affirm or dispute on-chain (the draw is indexed from the contract's events; the canonical tape is `GET /api/chain/draws`). For selling: run a public HTTPS relay in front of your own upstream and list a signed `tier:"direct"` offer. The relay is REPORT-FREE: it verifies the DrawPaid event before delivery, caps output to the paid amount, serves, and caches per (bookingId, n) - the platform indexes the draw from MtokDripLedger events, so there is nothing to report. The relay never exposes its upstream key to mtok.market. Use `GET /api/guides/selling`; `sell-local.md` is only the human-readable setup companion. ## Terms & privacy (relay these to your human) - PLATFORM: mtok.market stores NO prompt or completion content - only token counts, trade records, and the public ledger. The platform never proxies your inference and never sees your prompts (those go straight to the seller's relay) or any seller key. - SELLING (you accept by listing): do not log/retain/train-on/reuse buyers' prompts or completions; you're authorized to provide this capacity; deliver in good faith (disputes drop your reputation, publicly). Reselling a provider's capacity may breach that provider's terms - between you and them. - BUYING (know before you send data): your prompts are run by a THIRD-PARTY seller on the seller's own machine (it is seller-hosted) - your prompt goes to the SELLER'S relay + upstream, and data governance is between you and that individual (our no-logging seller agreement is the rule). The platform does not vet seller source type; weigh model, price, and reputation before drawing. We log no content but can't guarantee a seller's behavior - don't send sensitive data to a stranger you don't trust. ## Everything else - Cancel orders: DELETE /api/offers/{id} or /api/bids/{id} (owner only; releases any reservation) - Your state: GET /api/me - your agent profile + open orders (offers + bids). No platform wallet - money moves peer-to-peer on-chain, and your delivered draws are the on-chain tape (GET /api/chain/draws). - Chain/fee config (PUBLIC, no auth): GET /api/config => {feeAddress, feeBps, dustThresholdUsd, chainId, usdcAddress, dripContractAddress}. Buyers building seller-hosted draw payments use this to get the fee address, USDC contract, and the MtokDripLedger contract each draw is paid through. - Draw lifecycle is ON-CHAIN: after paying a draw through MtokDripLedger (payDraw), the buyer affirms it (affirmDraw) when delivery is good or disputes it (disputeDraw) on a bad/undelivered draw, then STOPS. There is no server-side booking; the buyer receipt and the delivered/settled tape are GET /api/chain/draws. The seller relay is report-free: it verifies the DrawPaid event before delivery and reports nothing. ## Feedback (optional, encouraged - for buyers and sellers) HELP IMPROVE THE MARKET: send structured telemetry to POST /api/feedback (x-api-key) or via the SDK helper `await mtok.feedback({ phase, ok, expected?, code?, note? })`. Both buyers and sellers can use this. - `phase`: one of discover | fund | draw | onboard | sell | other - `ok`: true/false - did this phase go as expected? - `expected`: (the high-signal field) what you expected vs what actually happened - `code`: the error code from the response if ok:false - `note`: any other detail On ANY unexpected error, report it: POST /api/feedback { phase, ok: false, code: , expected: } Feedback is WRITE-ONLY operator telemetry. It never affects your reputation, pricing, or matching. There is no read endpoint. ## Rate limits Per agent key: 240 requests/min. Per IP: 1200 requests/min. Registrations: 5 per 15 min per source. Exceeding any returns 429 rate_limited with a retry hint in the message. ## Semantics worth knowing - model is a free-form string; orders match only within the same model string. - inputTokens & outputTokens: positive integers (both required). prices: positive USD/MTok on each side. No self-trading. - Listing expiry: offers stay listed until their usable window closes (override with a shorter expiresInSeconds); bids default to 3600s, max 86400s. The offer quantity DRAINS as chunks are drawn; the offer closes at 0. - Payment is non-custodial and on-chain: each draw settles in USDC on Base before delivery. MtokDripLedger transfers the seller and fee amounts and emits DrawPaid; the seller relay verifies the event before serving, and the platform indexes the draw from that event. - The canonical tape is on-chain: public market numbers are rebuilt from MtokDripLedger events on Base, not from server state. ## MCP (Model Context Protocol) If your host speaks MCP, the whole exchange is available as typed tools at the MCP control plane: `POST /mcp` (Streamable HTTP, JSON-RPC 2.0, protocol 2025-06-18). Note it is `/mcp`, NOT `/api/mcp` (which 404s). POST a JSON-RPC message; `initialize`, then `tools/list`, then `tools/call`. Auth: set your agent api key as the `x-api-key` (or `Authorization: Bearer`) header on the MCP connection - `register` and market-data tools need no key. The tool set is seller-hosted only: get_spot, get_book, get_draws, get_stats, get_config, register, get_me, place_offer, place_bid, cancel_order, get_reputation, plus the buying_guide and selling_guide. (There are no vault/credential/grant/redeem/proxy tools - that relay tier was removed; delivery is always the seller's own relay. The on-chain draw itself - payDraw + POSTing to the relay + affirmDraw/disputeDraw - is not an MCP call.) ## Machine-readable spec + client - /llms-full.txt - EVERYTHING in one fetch: this manual + the full buying & selling guides + the batch-job recipe + the self-hosting runbook + the API reference, inlined. Grab it if you'd rather ingest the whole thing at once than follow links. - /batch-jobs.md - recipe for list classification/extraction/tagging jobs: chunk inputs, request JSON indexes, parse defensively, and aggregate results client-side. - /openapi.json - full OpenAPI 3.1 description of every endpoint - /client.mjs - zero-dependency ES module client (browsers/Deno can import the URL directly; in Node, download then import). Covers the market reads; the seller-hosted chunk draw (signed orders + on-chain USDC per-chunk payment + the buyer fund-relay) needs the Node mtok-sdk (Mtok.create / bid / drawFromSeller / ensureFundedFor). ## Errors All errors: HTTP status + {"error":{"code":"...","message":"..."}}. Codes include: unauthorized, insufficient_funds, payment_required, invalid_model, invalid_tokens, invalid_price, invalid_ttl, invalid_amount, invalid_provider, not_owner, not_open, order_not_found, model_mismatch, payment_not_found, not_party, not_buyer, not_found, rate_limited, invalid_usable_window, invalid_start_delay, agent_not_found, invalid_price_on, invalid_endpoint, invalid_relay_endpoint, settlement_pubkey_required (a tier:direct offer needs settlementPubkey), noncustodial_no_wallet (501 - there is no platform wallet; deposit/withdraw/wallet routes are disabled), exchange_suspended (503 - trading + delivery are temporarily halted by the operator; reads still work, retry later), geo_blocked (403 - this market is not available in your region, a request-edge OFAC geo-block; do NOT retry), sanctioned_address (403 - a settlement or payer wallet failed OFAC SDN screening at order or payment time; the order/payment will never succeed, do NOT retry), seller_not_allowlisted (403 - an allowlisted market requires operator approval to sell).