{
  "openapi": "3.1.0",
  "info": {
    "title": "Isocast API",
    "version": "1.1.0",
    "summary": "Per-signal Polymarket weather-market bucket-transition signals for AI agents.",
    "x-guidance": "Start with GET /v1/cities to pick a city slug. Cheapest paid read: GET /v1/spot?city={slug} buys a $0.01 snapshot of the city's LATEST signal — payable by a STOCK x402 client (plain @x402/fetch, no binding nonce); it never returns history. For a running feed: POST /v1/subscribe?city={slug}&count={n} buys the next n signals via x402 (USDC on Base, eip155:8453); the EIP-3009 authorization nonce MUST bind (payer, citySlug, count) using the salt served in the 402 body's extra.binding — a random nonce is rejected. Then read with GET /v1/signals?city={slug} using the receiptToken from subscribe as a Bearer token (or pay inline). Free, no auth: /v1/cities, /v1/cities/{slug}, /v1/sample, /v1/signals/meta, /terms.json, /health.",
    "description": "Agents pay per-signal bundles via x402 and get pushed (Telegram) the instant a city's daily-high temperature crosses into a new Polymarket bucket — market URL, old/new reading, and live odds for every bucket. 37 cities. Free endpoints (cities, sample, pricing meta, terms) need no auth; paid endpoints settle USDC on Base via x402. Every JSON object response carries a legal `disclaimer` field: informational data only, not financial or betting advice.",
    "termsOfService": "https://isocast.dev/terms",
    "contact": {
      "name": "Isocast",
      "url": "https://isocast.dev",
      "email": "jcislo918@gmail.com"
    },
    "license": {
      "name": "Proprietary — signals licensed for your own use; resale/redistribution not permitted"
    }
  },
  "x-agentcash-provenance": {
    "ownershipProofs": [
      "x402-ownership|origin=https://api.isocast.dev|wallet=0x44b16952484B6dbEa40EDe12927ee03A5Ea6cDd3|issued=2026-07-15|sig=0xda633061902e5cd3f65adfce7c943a3d86ba4a29f29b44d97d0c83c863a14971299bb665b940af5fc309f03931a6ee771eb6689a21d5d193acb83161db29ee981c"
    ]
  },
  "x-discovery": {
    "ownershipProofs": [
      "x402-ownership|origin=https://api.isocast.dev|wallet=0x44b16952484B6dbEa40EDe12927ee03A5Ea6cDd3|issued=2026-07-15|sig=0xda633061902e5cd3f65adfce7c943a3d86ba4a29f29b44d97d0c83c863a14971299bb665b940af5fc309f03931a6ee771eb6689a21d5d193acb83161db29ee981c"
    ]
  },
  "servers": [
    {
      "url": "https://api.isocast.dev",
      "description": "Isocast public edge"
    }
  ],
  "externalDocs": {
    "description": "Agent-facing reference (llms.txt)",
    "url": "https://api.isocast.dev/llms.txt"
  },
  "tags": [
    {
      "name": "free",
      "description": "No auth, no payment. Every JSON object response carries a `disclaimer`."
    },
    {
      "name": "delivery",
      "description": "Free EIP-712-signed Telegram push registration (no payment)."
    },
    {
      "name": "paid",
      "description": "x402 bundle-prepay + entitlement-gated signal reads (USDC on Base, eip155:8453)."
    }
  ],
  "paths": {
    "/": {
      "get": {
        "tags": [
          "free"
        ],
        "operationId": "getDocsIndex",
        "summary": "Docs index",
        "description": "Human HTML docs when Accept includes text/html; otherwise the same JSON index as /discovery.",
        "security": [],
        "responses": {
          "200": {
            "description": "Docs index — HTML for browsers, the /discovery JSON otherwise.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              },
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "tags": [
          "free"
        ],
        "operationId": "getHealth",
        "summary": "Liveness + configured payment mode",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is up.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "service": {
                      "type": "string",
                      "const": "isocast-api"
                    },
                    "mode": {
                      "type": "string",
                      "enum": [
                        "live",
                        "mock"
                      ],
                      "description": "x402 payment plane mode"
                    },
                    "ofac": {
                      "type": "object",
                      "description": "Sanctions-screening status (list loaded / extra configured)"
                    },
                    "ts": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "disclaimer": {
                      "$ref": "#/components/schemas/Disclaimer"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/cities": {
      "get": {
        "tags": [
          "free"
        ],
        "operationId": "listCities",
        "summary": "List all active cities (30s server cache)",
        "security": [],
        "responses": {
          "200": {
            "description": "Active cities.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "cities": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/City"
                      }
                    },
                    "count": {
                      "type": "integer"
                    },
                    "disclaimer": {
                      "$ref": "#/components/schemas/Disclaimer"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/cities/{slug}": {
      "get": {
        "tags": [
          "free"
        ],
        "operationId": "getCity",
        "summary": "One city + today's target market day and Polymarket event URL",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "City slug, e.g. `istanbul`, `nyc`, `los-angeles`."
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "City detail.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "city": {
                      "$ref": "#/components/schemas/City"
                    },
                    "targetDate": {
                      "type": "string",
                      "description": "City-local market day, YYYY-MM-DD."
                    },
                    "marketUrl": {
                      "type": "string",
                      "format": "uri"
                    },
                    "disclaimer": {
                      "$ref": "#/components/schemas/Disclaimer"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/UnknownCity"
          }
        }
      }
    },
    "/v1/sample": {
      "get": {
        "tags": [
          "free"
        ],
        "operationId": "getSample",
        "summary": "Promo sample signal (fixed seq-1, never the latest)",
        "description": "Returns the requested city's seq-1 signal as a shape demo, or — when that city has no servable sample yet — a REAL past signal from another city, labeled via `sampleNote` with its city and date. Never returns fabricated data and never reveals any city's latest signal. Marked `sample: true`.",
        "parameters": [
          {
            "name": "city",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "City slug. Omitted or unknown → a real past signal from another city (see sampleNote)."
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "A sample signal.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/IsocastSignalPublic"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "sample": {
                          "type": "boolean",
                          "const": true
                        },
                        "sampleNote": {
                          "type": "string",
                          "description": "Present on fallback: which real city/date this sample came from."
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/signals/meta": {
      "get": {
        "tags": [
          "free"
        ],
        "operationId": "getSignalsMeta",
        "summary": "Pricing + payment metadata (never returns signal rows)",
        "parameters": [
          {
            "name": "city",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional city slug — echoes `city` + `latestSeq` alongside pricing."
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Pricing metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignalsMeta"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/UnknownCity"
          }
        }
      }
    },
    "/terms.txt": {
      "get": {
        "tags": [
          "free"
        ],
        "operationId": "getTermsTxt",
        "summary": "Terms of Service (plain text)",
        "security": [],
        "responses": {
          "200": {
            "description": "Terms as text/plain. Sets `X-Terms-URL` header.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/terms.json": {
      "get": {
        "tags": [
          "free"
        ],
        "operationId": "getTermsJson",
        "summary": "Terms of Service (structured)",
        "security": [],
        "responses": {
          "200": {
            "description": "Terms as JSON. Sets `X-Terms-URL` header.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "version": {
                      "type": "string"
                    },
                    "effectiveDate": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "summary": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "disclaimer": {
                      "$ref": "#/components/schemas/Disclaimer"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/hexanon": {
      "get": {
        "tags": [
          "free"
        ],
        "operationId": "getHexanonFamily",
        "summary": "Hexanon family catalog",
        "description": "Catalog of all products in the Hexanon family (x402 data & intelligence APIs for AI agents) — names, taglines, API base URLs, docs, MCP packages. Same catalog on every Hexanon product; canonical copy at https://api.moltalyzer.xyz/.well-known/hexanon.",
        "security": [],
        "responses": {
          "200": {
            "description": "The Hexanon family catalog.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "family": {
                      "type": "string",
                      "const": "Hexanon"
                    },
                    "products": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/delivery/telegram": {
      "post": {
        "tags": [
          "delivery"
        ],
        "operationId": "registerTelegramDelivery",
        "summary": "Register/change/delete the Telegram chat that receives your entitled signals (FREE, wallet-signed)",
        "description": "Authorised by an EIP-712 signature that must recover to the claimed `wallet`. No payment; never touches entitlement. On `set`, a Telegram test message is sent BEFORE persisting — an unreachable chat returns 502. An empty `citySlug` means all cities (stored as null). Bot: @IsoCast_bot (https://t.me/IsoCast_bot) — DM /start, or add it to a group/channel and send /id, to obtain the chat_id. Telegram requires the user to press Start before a bot can message them; channels need the bot as admin with post permission. A later blocked/removed bot deactivates the delivery — re-register.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TelegramDeliveryRequest"
              }
            }
          }
        },
        "security": [],
        "responses": {
          "200": {
            "description": "Delivery configured / deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "wallet": {
                      "type": "string"
                    },
                    "chatId": {
                      "type": "string"
                    },
                    "citySlug": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "action": {
                      "type": "string",
                      "enum": [
                        "set",
                        "delete"
                      ]
                    },
                    "disclaimer": {
                      "$ref": "#/components/schemas/Disclaimer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Shape validation failed (invalid wallet, bad action, missing chatId/signature/timestamp)."
          },
          "401": {
            "description": "Stale timestamp (>600s skew) or signature verification failed."
          },
          "404": {
            "description": "Unknown city, or nothing to delete."
          },
          "502": {
            "description": "Telegram test message failed (chat unreachable / bot not configured)."
          }
        }
      }
    },
    "/v1/spot": {
      "get": {
        "tags": [
          "paid"
        ],
        "operationId": "getSpot",
        "summary": "Buy a snapshot of a city's LATEST signal (stock x402, $0.01)",
        "x-payment-info": {
          "price": {
            "mode": "fixed",
            "amount": "0.01",
            "currency": "USD"
          },
          "protocols": [
            {
              "x402": {}
            }
          ]
        },
        "description": "Single-signal SPOT read — a snapshot of the city's LATEST observed signal, like a market-data quote. Payable by a STOCK x402 client (plain @x402/fetch): the payment carries NO binding nonce. Compute-first-settle-after: an unknown/missing city (400) or a city with no signal yet (404) is NOT charged. NEVER returns history or more than one signal. Preview the signal shape for free via GET /v1/cities and GET /v1/signals/meta.",
        "parameters": [
          {
            "name": "city",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "City slug (see GET /v1/cities), e.g. `istanbul`, `nyc`, `los-angeles`."
          }
        ],
        "security": [
          {
            "x402": []
          }
        ],
        "responses": {
          "200": {
            "description": "The city's latest signal snapshot (exactly one signal).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SpotResult"
                }
              }
            }
          },
          "400": {
            "description": "Unknown or missing city — not charged.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "unknown city — see GET /v1/cities"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "description": "Payer wallet is on a sanctions list; payment refused (never settled)."
          },
          "404": {
            "description": "No signals observed yet for this city — not charged.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "no signals observed yet for this city"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/subscribe": {
      "post": {
        "tags": [
          "paid"
        ],
        "operationId": "subscribe",
        "summary": "Buy the next N signals for a city (x402 bundle prepay)",
        "x-payment-info": {
          "price": {
            "mode": "dynamic",
            "currency": "USD",
            "min": "0.01",
            "max": "7.00"
          },
          "protocols": [
            {
              "x402": {}
            }
          ]
        },
        "description": "Without a payment credential returns a 402 challenge (see PaymentRequired). With a valid x402 payment: verify → OFAC screen → nonce-binding check → settle → record payment → apply entitlement atomically. The authorization `nonce` MUST bind (payer, citySlug, count) using the salt served in the 402 `extra.binding`; a mismatch is refused before settlement. Top-up semantics: paidThroughSeq becomes max(current, latestSeq) + count. No expiry. Idempotent per settlement txHash.",
        "parameters": [
          {
            "name": "city",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "count",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 2
            },
            "description": "Number of signals to buy (integer ≥ 2, and total ≥ 0.01 USDC min spend)."
          }
        ],
        "security": [
          {
            "x402": []
          }
        ],
        "responses": {
          "200": {
            "description": "Purchase applied (or idempotent replay).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscribeResult"
                }
              }
            }
          },
          "400": {
            "description": "count < 2 / non-integer, or below the 0.01 USDC minimum spend.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "minSpendUsd": {
                      "type": "number"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "description": "Payer wallet is on a sanctions list; payment refused (never settled)."
          },
          "404": {
            "$ref": "#/components/responses/UnknownCity"
          },
          "502": {
            "description": "Charged but entitlement update failed → refund queued. Body `{ error, refund: 'queued' }`."
          },
          "503": {
            "description": "Sanctions screening unavailable — fail-closed, nothing settled."
          }
        }
      }
    },
    "/v1/signals": {
      "get": {
        "tags": [
          "paid"
        ],
        "operationId": "getSignals",
        "summary": "Read the signals you are entitled to (Bearer receipt or x402 payment)",
        "x-payment-info": {
          "price": {
            "mode": "dynamic",
            "currency": "USD",
            "min": "0.01",
            "max": "7.00"
          },
          "protocols": [
            {
              "x402": {}
            }
          ]
        },
        "description": "Authenticate with a Bearer receipt token from a prior /v1/subscribe, OR present an x402 payment (runs the full subscribe flow, then reads). Returns signals with since < seq ≤ paidThroughSeq, ascending, capped at 100/page. Never returns seq > paidThroughSeq. Returns 402 on exhaustion (since ≥ paidThroughSeq), with `paid_through` and `latest` echoed so you know how many more to buy.",
        "parameters": [
          {
            "name": "city",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Return signals with seq strictly greater than this (cursor)."
          },
          {
            "name": "count",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 2,
              "default": 20
            },
            "description": "Bundle size proposed in the 402 challenge if payment is required."
          }
        ],
        "security": [
          {
            "receiptToken": []
          },
          {
            "x402": []
          }
        ],
        "responses": {
          "200": {
            "description": "Entitled signals page.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "signals": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/IsocastSignalPublic"
                      }
                    },
                    "next_cursor": {
                      "type": "integer",
                      "description": "Pass as `since` to page forward."
                    },
                    "paid_through": {
                      "type": "integer"
                    },
                    "latest": {
                      "type": "integer"
                    },
                    "more": {
                      "type": "boolean"
                    },
                    "disclaimer": {
                      "$ref": "#/components/schemas/Disclaimer"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "description": "Payer wallet sanctioned; payment refused."
          },
          "404": {
            "$ref": "#/components/responses/UnknownCity"
          }
        }
      }
    }
  },
  "components": {
    "responses": {
      "UnknownCity": {
        "description": "Unknown or inactive city.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string",
                  "const": "unknown city"
                },
                "disclaimer": {
                  "$ref": "#/components/schemas/Disclaimer"
                }
              }
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "x402 V2 payment challenge. Construct the EIP-3009 authorization nonce from `extra.binding` before paying; amounts in `accepts[]` are atomic units.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PaymentRequired"
            }
          }
        }
      }
    },
    "securitySchemes": {
      "x402": {
        "type": "http",
        "scheme": "x402",
        "description": "x402 V2 payment flow. Unpaid paid-route requests get a 402 whose JSON body is the PaymentRequired envelope (x402Version 2, resource, accepts[], extra.binding, terms). Sign an EIP-3009 exact-scheme USDC authorization on Base (eip155:8453) for accepts[0].amount atomic units whose nonce binds (payer, citySlug, count) via extra.binding, then retry with the X-PAYMENT header. Settle-early: verify → OFAC screen → bind → settle → entitle."
      },
      "receiptToken": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "isocast-receipt",
        "description": "Opaque receipt token returned by POST /v1/subscribe. Present as `Authorization: Bearer <token>` to read /v1/signals for the paying wallet without re-paying."
      }
    },
    "schemas": {
      "Disclaimer": {
        "type": "string",
        "description": "Legal disclaimer stamped onto every JSON object response.",
        "example": "Informational data only; not financial or betting advice. No guarantee of accuracy or outcome. User assumes all risk. Prediction markets restricted in some jurisdictions. See https://isocast.dev/terms"
      },
      "City": {
        "type": "object",
        "description": "Public-safe city record.",
        "properties": {
          "slug": {
            "type": "string",
            "example": "istanbul"
          },
          "displayName": {
            "type": "string",
            "example": "Istanbul"
          },
          "country": {
            "type": [
              "string",
              "null"
            ]
          },
          "unit": {
            "type": "string",
            "description": "Temperature unit, e.g. `C` or `F`.",
            "example": "C"
          },
          "bucketWidth": {
            "type": "number",
            "description": "Polymarket bucket width in the city's unit (e.g. 1°C).",
            "example": 1
          },
          "timezone": {
            "type": "string",
            "description": "IANA timezone for the city's market day.",
            "example": "Europe/Istanbul"
          },
          "resolutionSource": {
            "type": "string",
            "description": "Weather resolution source.",
            "example": "wu"
          },
          "station": {
            "type": [
              "string",
              "null"
            ]
          },
          "latestSeq": {
            "type": "integer",
            "description": "Highest published signal seq for this city."
          }
        }
      },
      "IsocastSignalPublic": {
        "type": "object",
        "description": "A bucket-transition signal: a city's daily-high crossed from `old.bucket` into `new.bucket`.",
        "properties": {
          "city": {
            "type": "string",
            "example": "istanbul"
          },
          "displayName": {
            "type": "string",
            "example": "Istanbul"
          },
          "seq": {
            "type": "integer",
            "description": "Monotonic per-city sequence (1-based).",
            "example": 1
          },
          "targetDate": {
            "type": "string",
            "description": "City-local market day, YYYY-MM-DD.",
            "example": "2026-07-26"
          },
          "marketUrl": {
            "type": "string",
            "format": "uri",
            "example": "https://polymarket.com/event/highest-temperature-in-istanbul-on-july-26-2026"
          },
          "conditionId": {
            "type": "string",
            "description": "Polymarket event/condition id (0x…)."
          },
          "old": {
            "$ref": "#/components/schemas/Reading"
          },
          "new": {
            "$ref": "#/components/schemas/Reading"
          },
          "buckets": {
            "type": [
              "array",
              "null"
            ],
            "description": "Live odds for every bucket at bucketsAsOf.",
            "items": {
              "$ref": "#/components/schemas/Bucket"
            }
          },
          "bucketsAsOf": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When bucket odds were sampled (freshness ≤ ~10s of the signal)."
          },
          "source": {
            "type": "string",
            "description": "Observation source, e.g. `wu`.",
            "example": "wu"
          },
          "observedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Reading": {
        "type": "object",
        "properties": {
          "reading": {
            "type": "number",
            "description": "Daily-high temperature reading.",
            "example": 30.1
          },
          "unit": {
            "type": "string",
            "example": "C"
          },
          "bucket": {
            "type": [
              "string",
              "null"
            ],
            "description": "Polymarket bucket label.",
            "example": "29-30°C"
          }
        }
      },
      "Bucket": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "example": "29-30°C"
          },
          "conditionId": {
            "type": "string"
          },
          "yes": {
            "type": "number",
            "description": "YES price (0–1).",
            "example": 0.55
          },
          "no": {
            "type": "number",
            "description": "NO price (0–1).",
            "example": 0.45
          },
          "midpoint": {
            "type": "number",
            "description": "Order-book midpoint (0–1).",
            "example": 0.55
          },
          "dead": {
            "type": "boolean",
            "description": "Present+true when the bucket is no longer reachable given the current reading."
          }
        }
      },
      "SignalsMeta": {
        "type": "object",
        "properties": {
          "city": {
            "type": "string",
            "description": "Only present when `?city` was supplied."
          },
          "latestSeq": {
            "type": "integer",
            "description": "Only present when `?city` was supplied."
          },
          "unitPriceUsd": {
            "type": "number",
            "example": 0.005
          },
          "minSpendUsd": {
            "type": "number",
            "example": 0.01
          },
          "currency": {
            "type": "string",
            "const": "USDC"
          },
          "network": {
            "type": "string",
            "const": "eip155:8453"
          },
          "payTo": {
            "type": [
              "string",
              "null"
            ],
            "description": "Settlement wallet (or null if unset)."
          },
          "tiers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Tier"
            }
          },
          "disclaimer": {
            "$ref": "#/components/schemas/Disclaimer"
          }
        }
      },
      "Tier": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "example": "Standard"
          },
          "minCount": {
            "type": "integer",
            "example": 100
          },
          "unitPriceUsd": {
            "type": "number",
            "example": 0.0045
          },
          "totalUsd": {
            "type": "number",
            "example": 0.45
          }
        }
      },
      "TelegramDeliveryRequest": {
        "type": "object",
        "required": [
          "wallet",
          "action",
          "signature",
          "timestamp"
        ],
        "description": "EIP-712 typed-data. Domain: {name:'Isocast', version:'1', chainId:8453}. primaryType: TelegramDelivery. Types.TelegramDelivery = [{wallet:address},{chatId:string},{citySlug:string},{action:string},{timestamp:uint256}]. The signature must recover to `wallet`.",
        "properties": {
          "wallet": {
            "type": "string",
            "pattern": "^0x[0-9a-fA-F]{40}$"
          },
          "chatId": {
            "type": "string",
            "description": "Telegram chat id. Required when action='set'. Obtain it by sending /start to @IsoCast_bot (private: positive integer), or /id after adding the bot to a group (negative integer) or channel (-100…; a public channel's @handle is also accepted)."
          },
          "citySlug": {
            "type": "string",
            "description": "City slug, or empty string for all cities."
          },
          "action": {
            "type": "string",
            "enum": [
              "set",
              "delete"
            ]
          },
          "signature": {
            "type": "string",
            "description": "EIP-712 signature (0x…)."
          },
          "timestamp": {
            "type": "integer",
            "description": "Unix seconds; must be within 600s of server time."
          }
        }
      },
      "SubscribeResult": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "city": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          },
          "unitPriceUsd": {
            "type": "number"
          },
          "amountUsd": {
            "type": "number"
          },
          "latestSeq": {
            "type": "integer"
          },
          "paidThroughSeq": {
            "type": "integer"
          },
          "receiptToken": {
            "type": "string",
            "description": "Bearer token for reading /v1/signals without re-paying."
          },
          "txHash": {
            "type": [
              "string",
              "null"
            ]
          },
          "idempotent": {
            "type": "boolean",
            "description": "true when this settlement was already applied."
          },
          "disclaimer": {
            "$ref": "#/components/schemas/Disclaimer"
          }
        }
      },
      "SpotResult": {
        "type": "object",
        "description": "A single-signal SPOT snapshot: the city's LATEST observed signal. Never history, never more than one signal.",
        "properties": {
          "city": {
            "type": "string",
            "example": "istanbul"
          },
          "displayName": {
            "type": "string",
            "example": "Istanbul"
          },
          "unit": {
            "type": "string",
            "example": "C"
          },
          "latest": {
            "$ref": "#/components/schemas/IsocastSignalPublic"
          },
          "latestSeq": {
            "type": "integer",
            "description": "The seq of the returned latest signal."
          },
          "observedAt": {
            "type": "string",
            "format": "date-time"
          },
          "charged": {
            "type": "boolean",
            "const": true,
            "description": "Always true on a 200 (settlement succeeded before delivery)."
          },
          "payment": {
            "type": "object",
            "description": "Settlement facts injected by the shared paid-route handler.",
            "properties": {
              "txHash": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "network": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "priceUsd": {
                "type": "number",
                "example": 0.01
              },
              "payer": {
                "type": "string"
              }
            }
          },
          "disclaimer": {
            "$ref": "#/components/schemas/Disclaimer"
          }
        }
      },
      "PaymentRequired": {
        "type": "object",
        "description": "x402 V2 PaymentRequired envelope — matches the live 402 body byte-for-byte in structure.",
        "properties": {
          "x402Version": {
            "type": "integer",
            "const": 2
          },
          "error": {
            "type": "string",
            "const": "Payment required"
          },
          "resource": {
            "type": "object",
            "properties": {
              "url": {
                "type": "string",
                "format": "uri",
                "description": "The requested resource URL (echoed)."
              },
              "description": {
                "type": "string"
              },
              "mimeType": {
                "type": "string",
                "const": "application/json"
              }
            }
          },
          "accepts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "scheme": {
                  "type": "string",
                  "const": "exact"
                },
                "network": {
                  "type": "string",
                  "description": "CAIP-2 network id.",
                  "example": "eip155:8453"
                },
                "amount": {
                  "type": "string",
                  "description": "Amount in the asset's atomic units (USDC has 6 decimals: $0.10 = \"100000\")."
                },
                "asset": {
                  "type": "string",
                  "description": "Payment token contract address (USDC on Base).",
                  "example": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
                },
                "payTo": {
                  "type": "string",
                  "description": "Settlement wallet address."
                },
                "maxTimeoutSeconds": {
                  "type": "integer",
                  "example": 300
                },
                "extra": {
                  "type": "object",
                  "description": "Token metadata, e.g. { name: 'USD Coin', version: '2' }."
                }
              }
            }
          },
          "extra": {
            "type": "object",
            "properties": {
              "binding": {
                "type": "object",
                "description": "Anti-grief binding. EVM: EIP-3009 authorization nonce = keccak256(abi.encodePacked(payer, citySlug, uint256 count, bytes32 salt)) using the served salt. Solana: put the derived memo string in an SPL Memo instruction inside the signed transfer.",
                "properties": {
                  "formula": {
                    "type": "string",
                    "const": "keccak256(abi.encodePacked(payer, citySlug, uint256 count, bytes32 salt))"
                  },
                  "citySlug": {
                    "type": "string"
                  },
                  "count": {
                    "type": "integer"
                  },
                  "salt": {
                    "type": "string",
                    "description": "bytes32 salt (0x…) — required to compute the nonce; not derivable client-side."
                  },
                  "svmMemoFormula": {
                    "type": "string"
                  },
                  "svmNote": {
                    "type": "string"
                  },
                  "mppMemoFormula": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "terms": {
            "type": "string",
            "format": "uri",
            "const": "https://isocast.dev/terms"
          },
          "paid_through": {
            "type": "integer",
            "description": "Only on /v1/signals exhaustion — your current entitlement ceiling."
          },
          "latest": {
            "type": "integer",
            "description": "Only on /v1/signals exhaustion — the city's latest seq."
          },
          "disclaimer": {
            "$ref": "#/components/schemas/Disclaimer"
          }
        }
      }
    }
  }
}
