{
  "openapi": "3.0.3",
  "info": {
    "title": "shelf-smoke",
    "description": "Machine-buyable URL smoke for other AI bots.\nSole advertised bot entry is GET /llms.txt — do not start here.\nPrefer Authorization: Bearer; X-API-Key is an alias only.\nHealth is locked (LAS-188). POST /smoke is a provisional contract\n(LAS-189) until Probe locks LAS-187. Caller keys (LAS-190): issue\nat POST /keys. First key grants 1 free credit (LAS-202,\ncredit.grant reason=first_key). Quote before debit:\nGET /credits/quote?op=smoke. Prepaid credits (LAS-193): Stripe\nTEST packs → credit.purchase; successful smoke (ok:true) appends\ncredit.debit (reason=smoke_success). caller_id is the stable API\nkey id (k_…). Live Stripe is gated (unlock-live). Public host is\nhttps://shelf.kymac.co. Golden proof URL: https://example.com/.\nFail bodies use code / message / retryable (error aliases message).\nMCP Streamable HTTP is POST /mcp (not an install entry).\n",
    "version": "0.1.0"
  },
  "servers": [
    {
      "url": "https://shelf.kymac.co",
      "description": "Public live host."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "apiKeyHeader": []
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "operationId": "getHealth",
        "summary": "Liveness",
        "description": "Predictable JSON used to confirm the service is up. Public; no key.",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is up.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                },
                "example": {
                  "ok": true,
                  "status": "ok",
                  "service": "shelf-smoke",
                  "version": "0.1.0"
                }
              }
            }
          }
        }
      }
    },
    "/mcp": {
      "get": {
        "operationId": "mcpSse",
        "summary": "MCP Streamable HTTP (SSE)",
        "description": "Optional GET for the MCP Streamable HTTP event stream.\nPrefer POST /mcp for initialize, tools/list, and tools/call.\nPublic path; gated tools still require a caller key.\n",
        "security": [],
        "responses": {
          "200": {
            "description": "SSE stream when the client requested text/event-stream.",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "mcpStreamableHttp",
        "summary": "MCP Streamable HTTP",
        "description": "Model Context Protocol Streamable HTTP (JSON-RPC 2.0).\n`tools/list` includes `issue_key`, `verify_key`, `quote_credits`,\n`list_credit_packs`, `checkout_credits`, `confirm_credits`,\n`credit_balance`, `credit_events`, `smoke`, and optional `health`.\nBot install starts at GET /llms.txt (sole advertised entry).\nGated tools prefer `Authorization: Bearer ssm_…` or env\n`SHELF_SMOKE_API_KEY`. `X-API-Key` is an alias only.\nPublic path; auth is per-tool.\nStdio alternative: `npx tsx src/mcp-stdio.ts`.\n",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/McpJsonRpcRequest"
              },
              "examples": {
                "toolsList": {
                  "summary": "List MCP tools",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "tools/list",
                    "params": {}
                  }
                },
                "initialize": {
                  "summary": "Initialize MCP session",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "initialize",
                    "params": {
                      "protocolVersion": "2025-03-26",
                      "capabilities": {},
                      "clientInfo": {
                        "name": "shelf-bot",
                        "version": "0.1.0"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response or SSE stream.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/McpJsonRpcResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/smoke": {
      "post": {
        "operationId": "postSmoke",
        "summary": "Headless smoke",
        "description": "Provisional (LAS-189 / pending LAS-187). Requires a caller key\n(prefer Authorization: Bearer; X-API-Key is an alias). First key\nincludes 1 free credit; quote first with GET /credits/quote?op=smoke\n(no debit). Golden URL: https://example.com/. Desktop (1280) and\nphone (~390) load-success checks; machine-parseable pass/fail JSON\nout. HTTP 200 always means the runner finished — read `ok` for the\nsmoke verdict. On `ok: true`, burns 1 credit and appends\n`credit.debit` (reason=smoke_success, request_id, caller_id).\nFailed smokes do not debit. Insufficient balance → 402 before\nthe runner starts. First-paint / navigation only; not visual QA.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SmokeRequest"
              },
              "examples": {
                "golden": {
                  "summary": "Known-good golden URL",
                  "value": {
                    "url": "https://example.com/"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Smoke finished. `ok` is the overall pass/fail.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SmokeResponse"
                },
                "examples": {
                  "goldenPass": {
                    "summary": "Golden pass (https://example.com/)",
                    "value": {
                      "ok": true,
                      "url": "https://example.com/",
                      "viewports": [
                        {
                          "name": "desktop",
                          "width": 1280,
                          "url": "https://example.com/",
                          "ok": true,
                          "status": 200
                        },
                        {
                          "name": "phone",
                          "width": 390,
                          "url": "https://example.com/",
                          "ok": true,
                          "status": 200
                        }
                      ]
                    }
                  },
                  "goldenFail": {
                    "summary": "Golden fail shape (HTTP 404, no debit)",
                    "value": {
                      "ok": false,
                      "url": "https://example.com/missing",
                      "viewports": [
                        {
                          "name": "desktop",
                          "width": 1280,
                          "url": "https://example.com/missing",
                          "ok": false,
                          "status": 404,
                          "error": "HTTP 404"
                        },
                        {
                          "name": "phone",
                          "width": 390,
                          "url": "https://example.com/missing",
                          "ok": false,
                          "status": 404,
                          "error": "HTTP 404"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Request could not be smoked (bad JSON or URL).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "invalid_request",
                  "message": "url must be http or https",
                  "retryable": false,
                  "error": "url must be http or https"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          }
        }
      }
    },
    "/keys": {
      "post": {
        "operationId": "issueKey",
        "summary": "Issue a caller key",
        "description": "Mints a caller key. Secret is returned once. Prefer\n`Authorization: Bearer <key>`; `X-API-Key` is an alias only.\nKey `id` is the stable `caller_id` for credit ledger events.\nFirst issue grants 1 free credit (`credit.grant` reason=first_key)\nso GET /credits/quote?op=smoke then POST /smoke can complete\nwithout prepaid checkout.\n",
        "security": [],
        "responses": {
          "201": {
            "description": "Key issued. `key` is shown only here.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IssuedKey"
                },
                "example": {
                  "ok": true,
                  "key": "ssm_abcdefghijklmnopqrstuvwxyz012345",
                  "id": "k_0123456789abcdef",
                  "createdAt": "2026-09-16T10:00:00.000Z"
                }
              }
            }
          }
        }
      }
    },
    "/keys/verify": {
      "get": {
        "operationId": "verifyKey",
        "summary": "Verify a caller key",
        "description": "Confirms the presented key is valid. Does not echo the secret.",
        "responses": {
          "200": {
            "description": "Key is valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerifiedKey"
                },
                "example": {
                  "ok": true,
                  "id": "k_0123456789abcdef",
                  "createdAt": "2026-09-16T10:00:00.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/credits/packs": {
      "get": {
        "operationId": "listCreditPacks",
        "summary": "List prepaid credit packs",
        "description": "Machine-buyable Stripe TEST packs. Public. Live Stripe is gated\n(unlock-live). Buy with POST /credits/checkout.\n",
        "security": [],
        "responses": {
          "200": {
            "description": "TEST pack catalog.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditPackList"
                },
                "example": {
                  "ok": true,
                  "mode": "test",
                  "packs": [
                    {
                      "pack_id": "pack_10",
                      "credits": 10,
                      "amount_cents": 100,
                      "currency": "usd"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/credits/checkout": {
      "post": {
        "operationId": "checkoutCredits",
        "summary": "Create a TEST PaymentIntent for a pack",
        "description": "Stripe TEST PaymentIntent for `pack_id`, tied to this caller's\n`caller_id` (API key id). Confirm with POST /credits/confirm\n(TEST card pm_card_visa) or a Stripe TEST webhook. Fail-closed\nif live keys are configured.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreditCheckoutRequest"
              },
              "example": {
                "pack_id": "pack_10"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "TEST PaymentIntent created. Not yet credited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditCheckout"
                }
              }
            }
          },
          "400": {
            "description": "Bad pack_id or body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "invalid_request",
                  "message": "pack_id is required",
                  "retryable": false,
                  "error": "pack_id is required"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/StripeUnavailable"
          }
        }
      }
    },
    "/credits/confirm": {
      "post": {
        "operationId": "confirmCredits",
        "summary": "Confirm a TEST credit purchase",
        "description": "Confirms the PaymentIntent with Stripe test payment method\n`pm_card_visa` (TEST only). On success, appends `credit.purchase`\n(caller_id, pack_id, credits_added, amount_cents, currency,\nstripe_payment_intent_id, ts) and an optional\n`credit.balance_snapshot`. Idempotent on payment intent id.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreditConfirmRequest"
              },
              "example": {
                "stripe_payment_intent_id": "pi_test_123"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Purchase recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditConfirm"
                }
              }
            }
          },
          "400": {
            "description": "Missing id, unpaid, or intent belongs to another caller.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "invalid_request",
                  "message": "stripe_payment_intent_id is required",
                  "retryable": false,
                  "error": "stripe_payment_intent_id is required"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/StripeUnavailable"
          }
        }
      }
    },
    "/credits/quote": {
      "get": {
        "operationId": "quoteCredits",
        "summary": "Quote an operation without debiting",
        "description": "Dry-run cost for `op=smoke`. Returns whether the caller can run\nPOST /smoke (`would_succeed`) and the 1-credit `cost` without\nappending `credit.debit`. Requires a caller key. Unknown or\nmissing `op` → 400.\n",
        "parameters": [
          {
            "in": "query",
            "name": "op",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "smoke"
              ]
            },
            "description": "Operation to quote. Only smoke is supported."
          }
        ],
        "responses": {
          "200": {
            "description": "Quote for the operation. Does not debit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditQuote"
                },
                "example": {
                  "ok": true,
                  "caller_id": "k_0123456789abcdef",
                  "op": "smoke",
                  "cost": 1,
                  "balance": 1,
                  "would_succeed": true
                }
              }
            }
          },
          "400": {
            "description": "Missing or unknown op.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing": {
                    "value": {
                      "code": "invalid_request",
                      "message": "op is required",
                      "retryable": false,
                      "error": "op is required"
                    }
                  },
                  "unknown": {
                    "value": {
                      "code": "invalid_request",
                      "message": "unknown op",
                      "retryable": false,
                      "error": "unknown op"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/credits/balance": {
      "get": {
        "operationId": "getCreditBalance",
        "summary": "Caller credit balance",
        "description": "Derived balance for this `caller_id` (grant + purchase − debit).\nSame fields as optional `credit.balance_snapshot` plus `ok`.\nLedger may also derive this from GET /credits/events.\n",
        "responses": {
          "200": {
            "description": "Current balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditBalance"
                },
                "example": {
                  "ok": true,
                  "caller_id": "k_0123456789abcdef",
                  "balance": 9,
                  "ts": "2026-09-16T10:00:00.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/credits/events": {
      "get": {
        "operationId": "getCreditEvents",
        "summary": "Append-only credit ledger",
        "description": "Metering-truth for Ledger. Events for this `caller_id` only.\nTypes: `credit.grant` (reason=first_key), `credit.purchase`,\n`credit.debit` (reason=smoke_success), optional\n`credit.balance_snapshot`. Balance may be derived from events\n(sum credits_added − sum credits_burned).\n",
        "responses": {
          "200": {
            "description": "Append-only events, oldest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditEventList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/webhooks/stripe": {
      "post": {
        "operationId": "stripeWebhook",
        "summary": "Stripe TEST webhook",
        "description": "Stripe signature (`Stripe-Signature`). On\n`payment_intent.succeeded`, appends `credit.purchase` using\nPaymentIntent metadata (`caller_id`, `pack_id`, `credits_added`).\nTEST webhook secret only (`STRIPE_WEBHOOK_SECRET`). Public;\nauth is the Stripe signature.\n",
        "security": [],
        "parameters": [
          {
            "in": "header",
            "name": "Stripe-Signature",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "description": "Stripe event payload."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Event accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StripeWebhookAck"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid signature.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "invalid_request",
                  "message": "missing stripe-signature",
                  "retryable": false,
                  "error": "missing stripe-signature"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/StripeUnavailable"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Preferred. Caller key from POST /keys (`Authorization: Bearer <key>`)."
      },
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Alias only. Same secret as Bearer. Prefer Authorization Bearer."
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid caller key.",
        "headers": {
          "WWW-Authenticate": {
            "schema": {
              "type": "string",
              "example": "Bearer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "missing": {
                "summary": "No key presented",
                "value": {
                  "code": "missing_api_key",
                  "message": "missing API key",
                  "retryable": false,
                  "error": "missing API key"
                }
              },
              "invalid": {
                "summary": "Key not recognized",
                "value": {
                  "code": "invalid_api_key",
                  "message": "invalid API key",
                  "retryable": false,
                  "error": "invalid API key"
                }
              }
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "Caller balance is below the 1-credit smoke debit.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/InsufficientCredits"
            },
            "example": {
              "code": "insufficient_credits",
              "message": "insufficient credits",
              "retryable": false,
              "error": "insufficient credits",
              "caller_id": "k_0123456789abcdef",
              "balance": 0,
              "required": 1
            }
          }
        }
      },
      "StripeUnavailable": {
        "description": "Stripe TEST keys missing, or live keys rejected.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "notConfigured": {
                "summary": "TEST keys not set",
                "value": {
                  "code": "stripe_not_configured",
                  "message": "stripe test mode not configured",
                  "retryable": true,
                  "error": "stripe test mode not configured"
                }
              },
              "misconfigured": {
                "summary": "Live or invalid Stripe keys",
                "value": {
                  "code": "stripe_misconfigured",
                  "message": "STRIPE_SECRET_KEY is a live Stripe key; live mode is gated (unlock-live). Use TEST keys only (sk_test_ / pk_test_).",
                  "retryable": false,
                  "error": "STRIPE_SECRET_KEY is a live Stripe key; live mode is gated (unlock-live). Use TEST keys only (sk_test_ / pk_test_)."
                }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Health": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "status",
          "service",
          "version"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "service": {
            "type": "string",
            "enum": [
              "shelf-smoke"
            ]
          },
          "version": {
            "type": "string",
            "example": "0.1.0"
          }
        }
      },
      "SmokeRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Absolute http(s) URL to smoke."
          }
        }
      },
      "SmokeViewportResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "width",
          "url",
          "ok"
        ],
        "properties": {
          "name": {
            "type": "string",
            "enum": [
              "desktop",
              "phone"
            ]
          },
          "width": {
            "type": "integer",
            "description": "Viewport width in CSS pixels (1280 desktop, 390 phone)."
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "ok": {
            "type": "boolean",
            "description": "True when navigation reached a <400 HTTP response."
          },
          "status": {
            "type": "integer",
            "description": "HTTP status of the navigation response, if any."
          },
          "error": {
            "type": "string",
            "description": "Failure reason when ok is false."
          }
        }
      },
      "SmokeResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "url",
          "viewports"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "description": "True only if every viewport passed."
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "viewports": {
            "type": "array",
            "minItems": 2,
            "maxItems": 2,
            "description": "Desktop then phone, in that order.",
            "items": {
              "$ref": "#/components/schemas/SmokeViewportResult"
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "code",
          "message",
          "retryable",
          "error"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "Stable machine code (snake_case).",
            "enum": [
              "missing_api_key",
              "invalid_api_key",
              "invalid_request",
              "stripe_not_configured",
              "stripe_misconfigured",
              "internal_error"
            ]
          },
          "message": {
            "type": "string",
            "description": "Human-readable reason."
          },
          "retryable": {
            "type": "boolean",
            "description": "True only when the same request may succeed without changing inputs."
          },
          "error": {
            "type": "string",
            "description": "Alias of message (kept for existing clients)."
          }
        }
      },
      "IssuedKey": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "key",
          "id",
          "createdAt"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "key": {
            "type": "string",
            "description": "Secret. Shown only at issue time. Prefix ssm_."
          },
          "id": {
            "type": "string",
            "description": "Public key id (k_ + hex)."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "VerifiedKey": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "id",
          "createdAt"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "id": {
            "type": "string",
            "description": "Stable caller_id for credit ledger events."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "InsufficientCredits": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "code",
          "message",
          "retryable",
          "error",
          "caller_id",
          "balance",
          "required"
        ],
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "insufficient_credits"
            ]
          },
          "message": {
            "type": "string",
            "enum": [
              "insufficient credits"
            ]
          },
          "retryable": {
            "type": "boolean",
            "enum": [
              false
            ]
          },
          "error": {
            "type": "string",
            "enum": [
              "insufficient credits"
            ],
            "description": "Alias of message (kept for existing clients)."
          },
          "caller_id": {
            "type": "string",
            "description": "Stable API key id (k_…)."
          },
          "balance": {
            "type": "integer",
            "minimum": 0
          },
          "required": {
            "type": "integer",
            "minimum": 1,
            "example": 1
          }
        }
      },
      "CreditPack": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "pack_id",
          "credits",
          "amount_cents",
          "currency"
        ],
        "properties": {
          "pack_id": {
            "type": "string"
          },
          "credits": {
            "type": "integer",
            "minimum": 1
          },
          "amount_cents": {
            "type": "integer",
            "minimum": 1
          },
          "currency": {
            "type": "string",
            "enum": [
              "usd"
            ]
          }
        }
      },
      "CreditPackList": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "mode",
          "packs"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "mode": {
            "type": "string",
            "enum": [
              "test"
            ]
          },
          "packs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CreditPack"
            }
          }
        }
      },
      "CreditCheckoutRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "pack_id"
        ],
        "properties": {
          "pack_id": {
            "type": "string"
          }
        }
      },
      "CreditCheckout": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "mode",
          "caller_id",
          "pack_id",
          "credits_added",
          "amount_cents",
          "currency",
          "stripe_payment_intent_id",
          "publishable_key"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "mode": {
            "type": "string",
            "enum": [
              "test"
            ]
          },
          "caller_id": {
            "type": "string"
          },
          "pack_id": {
            "type": "string"
          },
          "credits_added": {
            "type": "integer"
          },
          "amount_cents": {
            "type": "integer"
          },
          "currency": {
            "type": "string",
            "enum": [
              "usd"
            ]
          },
          "stripe_payment_intent_id": {
            "type": "string",
            "description": "Stripe TEST PaymentIntent id (pi_…)."
          },
          "client_secret": {
            "type": "string",
            "nullable": true
          },
          "publishable_key": {
            "type": "string",
            "description": "pk_test_… only."
          }
        }
      },
      "CreditConfirmRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "stripe_payment_intent_id"
        ],
        "properties": {
          "stripe_payment_intent_id": {
            "type": "string"
          }
        }
      },
      "CreditPurchaseEvent": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "type",
          "caller_id",
          "pack_id",
          "credits_added",
          "amount_cents",
          "currency",
          "stripe_payment_intent_id",
          "ts"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "credit.purchase"
            ]
          },
          "caller_id": {
            "type": "string"
          },
          "pack_id": {
            "type": "string"
          },
          "credits_added": {
            "type": "integer",
            "minimum": 1
          },
          "amount_cents": {
            "type": "integer",
            "minimum": 1
          },
          "currency": {
            "type": "string",
            "enum": [
              "usd"
            ]
          },
          "stripe_payment_intent_id": {
            "type": "string",
            "description": "Stripe TEST PaymentIntent id (pi_…)."
          },
          "ts": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreditGrantEvent": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "type",
          "caller_id",
          "credits_added",
          "reason",
          "ts"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "credit.grant"
            ]
          },
          "caller_id": {
            "type": "string"
          },
          "credits_added": {
            "type": "integer",
            "minimum": 1
          },
          "reason": {
            "type": "string",
            "enum": [
              "first_key"
            ]
          },
          "ts": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreditQuote": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "caller_id",
          "op",
          "cost",
          "balance",
          "would_succeed"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "caller_id": {
            "type": "string"
          },
          "op": {
            "type": "string",
            "enum": [
              "smoke"
            ]
          },
          "cost": {
            "type": "integer",
            "minimum": 1,
            "example": 1
          },
          "balance": {
            "type": "integer",
            "minimum": 0
          },
          "would_succeed": {
            "type": "boolean",
            "description": "True when current balance covers cost. Does not debit."
          }
        }
      },
      "CreditDebitEvent": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "type",
          "caller_id",
          "credits_burned",
          "reason",
          "request_id",
          "ts"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "credit.debit"
            ]
          },
          "caller_id": {
            "type": "string"
          },
          "credits_burned": {
            "type": "integer",
            "minimum": 1
          },
          "reason": {
            "type": "string",
            "enum": [
              "smoke_success"
            ]
          },
          "request_id": {
            "type": "string"
          },
          "ts": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreditBalanceSnapshotEvent": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "type",
          "caller_id",
          "balance",
          "ts"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "credit.balance_snapshot"
            ]
          },
          "caller_id": {
            "type": "string"
          },
          "balance": {
            "type": "integer",
            "minimum": 0
          },
          "ts": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "LedgerEvent": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/CreditPurchaseEvent"
          },
          {
            "$ref": "#/components/schemas/CreditGrantEvent"
          },
          {
            "$ref": "#/components/schemas/CreditDebitEvent"
          },
          {
            "$ref": "#/components/schemas/CreditBalanceSnapshotEvent"
          }
        ],
        "discriminator": {
          "propertyName": "type",
          "mapping": {
            "credit.purchase": "#/components/schemas/CreditPurchaseEvent",
            "credit.grant": "#/components/schemas/CreditGrantEvent",
            "credit.debit": "#/components/schemas/CreditDebitEvent",
            "credit.balance_snapshot": "#/components/schemas/CreditBalanceSnapshotEvent"
          }
        }
      },
      "CreditConfirm": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "mode",
          "event",
          "snapshot",
          "balance"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "mode": {
            "type": "string",
            "enum": [
              "test"
            ]
          },
          "event": {
            "$ref": "#/components/schemas/CreditPurchaseEvent"
          },
          "snapshot": {
            "$ref": "#/components/schemas/CreditBalanceSnapshotEvent"
          },
          "balance": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "CreditBalance": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "caller_id",
          "balance",
          "ts"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "caller_id": {
            "type": "string"
          },
          "balance": {
            "type": "integer",
            "minimum": 0
          },
          "ts": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreditEventList": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "caller_id",
          "events"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "caller_id": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LedgerEvent"
            }
          }
        }
      },
      "StripeWebhookAck": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "received"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "received": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "credited": {
            "type": "boolean"
          }
        }
      },
      "McpJsonRpcRequest": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "enum": [
              "2.0"
            ]
          },
          "id": {
            "description": "Request id. Omitted for notifications."
          },
          "method": {
            "type": "string",
            "description": "MCP method (initialize, tools/list, tools/call, …)."
          },
          "params": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "McpJsonRpcResponse": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "jsonrpc"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "enum": [
              "2.0"
            ]
          },
          "id": {
            "description": "Request id. Null for notifications."
          },
          "result": {
            "type": "object",
            "additionalProperties": true
          },
          "error": {
            "type": "object",
            "additionalProperties": true
          }
        }
      }
    }
  }
}
