{
  "openapi": "3.1.0",
  "info": {
    "title": "Regulatory Snapshot API",
    "version": "1.0.0",
    "description": "Public HTTP API for Regulatory Snapshot. Programmatic access to scans,\nsnapshots, exports, and usage.\n\n**Authentication.** All `/v1/*` endpoints (except `/v1/keys/*` and the\nClerk-session variants of `/v1/usage` and `/v1/usage/budget`) require a\nbearer key. `Authorization: Bearer regsn_live_<32 chars>`.\n\n**Idempotency.** `POST /v1/scans` and `POST /v1/exports` accept an\noptional `Idempotency-Key` header (1-255 printable ASCII chars).\nStripe-style 24-hour replay window; replays carry an\n`Idempotency-Replayed: true` header. Mismatched body returns\n409 `idempotency_key_in_use`; a still-running first request returns\n409 `idempotency_in_progress`.\n\n**Errors.** Every error response is `application/problem+json` per\nRFC 9457 with shape `{ type, title, status, detail, instance, code,\nrequest_id, errors }`. Every response carries an `X-Request-Id`.\n\n**Budget.** All scan + export work depletes the same Clerk budget pool\nconsumed by the regsn.app UI. There is no separate API meter.\n"
  },
  "servers": [
    {
      "url": "https://api.regsn.app"
    },
    {
      "url": "http://localhost:3001"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/v1/keys": {
      "post": {
        "summary": "Create a new API key",
        "description": "Returns the raw key exactly once. Subsequent listings show only the\nmasked prefix. Required Clerk session (dashboard-only).\n",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "example": "production-backend"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Key created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyWithRaw"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "get": {
        "summary": "List API keys (masked)",
        "security": [],
        "responses": {
          "200": {
            "description": "Keys list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApiKey"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/keys/{id}/revoke": {
      "post": {
        "summary": "Revoke an API key",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "Revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "revoked_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/v1/scans": {
      "post": {
        "summary": "Create a scan",
        "description": "Creates a regulatory scan.\n\n**Required parameters** (request body):\n- `jurisdictions` — non-empty array, ≤20 entries, each ≤100 chars. The\n  authoritative list of IDs is at `GET /v1/meta/jurisdictions`.\n- `areas` — non-empty array, ≤20 entries, each ≤200 chars. List at\n  `GET /v1/meta/areas`.\n- `horizon` — one of `3, 6, 12, 18, 24, 36` (months).\n\n**Optional parameters** with notable interactions:\n- `engine` — defaults to `v4`. Valid values at `GET /v1/meta/engines`\n  (`v4`, `v4.5-alpha`, `v4.5-beta`, `council`). The retired engines\n  `v1`, `v2`, `v3` and `admiral` are rejected with 422\n  `engine_deprecated`. `council` runs the Analyst Council: two fixed\n  seats (V4 · Opus 4.6 and V4.5β · Opus 5) in parallel, a\n  deterministic merge, and an Opus-4.6 Writer composing one definitive\n  briefing; `model` does not apply (422 `council_model_fixed` unless\n  omitted or `opus`), and the run persists the composite plus two\n  seat snapshots. Council runs are long (~15 minutes) — use\n  `mode=async`.\n- `model` / `searchModel` — see `GET /v1/meta/models`. `haiku` is\n  rejected on verifier-aware paths (`v4` with `realist`/`auditor`, or\n  `v4.5-*`).\n- `verificationMode` — only valid when `engine` is `v4`, `v4.5-alpha`\n  or `v4.5-beta`. Setting it auto-enables `realist=true` and\n  `auditor=true` if those are unset.\n- `realist` / `auditor` — boolean; only valid on verifier-aware\n  engines.\n- `auditorModel` — `luna` (default) or `sonnet` (legacy/rollback).\n- `fetchProvider` — `crw` or `firecrawl`; only valid when `engine`\n  is `v4.5-beta`. Omitted: the deployment's `FETCH_PROVIDER` dial,\n  else `crw`.\n- `searchProvider` — `serper` or `serpapi`; the `search_web` backend,\n  only valid when `engine` is `v4.5-beta`. Omitted: the deployment's\n  `SEARCH_PROVIDER` dial, else `serper`.\n- `verifyProvider` — `firecrawl` or `crw`; the page-read backend for\n  event verification in the QA tail, valid on every engine. Omitted:\n  `crw` for a scan carrying `fetchProvider: crw` (any engine), or for\n  a `v4.5-beta` scan whose page reads resolve to `crw` (the\n  `fetchProvider` field, else the `FETCH_PROVIDER` dial, else the\n  `crw` default); else the `DRIFTER_SCRAPE_PROVIDER` dial, else\n  `firecrawl`.\n\n**Modes** (query):\n- `mode=sync` (default): blocks up to 120 seconds. If the engine\n  finishes inside that window, returns 200 + full snapshot envelope.\n  If not, returns 202 with `scan_id` and the work continues in the\n  background.\n- `mode=async`: returns 202 immediately; poll `status_url` or\n  subscribe to `stream_url`.\n\n**Idempotency.** Pass `Idempotency-Key` to make retries safe. Replays\nwithin 24 hours that match the original body fingerprint return the\ncached response (`Idempotency-Replayed: true`); mismatched bodies\nreturn 409.\n\n**Failure modes.**\n- `402 budget_exhausted` — Clerk budget pool depleted.\n- `409 idempotency_key_in_use` — replayed key with a different body.\n- `409 idempotency_in_progress` — first request with this key is\n  still running.\n- `422 validation_error` — see `errors[]` for per-field reasons.\n- `429` — per-bucket rate limit; honour `Retry-After`.\n- `503 engine_unavailable` — selected engine not loaded in this build.\n",
        "parameters": [
          {
            "name": "mode",
            "in": "query",
            "description": "`sync` (default) waits up to 120s before returning 202; `async` returns 202 immediately.",
            "schema": {
              "type": "string",
              "enum": [
                "sync",
                "async"
              ],
              "default": "sync"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "1-255 ASCII chars. 24-hour replay window.",
            "schema": {
              "type": "string",
              "maxLength": 255,
              "minLength": 1
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScanConfig"
              },
              "examples": {
                "minimal": {
                  "summary": "Minimal config — engine defaults to v4, sync mode",
                  "value": {
                    "jurisdictions": [
                      "UK",
                      "EU"
                    ],
                    "areas": [
                      "AML / KYC",
                      "Sanctions"
                    ],
                    "horizon": 12
                  }
                },
                "verifierAware": {
                  "summary": "Verifier-aware v4.5-alpha",
                  "value": {
                    "jurisdictions": [
                      "US"
                    ],
                    "areas": [
                      "Capital Markets"
                    ],
                    "horizon": 6,
                    "engine": "v4.5-alpha",
                    "model": "sonnet",
                    "verificationMode": "in-analyst"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Scan completed within sync ceiling.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "scan_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "completed"
                      ]
                    },
                    "snapshot_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "snapshot": {
                      "$ref": "#/components/schemas/SnapshotEnvelope"
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "Scan queued or exceeded sync ceiling.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScanQueued"
                }
              }
            }
          },
          "402": {
            "$ref": "#/components/responses/Problem"
          },
          "409": {
            "$ref": "#/components/responses/Problem"
          },
          "422": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          },
          "503": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/v1/scans/estimate": {
      "post": {
        "summary": "Estimate cost for a scan config",
        "description": "Returns a cost envelope (low / expected / high in cents) for the\nsupplied scan config without actually queueing a scan. Counts of\njurisdictions and areas are what the estimator buckets on; names are\nnot consulted, so callers may pass stub strings if they only want a\nballpark by count. Unlike `POST /v1/scans`, `horizon` is optional\nhere (validated only when supplied).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScanEstimateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cost envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScanEstimate"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/v1/scans/{id}": {
      "get": {
        "summary": "Poll a scan",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "Scan status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScanStatus"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/v1/scans/{id}/cancel": {
      "post": {
        "summary": "Cancel a running scan",
        "description": "Marks the scan `cancelled` and notifies SSE listeners. Already-terminal\nscans return 409. Best-effort: if the engine call is mid-flight, it\ncontinues in-process but its terminal DB write is suppressed.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "cancelled"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "409": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/v1/scans/{id}/stream": {
      "get": {
        "summary": "Stream scan progress (SSE)",
        "description": "Server-Sent Events channel. Event types:\n- `progress` — `{ phase, percent, message }` per phase advance\n- `complete` — `{ data: <snapshot envelope>, snapshotId }`\n- `error` — `{ error }` terminal\nA late `complete` event read from persistence uses the same public,\nsanitizer-backed and legacy-Auditor-backfilled projection as GET\n`/v1/snapshots/{id}`. Live and synchronous completions retain their\nin-memory prose citation markers. Private Auditor failure diagnostics\nare omitted from every completion path.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "text/event-stream",
            "content": {
              "text/event-stream": {}
            }
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/v1/snapshots": {
      "get": {
        "summary": "List snapshots",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "jurisdiction",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "area",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Snapshots list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SnapshotListItem"
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "limit": {
                          "type": "integer"
                        },
                        "offset": {
                          "type": "integer"
                        },
                        "total": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/snapshots/{id}": {
      "get": {
        "summary": "Retrieve full snapshot envelope",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "language",
            "in": "query",
            "description": "Optional content locale to project through the snapshot read composer when a complete owned translation is available.",
            "schema": {
              "type": "string",
              "enum": [
                "en",
                "fr",
                "de",
                "es",
                "it",
                "zh",
                "ja",
                "simple"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Snapshot.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "title": {
                      "type": "string"
                    },
                    "config": {
                      "type": "object"
                    },
                    "data": {
                      "$ref": "#/components/schemas/SnapshotEnvelope"
                    },
                    "cost_cents": {
                      "type": "number",
                      "nullable": true,
                      "description": "Reconciled ledger actual when available (sub-cent float, e.g. 131.2245), else the original whole-cent estimate."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/v1/snapshots/{id}/items": {
      "get": {
        "summary": "Marquee — items array",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "items (`data: null` when the snapshot has no items key); inert inline citation-marker tokens are omitted from plain-prose fields",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/v1/snapshots/{id}/trends": {
      "get": {
        "summary": "Marquee — trends array",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "trends (`data: null` when the snapshot has no trends key); inert inline citation-marker tokens are omitted from plain-prose fields",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/v1/snapshots/{id}/executive-summary": {
      "get": {
        "summary": "Marquee — executive summary block",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "executive_summary (`data: null` when absent); inert inline citation-marker tokens are omitted from plain-prose fields",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/v1/snapshots/{id}/executive-narrative": {
      "get": {
        "summary": "Marquee — executive narrative prose",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "language",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "en"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "narrative",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "executive_narrative": {
                      "type": "string",
                      "description": "Plain narrative prose; inert inline citation-marker tokens are omitted."
                    },
                    "language": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Snapshot not found (`not_found`), no narrative on the snapshot\n(`not_found`), or no translation for the requested language\n(`translation_not_available`).\n",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/snapshots/{id}/drift": {
      "get": {
        "summary": "Marquee — drift overlay",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "drift overlay or { available: false }",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/v1/snapshots/{id}/briefing": {
      "get": {
        "summary": "Marquee — sub-editor briefing block",
        "description": "The snapshot's editorial furniture — headline, standfirst, priority\nlabels, tight bottom line, pull quote, and sentiment — as written by\nthe sub-editor at scan time. Snapshots that predate the sub-editor\n(or whose pass failed open) return `{ available: false }` with 200;\n404 is reserved for the snapshot itself being missing.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "`{ available: true, ...SnapshotBriefing }` or `{ available: false }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "available": {
                      "type": "boolean"
                    }
                  },
                  "additionalProperties": true,
                  "description": "When available is true, the remaining properties are the SnapshotBriefing fields."
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/v1/exports": {
      "post": {
        "summary": "Create an export job",
        "description": "Queues an export against a snapshot you own. Exports run async on a\nworker — poll `status_url`, then fetch `download_url` once\n`status=completed`.\n\n**The recipe:**\n1. `GET /v1/meta/export-types` — each row carries `provider`,\n   `artifact_type`, and `options_schema` (field → type/enum/default).\n2. POST here with `snapshot_id` + the `provider`/`artifact_type` pair\n   and any `options` from that schema.\n3. Poll `GET /v1/exports/{id}` until `completed`; download.\n\n**Required body fields:**\n- `snapshot_id` — UUID of a snapshot owned by the caller (from\n  `POST /v1/scans` or `GET /v1/snapshots`).\n- `provider` + `artifact_type` — must be a registered pair\n  (422 with the valid list otherwise).\n\n**`options` is validated, not a free-for-all.** All option values are\nstrings. Unknown option names and out-of-vocabulary enum values return\n422 with did-you-mean hints and the full valid list. The authoritative\nper-type schema is in `GET /v1/meta/export-types` → `options_schema`.\n\n**targetLanguage compatibility note (July 2026; `simple` added\nAugust 2026).** This option is now a closed product-locale enum:\n`en, fr, de, es, it, zh, ja, simple` — `simple` is Simplified English,\na plain-language register of English rather than a translation. Earlier\nAPI-v1 metadata described an open BCP-47-like value. Unsupported values\nnow return 422 `unsupported_locale`. `targetLanguage` records requested\noutput intent; consumers must inspect `effective_locale` and\n`locale_provenance` for the language actually produced.\n\n**styleSlug availability note (August 2026).** The imagery `styleSlug`\nenums (`gemini-infographic:*`, `openai-infographic:*`) can be reduced\nat any time by platform administration: a style withdrawn from the\ncatalogue disappears from `GET /v1/meta/export-types` →\n`options_schema` and returns 422 `invalid_value` here. Always read the\ncurrent enum from the meta endpoint rather than caching it.\n\nFull map:\n\n| provider:artifact_type | options |\n|---|---|\n| `internal-pdf:pdf`, `internal-csv:csv` | none |\n| `internal-briefing-pdf:briefing-pdf` | `targetLanguage` |\n| `internal-pptx:slide-deck` | `theme` (light/dark), `editorialModel` (**deprecated — accepted but ignored**), `targetLanguage` |\n| `internal-tearsheet:tearsheet` | `editorialModel` (**deprecated — accepted but ignored**), `targetLanguage` |\n| `frontend-slides:slide-web` | `styleSlug` (dark-editorial/light-consulting), `targetLanguage` |\n| `gemini-infographic:*` | `styleSlug` (visual styles; see meta), `planModel` (sonnet5/sonnet/opus/opus48), `targetLanguage` |\n| `openai-infographic:*` | `styleSlug` (visual styles; see meta), `targetLanguage` |\n| `notebooklm:audio` | `audio_format` (DEBATE/DEEP_DIVE/BRIEF/CRITIQUE), `audio_length` (SHORT/DEFAULT/LONG), `instructions`, `targetLanguage` |\n| `notebooklm:infographic` | `style`, `detail_level`, `orientation`, `instructions`, `targetLanguage` |\n| `notebooklm:slide-detailed` | `slide_length` (SHORT/DEFAULT), `instructions`, `targetLanguage` |\n| `notebooklm:video-explainer` | `instructions`, `targetLanguage` |\n| `notebooklm:video` | `video_format` (CINEMATIC), `instructions`, `targetLanguage` |\n| `elevenlabs-briefing:podcast-briefing` | `voiceId`, `modelId` (alias `model_id`), `targetLanguage` |\n| `elevenlabs-discussion:podcast-discussion` | `modelId` (alias `model_id`), `targetLanguage` |\n\nNote: free-text `instructions` are subject to the account's\nartefact-customisation gate — when customisation is disabled for your\naccount, admin defaults are substituted server-side.\n\n**Idempotency.** Send `Idempotency-Key` to make retries safe; same\n24h window and 409 codes as `/v1/scans`.\n\n**Failure modes.**\n- `404 not_found` — snapshot_id not owned by caller.\n- `409 idempotency_key_in_use` / `idempotency_in_progress` — see\n  Idempotency above.\n- `422 validation_error` — missing field, unknown\n  `provider:artifact_type` combination, or invalid `options`\n  (each error names the offending field and the valid vocabulary).\n- `503` — database unavailable.\n",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "1-255 ASCII chars. 24-hour replay window.",
            "schema": {
              "type": "string",
              "maxLength": 255,
              "minLength": 1
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExportCreate"
              },
              "examples": {
                "pdf": {
                  "summary": "Internal PDF (no options)",
                  "value": {
                    "snapshot_id": "00000000-0000-0000-0000-000000000000",
                    "provider": "internal-pdf",
                    "artifact_type": "pdf"
                  }
                },
                "slideDeck": {
                  "summary": "PPTX slide deck, light theme",
                  "value": {
                    "snapshot_id": "00000000-0000-0000-0000-000000000000",
                    "provider": "internal-pptx",
                    "artifact_type": "slide-deck",
                    "options": {
                      "theme": "light"
                    }
                  }
                },
                "geminiInfographic": {
                  "summary": "Gemini infographic in a riso style",
                  "value": {
                    "snapshot_id": "00000000-0000-0000-0000-000000000000",
                    "provider": "gemini-infographic",
                    "artifact_type": "infographic-custom",
                    "options": {
                      "styleSlug": "riso-tritone-gem",
                      "planModel": "opus"
                    }
                  }
                },
                "notebooklmPodcast": {
                  "summary": "NotebookLM podcast — deep-dive, long",
                  "value": {
                    "snapshot_id": "00000000-0000-0000-0000-000000000000",
                    "provider": "notebooklm",
                    "artifact_type": "audio",
                    "options": {
                      "audio_format": "DEEP_DIVE",
                      "audio_length": "LONG",
                      "instructions": "Focus on enforcement-action implications."
                    }
                  }
                },
                "elevenLabsDiscussion": {
                  "summary": "ElevenLabs two-voice discussion in French",
                  "value": {
                    "snapshot_id": "00000000-0000-0000-0000-000000000000",
                    "provider": "elevenlabs-discussion",
                    "artifact_type": "podcast-discussion",
                    "options": {
                      "targetLanguage": "fr"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Job queued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "export_job_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "queued",
                        "processing",
                        "completed",
                        "failed",
                        "undelivered",
                        "cancelled"
                      ]
                    },
                    "status_url": {
                      "type": "string",
                      "example": "/v1/exports/abc-123"
                    },
                    "download_url": {
                      "type": "string",
                      "nullable": true,
                      "description": "Populated only when status is `completed`.",
                      "example": "/v1/exports/abc-123/download"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "409": {
            "$ref": "#/components/responses/Problem"
          },
          "422": {
            "$ref": "#/components/responses/Problem"
          },
          "503": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "get": {
        "summary": "List export jobs for the caller",
        "parameters": [
          {
            "name": "snapshot_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated list (e.g. queued,processing,completed). Unknown statuses return 422."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "jobs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ExportJob"
                      }
                    }
                  }
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/v1/exports/{id}": {
      "get": {
        "summary": "Poll an export job",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "job status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportJob"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "delete": {
        "summary": "Delete an export job (and its retained artifact bytes)",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/v1/exports/{id}/download": {
      "get": {
        "summary": "Stream the completed artifact",
        "description": "Returns the raw artifact bytes. Inline disposition for audio/video/PDF/\nimage MIME types so embeddable players can fetch the same URL; otherwise\nattachment disposition. Audio + video support HTTP range requests.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "The artifact.",
            "content": {
              "application/octet-stream": {}
            }
          },
          "206": {
            "description": "Partial content (range request)."
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "409": {
            "$ref": "#/components/responses/Problem"
          },
          "410": {
            "$ref": "#/components/responses/Problem"
          },
          "416": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/v1/meta/engines": {
      "get": {
        "summary": "List supported research engines",
        "responses": {
          "200": {
            "description": "Engines + valid horizons.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "label": {
                            "type": "string"
                          },
                          "description": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "horizons": {
                      "type": "array",
                      "items": {
                        "type": "integer"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/meta/models": {
      "get": {
        "summary": "List provider models available for scan configs",
        "responses": {
          "200": {
            "description": "Models grouped by provider.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "provider": {
                            "type": "string"
                          },
                          "label": {
                            "type": "string"
                          },
                          "configured": {
                            "type": "boolean"
                          },
                          "models": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "id": {
                                  "type": "string"
                                },
                                "label": {
                                  "type": "string"
                                },
                                "role": {
                                  "type": "string"
                                },
                                "tier": {
                                  "type": "string",
                                  "nullable": true
                                },
                                "model_id": {
                                  "type": "string"
                                },
                                "pro_variant": {
                                  "type": "object",
                                  "nullable": true,
                                  "properties": {
                                    "id": {
                                      "type": "string"
                                    },
                                    "label": {
                                      "type": "string"
                                    },
                                    "model_id": {
                                      "type": "string"
                                    }
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/meta/jurisdictions": {
      "get": {
        "summary": "List supported jurisdictions (with regulators)",
        "responses": {
          "200": {
            "description": "Jurisdictions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "label": {
                            "type": "string"
                          },
                          "regulators": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/meta/areas": {
      "get": {
        "summary": "List supported regulatory areas",
        "responses": {
          "200": {
            "description": "Areas.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "label": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/meta/export-types": {
      "get": {
        "summary": "List supported export (provider, artifact_type) pairs",
        "responses": {
          "200": {
            "description": "Export types.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "key": {
                            "type": "string"
                          },
                          "label": {
                            "type": "string"
                          },
                          "provider": {
                            "type": "string"
                          },
                          "artifact_type": {
                            "type": "string"
                          },
                          "group": {
                            "type": "string"
                          },
                          "provider_label": {
                            "type": "string"
                          },
                          "requires_provider": {
                            "type": "string",
                            "nullable": true
                          },
                          "has_options": {
                            "type": "boolean"
                          },
                          "options_schema": {
                            "type": "object",
                            "description": "Option vocabulary for POST /v1/exports `options`,\nkeyed by field name. Each descriptor carries\n`type` (string), optional `enum` (valid values),\n`default`, `description`, and `maxLength`.\nEmpty object = this type takes no options.\n",
                            "additionalProperties": {
                              "type": "object",
                              "properties": {
                                "type": {
                                  "type": "string"
                                },
                                "enum": {
                                  "type": "array",
                                  "items": {
                                    "type": "string"
                                  }
                                },
                                "default": {
                                  "type": "string"
                                },
                                "description": {
                                  "type": "string"
                                },
                                "maxLength": {
                                  "type": "integer"
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/usage/budget": {
      "get": {
        "summary": "Current account budget + spend",
        "description": "Returns the budget pool the caller's account is constrained by. Same\naccounting path the dashboard and `POST /v1/scans` (402) consult.\nAuthenticated via bearer or Clerk session.\n",
        "responses": {
          "200": {
            "description": "Budget snapshot.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BudgetSnapshot"
                }
              }
            }
          }
        }
      }
    },
    "/v1/usage": {
      "get": {
        "summary": "Usage totals + buckets",
        "description": "Defaults to last 30 days, grouped by day. Authenticated via bearer\n(API consumers) or Clerk session (dashboard). `from` must not be\nafter `to` (422 otherwise). When `group_by=key`, each bucket also\ncarries `key_name`, `key_prefix`, and `key_revoked` so callers can\nattribute usage without a second lookup.\n",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "group_by",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "day",
                "endpoint",
                "key"
              ],
              "default": "day"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "usage",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "totals": {
                      "type": "object",
                      "properties": {
                        "request_count": {
                          "type": "integer"
                        },
                        "scan_count": {
                          "type": "integer"
                        },
                        "export_count": {
                          "type": "integer"
                        },
                        "error_count": {
                          "type": "integer",
                          "description": "Requests with status >= 400"
                        },
                        "total_cost_cents": {
                          "type": "number",
                          "description": "May carry sub-cent precision from reconciled scan actuals."
                        },
                        "avg_latency_ms": {
                          "type": "integer",
                          "nullable": true
                        }
                      }
                    },
                    "buckets": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "key": {
                            "type": "string",
                            "description": "Day (YYYY-MM-DD), 'METHOD /path', or api_key id depending on group_by"
                          },
                          "request_count": {
                            "type": "integer"
                          },
                          "scan_count": {
                            "type": "integer"
                          },
                          "export_count": {
                            "type": "integer"
                          },
                          "error_count": {
                            "type": "integer"
                          },
                          "cost_cents": {
                            "type": "number",
                            "description": "May carry sub-cent precision from reconciled scan actuals."
                          },
                          "avg_latency_ms": {
                            "type": "integer",
                            "nullable": true
                          },
                          "key_name": {
                            "type": "string",
                            "nullable": true,
                            "description": "Present when group_by=key"
                          },
                          "key_prefix": {
                            "type": "string",
                            "nullable": true,
                            "description": "Present when group_by=key"
                          },
                          "key_revoked": {
                            "type": "boolean",
                            "nullable": true,
                            "description": "Present when group_by=key"
                          }
                        }
                      }
                    },
                    "filters": {
                      "type": "object",
                      "properties": {
                        "from": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "to": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "group_by": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "regsn_live_<32 base62 chars>"
      }
    },
    "parameters": {
      "PathId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      }
    },
    "responses": {
      "Problem": {
        "description": "Error",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      }
    },
    "schemas": {
      "Problem": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "instance": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "request_id": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string"
                },
                "code": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "ApiKey": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "key_prefix": {
            "type": "string",
            "example": "regsn_live_x9k…"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_used_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "revoked_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "project_id": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "ApiKeyWithRaw": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ApiKey"
          },
          {
            "type": "object",
            "properties": {
              "key": {
                "type": "string",
                "description": "Raw key — returned exactly once. Store securely.",
                "example": "regsn_live_aBcD1234eFgH5678iJkL9012mNoP3456"
              }
            }
          }
        ]
      },
      "ScanConfig": {
        "type": "object",
        "required": [
          "jurisdictions",
          "areas",
          "horizon"
        ],
        "description": "Scan request body. Mirrors the wizard config used by the regsn.app UI.\nDiscoverable enums live behind `/v1/meta/*`.\n",
        "properties": {
          "jurisdictions": {
            "type": "array",
            "description": "Jurisdiction IDs from GET /v1/meta/jurisdictions.",
            "items": {
              "type": "string",
              "maxLength": 99
            },
            "example": [
              "UK",
              "EU"
            ],
            "minItems": 1,
            "maxItems": 20
          },
          "areas": {
            "type": "array",
            "description": "Area labels from GET /v1/meta/areas.",
            "items": {
              "type": "string",
              "maxLength": 199
            },
            "example": [
              "AML / KYC",
              "Sanctions"
            ],
            "minItems": 1,
            "maxItems": 20
          },
          "horizon": {
            "type": "integer",
            "description": "Months of forward horizon.",
            "enum": [
              3,
              6,
              12,
              18,
              24,
              36
            ],
            "example": 12
          },
          "engine": {
            "type": "string",
            "enum": [
              "v4",
              "v4.5-alpha",
              "v4.5-beta"
            ],
            "default": "v4",
            "description": "Research engine to run. Verifier-aware engines (`v4`, `v4.5-alpha`,\n`v4.5-beta`) accept `verificationMode`, `realist`, `auditor`.\nThe retired engines `v1`, `v2`, `v3` and `admiral` are rejected\nwith 422 `engine_deprecated`.\n"
          },
          "model": {
            "type": "string",
            "example": "sonnet",
            "description": "Provider model ID; see GET /v1/meta/models. `haiku` is rejected on verifier-aware paths."
          },
          "searchModel": {
            "type": "string",
            "example": "sonnet",
            "description": "Provider model used for the search pass."
          },
          "verificationMode": {
            "type": "string",
            "enum": [
              "in-analyst"
            ],
            "nullable": true,
            "description": "Only valid when `engine` is `v4`, `v4.5-alpha` or `v4.5-beta`."
          },
          "realist": {
            "type": "boolean",
            "description": "Verifier-aware engines only."
          },
          "auditor": {
            "type": "boolean",
            "description": "Verifier-aware engines only."
          },
          "auditorModel": {
            "type": "string",
            "enum": [
              "luna",
              "sonnet"
            ],
            "description": "Auditor model. `luna` (GPT-5.6 Luna, reasoning effort xhigh) is the default since Aug 2026; `sonnet` (Sonnet 4.6) remains valid as the legacy/rollback value. The audited sidecar records which model ran in `auditor._meta.auditorModel`."
          },
          "fetchProvider": {
            "type": "string",
            "enum": [
              "firecrawl",
              "crw"
            ],
            "nullable": true,
            "description": "Page-fetch backend for the `fetch_url` tool. Only valid when\n`engine` is `v4.5-beta`. When omitted the server resolves the\ndeployment's `FETCH_PROVIDER` dial, else `crw`; the resolved\nvalue is recorded on the snapshot's scan config.\n"
          },
          "searchProvider": {
            "type": "string",
            "enum": [
              "serper",
              "serpapi"
            ],
            "nullable": true,
            "description": "Google-SERP backend for the `search_web` tool. Only valid when\n`engine` is `v4.5-beta`. When omitted the server resolves the\ndeployment's `SEARCH_PROVIDER` dial, else `serper`; the resolved\nvalue is recorded on the snapshot's scan config.\n"
          },
          "verifyProvider": {
            "type": "string",
            "enum": [
              "firecrawl",
              "crw"
            ],
            "nullable": true,
            "description": "Page-read backend for event verification in the QA tail (the\nDrifter station's setting). Valid on every engine. When omitted\nthe server falls through in order: `crw` for a scan carrying\n`fetchProvider: crw` on any engine, or for a `v4.5-beta` scan\nwhose page reads resolve to `crw` (the `fetchProvider` field,\nelse the deployment's `FETCH_PROVIDER` dial, else the `crw`\ndefault); else the deployment's `DRIFTER_SCRAPE_PROVIDER` dial,\nelse `firecrawl`. The resolved value is recorded on the\nsnapshot's scan config.\n"
          }
        }
      },
      "ScanEstimateRequest": {
        "type": "object",
        "required": [
          "jurisdictions",
          "areas"
        ],
        "description": "Body for POST /v1/scans/estimate. Same shape as ScanConfig, but\n`horizon` is optional (validated only when supplied) and the\nestimator additionally accepts provider hints that feed the\nfingerprint's provider mix.\n",
        "properties": {
          "jurisdictions": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "description": "Counted, not name-matched — stub strings are fine."
          },
          "areas": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "description": "Counted, not name-matched — stub strings are fine."
          },
          "horizon": {
            "type": "integer",
            "enum": [
              3,
              6,
              12,
              18,
              24,
              36
            ]
          },
          "engine": {
            "type": "string",
            "enum": [
              "v4",
              "v4.5-alpha",
              "v4.5-beta"
            ],
            "default": "v4"
          },
          "seekerProvider": {
            "type": "string",
            "description": "Provider hint for the fingerprint's provider mix."
          },
          "analystProvider": {
            "type": "string",
            "description": "Provider hint for the fingerprint's provider mix."
          },
          "translatorProvider": {
            "type": "string",
            "description": "Provider hint for the fingerprint's provider mix."
          }
        }
      },
      "ScanQueued": {
        "type": "object",
        "properties": {
          "scan_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "running"
            ]
          },
          "status_url": {
            "type": "string"
          },
          "stream_url": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "ScanEstimate": {
        "type": "object",
        "properties": {
          "low_cents": {
            "type": "number",
            "nullable": true,
            "description": "P10 of matched historical actuals (may be fractional cents)."
          },
          "expected_cents": {
            "type": "number",
            "nullable": true,
            "description": "P50 (tiers 1-2) or similarity-weighted mean (tier 3)."
          },
          "high_cents": {
            "type": "number",
            "nullable": true,
            "description": "P90 of matched historical actuals (may be fractional cents)."
          },
          "confidence": {
            "type": "string",
            "enum": [
              "high",
              "medium",
              "low",
              "cold_start"
            ]
          },
          "tier": {
            "type": "integer",
            "enum": [
              1,
              2,
              3
            ],
            "nullable": true,
            "description": "Bucket the estimator landed in. 1 = exact-match window, 2 = relaxed\none dimension at a time, 3 = global similarity-weighted mean.\nnull on cold-start fallback.\n"
          },
          "sample_size": {
            "type": "integer"
          },
          "fingerprint": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Structured fingerprint of the supplied scan config. Stable across\ncalls with equivalent counts/providers/horizon/engine; used to look\nup matching historical actuals.\n",
            "properties": {
              "scan_mode": {
                "type": "string"
              },
              "engine_version": {
                "type": "string"
              },
              "jurisdiction_bucket": {
                "type": "string"
              },
              "area_bucket": {
                "type": "string"
              },
              "enabled_exports_count": {
                "type": "integer"
              },
              "provider_mix_hash": {
                "type": "string"
              },
              "date_range_bucket": {
                "type": "string"
              }
            }
          },
          "estimator_version": {
            "type": "string",
            "example": "v2.0"
          },
          "engine": {
            "type": "string"
          }
        }
      },
      "ExportCreate": {
        "type": "object",
        "required": [
          "snapshot_id",
          "artifact_type",
          "provider"
        ],
        "properties": {
          "snapshot_id": {
            "type": "string",
            "format": "uuid",
            "description": "Snapshot to export. Must be owned by the caller."
          },
          "provider": {
            "type": "string",
            "description": "Provider key; see GET /v1/meta/export-types.",
            "example": "internal-pdf"
          },
          "artifact_type": {
            "type": "string",
            "description": "Artifact key, valid only paired with the matching provider.",
            "example": "pdf"
          },
          "options": {
            "type": "object",
            "description": "Per-type option fields — validated against the registry\n(GET /v1/meta/export-types → options_schema). All values must be\nstrings. Unknown fields or out-of-vocabulary values return 422\nwith did-you-mean hints.\n",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "BudgetSnapshot": {
        "type": "object",
        "properties": {
          "allowed": {
            "type": "boolean"
          },
          "budget_cents": {
            "type": "integer",
            "nullable": true,
            "description": "null means no budget cap."
          },
          "spent_cents": {
            "type": "number",
            "description": "May carry sub-cent precision from reconciled ledger actuals."
          },
          "remaining_cents": {
            "type": "number",
            "nullable": true,
            "description": "null means unlimited."
          },
          "role": {
            "type": "string",
            "enum": [
              "user",
              "admin"
            ]
          },
          "message": {
            "type": "string"
          }
        }
      },
      "ExportJob": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "processing",
              "completed",
              "failed",
              "undelivered",
              "cancelled"
            ]
          },
          "artifact_type": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "snapshot_id": {
            "type": "string",
            "format": "uuid"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "cost_cents": {
            "type": "number",
            "nullable": true
          },
          "error": {
            "type": "string",
            "nullable": true
          },
          "result_mime": {
            "type": "string",
            "nullable": true
          },
          "result_filename": {
            "type": "string",
            "nullable": true
          },
          "result_size_bytes": {
            "type": "integer",
            "nullable": true
          },
          "download_url": {
            "type": "string",
            "nullable": true,
            "description": "Populated only when status is `completed`."
          }
        }
      },
      "ScanStatus": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "running",
              "completed",
              "failed",
              "cancelled"
            ]
          },
          "progress": {
            "type": "object",
            "properties": {
              "phase": {
                "type": "string"
              },
              "percent": {
                "type": "integer"
              },
              "message": {
                "type": "string"
              }
            }
          },
          "snapshot_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "cost_cents": {
            "type": "number",
            "nullable": true,
            "description": "Reconciled ledger actual when available (sub-cent float)",
            "else the whole-cent estimate. Null until the scan completes.": null
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "error": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "SnapshotListItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "item_count": {
            "type": "integer"
          },
          "trend_count": {
            "type": "integer"
          },
          "jurisdiction_count": {
            "type": "integer"
          },
          "cost_cents": {
            "type": "number",
            "nullable": true,
            "description": "Legacy whole-cent estimate."
          },
          "actual_cost_cents": {
            "type": "number",
            "nullable": true,
            "description": "Reconciled ledger actual — sub-cent float (e.g. 131.2245)."
          }
        }
      },
      "SnapshotEnvelope": {
        "type": "object",
        "description": "The snapshot `data` JSONB envelope projected from the research engine\nand post-scan QA tail. Open shape — keys beyond those documented here\nmay appear depending on engine and config. Inert inline citation-marker\ntokens are omitted from every plain-prose field, including materialised\ntranslations. Structured Auditor, source, citation, URL and identity\nsidecars retain their schema; operational Auditor failure excerpts are\nconfined to the private audit record and are never included here.\n",
        "required": [
          "items",
          "trends",
          "executive_summary",
          "_meta"
        ],
        "properties": {
          "items": {
            "type": "array",
            "description": "Regulatory items emitted by the Analyst, with inert inline citation-marker tokens omitted from plain-prose fields.",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "trends": {
            "type": "array",
            "description": "Cross-item trends, with inert inline citation-marker tokens omitted from plain-prose fields.",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "executive_summary": {
            "type": "object",
            "additionalProperties": true,
            "description": "Executive-summary prose with inert inline citation-marker tokens omitted."
          },
          "executive_narrative": {
            "type": "string",
            "nullable": true,
            "description": "English executive narrative prose. Public API responses omit inert inline citation-marker tokens; structured Auditor metadata remains in `auditor`."
          },
          "_meta": {
            "type": "object",
            "additionalProperties": true,
            "description": "Engine/run metadata (engine, model, searches, ...)."
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Non-fatal engine warnings (truncation recovery etc.)."
          },
          "auditor": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Auditor sidecar — present on verifier-aware runs with `auditor` enabled. Operational `_meta.failures` evidence is private to `audit_records` and is omitted."
          },
          "_briefing": {
            "nullable": true,
            "description": "Sub-editor briefing block (post-scan QA tail). See SnapshotBriefing.",
            "allOf": [
              {
                "$ref": "#/components/schemas/SnapshotBriefing"
              }
            ]
          },
          "translations": {
            "type": "object",
            "nullable": true,
            "description": "Translated blocks keyed by language code (e.g. `fr`), each carrying `executive_narrative` and friends with inert inline citation-marker tokens omitted from plain prose.",
            "additionalProperties": {
              "type": "object"
            }
          },
          "driftOverlay": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Legacy drift overlay (older snapshots only) — served by GET /v1/snapshots/{id}/drift, which returns `{ available: false }` when absent."
          }
        },
        "additionalProperties": true
      },
      "SnapshotBriefing": {
        "type": "object",
        "description": "Sub-editor briefing block — the editorial furniture written by the\npost-scan QA tail (server/subeditor.js) and spread verbatim onto the\nenvelope as `_briefing`. Seven fields total. The three live fields\n(`pull_quote`, `sentiment_label`, `sentiment_rationale`) are always\npresent when the block exists; the four A-fields\n(`briefing_headline`, `briefing_standfirst`, `priorities`,\n`bottom_line_tight`) are validated per-field and ABSENT on validation\nfailure — clients render deterministic fallbacks, never partial junk.\n",
        "required": [
          "pull_quote",
          "sentiment_label",
          "sentiment_rationale"
        ],
        "properties": {
          "briefing_headline": {
            "type": "string",
            "description": "Editorial headline, 6 words or fewer, ends with a full stop. Absent when validation failed."
          },
          "briefing_standfirst": {
            "type": "string",
            "description": "One-sentence standfirst under the headline. Absent when validation failed."
          },
          "priorities": {
            "type": "array",
            "description": "Exactly one entry per executive-summary priority, in the same\norder (1:1 with the briefing's numbered Priority sections).\nWithin an aligned array, each field is individually optional — a\nbad label or tag is omitted from that entry only. The whole array\nis ABSENT when it could not be aligned with the source priorities.\n",
            "items": {
              "type": "object",
              "properties": {
                "short_label": {
                  "type": "string",
                  "description": "Contents-list label, 1-3 words (e.g. \"MiCA enforcement\")."
                },
                "tags": {
                  "type": "string",
                  "description": "Single tag string in the exact format \"JURISDICTION · INSTRUMENT\" (middle-dot separator, e.g. \"EU · MiCA\"). Singular value despite the plural key."
                }
              },
              "additionalProperties": true
            }
          },
          "bottom_line_tight": {
            "type": "string",
            "description": "The briefing's \"so what\" in 1-2 sentences; closes the page. Absent when validation failed."
          },
          "pull_quote": {
            "type": "string",
            "description": "Lean 2-sentence executive pull-quote (~40 words) lifted from the body of the analyst narrative."
          },
          "sentiment_label": {
            "type": "string",
            "enum": [
              "Tightening",
              "Stable",
              "Easing",
              "Mixed",
              "Volatile"
            ],
            "description": "One-word overall regulatory-posture read."
          },
          "sentiment_rationale": {
            "type": "string",
            "description": "One sentence (max 30 words) explaining the sentiment label."
          }
        },
        "additionalProperties": true
      }
    }
  }
}