{
  "openapi": "3.1.0",
  "info": {
    "title": "Predictonomy v1 API",
    "version": "1.0.0",
    "description": "Read-only JSON API for economic indicators, countries, macro-health, before-consensus signals and derived macro analysis (sentiment, regime, correlation, country briefs). Every value conforms to the typed response envelope published at https://predictonomy.com/api/v1/schema/envelope.",
    "license": {
      "name": "Research and analysis only — not investment advice."
    }
  },
  "servers": [
    {
      "url": "https://predictonomy.com",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Catalogue",
      "description": "Indicators, countries and the signal feed. Requires a Pro-plan API key."
    },
    {
      "name": "Analysis",
      "description": "Derived macro analysis — sentiment, regime, correlation, brief and the ticker resolver. Open, rate-limited per IP."
    },
    {
      "name": "Markets",
      "description": "Markets-axis values (FX today). Open, rate-limited per IP."
    },
    {
      "name": "Data",
      "description": "Bulk multi-series slices — the Data Workbench API. Any indicators × countries × year range in one call (JSON envelope or CSV)."
    },
    {
      "name": "Meta",
      "description": "Self-describing contract surfaces — the response-envelope schema and this OpenAPI document."
    }
  ],
  "security": [],
  "paths": {
    "/api/v1/indicators": {
      "get": {
        "operationId": "listIndicators",
        "summary": "List all live-backed indicators.",
        "description": "Catalogue of every indicator with a live data backing — id, name, category, unit and source. Use the ids here to query the detail, country and analysis routes.",
        "tags": [
          "Catalogue"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataApiResult"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/indicators/{key}": {
      "get": {
        "operationId": "getIndicator",
        "summary": "Global history + forecast for one indicator.",
        "description": "Latest global observation, full history and forecast for a single indicator. A known-but-uncovered indicator returns a typed `not_covered` envelope (200), not a 404.",
        "tags": [
          "Catalogue"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataApiResult"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "description": "Indicator id, e.g. `gdp_growth_real`.",
            "schema": {
              "type": "string",
              "example": "gdp_growth_real"
            }
          }
        ]
      }
    },
    "/api/v1/countries": {
      "get": {
        "operationId": "listCountries",
        "summary": "List countries with headline metrics.",
        "description": "Every covered country with its headline economic metrics. Add `?include=aggregates` to include aggregate/region rows.",
        "tags": [
          "Catalogue"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataApiResult"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Pass `aggregates` to include aggregate/region entities (default: countries only).",
            "schema": {
              "type": "string",
              "example": "aggregates"
            }
          }
        ]
      }
    },
    "/api/v1/countries/{code}": {
      "get": {
        "operationId": "getCountry",
        "summary": "Country profile + macro-pressure score.",
        "description": "Profile, headline metrics, latest indicator values and the 0–100 macro-pressure composite for one country.",
        "tags": [
          "Catalogue"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataApiResult"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "description": "ISO country code (ISO2 or ISO3).",
            "schema": {
              "type": "string",
              "example": "USA"
            }
          }
        ]
      }
    },
    "/api/v1/signals": {
      "get": {
        "operationId": "listSignals",
        "summary": "The before-consensus signal feed.",
        "description": "The ranked feed of notable macro signals across covered countries and indicators.",
        "tags": [
          "Catalogue"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataApiResult"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/resolve": {
      "get": {
        "operationId": "resolveSymbol",
        "summary": "Resolve a ticker symbol to its macro subject.",
        "description": "Maps an instrument symbol (e.g. `VOD.L`) to its listing-venue country (ISO3) plus the macro sectors you can query for sentiment/regime/brief. Serves no economic numbers — a routing helper. Returns 400 if `symbol` is missing.",
        "tags": [
          "Analysis"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "400": {
            "description": "A required parameter is missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "symbol",
            "in": "query",
            "required": true,
            "description": "Instrument symbol with optional exchange suffix.",
            "schema": {
              "type": "string",
              "example": "VOD.L"
            }
          }
        ]
      }
    },
    "/api/v1/resolve/batch": {
      "get": {
        "operationId": "resolveSymbolBatch",
        "summary": "Resolve many ticker symbols in one call.",
        "description": "Resolves a comma-separated list of instrument symbols to their listing-venue countries in ONE call — `resolutions` carries one per-symbol body (the same mapping the single endpoint serves), with unresolvable symbols listed in `unresolved` so a bad symbol never fails the batch. Serves no economic numbers — a routing helper. Max 25 symbols per request (400 if exceeded); returns 400 if `symbols` is missing.",
        "tags": [
          "Analysis"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "400": {
            "description": "A required parameter is missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "symbols",
            "in": "query",
            "required": true,
            "description": "Comma-separated instrument symbols.",
            "schema": {
              "type": "string",
              "example": "AAPL,VOD.L,7203.T"
            }
          }
        ]
      }
    },
    "/api/v1/sentiment/{domain}/{id}": {
      "get": {
        "operationId": "getSentiment",
        "summary": "Derived macro-sentiment composite.",
        "description": "A `derived` macro-sentiment composite for a country or sector, computed from live macro-pressure series (method + inputs + confidence disclosed). For a country, `?include=` consolidates the sentiment reads into one call: `dimensions` (the 5 macro dimensions), `survey` (the observed OECD Consumer Confidence — SURV-1), `divergence` (soft-vs-hard divergence — DIV-1), `forecast` (forward prediction-engine projections — FCAST-1), and `outlook` (the sentiment composite PROJECTED to a horizon from the baseline forecasts — a projection, not observed). Comma-separate to combine; survey/divergence omit honestly when no OECD CCI is held, forecast/outlook when no projections.",
        "tags": [
          "Analysis"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "description": "`country` or `sector`.",
            "schema": {
              "type": "string",
              "example": "country"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ISO3 country code or sector slug.",
            "schema": {
              "type": "string",
              "example": "USA"
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Country only (comma-separated): `dimensions` (5 macro dimensions), `survey` (observed OECD CCI), `divergence` (soft-vs-hard), `forecast` (forward projections), `outlook` (forward sentiment projection).",
            "schema": {
              "type": "string",
              "example": "dimensions,outlook"
            }
          },
          {
            "name": "geo",
            "in": "query",
            "required": false,
            "description": "Sector only: the geography to score (default `GBR`).",
            "schema": {
              "type": "string",
              "example": "GBR"
            }
          }
        ]
      }
    },
    "/api/v1/sentiment/{domain}/{id}/history": {
      "get": {
        "operationId": "getSentimentHistory",
        "summary": "Derived macro-sentiment trajectory (history).",
        "description": "A `derived` macro-sentiment trajectory for a country — the sentiment composite recomputed for each year of shared live macro history (so callers can read momentum, not only the latest level), with a span + momentum summary in `data.history`. Country domain only; needs ≥2 years of shared history.",
        "tags": [
          "Analysis"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "description": "`country` (the only supported domain for history).",
            "schema": {
              "type": "string",
              "example": "country"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ISO3 country code.",
            "schema": {
              "type": "string",
              "example": "USA"
            }
          }
        ]
      }
    },
    "/api/v1/sentiment/{domain}/{id}/survey": {
      "get": {
        "operationId": "getSentimentSurvey",
        "summary": "Observed survey sentiment (OECD consumer confidence).",
        "description": "An `observed` survey-sentiment reading for a country — the real ingested OECD Consumer Confidence Index (amplitude-adjusted leading indicator; 100 = long-run trend) with full provenance, freshness and the index series, plus an additive `data.survey` block (stance vs the 100 baseline + signed MoM/3m/YoY momentum + trend). The first non-derived signal in the sentiment product. Country domain only; OECD covers ~41 economies, so an uncovered country resolves to `not_covered`. An optional grounded `aiInterpretation` (§4) narrative is added when Ollama is enabled (fail-safe: omitted on any AI failure; interprets only the observed figures).",
        "tags": [
          "Analysis"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "description": "`country` (the only supported domain for the survey reading).",
            "schema": {
              "type": "string",
              "example": "country"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ISO3 country code.",
            "schema": {
              "type": "string",
              "example": "GBR"
            }
          }
        ]
      }
    },
    "/api/v1/sentiment/{domain}/{id}/divergence": {
      "get": {
        "operationId": "getSentimentDivergence",
        "summary": "Soft-vs-hard sentiment divergence.",
        "description": "A `derived` divergence read for a country — compares the OBSERVED OECD Consumer Confidence (soft, \"how people feel\") against the DERIVED macro-pressure sentiment (hard, \"what the fundamentals say\"), both normalized to −1..+1. The headline `value` is the signed `gap` (soft − hard; >0 = confidence ahead of fundamentals), with an additive `data.divergence` block (soft/hard breakdown + classification aligned/divergent/mixed + direction). Country domain only; needs BOTH the observed CCI (~41 OECD economies) and a derivable macro sentiment, else `not_covered`. An optional grounded `aiInterpretation` (§4) narrative is added when Ollama is enabled (fail-safe: omitted on any AI failure; interprets only the computed figures).",
        "tags": [
          "Analysis"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "description": "`country` (the only supported domain for the divergence read).",
            "schema": {
              "type": "string",
              "example": "country"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ISO3 country code.",
            "schema": {
              "type": "string",
              "example": "USA"
            }
          }
        ]
      }
    },
    "/api/v1/sentiment/countries": {
      "get": {
        "operationId": "getSentimentBatch",
        "summary": "Batch macro-sentiment for many countries.",
        "description": "The `derived` macro-sentiment composite for many countries in ONE call — `data.items` carries one per-country sentiment envelope (the same composite the single endpoint serves), with unresolvable codes listed in `data.omitted` so a bad code never fails the batch. Add `?include=dimensions` to attach each country's 5 macro dimensions. Max 25 codes per request (400 if exceeded); no AI narrative in batch mode — use the single endpoint for that.",
        "tags": [
          "Analysis"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "400": {
            "description": "A required parameter is missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "codes",
            "in": "query",
            "required": true,
            "description": "Comma-separated ISO3 country codes (also accepted as `countries`).",
            "schema": {
              "type": "string",
              "example": "USA,GBR,JPN"
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "`dimensions` adds each country's 5 macro dimensions.",
            "schema": {
              "type": "string",
              "example": "dimensions"
            }
          }
        ]
      }
    },
    "/api/v1/screener/sentiment": {
      "get": {
        "operationId": "getSentimentScreener",
        "summary": "Rank the covered universe by macro stress.",
        "description": "A screener: ranks the WHOLE covered country universe by the `derived` macro-stress composite and returns the top/bottom N in ONE call — unlike the point/batch endpoints, you do NOT supply codes. `data.items` carries the per-country sentiment envelopes (the same composite the single endpoint serves) ordered by rank, and `data.pagination.total` discloses the full covered universe size. Uncovered countries never appear. `order=most` (default) = most stressed first, `order=least` = least stressed first; `limit` default 10, max 25 (clamped). No AI narrative — use the single endpoint for that.",
        "tags": [
          "Analysis"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "`most` (default, most stressed first) or `least`.",
            "schema": {
              "type": "string",
              "example": "most"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "How many to return (default 10, max 25; clamped).",
            "schema": {
              "type": "string",
              "example": "10"
            }
          }
        ]
      }
    },
    "/api/v1/screener/regime": {
      "get": {
        "operationId": "getRegimeScreener",
        "summary": "Rank the covered universe by business-cycle phase.",
        "description": "A screener: ranks the WHOLE covered country universe by a `derived` business-cycle phase-stress ordinal (contraction → slowdown → recovery → expansion) and returns the most RISK_OFF markets in ONE call — unlike the point/batch regime endpoints, you do NOT supply codes. `data.items` carries the per-country regime envelopes (the same read the single endpoint serves) ordered by phase-stress, and `data.pagination.total` discloses the population size. Uncovered countries never appear. `phase` (optional) narrows to one of expansion|slowdown|contraction|recovery (absent/unrecognised → no filter); `limit` default 25, max 100 (clamped). No AI narrative — use the single endpoint for that.",
        "tags": [
          "Analysis"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "phase",
            "in": "query",
            "required": false,
            "description": "Filter to one phase: expansion|slowdown|contraction|recovery (unrecognised → no filter).",
            "schema": {
              "type": "string",
              "example": "contraction"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "How many to return (default 25, max 100; clamped).",
            "schema": {
              "type": "string",
              "example": "25"
            }
          }
        ]
      }
    },
    "/api/v1/regime/country/{iso3}": {
      "get": {
        "operationId": "getRegime",
        "summary": "Macro regime & turning-point detection.",
        "description": "A `derived` business-cycle phase (growth level × momentum) plus momentum-flip / z-score turning points over live macro series, with an optional grounded AI narrative (labelled not-a-forecast).",
        "tags": [
          "Analysis"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "iso3",
            "in": "path",
            "required": true,
            "description": "ISO3 country code.",
            "schema": {
              "type": "string",
              "example": "USA"
            }
          }
        ]
      }
    },
    "/api/v1/regime/countries": {
      "get": {
        "operationId": "getRegimeBatch",
        "summary": "Batch macro regime for many countries.",
        "description": "The `derived` business-cycle regime (phase + turning points) for many countries in ONE call — `data.items` carries one per-country regime envelope (the same read the single endpoint serves), with unresolvable codes listed in `data.omitted` so a bad code never fails the batch. Max 25 codes per request (400 if exceeded); no AI narrative in batch mode — use the single endpoint for that.",
        "tags": [
          "Analysis"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "400": {
            "description": "A required parameter is missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "codes",
            "in": "query",
            "required": true,
            "description": "Comma-separated ISO3 country codes (also accepted as `countries`).",
            "schema": {
              "type": "string",
              "example": "USA,GBR,JPN"
            }
          }
        ]
      }
    },
    "/api/v1/brief/country/{iso3}": {
      "get": {
        "operationId": "getCountryBrief",
        "summary": "Country intelligence brief (one call).",
        "description": "Composes observed macro, derived sentiment, derived regime and the forward forecast (prediction-engine projections) into ONE response — one complete backward+forward snapshot, each a proper typed v1 envelope (a section degrades to `not_covered` when absent), plus a top-line summary. The summary also carries `forecastReliability` for the headline forecast (backtest skill vs naive + the realized track record). Coverage status reflects the three backward core reads; the forecast section is an additive forward enrichment.",
        "tags": [
          "Analysis"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "iso3",
            "in": "path",
            "required": true,
            "description": "ISO3 country code.",
            "schema": {
              "type": "string",
              "example": "USA"
            }
          }
        ]
      }
    },
    "/api/v1/forecast/country/{iso3}": {
      "get": {
        "operationId": "getCountryForecast",
        "summary": "Forward macro projections for one country.",
        "description": "The platform's prediction-engine projections (baseline + optimistic/pessimistic + confidence band per target year) for every macro indicator we forecast, as a typed `derived` envelope. These are PROJECTIONS, not observations (`unit:\"projection_baseline\"`; each projection carries its confidence band, methodology and the engine model version/generation time). Each indicator also carries an `observedAnchor` — its latest REAL observed value (provider-sourced; `null` when none held) so the projection can be read against where the indicator stands today. An optional grounded `aiInterpretation` (§4) narrative is added when Ollama is enabled (fail-safe; interprets only the projections + anchor, never invents). Pass `?include=skill` to attach a `forecastSkill` block — each held indicator backtested out-of-sample (one-step-ahead) so you see how reliable that forecast has actually been: best model, skill vs a naive last-value baseline, typical error and tested years (a track record, not a guarantee). A country with no projections held → `not_covered`.",
        "tags": [
          "Analysis"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "iso3",
            "in": "path",
            "required": true,
            "description": "ISO3 country code.",
            "schema": {
              "type": "string",
              "example": "USA"
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated extras: `skill` (out-of-sample backtest reliability per indicator).",
            "schema": {
              "type": "string",
              "example": "skill"
            }
          }
        ]
      }
    },
    "/api/v1/forecast/reliability": {
      "get": {
        "operationId": "getForecastReliabilityLeaderboard",
        "summary": "Forecast reliability leaderboard (realized track record).",
        "description": "Indicators and countries ranked by their REALIZED forecast error from the rolling accuracy ledger (past predictions vs the actuals that landed; lower MAPE/MAE = more reliable). A track record, not a guarantee; groups with too few matured predictions are omitted. Returns an empty board (never an error) until the ledger is populated.",
        "tags": [
          "Analysis"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataApiResult"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "minEntries",
            "in": "query",
            "required": false,
            "description": "Minimum matured predictions for a group to rank (default 3).",
            "schema": {
              "type": "string",
              "example": "3"
            }
          }
        ]
      }
    },
    "/api/v1/correlation/country/{iso3}": {
      "get": {
        "operationId": "getCountryCorrelation",
        "summary": "Lead-lag correlation between two indicators (one country).",
        "description": "Pearson lead-lag correlation between two macro indicators for one country over shared observed annual history (±3y lag scan, best by |r|). `a`/`b` default to growth × unemployment.",
        "tags": [
          "Analysis"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "iso3",
            "in": "path",
            "required": true,
            "description": "ISO3 country code.",
            "schema": {
              "type": "string",
              "example": "USA"
            }
          },
          {
            "name": "a",
            "in": "query",
            "required": false,
            "description": "First indicator id.",
            "schema": {
              "type": "string",
              "example": "gdp_growth_real"
            }
          },
          {
            "name": "b",
            "in": "query",
            "required": false,
            "description": "Second indicator id.",
            "schema": {
              "type": "string",
              "example": "unemployment_rate"
            }
          }
        ]
      }
    },
    "/api/v1/correlation/cross": {
      "get": {
        "operationId": "getCrossCorrelation",
        "summary": "Cross-country lead-lag correlation.",
        "description": "Pearson cross-correlation between two countries' macro series over shared observed annual history (±3y lag scan, best by |r|; >0 ⇒ `b` leads `a`). `b`’s indicator defaults to `a`’s. Bare route defaults to USA × GBR growth.",
        "tags": [
          "Analysis"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "a",
            "in": "query",
            "required": false,
            "description": "First subject `ISO3[:indicator]`.",
            "schema": {
              "type": "string",
              "example": "USA:gdp_growth_real"
            }
          },
          {
            "name": "b",
            "in": "query",
            "required": false,
            "description": "Second subject `ISO3[:indicator]`.",
            "schema": {
              "type": "string",
              "example": "GBR"
            }
          }
        ]
      }
    },
    "/api/v1/nowcast/country/{iso3}": {
      "get": {
        "operationId": "getCountryNowcast",
        "summary": "Leading-indicator nowcast of one macro indicator.",
        "description": "Nowcast a country's macro indicator from its strongest LEADING series: the indicator that best leads the target (1–3y) over shared observed history is fit by OLS, then projected from the leader's already-observed recent values so the next year or two is inferable today. A `derived` envelope (`unit:\"nowcast_value\"`); correlation, not causation, and a nowcast, not a forecast guarantee. `indicator` defaults to unemployment. No strong-enough leader → `not_covered`.",
        "tags": [
          "Analysis"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "iso3",
            "in": "path",
            "required": true,
            "description": "ISO3 country code.",
            "schema": {
              "type": "string",
              "example": "USA"
            }
          },
          {
            "name": "indicator",
            "in": "query",
            "required": false,
            "description": "Target indicator id to nowcast.",
            "schema": {
              "type": "string",
              "example": "unemployment_rate"
            }
          }
        ]
      }
    },
    "/api/v1/markets/latest": {
      "get": {
        "operationId": "getMarketsLatest",
        "summary": "Latest markets values (FX).",
        "description": "Latest value for each live markets indicator (region GLOBAL), as typed `observed` envelopes with provenance + freshness.",
        "tags": [
          "Markets"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/markets/series": {
      "get": {
        "operationId": "getMarketsSeries",
        "summary": "Full history for one markets indicator.",
        "description": "Full history for one markets indicator (latest as headline, history in `data.series`). Returns 400 if `indicator` is missing.",
        "tags": [
          "Markets"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "400": {
            "description": "A required parameter is missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "indicator",
            "in": "query",
            "required": true,
            "description": "Markets indicator id.",
            "schema": {
              "type": "string",
              "example": "fx_eur_usd"
            }
          }
        ]
      }
    },
    "/api/v1/data": {
      "get": {
        "operationId": "getMultiSeriesData",
        "summary": "Bulk multi-series slice: indicators × countries × year range in one call.",
        "description": "Any indicators (≤10) × any countries (≤20) × a year range (≤60y) in one call. Per (indicator, country, year) the highest-priority provider wins; unknown indicator ids are reported in `data.omitted`, never a 404. `format=csv` returns the same slice as text/csv.",
        "tags": [
          "Data"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "400": {
            "description": "A required parameter is missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "indicators",
            "in": "query",
            "required": true,
            "description": "Comma-separated economic indicator ids (max 10).",
            "schema": {
              "type": "string",
              "example": "gdp_real_growth,inflation_cpi_annual"
            }
          },
          {
            "name": "countries",
            "in": "query",
            "required": true,
            "description": "Comma-separated ISO3 country codes (max 20).",
            "schema": {
              "type": "string",
              "example": "USA,GBR,JPN"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Start year (default: to − 20).",
            "schema": {
              "type": "string",
              "example": "2010"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "End year (default: current year + 5).",
            "schema": {
              "type": "string",
              "example": "2030"
            }
          },
          {
            "name": "forecast",
            "in": "query",
            "required": false,
            "description": "Include forecast rows (default true; `false` = observed only).",
            "schema": {
              "type": "string",
              "example": "true"
            }
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "`json` (default, envelope) or `csv` (flat rows).",
            "schema": {
              "type": "string",
              "example": "csv"
            }
          }
        ]
      }
    },
    "/api/v1/schema/envelope": {
      "get": {
        "operationId": "getEnvelopeSchema",
        "summary": "The v1 response envelope JSON Schema.",
        "description": "The published JSON Schema (Draft 2020-12) every `apiVersion: \"1.0\"` value conforms to. `application/schema+json`.",
        "tags": [
          "Meta"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/schema+json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/openapi.json": {
      "get": {
        "operationId": "getOpenApi",
        "summary": "This OpenAPI 3.1 specification.",
        "description": "The machine-readable OpenAPI 3.1 description of every v1 route — generated from the same registry that drives the docs.",
        "tags": [
          "Meta"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "An OpenAPI 3.1 document."
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — see `Retry-After` and the `X-RateLimit-*` headers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "A Pro-plan API key passed as a bearer token."
      },
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "A Pro-plan API key passed in the `x-api-key` header."
      }
    },
    "schemas": {
      "Envelope": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "$id": "https://predictonomy.com/api/v1/schema/envelope",
        "title": "Predictonomy API response envelope (v1)",
        "description": "The contract every value Predictonomy serves under apiVersion 1.0 conforms to: a typed, sourced envelope. observed/derived/ai_interpretation/collection/not_covered are never blurred. See docs/api-response-envelope-v1.md.",
        "type": "object",
        "required": [
          "apiVersion",
          "type"
        ],
        "properties": {
          "apiVersion": {
            "const": "1.0"
          },
          "type": {
            "type": "string",
            "enum": [
              "observed",
              "derived",
              "ai_interpretation",
              "collection",
              "not_covered"
            ]
          }
        },
        "oneOf": [
          {
            "$ref": "#/$defs/observed"
          },
          {
            "$ref": "#/$defs/derived"
          },
          {
            "$ref": "#/$defs/aiInterpretation"
          },
          {
            "$ref": "#/$defs/collection"
          },
          {
            "$ref": "#/$defs/notCovered"
          }
        ],
        "$defs": {
          "observed": {
            "type": "object",
            "required": [
              "apiVersion",
              "type",
              "topic",
              "coverage",
              "freshness",
              "data",
              "disclosure"
            ],
            "properties": {
              "apiVersion": {
                "const": "1.0"
              },
              "type": {
                "const": "observed"
              },
              "topic": {
                "type": "object",
                "required": [
                  "id",
                  "label",
                  "domain"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "label": {
                    "type": "string"
                  },
                  "domain": {
                    "type": "string",
                    "description": "macro | markets | industry | sentiment | derived"
                  },
                  "region": {
                    "type": "string",
                    "description": "ISO code, region, or GLOBAL"
                  },
                  "industry": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "coverage": {
                "type": "object",
                "required": [
                  "status",
                  "ledgerVersion"
                ],
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "live",
                      "partial",
                      "stale",
                      "not_covered"
                    ]
                  },
                  "ledgerVersion": {
                    "type": "string",
                    "description": "git sha of DATA_COVERAGE.md"
                  },
                  "gaps": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "freshness": {
                "type": "object",
                "required": [
                  "lastUpdated",
                  "asOf",
                  "slaMet",
                  "stale"
                ],
                "properties": {
                  "lastUpdated": {
                    "type": "string",
                    "description": "ISO — when we last ingested"
                  },
                  "asOf": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "the period the data describes"
                  },
                  "slaMet": {
                    "type": "boolean"
                  },
                  "stale": {
                    "type": "boolean"
                  }
                }
              },
              "data": {
                "type": "object",
                "required": [
                  "value",
                  "unit",
                  "provenance",
                  "quality"
                ],
                "properties": {
                  "value": {
                    "type": "number"
                  },
                  "unit": {
                    "type": "string"
                  },
                  "series": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "asOf",
                        "value"
                      ],
                      "properties": {
                        "asOf": {
                          "type": "string"
                        },
                        "value": {
                          "type": "number"
                        }
                      }
                    }
                  },
                  "forecastSeries": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "asOf",
                        "value"
                      ],
                      "properties": {
                        "asOf": {
                          "type": "string"
                        },
                        "value": {
                          "type": "number"
                        }
                      }
                    }
                  },
                  "provenance": {
                    "type": "object",
                    "required": [
                      "source",
                      "sourceSeriesId",
                      "license",
                      "retrievedAt"
                    ],
                    "properties": {
                      "source": {
                        "type": "string",
                        "minLength": 1
                      },
                      "sourceSeriesId": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "license": {
                        "type": "string",
                        "enum": [
                          "redistribution-restricted",
                          "open",
                          "attribution-required"
                        ]
                      },
                      "retrievedAt": {
                        "type": "string"
                      },
                      "sourceUrl": {
                        "type": "string"
                      }
                    }
                  },
                  "quality": {
                    "type": "object",
                    "required": [
                      "revisable",
                      "caveats"
                    ],
                    "properties": {
                      "revisable": {
                        "type": "boolean"
                      },
                      "caveats": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              },
              "disclosure": {
                "type": "object",
                "required": [
                  "notFinancialAdvice"
                ],
                "properties": {
                  "notFinancialAdvice": {
                    "const": true
                  },
                  "methodologyUrl": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "derived": {
            "type": "object",
            "required": [
              "apiVersion",
              "type",
              "topic",
              "coverage",
              "freshness",
              "data",
              "disclosure"
            ],
            "properties": {
              "apiVersion": {
                "const": "1.0"
              },
              "type": {
                "const": "derived"
              },
              "topic": {
                "type": "object",
                "required": [
                  "id",
                  "label",
                  "domain"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "label": {
                    "type": "string"
                  },
                  "domain": {
                    "type": "string",
                    "description": "macro | markets | industry | sentiment | derived"
                  },
                  "region": {
                    "type": "string",
                    "description": "ISO code, region, or GLOBAL"
                  },
                  "industry": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "coverage": {
                "type": "object",
                "required": [
                  "status",
                  "ledgerVersion"
                ],
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "live",
                      "partial",
                      "stale",
                      "not_covered"
                    ]
                  },
                  "ledgerVersion": {
                    "type": "string",
                    "description": "git sha of DATA_COVERAGE.md"
                  },
                  "gaps": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "freshness": {
                "type": "object",
                "required": [
                  "lastUpdated",
                  "asOf",
                  "slaMet",
                  "stale"
                ],
                "properties": {
                  "lastUpdated": {
                    "type": "string",
                    "description": "ISO — when we last ingested"
                  },
                  "asOf": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "the period the data describes"
                  },
                  "slaMet": {
                    "type": "boolean"
                  },
                  "stale": {
                    "type": "boolean"
                  }
                }
              },
              "data": {
                "type": "object",
                "required": [
                  "value",
                  "unit",
                  "method",
                  "inputs",
                  "degradedInputs"
                ],
                "properties": {
                  "value": {
                    "type": "number"
                  },
                  "unit": {
                    "type": "string"
                  },
                  "method": {
                    "type": "object",
                    "required": [
                      "id",
                      "summary",
                      "reproducible"
                    ],
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "summary": {
                        "type": "string"
                      },
                      "methodologyUrl": {
                        "type": "string"
                      },
                      "reproducible": {
                        "type": "boolean"
                      }
                    }
                  },
                  "inputs": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "topicId",
                        "weight",
                        "coverage"
                      ],
                      "properties": {
                        "topicId": {
                          "type": "string",
                          "minLength": 1
                        },
                        "label": {
                          "type": "string"
                        },
                        "weight": {
                          "type": "number"
                        },
                        "coverage": {
                          "type": "string",
                          "enum": [
                            "live",
                            "partial"
                          ]
                        }
                      }
                    }
                  },
                  "degradedInputs": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "disclosure": {
                "type": "object",
                "required": [
                  "notFinancialAdvice"
                ],
                "properties": {
                  "notFinancialAdvice": {
                    "const": true
                  },
                  "methodologyUrl": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "aiInterpretation": {
            "type": "object",
            "required": [
              "apiVersion",
              "type",
              "topic",
              "coverage",
              "data",
              "disclosure"
            ],
            "properties": {
              "apiVersion": {
                "const": "1.0"
              },
              "type": {
                "const": "ai_interpretation"
              },
              "topic": {
                "type": "object",
                "required": [
                  "id",
                  "label",
                  "domain"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "label": {
                    "type": "string"
                  },
                  "domain": {
                    "type": "string",
                    "description": "macro | markets | industry | sentiment | derived"
                  },
                  "region": {
                    "type": "string",
                    "description": "ISO code, region, or GLOBAL"
                  },
                  "industry": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "coverage": {
                "type": "object",
                "required": [
                  "status",
                  "ledgerVersion"
                ],
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "live",
                      "partial",
                      "stale",
                      "not_covered"
                    ]
                  },
                  "ledgerVersion": {
                    "type": "string",
                    "description": "git sha of DATA_COVERAGE.md"
                  },
                  "gaps": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "freshness": {
                "type": "object",
                "required": [
                  "lastUpdated",
                  "asOf",
                  "slaMet",
                  "stale"
                ],
                "properties": {
                  "lastUpdated": {
                    "type": "string",
                    "description": "ISO — when we last ingested"
                  },
                  "asOf": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "the period the data describes"
                  },
                  "slaMet": {
                    "type": "boolean"
                  },
                  "stale": {
                    "type": "boolean"
                  }
                }
              },
              "data": {
                "type": "object",
                "required": [
                  "text",
                  "model",
                  "dataBasis",
                  "confidence",
                  "forecasts",
                  "limitations",
                  "isOpinion"
                ],
                "properties": {
                  "text": {
                    "type": "string"
                  },
                  "model": {
                    "type": "object",
                    "required": [
                      "name",
                      "promptTemplateId",
                      "generatedAt"
                    ],
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "promptTemplateId": {
                        "type": "string"
                      },
                      "generatedAt": {
                        "type": "string"
                      }
                    }
                  },
                  "dataBasis": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "topicId",
                        "coverage"
                      ],
                      "properties": {
                        "topicId": {
                          "type": "string",
                          "minLength": 1
                        },
                        "asOfRange": {
                          "type": [
                            "array",
                            "null"
                          ],
                          "items": {
                            "type": "string"
                          },
                          "minItems": 2,
                          "maxItems": 2
                        },
                        "coverage": {
                          "type": "string",
                          "enum": [
                            "live",
                            "partial"
                          ]
                        }
                      }
                    }
                  },
                  "confidence": {
                    "type": "object",
                    "required": [
                      "level",
                      "rationale"
                    ],
                    "properties": {
                      "level": {
                        "type": "string",
                        "enum": [
                          "low",
                          "moderate",
                          "high"
                        ]
                      },
                      "score": {
                        "type": "number"
                      },
                      "rationale": {
                        "type": "string"
                      }
                    }
                  },
                  "forecasts": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "statement",
                        "basis",
                        "horizon",
                        "uncertainty"
                      ],
                      "properties": {
                        "statement": {
                          "type": "string"
                        },
                        "basis": {
                          "type": "string"
                        },
                        "horizon": {
                          "type": "string"
                        },
                        "uncertainty": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "limitations": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "isOpinion": {
                    "const": true
                  }
                }
              },
              "disclosure": {
                "type": "object",
                "required": [
                  "notFinancialAdvice"
                ],
                "properties": {
                  "notFinancialAdvice": {
                    "const": true
                  },
                  "methodologyUrl": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "collection": {
            "type": "object",
            "required": [
              "apiVersion",
              "type",
              "topic",
              "coverage",
              "data",
              "disclosure"
            ],
            "properties": {
              "apiVersion": {
                "const": "1.0"
              },
              "type": {
                "const": "collection"
              },
              "topic": {
                "type": "object",
                "required": [
                  "id",
                  "label",
                  "domain"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "label": {
                    "type": "string"
                  },
                  "domain": {
                    "type": "string",
                    "description": "macro | markets | industry | sentiment | derived"
                  },
                  "region": {
                    "type": "string",
                    "description": "ISO code, region, or GLOBAL"
                  },
                  "industry": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "coverage": {
                "type": "object",
                "required": [
                  "status",
                  "ledgerVersion"
                ],
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "live",
                      "partial",
                      "stale",
                      "not_covered"
                    ]
                  },
                  "ledgerVersion": {
                    "type": "string",
                    "description": "git sha of DATA_COVERAGE.md"
                  },
                  "gaps": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "freshness": {
                "type": "object",
                "required": [
                  "lastUpdated",
                  "asOf",
                  "slaMet",
                  "stale"
                ],
                "properties": {
                  "lastUpdated": {
                    "type": "string",
                    "description": "ISO — when we last ingested"
                  },
                  "asOf": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "the period the data describes"
                  },
                  "slaMet": {
                    "type": "boolean"
                  },
                  "stale": {
                    "type": "boolean"
                  }
                }
              },
              "data": {
                "type": "object",
                "required": [
                  "items"
                ],
                "properties": {
                  "items": {
                    "type": "array",
                    "description": "value envelopes (observed) OR catalogue coverage descriptors"
                  },
                  "pagination": {
                    "type": "object",
                    "properties": {
                      "page": {
                        "type": "number"
                      },
                      "pageSize": {
                        "type": "number"
                      },
                      "total": {
                        "type": "number"
                      }
                    }
                  },
                  "partialCoverageNote": {
                    "type": "string"
                  },
                  "omitted": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "topicId",
                        "reason"
                      ],
                      "properties": {
                        "topicId": {
                          "type": "string"
                        },
                        "reason": {
                          "const": "not_covered"
                        }
                      }
                    }
                  }
                }
              },
              "disclosure": {
                "type": "object",
                "required": [
                  "notFinancialAdvice"
                ],
                "properties": {
                  "notFinancialAdvice": {
                    "const": true
                  },
                  "methodologyUrl": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "notCovered": {
            "type": "object",
            "required": [
              "apiVersion",
              "type",
              "topic",
              "coverage",
              "data",
              "message",
              "wouldRequire"
            ],
            "properties": {
              "apiVersion": {
                "const": "1.0"
              },
              "type": {
                "const": "not_covered"
              },
              "topic": {
                "type": "object",
                "required": [
                  "id",
                  "label",
                  "domain"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "label": {
                    "type": "string"
                  },
                  "domain": {
                    "type": "string",
                    "description": "macro | markets | industry | sentiment | derived"
                  },
                  "region": {
                    "type": "string",
                    "description": "ISO code, region, or GLOBAL"
                  },
                  "industry": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "coverage": {
                "type": "object",
                "required": [
                  "status",
                  "ledgerVersion"
                ],
                "properties": {
                  "status": {
                    "const": "not_covered"
                  },
                  "ledgerVersion": {
                    "type": "string"
                  }
                }
              },
              "data": {
                "type": "null"
              },
              "message": {
                "type": "string"
              },
              "wouldRequire": {
                "type": "string"
              },
              "trackedAs": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "DataApiResult": {
        "type": "object",
        "description": "The simple data-API result: a `{ data, count }` list, a detail object, or a typed `not_covered` envelope for known-but-uncovered ids.",
        "properties": {
          "data": {
            "description": "The result payload (list rows or a detail object)."
          },
          "count": {
            "type": "integer",
            "description": "Row count (list routes)."
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable error message."
          }
        }
      }
    }
  }
}