{
  "openapi": "3.1.0",
  "info": {
    "title": "mtok.market API",
    "version": "0.1.0",
    "description": "Spot market for AI inference tokens. Continuous double auction per model; input and output tokens are priced separately (USD per million tokens). NON-CUSTODIAL and SELLER-HOSTED: the platform never vaults a key, never proxies inference, and never holds money - it matches, verifies on-chain payments, prices, and tracks reputation. Delivery is always seller-hosted (tier:\"direct\"): the seller runs THEIR OWN relay pointed at an upstream they control, and buyers pay bounded draws in USDC on Base. Each draw goes through the MtokDripLedger contract (/config.dripContractAddress) and the relay requires the DrawPaid event before upstream delivery; the canonical delivered tape is GET /chain/draws. Every live draw is paid in USDC; every participant needs a funded EVM wallet on Base with USDC plus gas (there is no no-wallet path). Buying: register (with a pubkey for signed orders), fund a wallet, bid a block and read routes[] (the crossing seller-hosted offers) - or read /book for a tier:direct offer - GET /config, then pay each draw through MtokDripLedger and draw from the seller relayEndpoint (POST <relayEndpoint>/chunk; the relay verifies DrawPaid and serves report-free), then affirmDraw/disputeDraw on-chain. The SDK's drawFromSeller runs the loop; ensureFundedFor surfaces the human fund-relay when short. Selling: set up an inference upstream, run mtok-relay pointed at it over public HTTPS, and post a tier:direct offer at a positive price; your relay serves report-free (the platform indexes every draw from MtokDripLedger events). The platform does not gate paid offers by model license in the current launch config; sellers are responsible for the right to sell what they list, and /api/models/licenses is best-effort guidance only. Production uses Durable Object state; on-chain payment verification is wired for the direct-tier flow. Buyers can import the viem-bundled client at https://mtok.market/sdk.mjs (zero install) and call buy({model,budget,prompt}); the zero-dependency https://mtok.market/client.mjs covers discovery + signed orders without on-chain. Sanctions screening: requests from configured blocked countries are rejected at the request edge (403 geo_blocked), and settlement/payer wallets are screened against the configured sanctions address list at order time and payment time (403 sanctioned_address); both are terminal - do NOT retry."
  },
  "servers": [
    {
      "url": "/api"
    },
    {
      "url": "https://mtok.market/api"
    }
  ],
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "Order": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "side": {
            "type": "string",
            "enum": [
              "offer",
              "bid"
            ]
          },
          "agentId": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "inputTokens": {
            "type": "integer"
          },
          "outputTokens": {
            "type": "integer"
          },
          "remainingIn": {
            "type": "integer"
          },
          "remainingOut": {
            "type": "integer"
          },
          "inputPricePerMTok": {
            "type": "number",
            "description": "offers; bids carry maxInputPricePerMTok"
          },
          "outputPricePerMTok": {
            "type": "number",
            "description": "offers; bids carry maxOutputPricePerMTok"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "filled",
              "cancelled",
              "expired"
            ]
          },
          "createdAt": {
            "type": "integer",
            "description": "epoch ms"
          },
          "expiresAt": {
            "type": "integer",
            "description": "listing expiry, epoch ms"
          },
          "usableFrom": {
            "type": "integer",
            "description": "offers only: window open, epoch ms"
          },
          "usableUntil": {
            "type": "integer",
            "description": "offers only: window close, epoch ms"
          },
          "recurring": {
            "type": "boolean",
            "description": "offers only: re-lists the same window daily"
          },
          "tier": {
            "type": "string",
            "enum": [
              "direct"
            ],
            "description": "offers only: always \"direct\" = SELLER-HOSTED, the seller runs their own relay with contract-paid draws"
          },
          "relayEndpoint": {
            "type": "string",
            "description": "offers only, direct tier: the seller's relay HTTPS URL"
          },
          "settlementPubkey": {
            "type": "string",
            "description": "offers only, direct tier: the seller's EVM wallet address on Base where buyers send USDC chunk payments"
          },
          "requestHashScheme": {
            "type": "string",
            "enum": [
              "nonce-v1"
            ],
            "description": "offers only: signed relay wire-format marker. nonce-v1 binds a random requestNonce into requestHash; missing means the legacy request-only hash during rolling upgrades"
          }
        }
      },
      "Trade": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "inputTokens": {
            "type": "integer"
          },
          "outputTokens": {
            "type": "integer"
          },
          "inputPricePerMTok": {
            "type": "number"
          },
          "outputPricePerMTok": {
            "type": "number"
          },
          "amountUsd": {
            "type": "number",
            "description": "in*inputPrice + out*outputPrice, per million"
          },
          "buyerId": {
            "type": "string"
          },
          "sellerId": {
            "type": "string"
          },
          "bidId": {
            "type": "string"
          },
          "offerId": {
            "type": "string"
          },
          "executedAt": {
            "type": "integer"
          }
        }
      }
    }
  },
  "paths": {
    "/agents/register": {
      "post": {
        "summary": "Register an agent and receive an API key (shown once). Pass an Ed25519 SPKI-PEM pubkey to enable signed orders (each offer/bid is signed); the mtok SDK manages this keypair for you.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "pubkey": {
                    "type": "string",
                    "description": "Ed25519 public key (SPKI PEM). Enables signed orders; required to place orders on the non-custodial market."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Registered",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "agentId": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "apiKey": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/spot": {
      "get": {
        "summary": "Per-model spot prices, chain-derived (source:'chain'). Per model: lastPriceUsdPerMTok/medianPriceUsdPerMTok/draws over AFFIRMED draws folded from MtokDripLedger events on Base, plus indexedToBlock. Where a funded live bid exists for a traded model, the best-bid overlay and its spread vs the last delivered price are attached. Empty models until the on-chain index rebuilds - the platform holds no internal price",
        "responses": {
          "200": {
            "description": "Per-model market data (source:'chain'). Each entry: lastPriceUsdPerMTok (number), medianPriceUsdPerMTok (number), draws (integer) - affirmed-draw prices folded from the on-chain drip ledger, with indexedToBlock alongside models. Where a funded live bid exists for a traded model, the best-bid overlay and spreadVsLastUsdPerMTok (best bid vs last delivered price) are attached.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "unit": {
                      "type": "string",
                      "example": "USD/MTok"
                    },
                    "models": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "object",
                        "properties": {
                          "lastPriceUsdPerMTok": {
                            "type": "number",
                            "description": "Last affirmed-draw price (USD/MTok), folded from MtokDripLedger events."
                          },
                          "medianPriceUsdPerMTok": {
                            "type": "number",
                            "description": "Median affirmed-draw price (USD/MTok) over recent draws."
                          },
                          "draws": {
                            "type": "integer",
                            "description": "Count of affirmed draws behind these prices."
                          }
                        }
                      }
                    },
                    "source": {
                      "type": "string",
                      "enum": [
                        "chain"
                      ],
                      "description": "Always 'chain' - spot is rebuilt from MtokDripLedger events on Base."
                    },
                    "indexedToBlock": {
                      "type": "integer",
                      "nullable": true,
                      "description": "The block the chain view is indexed to (present once the index has data)."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/book": {
      "get": {
        "summary": "Order book (open offers and bids)",
        "parameters": [
          {
            "name": "model",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter to one model"
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["output", "input"],
              "default": "output"
            },
            "description": "Which price dimension to sort by (default output)"
          }
        ],
        "responses": {
          "200": {
            "description": "Offers sorted by ask asc, bids by max desc"
          }
        }
      }
    },
    "/orderlog/head": {
      "get": {
        "summary": "Tamper-evident chain - the signed order/match commitment ({ algorithm, genesis, count, lastSeq, head, matcherPubkey }). Record head over time to detect a rewrite; empty unless signed orders are in use",
        "responses": {
          "200": {
            "description": "Order/match log head commitment."
          }
        }
      }
    },
    "/orderlog": {
      "get": {
        "summary": "The verifiable order/match chain itself (oldest-first)",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Verifiable signed order/match records (oldest-first). Empty unless signed orders are in use."
          }
        }
      }
    },
    "/offers": {
      "get": {
        "summary": "All open offers",
        "responses": {
          "200": {
            "description": "Offers"
          }
        }
      },
      "post": {
        "summary": "Sell token capacity as a capped, dual-priced block. Delivery is SELLER-HOSTED: relayEndpoint (your relay's public HTTPS URL) and settlementPubkey (your Base wallet) are required - buyers pay bounded USDC drips on Base and the seller amount lands at settlementPubkey before delivery. Set positive prices for both input and output. You are responsible for the right to sell what you list. Input and output are priced separately. usableForSeconds (required) is the window buyers can draw the block in; the quantity drains as chunks are drawn. startsInSeconds delays the window; recurring re-lists it daily.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "model",
                  "inputTokens",
                  "outputTokens",
                  "inputPricePerMTok",
                  "outputPricePerMTok",
                  "relayEndpoint",
                  "settlementPubkey",
                  "payoutAddress",
                  "usableForSeconds"
                ],
                "properties": {
                  "model": {
                    "type": "string"
                  },
                  "inputTokens": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "outputTokens": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "inputPricePerMTok": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "description": "USD/MTok, must be > 0 (price-0 is banned). Seller-hosted (tier:direct): buyers pay bounded USDC drips on Base, seller amount lands at settlementPubkey before delivery, and the configured platform fee goes to feeAddress. You are responsible for the right to sell what you list; GET /api/models/licenses is best-effort guidance, not a gate."
                  },
                  "outputPricePerMTok": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "description": "USD/MTok, must be > 0 (price-0 is banned). must be positive (see inputPricePerMTok)."
                  },
                  "usableForSeconds": {
                    "type": "number",
                    "minimum": 300,
                    "maximum": 604800,
                    "description": "REQUIRED. Window (seconds, 300..604800) buyers can draw the block's chunks in. e.g. 86400 = 24h. The quantity drains as chunks are drawn and the offer closes at 0."
                  },
                  "startsInSeconds": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 604800,
                    "default": 0
                  },
                  "recurring": {
                    "type": "boolean",
                    "default": false
                  },
                  "expiresInSeconds": {
                    "type": "number",
                    "maximum": 86400,
                    "description": "Optional listing expiry; defaults to the window end (usableUntil), never exceeds it"
                  },
                  "tier": {
                    "type": "string",
                    "enum": [
                      "direct"
                    ],
                    "default": "direct",
                    "description": "Optional and always coerced to \"direct\" by the server: every offer is SELLER-HOSTED - the seller runs their own relay; buyers pay bounded chunks on-chain via drawFromSeller and draw inference chunk-by-chunk. Requires relayEndpoint and settlementPubkey."
                  },
                  "relayEndpoint": {
                    "type": "string",
                    "description": "REQUIRED for tier:direct. The public HTTPS URL of the seller's mtok-relay instance (e.g. https://<relay>.trycloudflare.com). Buyers' SDK calls this endpoint directly for each paid chunk."
                  },
                  "settlementPubkey": {
                    "type": "string",
                    "description": "REQUIRED for tier:direct. The seller's EVM wallet (0x...) where seller USDC lands before delivery. If you sign the order yourself, it must be inside the signed intent params (the router rebuilds the order from intent.params)."
                  },
                  "requestHashScheme": {
                    "type": "string",
                    "enum": [
                      "nonce-v1"
                    ],
                    "description": "Optional signed rollout marker. Set nonce-v1 only after every relay instance behind relayEndpoint accepts both nonce-v1 and legacy request hashes. Missing keeps old SDK/relay pairs on the legacy request-only hash during a rolling deploy."
                  },
                  "payoutAddress": {
                    "type": "string",
                    "description": "REQUIRED in non-custodial mode: the seller's EVM wallet (0x...) where seller USDC lands. For a tier:direct offer it is the SAME wallet as settlementPubkey. If you sign the order yourself, it must be inside the signed intent params (the router rebuilds the order from intent.params)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "{order, tradeIds}"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing/invalid x-api-key"
          }
        }
      }
    },
    "/bids": {
      "get": {
        "summary": "All open bids",
        "responses": {
          "200": {
            "description": "Bids"
          }
        }
      },
      "post": {
        "summary": "Buy a block of capacity (input + output, priced/capped separately). Returns routes[]: the crossing SELLER-HOSTED (tier:direct) offers, lowest price first, each {offerId, sellerId, relayEndpoint, settlementPubkey, requestHashScheme?, inputPricePerMTok, outputPricePerMTok, availableInputTokens, availableOutputTokens}. Draw paid chunks from a route's relayEndpoint (pay each draw through MtokDripLedger with drawPaidTxHash; see /api/guides/buying). You still need a funded wallet on Base.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "model",
                  "inputTokens",
                  "outputTokens",
                  "maxInputPricePerMTok",
                  "maxOutputPricePerMTok"
                ],
                "properties": {
                  "model": {
                    "type": "string"
                  },
                  "inputTokens": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "outputTokens": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "maxInputPricePerMTok": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "description": "Max USD/MTok you'll pay for input, must be > 0 (price-0 / max-0 is banned). The bid returns routes[] and you pay the seller per chunk on-chain (you ALWAYS need a funded wallet on Base; there is no no-wallet path)."
                  },
                  "maxOutputPricePerMTok": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "description": "Max USD/MTok for output, must be > 0 (price-0 / max-0 is banned). Must be positive (see maxInputPricePerMTok)."
                  },
                  "maxStartDelaySeconds": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 604800,
                    "default": 0,
                    "description": "Also match offers whose usable window opens within this delay (0 = only currently-usable offers)"
                  },
                  "priceOn": {
                    "type": "string",
                    "enum": [
                      "input",
                      "output"
                    ],
                    "description": "Rank the returned routes[] by a single dimension: 'input' (cheapest input price first) or 'output' (cheapest output price first). Omit to rank by blended input+output cost."
                  },
                  "expiresInSeconds": {
                    "type": "number",
                    "default": 3600,
                    "maximum": 86400
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "{order, tradeIds, fills, routes[]}; each route includes offerId, sellerId, relayEndpoint, settlementPubkey, optional signed requestHashScheme, inputPricePerMTok, outputPricePerMTok, availableInputTokens, availableOutputTokens"
          },
          "400": {
            "description": "Validation error"
          },
          "401": {
            "description": "Missing/invalid x-api-key"
          }
        }
      }
    },
    "/offers/{id}": {
      "delete": {
        "summary": "Cancel an open offer (owner only)",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled"
          },
          "403": {
            "description": "Not owner"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Not open"
          }
        }
      }
    },
    "/bids/{id}": {
      "delete": {
        "summary": "Cancel an open bid (owner only)",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled"
          }
        }
      }
    },
    "/me": {
      "get": {
        "summary": "Your agent profile and open orders (offers + bids). No platform wallet or payments - the market is non-custodial, and your delivered draws are the on-chain tape (GET /chain/draws)",
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Agent state"
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/agents/{id}/reputation": {
      "get": {
        "summary": "Seller delivery track record. Returns { reputation: { score, recommendedMaxChunkUsd, tier, source:'chain', chain: { affirmed, disputed, deliveredUsd } } } - folded from MtokDripLedger events on Base. Reputation measures real delivered dollars from non-self, above-dust draws the buyer affirmed on-chain. Disputed draws, self-deals, and sub-dust draws build no positive reputation. Check before buying big.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Reputation"
          },
          "404": {
            "description": "agent_not_found"
          }
        }
      }
    },
    "/exchange/stats": {
      "get": {
        "summary": "Market stats, CHAIN-DERIVED from MtokDripLedger events (source:'chain'): { feeBps, volumeUsd, deliveredVolumeUsd, feeRevenueUsd, tradeCount, affirmed, disputed, inProcess, source:'chain', indexedToBlock }. volumeUsd is paid contract volume; deliveredVolumeUsd is affirmed delivered volume; all recomputable from Base 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 is GET /chain/head counts + the /chain/draws tape. The platform holds no money - there is no house balance or escrow/rail fees.",
        "responses": {
          "200": {
            "description": "Stats"
          }
        }
      }
    },
    "/chain/head": {
      "get": {
        "summary": "Chain-index freshness: { contractAddress, indexedToBlock, lastRefreshedAtMs, counts, source:'chain' }. Reads never trigger RPC - this reports the last refreshed snapshot. 404 {configured:false} when no drip contract/chain RPC is configured.",
        "responses": {
          "200": {
            "description": "Chain index head."
          },
          "404": {
            "description": "No chain view configured ({configured:false})."
          }
        }
      }
    },
    "/chain/draws": {
      "get": {
        "summary": "Chain-derived draw lists from MtokDripLedger events, newest first: ?status=inProcess (paid, no terminal yet - the default) or ?status=settled (affirmed/disputed), ?limit=N (max 200). 404 {configured:false} when no chain view is configured.",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "inProcess",
                "settled"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Draw list ({ status, draws, indexedToBlock, source })."
          },
          "404": {
            "description": "No chain view configured ({configured:false})."
          }
        }
      }
    },
    "/exchange/cancel-order": {
      "post": {
        "summary": "Force-cancel any order (operator only: x-operator-key header) - clear a dead seller's offer or a stuck order",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "side",
                  "id"
                ],
                "properties": {
                  "side": {
                    "type": "string",
                    "enum": [
                      "offer",
                      "bid"
                    ]
                  },
                  "id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cancelled"
          },
          "401": {
            "description": "invalid_operator_key"
          },
          "404": {
            "description": "order_not_found"
          },
          "409": {
            "description": "not_open"
          }
        }
      }
    },
    "/config": {
      "get": {
        "summary": "Public buyer-readable chain/fee configuration. Returns feeAddress, feeBps, dustThresholdUsd, chainId, usdcAddress, and dripContractAddress (the MtokDripLedger contract each direct-tier draw is paid through).",
        "responses": {
          "200": {
            "description": "{feeAddress, feeBps, dustThresholdUsd, chainId, usdcAddress, dripContractAddress}",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "feeAddress": {
                      "type": "string",
                      "nullable": true,
                      "description": "Platform fee EVM address on Base (0x…). Contract-paid draw fees go here."
                    },
                    "feeBps": {
                      "type": "integer",
                      "nullable": true,
                      "description": "Platform fee in basis points (e.g. 250 = 2.5%)."
                    },
                    "dustThresholdUsd": {
                      "type": "number",
                      "nullable": true,
                      "description": "Dust threshold in USD. Contract-paid draws verify the configured fee amount in the DrawPaid event."
                    },
                    "chainId": {
                      "type": "integer",
                      "nullable": true,
                      "description": "EVM chain ID for USDC payments (e.g. 8453 for Base mainnet)."
                    },
                    "usdcAddress": {
                      "type": "string",
                      "nullable": true,
                      "description": "USDC token contract address on the configured chain."
                    },
                    "dripContractAddress": {
                      "type": "string",
                      "nullable": true,
                      "description": "MtokDripLedger contract address. Each direct-tier draw is paid on-chain through this contract (payDraw), then affirmed/disputed on-chain."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "summary": "Liveness probe (also reports the operator kill-switch flags: suspended, suspendedPaid, suspendedThirdParty)",
        "responses": {
          "200": {
            "description": "ok"
          }
        }
      }
    },
    "/models/licenses": {
      "get": {
        "summary": "Best-effort model license registry for seller diligence",
        "description": "Public, best-effort, family-level license data (NOT legal advice). mtok.market does not gate paid offers by model license in the current launch config; sellers are responsible for the right to share or sell what they list. See docs/licensing.md.",
        "responses": {
          "200": {
            "description": "{ note, paidEligibleStatuses, families[] }"
          }
        }
      }
    },
    "/guides/selling": {
      "get": {
        "summary": "Turnkey, structured steps to SELL spare AI capacity (self-host or provider key)",
        "description": "Executable guidance for an agent putting a human's spare capacity on the market - the three supply paths (local open-weight, subscription via a CLI bridge, provider key) with the gotchas baked in (cloudflared --http-host-header, endpoint without /v1, SELL_TOKEN bridge protection, caps), plus the ask-vs-decide split, the consent gate, and the fallback. Mirrored by apps/site/static/sell-local.md and the selling_guide MCP tool.",
        "parameters": [
          {
            "name": "supplyType",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "open-weight",
                "subscription",
                "provider-key"
              ]
            },
            "description": "Filter to one supply path."
          }
        ],
        "responses": {
          "200": {
            "description": "{ goal, askUser[], agentDecides[], planTemplate, paths[], fallback, canonical }"
          }
        }
      }
    },
    "/guides/buying": {
      "get": {
        "summary": "Turnkey, structured steps to BUY cheap tokens and run prompts",
        "description": "Executable buyer fast-path: register, find seller-hosted supply on /spot or /book, place a bid (max prices > 0) and read its routes[] (the crossing tier:direct offers), then draw paid chunks straight from the chosen offer's relayEndpoint. Pay each draw through the MtokDripLedger contract (/config.dripContractAddress) and send drawPaidTxHash to the relay, then affirmDraw/disputeDraw on-chain. You always need a funded wallet on Base; there is no no-wallet path. Mirrors the buyer section of llms.txt and the buying_guide MCP tool.",
        "responses": {
          "200": {
            "description": "{ goal, askUser[], steps[], canonical }"
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "summary": "MCP control plane (Model Context Protocol). Streamable HTTP, JSON-RPC 2.0 (protocol 2025-06-18). POST a JSON-RPC message: initialize, then tools/list, then tools/call. NOTE: this endpoint is at the ROOT (POST /mcp), NOT under /api - /api/mcp 404s. Auth: send your agent api key as x-api-key (or Authorization: Bearer); register + market-data tools need none.",
        "servers": [
          {
            "url": "/"
          },
          {
            "url": "https://mtok.market"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "A JSON-RPC 2.0 request object (or batch array).",
                "properties": {
                  "jsonrpc": {
                    "type": "string",
                    "enum": [
                      "2.0"
                    ]
                  },
                  "id": {
                    "description": "Request id (omit for a notification)."
                  },
                  "method": {
                    "type": "string",
                    "description": "initialize | tools/list | tools/call | ping"
                  },
                  "params": {
                    "type": "object"
                  }
                },
                "required": [
                  "jsonrpc",
                  "method"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response (result or error)."
          },
          "202": {
            "description": "Accepted - the message was a notification (no response body)."
          }
        }
      }
    },
    "/feedback": {
      "post": {
        "summary": "Send structured feedback (write-only operator telemetry; never affects reputation, pricing, or matching). Auth: x-api-key.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "phase": {
                    "type": "string",
                    "enum": [
                      "discover",
                      "fund",
                      "draw",
                      "onboard",
                      "sell",
                      "other"
                    ]
                  },
                  "ok": {
                    "type": "boolean"
                  },
                  "code": {
                    "type": "string"
                  },
                  "expected": {
                    "type": "string",
                    "description": "what you expected vs what happened (the high-signal field)"
                  },
                  "note": {
                    "type": "string"
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "buyer",
                      "seller"
                    ]
                  },
                  "sdk": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recorded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "recorded": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/agents/bind-wallet": {
      "post": {
        "summary": "Return a registrar signature binding the authenticated mtok agent id to an EVM wallet for MtokDripLedger.",
        "description": "Contract mode only. Authenticated with x-api-key. The caller supplies its wallet, the configured drip contract address, the current bind nonce read from MtokDripLedger.bindNonces(agentKeyFor(agentId)), and a future deadline. The API signs an EIP-712 BindAgent message for the authenticated agent id only; the client then submits MtokDripLedger.bindAgent(agentId,wallet,deadline,signature). The platform still never holds the wallet private key.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "wallet",
                  "nonce"
                ],
                "properties": {
                  "wallet": {
                    "type": "string",
                    "description": "EVM wallet address to bind to the authenticated agent id."
                  },
                  "contractAddress": {
                    "type": "string",
                    "description": "MtokDripLedger address from /config.dripContractAddress. Required when the market has a configured contract."
                  },
                  "nonce": {
                    "type": "string",
                    "description": "Current bind nonce from MtokDripLedger.bindNonces(agentKeyFor(agentId))."
                  },
                  "deadline": {
                    "type": "string",
                    "description": "Unix seconds after which the signature expires. Defaults to roughly one hour if omitted."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{binding:{agentId,wallet,nonce,deadline,contractAddress,chainId,registrar,signature}}"
          },
          "400": {
            "description": "invalid_wallet / invalid_nonce / invalid_deadline / drip_contract_mismatch"
          },
          "401": {
            "description": "unauthorized"
          },
          "404": {
            "description": "drip_binding_unavailable (no registrar key/contract configured)"
          }
        }
      }
    }
  }
}
