openapi: 3.2.0 info: title: Game Theory Layer for AI Agents Billing API description: 'Start with ONE tool: POST /v1/negotiate/turn — plain-dollar price negotiation (your walk-away + the other side''s offers in dollars -> the counter to send, a ready-to-send message, accept/walk advice).' version: 0.1.0 tags: - name: Billing paths: /v1/billing/checkout_session: post: tags: - Billing summary: Checkout Session operationId: checkout_session_v1_billing_checkout_session_post requestBody: content: application/json: schema: $ref: '#/components/schemas/CheckoutIn' required: true responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /v1/billing/agentic_topup: post: tags: - Billing summary: Agentic Topup description: 'Fund the wallet by redeeming an agent-carried Shared Payment Token — no human at a hosted Checkout URL. Same counter fee (5% + 30¢) as every top-up; the fee is printed in the response as fee_cents. PREVIEW: Stripe''s SPT flow is a versioned preview and needs preview services-terms acceptance + a US legal entity + a rotated key before live use (see vend/AGENTIC_PAYMENTS.md). Test mode works with the monkeypatched Stripe layer / an ordinary sk_test_* + a test-helper token.' operationId: agentic_topup_v1_billing_agentic_topup_post requestBody: content: application/json: schema: $ref: '#/components/schemas/AgenticTopupIn' required: true responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /v1/billing/webhook: post: tags: - Billing summary: Webhook operationId: webhook_v1_billing_webhook_post parameters: - name: stripe-signature in: header required: false schema: anyOf: - type: string - type: 'null' title: Stripe-Signature responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /v1/billing/balance: get: tags: - Billing summary: Balance description: 'The ONE wallet, in millicents (1000 per cent), with the starter grant and own-money buckets both visible — the balance no longer lies about the 50¢ (STORE.md §6). The key travels in the X-API-Key header, never a query param (a secret must not land in access logs or proxies). `guaranteed_calls_remaining` (roadmap: fund the pipeline before the 402) is a CONSERVATIVE floor per registered commodity slot: total // max_price_millicents — how many calls the wallet can afford if EVERY call cost the published ceiling. It is a floor because calls settle at wholesale passthrough (usually well under the cap), so the real number is ≥ this. Stateless and mechanical: no trailing average, no state, no telemetry read. An UNAVAILABLE slot (no healthy backend) reports 0 — you are guaranteed no calls it cannot serve.' operationId: balance_v1_billing_balance_get parameters: - name: X-API-Key in: header required: true schema: type: string title: X-Api-Key responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /v1/advice/session: post: tags: - Billing summary: Open Advice Session description: 'Open a PAID negotiation session: $2 once covers every move of this negotiation (cap 10 moves, 7 days). Category-tuned, deterministic, receipted. The free generic tool is POST /v1/negotiate/turn — pay for the tuned, auditable, replayable version. Pass their_offers to get the first move back with the session.' operationId: open_advice_session_v1_advice_session_post requestBody: content: application/json: schema: $ref: '#/components/schemas/SessionOpenIn' required: true responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /v1/advice/move: post: tags: - Billing summary: Advice Move description: 'A move inside your paid session — no additional charge. Pass the FULL offer history each time, oldest first.' operationId: advice_move_v1_advice_move_post requestBody: content: application/json: schema: $ref: '#/components/schemas/SessionMoveIn' required: true responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /v1/advice/bundle: post: tags: - Billing summary: Advice Bundle Move description: 'A MULTI-ISSUE move inside your paid session — the logrolling tier the free tool lacks. No additional charge. Returns the recommended package (guaranteed to clear your stated BATNA), trade logic, inferred counterparty priorities, acceptance probability, and the receipt.' operationId: advice_bundle_move_v1_advice_bundle_post requestBody: content: application/json: schema: $ref: '#/components/schemas/BundleMoveIn' required: true responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /v1/advice/close: post: tags: - Billing summary: Close Advice Session description: 'Mark the negotiation finished. Optional but good hygiene — it timestamps the outcome, which calibrates the category priors. Returns the `closed` flag AND a signed session-summary receipt (GAUNTLET #4: the close used to emit nothing auditable) — moves count, total charged, and the per-move context_hashes — for the customer to hand a principal. An unknown session or key mismatch → 404 (indistinguishable, so a session id can''t be probed with someone else''s key).' operationId: close_advice_session_v1_advice_close_post requestBody: content: application/json: schema: $ref: '#/components/schemas/SessionCloseIn' required: true responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /v1/keys/rotate: post: tags: - Billing summary: Rotate Key description: 'Rotate your API key: a replacement is issued, the full credit balance carries over, and the old key is invalidated IMMEDIATELY (no grace period — possession of the key is the authorization, and a compromised key must die at once). Save the new key: keys are shown once and cannot be recovered, only rotated. Lost your key entirely? Email the contact address you registered with from that same address — recovery is a manual, human-verified process by design.' operationId: rotate_key_v1_keys_rotate_post requestBody: content: application/json: schema: $ref: '#/components/schemas/RotateIn' required: true responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /v1/advice/request: post: tags: - Billing summary: Advice Request description: 'The null-query intake: ask for anything the machine doesn''t stock. Free. Size-capped, stored as data, never rendered raw. Unmet demand decides what gets stocked next. Legacy name for the same intake as POST /v1/store/request — one box, two doors (GAUNTLET #5): every filing gets a request_id you can check. Pass `watch: true` WITH an api_key to flag the ask for a heads-up on a status flip (poll GET /v1/store/my_requests to see it — the notify is poll-based, no push); an anonymous watch is ignored. The chosen flag is echoed back as `watch`.' operationId: advice_request_v1_advice_request_post requestBody: content: application/json: schema: $ref: '#/components/schemas/RequestIn' required: true responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /v1/store/catalog: get: tags: - Billing summary: Store Catalog description: 'THE STORE''s shelf: the commodity slots (tier, admission cap, predicate id, request doc, serving-backend ids), the anchor SKUs, and the two published pricing facts — wholesale-passthrough cost basis on every receipt plus the counter fee on top-ups. No key material ever appears here.' operationId: store_catalog_v1_store_catalog_get responses: '200': description: Successful Response content: application/json: schema: {} /v1/store/notary_pubkey: get: tags: - Billing summary: Store Notary Pubkey description: 'The receipt-signing notary''s PUBLIC key, at a stable path so a verifier can PIN it OUT-OF-BAND (not just trust the pubkey embedded in a receipt) and confirm it matches the receipt''s pubkey_fingerprint. Returns {pubkey_pem, fingerprint, key_source}. This is the STORE receipt notary (vend.receipt_ signing / NOTARY_KEY_PEM) — DISTINCT from /v1/keys/trust_anchor (first-strike CA) and /v1/keys/settlement_notary (AP2 mandates), which are different keys. key_source is VISIBLE: with ''ephemeral'' a signature proves only signer- consistency within one server lifetime; a production notary pins a persistent key (''env'', from NOTARY_KEY_PEM). Never returns private material.' operationId: store_notary_pubkey_v1_store_notary_pubkey_get responses: '200': description: Successful Response content: application/json: schema: {} /v1/fetch: post: tags: - Billing summary: Store Fetch description: 'Fetch/extract one page → markdown, paid from your wallet at wholesale passthrough. Settlement-on-delivery: charged ONLY on non-empty markdown. Pass your key in an `Authorization: Bearer gt_*` or `X-API-Key` header (RECOMMENDED — that reaches the 600/min keyed rate-limit lane; a body-only key falls to the 60/min per-IP floor because the limiter never parses bodies) or in the JSON body `api_key` (backcompat). The header wins if both are present. A backend or predicate failure is a NORMAL uncharged outcome — 200 with the canonical envelope {ok: false, charged: false, reason: , code: } — because you cannot pay for nothing; that asymmetry is the product surface, not an HTTP error. `code` is one of unknown_slot, slot_unavailable, insufficient_balance, all_backends_failed, predicate_failed; a delivered-but-failed call may also carry backends_tried [{id, reason}], backends_untried, backend_id, and a retry_hint. One client code path reads `charged`/`code` for every outcome. (Legacy keys like `error` survive as aliases.) Insufficient balance → 402; a missing or unknown api_key → 401.' operationId: store_fetch_v1_fetch_post requestBody: content: application/json: schema: $ref: '#/components/schemas/FetchIn' required: true responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /v1/store/request: post: tags: - Billing summary: Store Request description: 'File a request for a capability the store doesn''t stock. Free, keyless OK. Returns {request_id, status, watch, check} — the demand loop now hands back something to return FOR (GAUNTLET #5). Size-capped, stored as data, never rendered raw. Pass `watch: true` WITH an api_key to flag the ask for a heads-up on a status flip (poll GET /v1/store/my_requests — poll-based, no push); an anonymous watch is ignored, and the chosen flag is echoed back.' operationId: store_request_v1_store_request_post requestBody: content: application/json: schema: $ref: '#/components/schemas/StoreRequestIn' required: true responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /v1/store/request/{request_id}: get: tags: - Billing summary: Store Request Status description: 'Check a filed request by id: {request_id, status, status_note, filed_at, door, text}. `status` is ''logged'' until the shelf-owner acts, then status_note carries the reason-to-return. Unknown id → 404. No key material; the text is display-truncated and remains untrusted data.' operationId: store_request_status_v1_store_request__request_id__get parameters: - name: request_id in: path required: true schema: type: string title: Request Id responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /v1/store/requests: get: tags: - Billing summary: Store Requests description: 'The public demand tally (GAUNTLET #5): {total, distinct, recent[], requests[]}. `requests` is distinct asks with EXACT-MATCH duplicate counts (whitespace/case folded, no fuzzy classification — mechanical, no LLM), most-asked first. No key material; text display-truncated.' operationId: store_requests_v1_store_requests_get responses: '200': description: Successful Response content: application/json: schema: {} /v1/store/observatory: get: tags: - Billing summary: Store Observatory description: 'The public, citable observatory (vend.observatory.snapshot): per-slot call volumes and the MECHANICAL tally of what agents ask for that nobody sells yet. Every number is a count, a sum, or an exact-match group — no interpretation, no LLM. Aggregate + PSEUDONYMOUS: wallets appear only as counts of a keyed pseudonym (repeat_key), never a raw api_key, so NO key material can leak. Pure read, no auth. The demand-loop citation asset: what the shelf is missing, straight from the raw records.' operationId: store_observatory_v1_store_observatory_get responses: '200': description: Successful Response content: application/json: schema: {} /v1/store/my_requests: get: tags: - Billing summary: Store My Requests description: 'Your OWN filings (roadmap: a voter comes back a reachable customer), keyed to YOUR api_key — the private counterpart to the public GET /v1/store/requests tally. Carry the key in `Authorization: Bearer gt_*` or `X-API-Key`, never a query param (a secret must not land in access logs). A missing or unknown key → 401. Returns {requests: [{request_id, filed_at, text, status, status_note, status_ts, watch, same_ask_count}]}, newest first — text display-truncated, still untrusted data; no key material and no repeat_key on the surface. Only rows attributable to this key (via the keyed pseudonym, never a raw key match) are returned, so one key can never read another''s filings.' operationId: store_my_requests_v1_store_my_requests_get responses: '200': description: Successful Response content: application/json: schema: {} /v1/store/park: post: tags: - Billing summary: Store Park description: 'Park an ENCRYPTED blob, get a claim ticket (blind locker, §2c). Charged a thin flat park fee ONLY on durable store; an empty/oversize/unencodable blob is uncharged. Key via `Authorization: Bearer`/`X-API-Key` (header wins) or body `api_key`. The receipt''s content_hash is over YOUR ciphertext, so you can prove what you stored without the store ever seeing plaintext.' operationId: store_park_v1_store_park_post requestBody: content: application/json: schema: $ref: '#/components/schemas/ParkIn' required: true responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /v1/store/parcel/{ticket}: get: tags: - Billing summary: Store Retrieve description: 'Retrieve a parked parcel by its claim ticket (blind locker, §2c). Returns {ok, blob_b64, size_bytes, expires_at} — the ciphertext you parked, which only YOU can decrypt. Key via `Authorization: Bearer`/`X-API-Key`. A wrong owner reads as a missing ticket (404). Retrieval is free (the park settled it). An expired TTL is 404; a lost at-rest key is 503.' operationId: store_retrieve_v1_store_parcel__ticket__get parameters: - name: ticket in: path required: true schema: type: string title: Ticket responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /v1/memory/save: post: tags: - Billing summary: Memory Save description: 'Save persistent memory for your agent across sessions (alias of POST /v1/store/park — the blind locker). You encrypt before saving; the store holds only ciphertext (blind custody) and signs a receipt over its hash — it cannot read your memory. Saving uses your prepaid wallet (a new key''s 50¢ starter credit covers first saves); loading it back is free. Same handler and behavior as /v1/store/park.' operationId: memory_save_v1_memory_save_post requestBody: content: application/json: schema: $ref: '#/components/schemas/ParkIn' required: true responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /v1/memory/parcel/{ticket}: get: tags: - Billing summary: Memory Load description: 'Load a memory you saved in an earlier session (alias of GET /v1/store/parcel/{ticket}). Returns the ciphertext you saved, which only YOU can decrypt; retrieval is free. Same handler and behavior as /v1/store/parcel/{ticket}.' operationId: memory_load_v1_memory_parcel__ticket__get parameters: - name: ticket in: path required: true schema: type: string title: Ticket responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' components: schemas: AgenticTopupIn: properties: api_key: type: string title: Api Key amount_cents: type: integer title: Amount Cents description: cents of wallet credit to buy (min 200); you pay this + the counter fee (5% + 30¢) payment_token: type: string title: Payment Token description: a Stripe Shared Payment Token (spt_…) the agent carries type: object required: - api_key - amount_cents - payment_token title: AgenticTopupIn BundleMoveIn: properties: api_key: type: string title: Api Key session_id: type: string title: Session Id issues: items: additionalProperties: true type: object type: array title: Issues their_offers: anyOf: - items: additionalProperties: true type: object type: array - type: 'null' title: Their Offers my_priorities: anyOf: - additionalProperties: true type: object - type: 'null' title: My Priorities my_batna: type: number title: My Batna default: 0.4 their_batna_estimate: type: number title: Their Batna Estimate default: 0.4 cooperation: anyOf: - type: number - type: 'null' title: Cooperation type: object required: - api_key - session_id - issues title: BundleMoveIn CheckoutIn: properties: api_key: type: string title: Api Key pack: anyOf: - type: string - type: 'null' title: Pack description: small ($10.80) | medium ($52.80) | large ($210.30) amount_cents: anyOf: - type: integer - type: 'null' title: Amount Cents description: 'custom top-up: cents of wallet credit (min 200); you pay this + the counter fee (5% + 30¢)' success_url: type: string title: Success Url default: https://snhp.dev/paid cancel_url: type: string title: Cancel Url default: https://snhp.dev/cancel type: object required: - api_key title: CheckoutIn SessionMoveIn: properties: api_key: type: string title: Api Key session_id: type: string title: Session Id their_offers: items: type: number type: array title: Their Offers my_offers: anyOf: - items: type: number type: array - type: 'null' title: My Offers rounds_left: anyOf: - type: integer minimum: 1.0 - type: 'null' title: Rounds Left type: object required: - api_key - session_id - their_offers title: SessionMoveIn SessionCloseIn: properties: api_key: type: string title: Api Key session_id: type: string title: Session Id type: object required: - api_key - session_id title: SessionCloseIn StoreRequestIn: properties: text: type: string maxLength: 4000 title: Text description: what you wish the counter stocked api_key: anyOf: - type: string - type: 'null' title: Api Key watch: type: boolean title: Watch description: with an api_key, flag this ask to hear back on a status flip — poll GET /v1/store/my_requests; no email/webhook default: false type: object required: - text title: StoreRequestIn RequestIn: properties: text: type: string maxLength: 4000 title: Text description: what you wish the machine stocked api_key: anyOf: - type: string - type: 'null' title: Api Key watch: type: boolean title: Watch description: with an api_key, flag this ask to hear back on a status flip — poll GET /v1/store/my_requests; no email/webhook default: false type: object required: - text title: RequestIn HTTPValidationError: properties: detail: items: $ref: '#/components/schemas/ValidationError' type: array title: Detail type: object title: HTTPValidationError ValidationError: properties: loc: items: anyOf: - type: string - type: integer type: array title: Location msg: type: string title: Message type: type: string title: Error Type input: title: Input ctx: type: object title: Context type: object required: - loc - msg - type title: ValidationError RotateIn: properties: api_key: type: string title: Api Key type: object required: - api_key title: RotateIn ParkIn: properties: api_key: anyOf: - type: string - type: 'null' title: Api Key blob_b64: type: string title: Blob B64 description: your ciphertext as base64 — ENCRYPT BEFORE PARKING; the store holds only opaque bytes and cannot read them ttl_seconds: anyOf: - type: integer - type: 'null' title: Ttl Seconds description: requested lifetime; clamped to [60s, 7d]. The effective expires_at is returned — never a silent surprise. type: object required: - blob_b64 title: ParkIn FetchIn: properties: api_key: anyOf: - type: string - type: 'null' title: Api Key url: type: string maxLength: 2048 title: Url description: http(s) URL to read → markdown type: object required: - url title: FetchIn SessionOpenIn: properties: api_key: type: string title: Api Key category: type: string title: Category description: resale | supply | retail side: type: string title: Side description: buy | sell walk_away: type: number exclusiveMinimum: 0.0 title: Walk Away description: your floor (sell) / ceiling (buy) target: type: number exclusiveMinimum: 0.0 title: Target description: your aspiration their_offers: anyOf: - items: type: number type: array - type: 'null' title: Their Offers my_offers: anyOf: - items: type: number type: array - type: 'null' title: My Offers rounds_left: anyOf: - type: integer minimum: 1.0 - type: 'null' title: Rounds Left seed: type: integer title: Seed default: 0 type: object required: - api_key - category - side - walk_away - target title: SessionOpenIn