{
  "openapi": "3.1.0",
  "info": {
    "title": "Certvas API",
    "version": "1.0.0",
    "summary": "Point-in-time African and emerging-market fundamentals, procurement and macro data.",
    "description": "Every record carries the source it came from and the licence that source is held under; the register naming all of them is public at https://certvas.com/license-register.\n\nThree properties are worth knowing before you integrate:\n\n1. **Nothing is estimated or interpolated.** Where a value is unknown it is absent rather than guessed, so a null means 'not known', never 'assumed zero'.\n2. **Restatements append a new vintage rather than overwriting**, so `as_of` queries on reported facts return what was knowable on that date and are reproducible. Derived metrics (`/v1/screen`, `/v1/metrics`) return only values that were exactly computable on that date; their `as_of` states the limits.\n3. **No natural-person data.** Parties not established to be organisations are withheld by an enforced check.\n\nPublished for research and informational use. Not administered as, and not for use as, a regulated benchmark.",
    "contact": {
      "name": "Certvas",
      "email": "mandase@certvas.com",
      "url": "https://certvas.com"
    }
  },
  "servers": [
    {
      "url": "https://api.certvas.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "A sandbox key (`cv_sandbox_public`) works without signup and caps row counts. Paid keys lift the cap."
      }
    }
  },
  "paths": {
    "/v1/status": {
      "get": {
        "summary": "Service and dependency status.",
        "description": "Liveness plus the build SHA, and a real read against each dependency (D1 and both key-value stores). 503 names the failing component. No key required.",
        "security": [],
        "responses": {
          "200": {
            "description": "Operational: every dependency answered."
          },
          "503": {
            "description": "Degraded: the body's `degraded` list names what failed."
          }
        }
      }
    },
    "/v1/fundamentals/{dataco_id}": {
      "get": {
        "summary": "Point-in-time company fundamentals for one issuer.",
        "description": "Reported financial facts. Every record carries the source it came from and the licence class that source is held under. Nothing is estimated or interpolated — where a value is unknown it is ABSENT rather than filled.",
        "parameters": [
          {
            "name": "dataco_id",
            "in": "path",
            "required": true,
            "description": "Certvas ID of the organisation, e.g. from /v1/tender-parties.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9]+$"
            }
          },
          {
            "name": "as_of",
            "in": "query",
            "description": "Point-in-time date (YYYY-MM-DD). Returns what was KNOWABLE on that date rather than what is known now — restatements append a new vintage instead of overwriting, so an as-of query is reproducible.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "period_type",
            "in": "query",
            "description": "Reporting cadence, e.g. FY, H1, Q1.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "vintages",
            "in": "query",
            "description": "`latest` (default): the latest vintage knowable by `as_of`. `as_reported`: the value originally filed, the right base for a backtest. `all`: every restatement, which is what makes restatement analysis possible.",
            "schema": {
              "type": "string",
              "enum": [
                "latest",
                "as_reported",
                "all"
              ],
              "default": "latest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Fundamentals facts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "A parameter is malformed or missing; the body names it."
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "404": {
            "description": "No record with that identifier."
          },
          "429": {
            "description": "Rate limited: the per-minute burst limit or the monthly cap was reached."
          },
          "503": {
            "description": "The key store is temporarily unavailable; retry."
          }
        }
      }
    },
    "/v1/tenders": {
      "get": {
        "summary": "Public-procurement tender releases (OCDS).",
        "description": "Open Contracting releases from portals whose licences permit commercial redistribution. Natural persons are excluded from every published table by an enforced check, not a convention. Keyset-paginated, newest release first.",
        "parameters": [
          {
            "name": "country",
            "in": "query",
            "description": "ISO 3166-1 alpha-2 country code.",
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 2,
              "default": "ZA"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Earliest release date (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum rows to return. Sandbox keys are capped well below the paid ceiling.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Opaque pagination cursor returned by the previous page; an altered one is a 400.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tender releases.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "A parameter is malformed or missing; the body names it."
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "402": {
            "description": "A sample-tier key asked for more than the sample tier serves: more rows than its cap, or a lookup in the paid crosswalk. The body and the X-Upgrade-URL header carry the upgrade link. Omit `limit` for the free preview."
          },
          "429": {
            "description": "Rate limited: the per-minute burst limit or the monthly cap was reached."
          },
          "503": {
            "description": "The key store is temporarily unavailable; retry."
          }
        }
      }
    },
    "/v1/shared/facts": {
      "get": {
        "summary": "Any dataset in one layout: entity + fact + provenance.",
        "description": "The shared shape the Python SDK builds from the release (`certvas.shared`), served: each row is one fact (fact_id, entity_id, entity_name, country_iso3, dataset, measure, value, unit, period_end, period_type, available_at, vintage, provenance_id) with its provenance (source_ref, license_class, extraction_confidence, observed_at, method). Pass exactly one of `entity_id` or `country`. The latest vintage of each value is served; award facts and every macro vintage are in the bulk release. Column reference: https://github.com/KingMandase/dataco/blob/main/docs/data/SHARED_SHAPE.md",
        "parameters": [
          {
            "name": "entity_id",
            "in": "query",
            "description": "A Certvas ID (DC...) for an issuer's AF-FUND facts, or an ISO 3166-1 alpha-3 country code for its AF-MACRO facts.",
            "schema": {
              "type": "string",
              "pattern": "^(DC[A-Za-z0-9]+|[A-Z]{3})$"
            }
          },
          {
            "name": "country",
            "in": "query",
            "description": "ISO 3166-1 alpha-3 country code; needs `dataset`.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z]{3}$"
            }
          },
          {
            "name": "dataset",
            "in": "query",
            "description": "With `country`: AF-TENDER (tender values, newest release first) or AF-MACRO. With an issuer's `entity_id` (DC...): AF-FUND (the default, its financials) or AF-TENDER (the contract awards it won).",
            "schema": {
              "type": "string",
              "enum": [
                "AF-FUND",
                "AF-TENDER",
                "AF-MACRO"
              ]
            }
          },
          {
            "name": "as_of",
            "in": "query",
            "description": "Keep only facts whose `available_at` is on or before this date (YYYY-MM-DD). Annual macro has no evidenced release date, so it is left out rather than guessed.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "measure",
            "in": "query",
            "description": "One measure only, e.g. revenue, FX.ZAR.USD, tender_value.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum rows to return. Sandbox keys are capped well below the paid ceiling.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Shared-shape facts with their provenance.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "A parameter is malformed or missing; the body names it."
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "402": {
            "description": "A sample-tier key asked for more than the sample tier serves: more rows than its cap, or a lookup in the paid crosswalk. The body and the X-Upgrade-URL header carry the upgrade link. Omit `limit` for the free preview."
          },
          "429": {
            "description": "Rate limited: the per-minute burst limit or the monthly cap was reached."
          },
          "503": {
            "description": "The key store is temporarily unavailable; retry."
          }
        }
      }
    },
    "/v1/tender-parties": {
      "get": {
        "summary": "Organisations that publish or win tenders, resolved to Certvas IDs.",
        "description": "Buyers and suppliers named in OCDS releases. A party not established to be an organisation is withheld — the exclusion is enforced at build time and runs conservatively, so some real firms are withheld too.",
        "parameters": [
          {
            "name": "country",
            "in": "query",
            "description": "ISO 3166-1 alpha-2 country code.",
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 2
            }
          },
          {
            "name": "role",
            "in": "query",
            "description": "Party role, e.g. buyer or supplier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dataco_id",
            "in": "query",
            "description": "Only the parties resolved to this Certvas ID.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum rows to return. Sandbox keys are capped well below the paid ceiling.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tender parties.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "429": {
            "description": "Rate limited: the per-minute burst limit or the monthly cap was reached."
          },
          "503": {
            "description": "The key store is temporarily unavailable; retry."
          }
        }
      }
    },
    "/v1/macro": {
      "get": {
        "summary": "Macroeconomic indicators for African and emerging markets.",
        "description": "Two modes. With `series` or `as_of`: point-in-time series (FX, CPI), returning the latest vintage known at `as_of` with no look-ahead. Otherwise: annual World Bank indicators by `country` and `indicator`. Attribution and notice conditions travel with the data and are stated per source in the public licence register.",
        "parameters": [
          {
            "name": "country",
            "in": "query",
            "description": "ISO 3166-1 alpha-2 country code.",
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 2
            }
          },
          {
            "name": "indicator",
            "in": "query",
            "description": "Annual mode: World Bank indicator code, e.g. FP.CPI.TOTL.ZG.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "series",
            "in": "query",
            "description": "Series mode: series key, e.g. FX.NGN.USD.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "series_type",
            "in": "query",
            "description": "Series mode: family, e.g. fx or cpi.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "frequency",
            "in": "query",
            "description": "Series mode: monthly or daily.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "as_of",
            "in": "query",
            "description": "Point-in-time date (YYYY-MM-DD). Returns what was KNOWABLE on that date rather than what is known now — restatements append a new vintage instead of overwriting, so an as-of query is reproducible.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Series mode: earliest observation date (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Series mode: latest observation date (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum rows to return. Sandbox keys are capped well below the paid ceiling.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Macro observations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "A parameter is malformed or missing; the body names it."
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "402": {
            "description": "A sample-tier key asked for more than the sample tier serves: more rows than its cap, or a lookup in the paid crosswalk. The body and the X-Upgrade-URL header carry the upgrade link. Omit `limit` for the free preview."
          },
          "429": {
            "description": "Rate limited: the per-minute burst limit or the monthly cap was reached."
          },
          "503": {
            "description": "The key store is temporarily unavailable; retry."
          }
        }
      }
    },
    "/v1/entities/{dataco_id}": {
      "get": {
        "summary": "One organisation by Certvas ID.",
        "description": "The resolved entity record: legal name, jurisdiction, LEI where one exists, and the licence class of the source. There is no name search; IDs come from the other endpoints and from certvas.com/id.",
        "parameters": [
          {
            "name": "dataco_id",
            "in": "path",
            "required": true,
            "description": "Certvas ID of the organisation, e.g. from /v1/tender-parties.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Entity.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "404": {
            "description": "No record with that identifier."
          },
          "429": {
            "description": "Rate limited: the per-minute burst limit or the monthly cap was reached."
          },
          "503": {
            "description": "The key store is temporarily unavailable; retry."
          }
        }
      }
    },
    "/v1/crosswalk/{dataco_id}": {
      "get": {
        "summary": "Every identifier Certvas holds for one organisation.",
        "description": "The Certvas ID's LEI and listing ticker are free, as on certvas.com/id. National registry numbers and OpenCorporates ids are the paid crosswalk (starter tier and above); on the sample tier they are listed in `withheld_id_types` instead. Current mappings only; `crosswalk_loaded` is false until the crosswalk table is loaded.",
        "parameters": [
          {
            "name": "dataco_id",
            "in": "path",
            "required": true,
            "description": "Certvas ID of the organisation, e.g. from /v1/tender-parties.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Identifiers.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "404": {
            "description": "No record with that identifier."
          },
          "429": {
            "description": "Rate limited: the per-minute burst limit or the monthly cap was reached."
          },
          "503": {
            "description": "The key store is temporarily unavailable; retry."
          }
        }
      }
    },
    "/v1/crosswalk": {
      "get": {
        "summary": "Find the Certvas ID for an outside identifier.",
        "description": "Look up by `lei` or `ticker` (EXCHANGE:TICKER) on any tier; by `registry`, `opencorporates`, `isin` or `figi` on the starter tier and above. A registry number is unique only within its registry, so a lookup can match more than one organisation; the response then carries `ambiguous: true`. At most 20 matches.",
        "parameters": [
          {
            "name": "id_type",
            "in": "query",
            "required": true,
            "description": "lei, ticker, registry, opencorporates, isin or figi.",
            "schema": {
              "type": "string",
              "enum": [
                "lei",
                "ticker",
                "registry",
                "opencorporates",
                "isin",
                "figi"
              ]
            }
          },
          {
            "name": "id_value",
            "in": "query",
            "required": true,
            "description": "The identifier, 1 to 64 characters.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching Certvas IDs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "A parameter is malformed or missing; the body names it."
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "402": {
            "description": "A sample-tier key asked for more than the sample tier serves: more rows than its cap, or a lookup in the paid crosswalk. The body and the X-Upgrade-URL header carry the upgrade link. Omit `limit` for the free preview."
          },
          "429": {
            "description": "Rate limited: the per-minute burst limit or the monthly cap was reached."
          },
          "503": {
            "description": "The key store is temporarily unavailable; retry."
          }
        }
      }
    },
    "/v1/screen": {
      "get": {
        "summary": "Rank issuers on a metric.",
        "description": "Per issuer, the latest full-year period published by `as_of` (see the parameter), ranked by the metric. `rank` is a mechanical sort position by metric size, not a rating.",
        "parameters": [
          {
            "name": "metric",
            "in": "query",
            "required": true,
            "description": "Metric slug from metrics-v1, e.g. gross_margin.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9_]+$"
            }
          },
          {
            "name": "order",
            "in": "query",
            "description": "Sort direction.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            }
          },
          {
            "name": "as_of",
            "in": "query",
            "description": "Point-in-time date (YYYY-MM-DD). Per issuer and metric, takes the latest full-year period that had been published by that date, and returns its value only if every filing the value is computed from was available by then with the figures it holds today: the value is exactly what was computable on that date, with no look-ahead. Where that year's figures were revised or completed later, the value is left out rather than replaced by the revised value or an older year, so a historical query can return fewer issuers than were covered then. Because values are recomputed from current figures, a later restatement can remove a value from a past `as_of`; it never adds one dated before its filing.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "country",
            "in": "query",
            "description": "ISO 3166-1 alpha-2 country code.",
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 2
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum rows to return. Sandbox keys are capped well below the paid ceiling.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Screen results.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "A parameter is malformed or missing; the body names it."
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "402": {
            "description": "A sample-tier key asked for more than the sample tier serves: more rows than its cap, or a lookup in the paid crosswalk. The body and the X-Upgrade-URL header carry the upgrade link. Omit `limit` for the free preview."
          },
          "429": {
            "description": "Rate limited: the per-minute burst limit or the monthly cap was reached."
          },
          "503": {
            "description": "The key store is temporarily unavailable; retry."
          }
        }
      }
    },
    "/v1/metrics/{dataco_id}": {
      "get": {
        "summary": "Every metrics-v1 value for one issuer.",
        "description": "Per metric, the latest full-year period published by `as_of`: the same value /v1/screen ranks. Rows carry the screener's fields without `rank`, and each `methodology_ref` names the documented formula. Informational only; not advice.",
        "parameters": [
          {
            "name": "dataco_id",
            "in": "path",
            "required": true,
            "description": "Certvas ID of the organisation, e.g. from /v1/tender-parties.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9]+$"
            }
          },
          {
            "name": "metric",
            "in": "query",
            "description": "One metrics-v1 slug, e.g. gross_margin. Omit for every metric.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9_]+$"
            }
          },
          {
            "name": "as_of",
            "in": "query",
            "description": "Point-in-time date (YYYY-MM-DD). Per issuer and metric, takes the latest full-year period that had been published by that date, and returns its value only if every filing the value is computed from was available by then with the figures it holds today: the value is exactly what was computable on that date, with no look-ahead. Where that year's figures were revised or completed later, the value is left out rather than replaced by the revised value or an older year, so a historical query can return fewer issuers than were covered then. Because values are recomputed from current figures, a later restatement can remove a value from a past `as_of`; it never adds one dated before its filing.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum rows to return. Sandbox keys are capped well below the paid ceiling.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Metric values.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "A parameter is malformed or missing; the body names it."
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "402": {
            "description": "A sample-tier key asked for more than the sample tier serves: more rows than its cap, or a lookup in the paid crosswalk. The body and the X-Upgrade-URL header carry the upgrade link. Omit `limit` for the free preview."
          },
          "429": {
            "description": "Rate limited: the per-minute burst limit or the monthly cap was reached."
          },
          "503": {
            "description": "The key store is temporarily unavailable; retry."
          }
        }
      }
    },
    "/v1/signals/procurement-demand": {
      "get": {
        "summary": "Procurement-Demand Signal: category share, buyer concentration, momentum.",
        "description": "Point-in-time: `as_of` returns only months closed by that date, with no look-ahead. Rows live under the `observations` key.",
        "parameters": [
          {
            "name": "country",
            "in": "query",
            "description": "ISO 3166-1 alpha-2 country code.",
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 2,
              "default": "ZA"
            }
          },
          {
            "name": "category",
            "in": "query",
            "description": "Procurement category as published by the source portal.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "as_of",
            "in": "query",
            "description": "Point-in-time date (YYYY-MM-DD). Returns what was KNOWABLE on that date rather than what is known now — restatements append a new vintage instead of overwriting, so an as-of query is reproducible.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Earliest month (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Latest month (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum rows to return. Sandbox keys are capped well below the paid ceiling.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Demand cells.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "402": {
            "description": "A sample-tier key asked for more than the sample tier serves: more rows than its cap, or a lookup in the paid crosswalk. The body and the X-Upgrade-URL header carry the upgrade link. Omit `limit` for the free preview."
          },
          "429": {
            "description": "Rate limited: the per-minute burst limit or the monthly cap was reached."
          },
          "503": {
            "description": "The key store is temporarily unavailable; retry."
          }
        }
      }
    },
    "/v1/signals/{signal_key}": {
      "get": {
        "summary": "One published analysed-data signal panel.",
        "description": "Signal panels computed over the licence-clean spine; /v1/export lists the open ones. Rows live under the `observations` key.",
        "parameters": [
          {
            "name": "signal_key",
            "in": "path",
            "required": true,
            "description": "Panel key, as listed by /v1/export.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum rows to return. Sandbox keys are capped well below the paid ceiling.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Signal panel.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "A parameter is malformed or missing; the body names it."
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "404": {
            "description": "No record with that identifier."
          },
          "429": {
            "description": "Rate limited: the per-minute burst limit or the monthly cap was reached."
          },
          "503": {
            "description": "The key store is temporarily unavailable; retry."
          }
        }
      }
    },
    "/v1/quality-metrics/{sku}": {
      "get": {
        "summary": "Measured quality metrics for one dataset.",
        "description": "Published so buyers can verify rather than trust. An unmeasured floor reports as UNMEASURED rather than as a pass.",
        "parameters": [
          {
            "name": "sku",
            "in": "path",
            "required": true,
            "description": "Dataset SKU, e.g. AF-FUND.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Quality metrics.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "404": {
            "description": "No record with that identifier."
          },
          "429": {
            "description": "Rate limited: the per-minute burst limit or the monthly cap was reached."
          },
          "503": {
            "description": "The key store is temporarily unavailable; retry."
          }
        }
      }
    },
    "/v1/export": {
      "get": {
        "summary": "Bulk-export manifest.",
        "description": "Published Parquet artifacts and where to fetch them.",
        "responses": {
          "200": {
            "description": "Export manifest.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "429": {
            "description": "Rate limited: the per-minute burst limit or the monthly cap was reached."
          },
          "503": {
            "description": "The key store is temporarily unavailable; retry."
          }
        }
      }
    }
  }
}