{
  "openapi": "3.1.0",
  "info": {
    "title": "Pricing State API",
    "version": "0.1.0",
    "summary": "Always-fresh, structured SaaS pricing data for AI agents — every plan, price, billing period and feature from 33 vendor pricing pages, plus a timestamped history of every change. Pay $0.01 in USDC per call with x402. No API key, no account, no signup.",
    "description": "# Pricing State API\n\nMachine-readable pricing for the SaaS and cloud vendors your agent needs to\nreason about — normalised, verified, and diffed over time.\n\n## What this actually does\n\nA fleet of persistent monitors watches 33 public pricing pages. Every six\nhours each one is loaded in a **real browser** (so JavaScript-rendered\nprices and interactive plan tables resolve properly), an extraction model\nconverts the rendered page into typed JSON, and a diff engine compares the\nresult against the previous snapshot. Anything that moved — a new plan, a\nprice rise, a billing period switch, a feature added or dropped — is\nwritten to a permanent, timestamped change log.\n\nTwo things come out of that pipeline, and this API sells both:\n\n- **Current state** — the pricing of a product right now, as structured\n  plans: `planName`, `price`, `currency`, `billingPeriod`, the raw\n  `priceText` exactly as printed on the page, and the extracted feature\n  list, with the snapshot timestamp so you can judge freshness yourself.\n- **Change history** — the audit trail of what changed and when, each\n  record carrying a one-line human-readable summary plus structured\n  before/after detail per plan.\n\n## Why an agent should pay for this instead of scraping\n\nPricing pages are the worst kind of scraping target: client-side rendered,\nA/B tested, restructured without notice, and different on every vendor's\nsite. Doing it yourself means running a browser, paying for an extraction\nmodel, and repairing a bespoke parser per vendor, forever — and you still\nonly see the page as it looks *today*, with no way to answer \"when did this\nchange?\".\n\nThis API has already paid that cost. One call returns a normalised answer\nacross every vendor, in the same shape, with history attached. Typical uses:\n\n- **Competitive pricing intelligence** — compare a whole category (say\n  `claude`, `mistral`, `together`) in one shape, no per-vendor parsing.\n- **Renewal and procurement checks** — confirm the list price before a\n  contract conversation, and show exactly when it last moved.\n- **Build-vs-buy and cost modelling** — pull real infrastructure rates\n  (`aws-lambda`, `aws-s3`, `neon`, `railway`, `cloudflare`) as numbers,\n  not prose.\n- **Change alerting** — poll the changes endpoint and act when a vendor\n  quietly raises a price or moves a feature behind a higher tier.\n\n## Coverage\n\n33 monitors, each checked every 6 hours:\n\n`airtable`, `asana`, `aws`, `aws-ec2`, `aws-lambda`, `aws-s3`, `claude`,\n`clerk`, `clickup`, `cloudflare`, `cursor`, `dropbox`, `figma`, `github`,\n`jira`, `linear`, `mistral`, `neon`, `notion`, `openai-api`, `plane`,\n`posthog`, `railway`, `resend`, `sentry`, `shortcut`, `slack`, `stripe`,\n`supabase`, `together`, `twilio`, `vercel`, `zoom`.\n\nCall the free `GET /monitors` for the authoritative, always-current list —\nit also reports each monitor's last check time and status, so an agent can\nverify freshness before spending anything.\n\n## Free vs paid\n\n| Endpoint | Cost | Purpose |\n| --- | --- | --- |\n| `GET /healthz` | **free** | System health, monitor counts, freshness of the newest successful check |\n| `GET /monitors` | **free** | Discovery: every monitor, its slug, and its operational status |\n| `GET /openapi.json`, `GET /openapi.yaml` | **free** | This document |\n| `GET /monitors/{slug}` | **$0.01 USDC** | Current structured pricing for one product |\n| `GET /monitors/{slug}/changes` | **$0.01 USDC** | Timestamped history of detected pricing changes |\n\nDiscovery is free on purpose: find the slug you want and confirm the data\nis fresh before you pay for anything.\n\n## Paying (x402, no account required)\n\nPaid endpoints are protected by [x402](https://x402.org) v2. Call one\nwithout payment and you get `402 Payment Required` carrying a\nmachine-readable challenge in the base64 `payment-required` response\nheader; sign it and retry with the payment in the `PAYMENT-SIGNATURE` header. An\nx402 client library does this for you:\n\n```js\nimport { wrapFetchWithPayment, x402Client } from \"@x402/fetch\";\nimport { ExactEvmScheme } from \"@x402/evm/exact/client\";\nimport { toClientEvmSigner } from \"@x402/evm\";\n\n// `account` is any EVM account holding USDC on Base mainnet\nconst client = new x402Client().register(\n  \"eip155:8453\",\n  new ExactEvmScheme(toClientEvmSigner(account)),\n);\nconst pay = wrapFetchWithPayment(fetch, client);\n\nconst res = await pay(\"https://pricing-state-api.replit.app/api/monitors/linear\");\nconst { pricing, snapshotAt } = await res.json();\n```\n\nSettlement is on-chain USDC on Base mainnet. There is no API key, no\naccount, no subscription and no minimum — an agent that has never\ninteracted with this service can discover it, pay, and get data in a single\nround trip.\n\n**Agent payment skills and cross-chain funding.** Nothing here is bespoke:\nit is stock x402 v2, so any agent payment skill, wallet or client library\nthat speaks the v2 `exact` scheme in USDC on Base pays it unmodified. An\nagent whose balance sits elsewhere can source USDC on Base from assets held\non 30+ chains via NEAR Intents and then pay as normal — that funding step\nhappens entirely client-side, before the request; this API itself neither\nbridges nor swaps, and only ever accepts and settles USDC on Base.\n\n## Operational notes\n\n- **Rate limit:** 100 requests per minute per IP across all endpoints,\n  answered with `429` and a JSON error body when exceeded. Discovery\n  endpoints count towards it too.\n- **Caching:** paid responses are returned with `Cache-Control: no-store`.\n- **CORS:** open by default; these are public, stateless, credential-free\n  reads.\n- **Bad requests are never charged for.** Unknown slugs (`404`), malformed\n  slugs or paths (`400`), an invalid `limit` (`400`), and a monitor that\n  cannot be verified at that moment (`503`) are all rejected before the\n  payment gate.\n",
    "contact": {
      "email": "xs10chill@proton.me"
    },
    "license": {
      "name": "Proprietary — pay-per-call access via x402",
      "url": "https://pricing-state-api.replit.app/"
    },
    "x-guidance": "Recommended agent flow:\n\n1. GET /monitors (free) — list every monitored product and pick a `slug`\n   (e.g. \"linear\", \"notion\", \"aws-lambda\"). Check `lastStatus` is \"ok\" and\n   `lastCheckedAt` is recent enough for your purpose before paying.\n2. GET /monitors/{slug} (paid, $0.01 USDC) — current structured pricing:\n   plans with name, numeric price, currency, billing period, the raw price\n   text from the page, and features, plus `snapshotAt`.\n3. GET /monitors/{slug}/changes (paid, $0.01 USDC) — history of detected\n   changes, most recent first; use `?limit=` (1-100, default 20) and page\n   with `?before=` using the `nextBefore` token each response returns.\n   Every response also reports `total`, so you know what you have. The free\n   catalog's `lastChangeAt` tells you whether a monitor has any history at\n   all before you pay for it.\n\nPaying: call a paid endpoint with no payment and you receive HTTP 402. The\nmachine-readable x402 v2 challenge is the base64-encoded \"payment-required\"\nresponse header (scheme, network, asset, amount, payTo); the JSON body\nrestates the same offer in plain fields. Sign and retry with the PAYMENT-SIGNATURE\nheader — x402 v2 client libraries such as @x402/fetch do this\nautomatically. No API key or account is involved at any point. No wallet\nyet? Any x402-compatible wallet holding USDC on Base works, e.g. Coinbase\nAgentic Wallet or Base MCP. Because this is stock x402 v2, any agent\npayment skill or client library that supports the v2 exact scheme in USDC\non Base works unmodified; if your funds are on another chain, source USDC\non Base from assets held on 30+ chains via NEAR Intents before paying —\nthat is a client-side step, and settlement here is always USDC on Base.\n\nInterpreting the data: `price` is a numeric string (\"0\", \"16\",\n\"0.0000166667\") except for quote-based plans, where it is \"custom\", and\nmetered options the page names without an amount, where it is \"usage\".\n`billingPeriod` distinguishes \"month\" / \"year\" / \"one-time\" / \"usage\"\n(metered) / \"custom\" (quote-based) / \"unknown\". `priceText` always holds\nthe string as printed on the page, so you can show a source-faithful quote\nor re-parse it yourself.\n\nError handling: 404 = unknown slug, 400 = malformed slug or path (codes\n\"invalid_slug\" / \"invalid_path\") or a `limit` outside 1-100 (code\n\"invalid_query\") — all rejected before the payment gate and never charged\n(discover valid slugs via GET /monitors). 429 =\nrate limited (100/min per IP), back off and retry. 503 = the monitor could\nnot be verified before charging, never charged, safe to retry. 402 =\npayment required, pay and retry.\n",
    "x-payments": {
      "protocol": "x402",
      "version": 2,
      "scheme": "exact",
      "network": "eip155:8453",
      "networkName": "Base mainnet",
      "asset": "USDC",
      "assetAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "pricePerCall": "0.01",
      "currency": "USD",
      "apiKeyRequired": false,
      "accountRequired": false,
      "challengeHeader": "PAYMENT-REQUIRED",
      "payWithHeader": "PAYMENT-SIGNATURE",
      "maxTimeoutSeconds": 300,
      "clientLibrary": "@x402/fetch",
      "facilitator": "https://facilitator.payai.network",
      "docs": "https://x402.org",
      "funding": "Advisory only, not a server capability — this API accepts and settles nothing but USDC on Base via the x402 v2 exact scheme, so any skill, wallet or client library speaking that pays unmodified. Agents whose assets sit on other chains can source Base USDC from 30+ chains via NEAR Intents client-side, before the request.",
      "authoritative": "The base64 \"payment-required\" header on a 402 response is the authoritative source for scheme, network, asset, amount and receiving address. The values published here describe the live production configuration and are for discovery only.",
      "paidEndpoints": [
        "GET /monitors/{slug}",
        "GET /monitors/{slug}/changes"
      ],
      "freeEndpoints": [
        "GET /healthz",
        "GET /monitors",
        "GET /openapi.yaml",
        "GET /openapi.json"
      ]
    }
  },
  "externalDocs": {
    "description": "Service homepage, live status and worked examples",
    "url": "https://pricing-state-api.replit.app/"
  },
  "servers": [
    {
      "url": "https://pricing-state-api.replit.app/api",
      "description": "Production"
    },
    {
      "url": "/api",
      "description": "Relative base API path"
    }
  ],
  "tags": [
    {
      "name": "discovery",
      "description": "Free, unauthenticated endpoints for finding out what exists and how fresh it is. Start here: pick a monitor slug and check its status before spending anything."
    },
    {
      "name": "pricing-data",
      "description": "The paid dataset ($0.01 USDC per call via x402): current structured pricing for a product, and the timestamped history of every change detected on its pricing page."
    },
    {
      "name": "health",
      "description": "Free service health and data freshness — monitor counts, how many are erroring, and when the most recent successful check completed."
    }
  ],
  "paths": {
    "/healthz": {
      "get": {
        "operationId": "healthCheck",
        "tags": [
          "health",
          "discovery"
        ],
        "summary": "Service health and data freshness (free)",
        "description": "Free, no payment required. Reports whether the monitoring fleet is healthy and how fresh the underlying data is: how many monitors exist, how many are enabled, how many are currently erroring, and the timestamp of the most recent successful check across the whole fleet.\n\nUseful as a pre-flight check before paying: if `lastSuccessfulCheckAt` is older than your tolerance, or `status` is \"degraded\", you may want to wait rather than spend a call.\n",
        "security": [],
        "x-paid": false,
        "responses": {
          "200": {
            "description": "Current service health and fleet freshness.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthStatus"
                },
                "example": {
                  "status": "ok",
                  "payments": {
                    "status": "ok",
                    "lastCheckedAt": "2026-08-12T20:41:02.114Z",
                    "detail": null
                  },
                  "monitors": {
                    "total": 30,
                    "enabled": 30,
                    "erroring": 0
                  },
                  "lastSuccessfulCheckAt": "2026-08-12T20:14:45.606Z",
                  "lastOutage": null
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/monitors": {
      "get": {
        "operationId": "listMonitors",
        "tags": [
          "discovery"
        ],
        "summary": "List every monitored pricing page (free)",
        "description": "Free, no payment required. Returns every monitored product with the `slug` you pass to the paid endpoints, the exact pricing page URL being watched, how often it is checked, its operational status (`lastCheckedAt`, `lastStatus`) and when it last recorded a change (`lastChangeAt`).\n\nThis is the discovery entry point: it is the authoritative, live list of what can be priced, and it lets an agent confirm a monitor is healthy and recently checked *before* paying for its data. `lastChangeAt` extends that to the change history — null means the paid changes endpoint has nothing to return for this monitor, and a timestamp you have already seen means nothing new since. Sorted by slug.\n",
        "security": [],
        "x-paid": false,
        "responses": {
          "200": {
            "description": "Every monitor, sorted by slug.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MonitorList"
                },
                "example": {
                  "monitors": [
                    {
                      "id": 9,
                      "slug": "airtable",
                      "name": "Airtable",
                      "url": "https://airtable.com/pricing",
                      "enabled": true,
                      "checkIntervalMinutes": 360,
                      "lastCheckedAt": "2026-08-12T19:20:44.939Z",
                      "lastStatus": "ok",
                      "lastError": null,
                      "lastChangeAt": "2026-08-11T07:14:02.551Z"
                    },
                    {
                      "id": 25,
                      "slug": "aws-lambda",
                      "name": "AWS Lambda",
                      "url": "https://aws.amazon.com/lambda/pricing/",
                      "enabled": true,
                      "checkIntervalMinutes": 360,
                      "lastCheckedAt": "2026-08-12T20:14:45.606Z",
                      "lastStatus": "ok",
                      "lastError": null,
                      "lastChangeAt": null
                    },
                    {
                      "id": 3,
                      "slug": "linear",
                      "name": "Linear",
                      "url": "https://linear.app/pricing",
                      "enabled": true,
                      "checkIntervalMinutes": 360,
                      "lastCheckedAt": "2026-08-12T17:02:10.083Z",
                      "lastStatus": "ok",
                      "lastError": null,
                      "lastChangeAt": null
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/monitors/{slug}": {
      "get": {
        "operationId": "getMonitorState",
        "tags": [
          "pricing-data"
        ],
        "summary": "Current structured pricing for one product (paid — $0.01 USDC)",
        "description": "**Paid: $0.01 USDC per call via x402 v2 on Base mainnet.** Requests without a valid `PAYMENT-SIGNATURE` header receive HTTP 402 with the machine-readable payment requirements in the base64-encoded `payment-required` response header; x402 client libraries pay and retry automatically.\n\nReturns the vendor's pricing exactly as it stands at the latest successful check: an array of plans, each with `planName`, a numeric `price` string (or \"custom\" for quote-based plans and \"usage\" for metered options), `currency`, `billingPeriod`, the raw `priceText` as printed on the page, and the extracted `features` list. The response also carries the full monitor record and `snapshotAt`, the moment that snapshot was captured, so an agent can decide for itself whether the data is fresh enough to act on.\n\nThis is one normalised call in place of loading a JavaScript-heavy pricing page in a browser and extracting it yourself — and it returns the same shape for every vendor, from seat-based SaaS to metered cloud infrastructure.\n\nPaid responses are returned with `Cache-Control: no-store`. You are never charged for a slug that does not exist: unknown and malformed slugs are rejected before the payment gate.\n",
        "security": [
          {
            "x402Payment": []
          }
        ],
        "x-paid": true,
        "x-price-usd": "0.01",
        "x-payment-info": {
          "price": {
            "mode": "fixed",
            "currency": "USD",
            "amount": "0.01"
          },
          "protocols": [
            {
              "x402": {
                "version": 2,
                "scheme": "exact",
                "network": "eip155:8453",
                "asset": "USDC"
              }
            }
          ]
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/MonitorSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The monitor and its latest extracted pricing snapshot. `pricing` and `snapshotAt` are null only if no check has ever succeeded.",
            "headers": {
              "Cache-Control": {
                "description": "Always `no-store` on paid responses: each call is individually paid for, so responses must never be reused from a cache.",
                "schema": {
                  "type": "string",
                  "example": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MonitorState"
                },
                "examples": {
                  "seatBasedSaas": {
                    "summary": "Seat-based SaaS with a free tier and a quote-based top tier",
                    "description": "Linear: note \"0\" for the free plan, \"custom\" for the enterprise plan, and `priceText` preserving the per-seat wording from the page.",
                    "value": {
                      "monitor": {
                        "id": 3,
                        "slug": "linear",
                        "name": "Linear",
                        "url": "https://linear.app/pricing",
                        "enabled": true,
                        "checkIntervalMinutes": 360,
                        "lastCheckedAt": "2026-08-12T17:02:10.083Z",
                        "lastStatus": "ok",
                        "lastError": null,
                        "lastChangeAt": null
                      },
                      "pricing": {
                        "plans": [
                          {
                            "planName": "Free",
                            "price": "0",
                            "currency": "USD",
                            "billingPeriod": "unknown",
                            "priceText": "$0",
                            "features": [
                              "Unlimited members",
                              "2 teams",
                              "250 issues"
                            ]
                          },
                          {
                            "planName": "Basic",
                            "price": "10",
                            "currency": "USD",
                            "billingPeriod": "month",
                            "priceText": "$10 per user/month",
                            "features": [
                              "Unlimited members",
                              "5 teams",
                              "Unlimited issues"
                            ]
                          },
                          {
                            "planName": "Business",
                            "price": "16",
                            "currency": "USD",
                            "billingPeriod": "month",
                            "priceText": "$16 per user/month",
                            "features": [
                              "Unlimited teams",
                              "Private teams and guest accounts"
                            ]
                          },
                          {
                            "planName": "Enterprise",
                            "price": "custom",
                            "currency": "USD",
                            "billingPeriod": "custom",
                            "priceText": "Custom",
                            "features": [
                              "Invoice / PO billing",
                              "SAML and SCIM"
                            ]
                          }
                        ],
                        "currency": "USD",
                        "notes": null
                      },
                      "snapshotAt": "2026-08-12T17:02:10.021Z"
                    }
                  },
                  "usageBasedCloud": {
                    "summary": "Metered cloud infrastructure pricing",
                    "description": "AWS Lambda: `billingPeriod` is \"usage\", `price` carries the full sub-cent rate, and `priceText` keeps the compound per-unit wording intact.",
                    "value": {
                      "monitor": {
                        "id": 25,
                        "slug": "aws-lambda",
                        "name": "AWS Lambda",
                        "url": "https://aws.amazon.com/lambda/pricing/",
                        "enabled": true,
                        "checkIntervalMinutes": 360,
                        "lastCheckedAt": "2026-08-12T20:14:45.606Z",
                        "lastStatus": "ok",
                        "lastError": null,
                        "lastChangeAt": null
                      },
                      "pricing": {
                        "plans": [
                          {
                            "planName": "Lambda Functions",
                            "price": "0.0000166667",
                            "currency": "USD",
                            "billingPeriod": "usage",
                            "priceText": "$0.0000166667 for every GB-second; $0.20 per 1M requests",
                            "features": [
                              "Request charge $0.20 per 1M requests",
                              "Free tier: 1M requests and 400,000 GB-seconds per month",
                              "Duration measured in GB-seconds (memory × time)"
                            ]
                          },
                          {
                            "planName": "Provisioned Concurrency",
                            "price": "usage",
                            "currency": "USD",
                            "billingPeriod": "usage",
                            "priceText": "Charged per GB-second of provisioned concurrency",
                            "features": [
                              "Billed for the time concurrency is enabled"
                            ]
                          }
                        ],
                        "currency": "USD",
                        "notes": null
                      },
                      "snapshotAt": "2026-08-12T20:14:45.517Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/monitors/{slug}/changes": {
      "get": {
        "operationId": "listMonitorChanges",
        "tags": [
          "pricing-data"
        ],
        "summary": "History of detected pricing changes (paid — $0.01 USDC)",
        "description": "**Paid: $0.01 USDC per call via x402 v2 on Base mainnet.** Requests without a valid `PAYMENT-SIGNATURE` header receive HTTP 402 with the machine-readable payment requirements in the base64-encoded `payment-required` response header; x402 client libraries pay and retry automatically.\n\nReturns the timestamped audit log of everything that has moved on this vendor's pricing page since monitoring began, most recent first: plans added or removed, prices changed, billing periods changed, and feature lists changed. Each record carries a one-line `summary` you can surface directly to a user, plus structured `details` giving the affected plan and the exact before/after values, and `detectedAt`.\n\nThis is the part that cannot be reconstructed by scraping: a page shows only today's prices, while this endpoint answers \"what changed, and when?\" — for price-rise alerting, renewal negotiations, and tracking a competitor's packaging over time.\n\n**Paging.** Every response says how much history exists (`total`), how much of it this page holds (`limit`), whether older records remain (`hasMore`), and how to ask for them (`nextBefore`). Pass that token back as `before` to walk beyond the 100-record per-call maximum, oldest page last. Poll for new records by re-reading the first page — it is always the newest, cursor or not.\n\nFree callers can tell whether there is anything here to buy before paying: `lastChangeAt` on the free `GET /monitors` catalog is null until a monitor records its first change.\n\nPaid responses are returned with `Cache-Control: no-store`. An empty `changes` array means no change has been detected yet, not an error.\n",
        "security": [
          {
            "x402Payment": []
          }
        ],
        "x-paid": true,
        "x-price-usd": "0.01",
        "x-payment-info": {
          "price": {
            "mode": "fixed",
            "currency": "USD",
            "amount": "0.01"
          },
          "protocols": [
            {
              "x402": {
                "version": 2,
                "scheme": "exact",
                "network": "eip155:8453",
                "asset": "USDC"
              }
            }
          ]
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/MonitorSlug"
          },
          {
            "$ref": "#/components/parameters/ChangesLimit"
          },
          {
            "$ref": "#/components/parameters/ChangesBefore"
          }
        ],
        "responses": {
          "200": {
            "description": "Detected changes for this monitor, newest first. Empty when nothing has changed yet.",
            "headers": {
              "Cache-Control": {
                "description": "Always `no-store` on paid responses: each call is individually paid for, so responses must never be reused from a cache.",
                "schema": {
                  "type": "string",
                  "example": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChangeList"
                },
                "examples": {
                  "priceIncrease": {
                    "summary": "A plan's price moving — the alerting case",
                    "value": {
                      "monitorSlug": "notion",
                      "changes": [
                        {
                          "id": 128,
                          "summary": "\"Business\" price changed: $18 → $20",
                          "details": [
                            {
                              "kind": "price_changed",
                              "planName": "Business",
                              "before": "18",
                              "after": "20"
                            }
                          ],
                          "detectedAt": "2026-08-09T11:02:57.118Z"
                        }
                      ],
                      "limit": 20,
                      "total": 1,
                      "hasMore": false,
                      "nextBefore": null
                    }
                  },
                  "firstPageOfALongHistory": {
                    "summary": "More history than one page — paging with `before`",
                    "value": {
                      "monitorSlug": "stripe",
                      "changes": [
                        {
                          "id": 212,
                          "summary": "Plan \"Custom\" added at Contact sales",
                          "details": [
                            {
                              "kind": "plan_added",
                              "planName": "Custom",
                              "before": null,
                              "after": "Contact sales"
                            }
                          ],
                          "detectedAt": "2026-08-21T04:18:09.552Z"
                        },
                        {
                          "id": 205,
                          "summary": "\"Standard\" price changed: 2.9% + 30¢ → 2.9% + 35¢",
                          "details": [
                            {
                              "kind": "price_changed",
                              "planName": "Standard",
                              "before": "2.9% + 30¢",
                              "after": "2.9% + 35¢"
                            }
                          ],
                          "detectedAt": "2026-08-19T22:03:44.101Z"
                        }
                      ],
                      "limit": 2,
                      "total": 26,
                      "hasMore": true,
                      "nextBefore": "MjAyNi0wOC0xOVQyMjowMzo0NC4xMDFafDIwNQ"
                    }
                  },
                  "noChangesYet": {
                    "summary": "Monitored, but nothing has changed yet",
                    "value": {
                      "monitorSlug": "resend",
                      "changes": [],
                      "limit": 20,
                      "total": 0,
                      "hasMore": false,
                      "nextBefore": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "x402Payment": {
        "type": "apiKey",
        "in": "header",
        "name": "PAYMENT-SIGNATURE",
        "description": "x402 v2 payment protocol (https://x402.org) — $0.01 USDC per call on Base mainnet, with no API key, account or subscription.\n\nCall the endpoint without this header to receive a 402 response whose base64-encoded \"payment-required\" header contains the payment requirements (scheme, network, asset, amount, payTo). Sign the payment and retry the identical request with it in the PAYMENT-SIGNATURE header. x402 v2 client libraries (e.g. @x402/fetch) perform the whole exchange automatically, so in practice an agent just makes the request twice — or once, through a paying fetch wrapper.\n"
      }
    },
    "parameters": {
      "MonitorSlug": {
        "name": "slug",
        "in": "path",
        "required": true,
        "description": "Monitor identifier. Discover valid slugs via the free GET /monitors endpoint — e.g. \"linear\", \"notion\", \"vercel\", \"aws-lambda\", \"claude\". Lowercase letters, digits and hyphens only; an invalid slug is rejected before payment.\nIn these paths {slug} is a placeholder to substitute, not part of a callable URL: requesting /monitors/{slug}, /monitors/%7Bslug%7D or /monitors/:slug literally is answered 400 \"invalid_slug\" before the payment gate and is never charged.",
        "schema": {
          "type": "string",
          "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
          "minLength": 1,
          "maxLength": 64,
          "example": "linear"
        },
        "example": "linear",
        "examples": {
          "seatBasedSaas": {
            "summary": "Seat-based SaaS (Linear)",
            "value": "linear"
          },
          "productivitySuite": {
            "summary": "Productivity suite (Notion)",
            "value": "notion"
          },
          "meteredCloud": {
            "summary": "Metered cloud infrastructure (AWS Lambda)",
            "value": "aws-lambda"
          },
          "aiModelProvider": {
            "summary": "AI model provider (Claude)",
            "value": "claude"
          }
        }
      },
      "ChangesLimit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Maximum number of change records to return, most recent first. Optional; defaults to 20, maximum 100. Values outside 1-100, and non-integers, are rejected with 400 (code \"invalid_query\") before the payment gate — never charged.\n\nThe response echoes the limit it applied and reports `total`, so a caller always knows whether it is holding the whole history or a window of it.",
        "schema": {
          "type": "integer",
          "format": "int32",
          "minimum": 1,
          "maximum": 100,
          "default": 20,
          "example": 20
        },
        "example": 20
      },
      "ChangesBefore": {
        "name": "before",
        "in": "query",
        "required": false,
        "description": "Cursor for the next page, taken verbatim from the previous response's `nextBefore`. Returns the records immediately older than the one that token points at, so a caller can walk a history longer than the 100-row per-call maximum.\n\nTreat it as opaque: it encodes the position of a specific record, not a date, and its format may change. Paging is stable across calls because the token names a record rather than an offset — new changes detected while you page appear on a fresh first page, and never shift or duplicate the rows behind your cursor.\n\nA malformed token is rejected with 400 (code \"invalid_query\") before the payment gate and is never charged. A well-formed token pointing at no record is not an error: it returns an empty page.",
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_-]{8,128}$",
          "example": "MjAyNi0wOC0wOVQxMTowMjo1Ny4xMThafDEyOA"
        },
        "example": "MjAyNi0wOC0wOVQxMTowMjo1Ny4xMThafDEyOA"
      }
    },
    "responses": {
      "NotFound": {
        "description": "No monitor exists with that slug. Never charged — the check happens before the payment gate. Call the free GET /monitors for valid slugs.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": {
                "code": "not_found",
                "message": "No monitor with slug \"linaer\""
              }
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "Payment required (x402 v2). The machine-readable payment requirements (an \"accepts\" array with scheme, network, asset, amount and receiving address) are delivered in the base64-encoded \"payment-required\" response header, whose decoded shape is X402PaymentRequirements. The JSON body is a human- and agent-readable summary of the same offer: price, what the endpoint returns, and how to pay. Retry the identical request with a signed payment in the PAYMENT-SIGNATURE header — x402 v2 client libraries (e.g. @x402/fetch) handle this automatically.\n\nNote: requests that look like a web browser (Accept: text/html with a Mozilla User-Agent) receive an HTML paywall page instead of JSON.\n",
        "headers": {
          "payment-required": {
            "description": "Base64-encoded JSON x402 v2 payment challenge; decodes to X402PaymentRequirements. This header is the authoritative payment instruction — pay from it rather than from the response body.\n",
            "schema": {
              "type": "string",
              "contentEncoding": "base64",
              "contentMediaType": "application/json",
              "contentSchema": {
                "$ref": "#/components/schemas/X402PaymentRequirements"
              }
            }
          },
          "Cache-Control": {
            "description": "Always `no-store`.",
            "schema": {
              "type": "string",
              "example": "no-store"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PaymentRequiredBody"
            }
          },
          "text/html": {
            "schema": {
              "type": "string",
              "description": "Human-facing paywall page (browsers only)."
            }
          }
        }
      },
      "BadRequest": {
        "description": "Malformed request. An invalid slug format (code \"invalid_slug\"), a malformed path (code \"invalid_path\"), a `limit` outside 1-100 and a malformed `before` cursor (both code \"invalid_query\") are all rejected before the payment gate and never charged.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "examples": {
              "invalidSlug": {
                "summary": "Slug is not in the allowed format",
                "value": {
                  "error": {
                    "code": "invalid_slug",
                    "message": "Invalid monitor slug. Slugs are lowercase letters, digits and hyphens (e.g. \"linear\"). Discover valid slugs via GET /api/monitors."
                  }
                }
              },
              "invalidQuery": {
                "summary": "limit out of range",
                "value": {
                  "error": {
                    "code": "invalid_query",
                    "message": "Invalid query parameters: limit must be an integer between 1 and 100"
                  }
                }
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded — 100 requests per minute per IP across all endpoints. Back off and retry; standard RateLimit headers (draft-7) accompany the response.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": {
                "code": "rate_limited",
                "message": "Too many requests. Please slow down and retry shortly."
              }
            }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "The pre-payment check could not confirm this monitor can be served — for example the database was unreachable when its existence was verified, or the service knows its data layer is mid-repair. **No payment is taken**: the request is refused before the payment gate rather than charged and failed. Safe to retry.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": {
                "code": "service_unavailable",
                "message": "Monitor data is temporarily unavailable. No payment was taken; please retry."
              }
            }
          }
        }
      },
      "InternalError": {
        "description": "Unexpected server error.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "error": {
                "code": "internal_error",
                "message": "An internal error occurred"
              }
            }
          }
        }
      }
    },
    "schemas": {
      "HealthStatus": {
        "type": "object",
        "description": "Service health and how fresh the monitored data is.",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "degraded"
            ],
            "description": "\"ok\" when all enabled monitors last checked successfully AND the payment rail is not known to be broken; \"degraded\" when one or more monitors are erroring or `payments.status` is \"broken\".\n"
          },
          "payments": {
            "type": "object",
            "description": "Whether a customer could actually pay right now, based on an hourly probe that performs a real x402 payment handshake with a throwaway unfunded wallet (no funds move). \"ok\" means the facilitator evaluated the probe payment and rejected the payer as expected; \"broken\" means the payment path is failing server-side (nobody can pay); \"unknown\" means the probe has not run yet.\n",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "ok",
                  "broken",
                  "unknown"
                ],
                "description": "Outcome of the most recent payment health probe."
              },
              "lastCheckedAt": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "When the payment path was last probed. Null when the probe has not run yet.\n"
              },
              "detail": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Human-readable evidence for a \"broken\" status, otherwise null.\n"
              },
              "reason": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "timeout",
                  "transport",
                  "server_error",
                  "unverified",
                  "healthy",
                  null
                ],
                "description": "Structured failure category for the most recent probe. \"timeout\": facilitator accepted the connection but never responded within the deadline. \"transport\": fetch threw before any HTTP response (ECONNREFUSED, DNS failure, etc.). \"server_error\": facilitator returned 5xx. \"unverified\": 402 with no payer-side reason, or unfunded payment accepted outright. \"healthy\": facilitator evaluated the probe and rejected the payer as expected. Null when the probe has not run yet.\n"
              }
            },
            "required": [
              "status",
              "lastCheckedAt",
              "detail",
              "reason"
            ]
          },
          "monitors": {
            "type": "object",
            "description": "Fleet counts.",
            "properties": {
              "total": {
                "type": "integer",
                "description": "Every monitor, enabled or not."
              },
              "enabled": {
                "type": "integer",
                "description": "Monitors currently on the checking schedule."
              },
              "erroring": {
                "type": "integer",
                "description": "Enabled monitors whose most recent check failed."
              }
            },
            "required": [
              "total",
              "enabled",
              "erroring"
            ]
          },
          "lastSuccessfulCheckAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Time of the most recent successful monitor check anywhere in the fleet (including checks where pricing was unchanged). Null if no monitor has ever checked successfully.\n"
          },
          "lastOutage": {
            "type": [
              "object",
              "null"
            ],
            "description": "The most recent proven downtime window, detected at boot by comparing a persisted liveness heartbeat against the time the process came back up. Null if no outage has ever been detected. This is after-the-fact detection only — it will not show an outage that is happening right now, only ones the service has already recovered from.\n",
            "properties": {
              "startedAt": {
                "type": "string",
                "format": "date-time",
                "description": "Last recorded heartbeat before the gap."
              },
              "endedAt": {
                "type": "string",
                "format": "date-time",
                "description": "When the recovering process detected the gap (its own boot time)."
              },
              "durationMs": {
                "type": "integer",
                "description": "Length of the outage window, in milliseconds."
              }
            },
            "required": [
              "startedAt",
              "endedAt",
              "durationMs"
            ]
          }
        },
        "required": [
          "status",
          "payments",
          "monitors",
          "lastSuccessfulCheckAt",
          "lastOutage"
        ]
      },
      "PaymentRequiredBody": {
        "type": "object",
        "description": "Body of a 402 response: the same offer as the payment-required header, restated for an agent (or human) reading the response directly. Informational only — pay from the header challenge.\n",
        "properties": {
          "error": {
            "type": "string",
            "const": "payment_required"
          },
          "message": {
            "type": "string",
            "description": "Plain-language summary: price, what this endpoint returns, and how to pay without an API key.\n"
          },
          "price": {
            "type": "object",
            "properties": {
              "amount": {
                "type": "string",
                "description": "Price per call in whole USDC, e.g. \"0.01\"."
              },
              "currency": {
                "type": "string",
                "const": "USDC"
              },
              "network": {
                "type": "string",
                "description": "Human-readable network name, e.g. \"Base\"."
              },
              "chainId": {
                "type": "string",
                "description": "CAIP-2 chain id, e.g. \"eip155:8453\"."
              }
            },
            "required": [
              "amount",
              "currency",
              "network",
              "chainId"
            ]
          },
          "howToPay": {
            "type": "object",
            "properties": {
              "protocol": {
                "type": "string",
                "const": "x402"
              },
              "version": {
                "type": "integer"
              },
              "apiKeyRequired": {
                "type": "boolean"
              },
              "challengeHeader": {
                "type": "string",
                "description": "Response header carrying the payment challenge."
              },
              "payWithHeader": {
                "type": "string",
                "description": "Request header to retry with, carrying the signed payment."
              },
              "clientLibrary": {
                "type": "string"
              },
              "walletSetup": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Ways an agent with no wallet can get one funded with USDC."
              },
              "docs": {
                "type": "string",
                "format": "uri"
              }
            },
            "required": [
              "protocol",
              "version",
              "apiKeyRequired",
              "challengeHeader",
              "payWithHeader"
            ]
          }
        },
        "required": [
          "error",
          "message",
          "price",
          "howToPay"
        ],
        "example": {
          "error": "payment_required",
          "message": "$0.01 USDC required (x402 / Base)\n\nReturns current structured SaaS pricing: plan names, prices, billing period, features, and snapshot time.\n\nNo API key needed. Pay with any x402-compatible wallet that holds USDC on Base. If your agent does not yet have a funded wallet, set one up via Coinbase Agentic Wallet or Base MCP — then standard x402 clients will pay and retry automatically.",
          "price": {
            "amount": "0.01",
            "currency": "USDC",
            "network": "Base",
            "chainId": "eip155:8453"
          },
          "howToPay": {
            "protocol": "x402",
            "version": 2,
            "apiKeyRequired": false,
            "challengeHeader": "PAYMENT-REQUIRED",
            "payWithHeader": "PAYMENT-SIGNATURE",
            "clientLibrary": "@x402/fetch",
            "walletSetup": [
              "Coinbase Agentic Wallet",
              "Base MCP"
            ],
            "docs": "https://x402.org"
          }
        }
      },
      "X402PaymentRequirements": {
        "type": "object",
        "description": "Decoded form of the base64 \"payment-required\" response header: the machine-readable x402 v2 challenge an x402 client pays from. See https://x402.org.\n",
        "properties": {
          "x402Version": {
            "type": "integer",
            "const": 2
          },
          "error": {
            "type": "string",
            "description": "Why payment is required, e.g. \"Payment required\"."
          },
          "resource": {
            "type": "object",
            "description": "What is being sold, and in what format.",
            "properties": {
              "url": {
                "type": "string",
                "format": "uri",
                "description": "Absolute URL of the protected resource."
              },
              "description": {
                "type": "string",
                "description": "What this endpoint returns, what it costs, and how to pay.\n"
              },
              "mimeType": {
                "type": "string"
              }
            },
            "required": [
              "url"
            ]
          },
          "accepts": {
            "type": "array",
            "description": "Acceptable payment methods; pay any one of them.",
            "items": {
              "$ref": "#/components/schemas/X402PaymentRequirementsAcceptsItem"
            }
          },
          "extensions": {
            "type": "object",
            "additionalProperties": true,
            "description": "Optional protocol extensions. This API publishes the \"bazaar\" discovery extension: input/output schemas and a response example for the paid endpoint.\n"
          }
        },
        "required": [
          "x402Version",
          "resource",
          "accepts"
        ],
        "example": {
          "x402Version": 2,
          "error": "Payment required",
          "resource": {
            "url": "https://pricing-state-api.replit.app/api/monitors/linear",
            "description": "$0.01 USDC required (x402 / Base)",
            "mimeType": "application/json"
          },
          "accepts": [
            {
              "scheme": "exact",
              "network": "eip155:8453",
              "amount": "10000",
              "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
              "payTo": "0xb23c3825de4ca8a479183092ae8d1d58d2736fc9",
              "maxTimeoutSeconds": 300,
              "extra": {
                "name": "USD Coin",
                "version": "2"
              }
            }
          ]
        }
      },
      "X402PaymentRequirementsAcceptsItem": {
        "type": "object",
        "description": "One acceptable payment method.",
        "properties": {
          "scheme": {
            "type": "string",
            "description": "Payment scheme, e.g. \"exact\"."
          },
          "network": {
            "type": "string",
            "description": "CAIP-2 chain id, e.g. \"eip155:8453\" (Base mainnet)."
          },
          "amount": {
            "type": "string",
            "description": "Price in the asset's base units — USDC has 6 decimals, so \"10000\" is $0.01."
          },
          "asset": {
            "type": "string",
            "description": "Token contract address (USDC)."
          },
          "payTo": {
            "type": "string",
            "description": "Receiving wallet address."
          },
          "maxTimeoutSeconds": {
            "type": "integer",
            "description": "How long the signed payment stays valid."
          },
          "extra": {
            "type": "object",
            "additionalProperties": true,
            "description": "Scheme-specific data; for EVM \"exact\" this carries the token's EIP-712 domain name and version.\n"
          }
        },
        "required": [
          "scheme",
          "network",
          "amount",
          "asset",
          "payTo"
        ]
      },
      "ErrorResponse": {
        "type": "object",
        "description": "Standard error envelope used by every endpoint (the 402 body is the one exception; see PaymentRequiredBody).",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "Machine-readable error code: \"not_found\", \"invalid_slug\", \"invalid_path\", \"invalid_query\", \"rate_limited\", \"service_unavailable\" or \"internal_error\"."
              },
              "message": {
                "type": "string",
                "description": "Human/agent-readable explanation."
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "error"
        ]
      },
      "MonitorSummary": {
        "type": "object",
        "description": "A monitored pricing page and its operational status.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Internal numeric id. Use `slug` to address the monitor."
          },
          "slug": {
            "type": "string",
            "description": "Stable identifier used in paid endpoint paths, e.g. \"linear\"."
          },
          "name": {
            "type": "string",
            "description": "Human-readable product name, e.g. \"Linear\"."
          },
          "url": {
            "type": "string",
            "description": "The exact public pricing page being monitored."
          },
          "enabled": {
            "type": "boolean",
            "description": "Whether this monitor is currently on the checking schedule."
          },
          "checkIntervalMinutes": {
            "type": "integer",
            "description": "How often the page is re-checked, in minutes (typically 360)."
          },
          "lastCheckedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When this page was last checked, successfully or not. Null if never checked."
          },
          "lastStatus": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "ok",
              "error",
              null
            ],
            "description": "Outcome of the most recent check. \"error\" means the most recent attempt failed, so the returned pricing may be older than `lastCheckedAt` suggests — use `snapshotAt` for true data age."
          },
          "lastError": {
            "type": [
              "string",
              "null"
            ],
            "description": "Generic failure indicator (\"Check failed\") when the last check errored, otherwise null. Diagnostic detail is deliberately not exposed."
          },
          "lastChangeAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When this monitor last recorded a detected change, or null when nothing has changed since monitoring began. Free: it tells you whether the paid changes endpoint has anything to sell you, and whether it has anything new since you last paid. The change records themselves — which plan, what moved, before and after — are the paid endpoint."
          }
        },
        "required": [
          "id",
          "slug",
          "name",
          "url",
          "enabled",
          "checkIntervalMinutes",
          "lastCheckedAt",
          "lastStatus",
          "lastError",
          "lastChangeAt"
        ]
      },
      "MonitorList": {
        "type": "object",
        "description": "Every monitor known to the service, sorted by slug.",
        "properties": {
          "monitors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MonitorSummary"
            }
          }
        },
        "required": [
          "monitors"
        ]
      },
      "PricingPlan": {
        "type": "object",
        "description": "One plan or priced option extracted from the pricing page.",
        "properties": {
          "planName": {
            "type": "string",
            "description": "Plan name as presented on the page, e.g. \"Business\"."
          },
          "price": {
            "type": "string",
            "description": "Numeric amount as a string (e.g. \"20\", \"0\", \"0.0000166667\"), \"custom\" for quote-based pricing, or \"usage\" for a metered pay-as-you-go option the page names without stating an amount. Kept as a string so precision and leading zeros survive intact."
          },
          "currency": {
            "type": "string",
            "description": "ISO currency code of `price`, e.g. \"USD\"."
          },
          "billingPeriod": {
            "type": "string",
            "description": "\"month\" / \"year\" / \"one-time\" for fixed plans; \"usage\" covers metered pricing (per hour, per GB, per request); \"custom\" covers quote-based plans; \"unknown\" when the page states a price without a period (common for free tiers).",
            "enum": [
              "month",
              "year",
              "one-time",
              "usage",
              "custom",
              "unknown"
            ]
          },
          "priceText": {
            "type": "string",
            "description": "The raw price string exactly as displayed on the page, e.g. \"$16 per user/month\". Use this when you need a source-faithful quote."
          },
          "features": {
            "type": "array",
            "description": "Feature bullets extracted for this plan, in page order. May be empty when a page lists no per-plan features.",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "planName",
          "price",
          "currency",
          "billingPeriod",
          "priceText",
          "features"
        ]
      },
      "PricingData": {
        "type": "object",
        "description": "The structured pricing extracted from one page snapshot.",
        "properties": {
          "plans": {
            "type": "array",
            "description": "Every plan or priced option found, in page order.",
            "items": {
              "$ref": "#/components/schemas/PricingPlan"
            }
          },
          "currency": {
            "type": [
              "string",
              "null"
            ],
            "description": "Page-level currency: the code the page states globally, or, when it states none, USD if every plan naming a currency uses it. Null when a snapshot mixes currencies or names none at all."
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "Page-level caveats worth knowing (e.g. annual-billing discounts), when present."
          }
        },
        "required": [
          "plans",
          "currency",
          "notes"
        ]
      },
      "MonitorState": {
        "type": "object",
        "description": "A monitor plus its most recent pricing snapshot — the payload of the paid state endpoint.",
        "properties": {
          "monitor": {
            "$ref": "#/components/schemas/MonitorSummary"
          },
          "pricing": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/PricingData"
              },
              {
                "type": "null"
              }
            ],
            "description": "Latest extracted pricing, or null if no successful check yet."
          },
          "snapshotAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the returned snapshot was captured — the true age of this data. Null if no successful check yet."
          }
        },
        "required": [
          "monitor",
          "pricing",
          "snapshotAt"
        ]
      },
      "ChangeDetail": {
        "type": "object",
        "description": "One specific difference detected between two snapshots.",
        "properties": {
          "kind": {
            "type": "string",
            "description": "What kind of movement this is: a plan appearing or disappearing, a price change, or a billing period change.\n\n`features_changed` appears only in records detected before feature churn was withdrawn from change detection — a page can reword a bullet without changing what it sells, so those records were noise. The value stays in this enum because historical records still carry it; nothing new is written with it.",
            "enum": [
              "plan_added",
              "plan_removed",
              "price_changed",
              "billing_period_changed",
              "features_changed"
            ]
          },
          "planName": {
            "type": "string",
            "description": "The plan this detail refers to."
          },
          "before": {
            "type": [
              "string",
              "null"
            ],
            "description": "Previous value; null when the plan did not exist before. For feature changes this is a count summary, e.g. \"7 features\"."
          },
          "after": {
            "type": [
              "string",
              "null"
            ],
            "description": "New value; null when the plan was removed."
          }
        },
        "required": [
          "kind",
          "planName",
          "before",
          "after"
        ]
      },
      "Change": {
        "type": "object",
        "description": "One detected change event, with its plan-level detail.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Stable identifier for this change record."
          },
          "summary": {
            "type": "string",
            "description": "Human/agent-readable one-line summary of everything in this change, suitable for surfacing directly in an alert."
          },
          "details": {
            "type": "array",
            "description": "Structured before/after detail, one entry per difference.",
            "items": {
              "$ref": "#/components/schemas/ChangeDetail"
            }
          },
          "detectedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the change was detected (the check that observed it), not necessarily when the vendor published it."
          }
        },
        "required": [
          "id",
          "summary",
          "details",
          "detectedAt"
        ]
      },
      "ChangeList": {
        "type": "object",
        "description": "Detected changes for one monitor, most recent first — the payload of the paid changes endpoint.\n\nSelf-describing: a caller can tell from the response alone how much history exists, how much of it this page holds, and how to reach the rest, without paying a second time to find out.",
        "properties": {
          "monitorSlug": {
            "type": "string",
            "description": "The monitor these changes belong to."
          },
          "changes": {
            "type": "array",
            "description": "Change records, newest first, capped by `limit`. Empty when nothing has changed since monitoring began.",
            "items": {
              "$ref": "#/components/schemas/Change"
            }
          },
          "limit": {
            "type": "integer",
            "description": "The page size actually applied, whether you asked for one or took the default. Echoed so a truncated page is never mistaken for the whole history."
          },
          "total": {
            "type": "integer",
            "description": "Total change records stored for this monitor, independent of `limit` and of any cursor. `changes.length` under `total` means there is more history behind this page."
          },
          "hasMore": {
            "type": "boolean",
            "description": "Whether records older than the last one on this page exist. False on the final page."
          },
          "nextBefore": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cursor for the next (older) page — pass it back as `before`. Null when this is the final page."
          }
        },
        "required": [
          "monitorSlug",
          "changes",
          "limit",
          "total",
          "hasMore",
          "nextBefore"
        ]
      }
    }
  }
}
