{
  "components": {
    "schemas": {
      "AnalyzeConflictsRequestDto": {
        "properties": {
          "figures": {
            "description": "Figures extracted across one or more documents. The engine groups compatible entities, periods, currencies, scales, bases and definitions, recomputes derivable metrics, and reports conflicts or unavailable comparisons.",
            "items": {
              "$ref": "#/components/schemas/FigureDto"
            },
            "type": "array"
          },
          "fxRates": {
            "description": "Source-linked FX observations. A rate is used only for its exact applicable period and declared conversion direction.",
            "items": {
              "$ref": "#/components/schemas/FxRateDto"
            },
            "type": "array"
          },
          "precedenceRules": {
            "description": "Reviewed, scope-exact decisions that select one observation and enumerate every superseded competing identity.",
            "items": {
              "$ref": "#/components/schemas/ObservationPrecedenceDto"
            },
            "type": "array"
          }
        },
        "required": [
          "figures"
        ],
        "type": "object"
      },
      "AnalyzeDocumentRequestDto": {
        "properties": {
          "asset_id": {
            "description": "Uploaded asset id (from POST /upload). The spreadsheet is downloaded, figures are extracted deterministically with cell provenance, then recomputed and cross-checked.",
            "example": "123e4567-e89b-12d3-a456-426614174000",
            "type": "string"
          }
        },
        "required": [
          "asset_id"
        ],
        "type": "object"
      },
      "AnchoredChunkDto": {
        "properties": {
          "ordinal": {
            "description": "Position of this chunk in the document, from 0.",
            "type": "number"
          },
          "page": {
            "description": "1-based page the chunk came from.",
            "type": "number"
          },
          "section": {
            "description": "Nearest preceding markdown heading.",
            "type": "string"
          },
          "sheet": {
            "description": "Workbook tab the chunk came from.",
            "type": "string"
          },
          "text": {
            "description": "The chunk text.",
            "type": "string"
          },
          "token_estimate": {
            "description": "Rough token count (~4 chars/token) for packing a retrieval budget.",
            "type": "number"
          }
        },
        "required": [
          "ordinal",
          "text",
          "token_estimate"
        ],
        "type": "object"
      },
      "BatchItemDto": {
        "properties": {
          "asset_id": {
            "description": "An uploaded asset id to check against.",
            "type": "string"
          },
          "id": {
            "description": "Caller id echoed back on the result.",
            "type": "string"
          },
          "meaning": {
            "description": "A meaning-preservation problem you have already found, from an assessment such as POST /api/v2/context/assess-meaning. Only the cautious states are accepted: \"changed\" and \"ambiguous\" stop a citation presenting as verified. \"faithful\" is deliberately NOT accepted here — a clearance asserted by the party asking for the check is not a check, and this field exists precisely so that \"nobody assessed this\" cannot be mistaken for \"an assessor cleared this\". Omit it when you found no problem; the result then reports meaning \"not_checked\", which is what absence honestly means.",
            "enum": [
              "changed",
              "ambiguous"
            ],
            "type": "string"
          },
          "page": {
            "description": "1-based page for an asset source.",
            "type": "number"
          },
          "quote": {
            "description": "The claim or quote to check.",
            "type": "string"
          },
          "representation_id": {
            "type": "string"
          },
          "source_text": {
            "description": "The source text to check the quote against. Provide this, or url, or asset_id.",
            "type": "string"
          },
          "source_unit_id": {
            "type": "string"
          },
          "source_version_id": {
            "description": "Immutable source version. Requires exact representation and unit IDs.",
            "type": "string"
          },
          "url": {
            "description": "A web source URL to resolve and check.",
            "type": "string"
          }
        },
        "required": [
          "quote"
        ],
        "type": "object"
      },
      "BillingUsageDto": {
        "properties": {
          "credits": {
            "description": "Credits actually deducted; null when a priced operation was not metered. Zero denotes a genuinely free operation.",
            "nullable": true,
            "type": "number"
          }
        },
        "required": [
          "credits"
        ],
        "type": "object"
      },
      "BindBackDto": {
        "properties": {
          "grounded": {
            "description": "True when the quote was found in the resolved source.",
            "type": "boolean"
          },
          "matched_quote": {
            "description": "The matched substring, when grounded verbatim.",
            "type": "string"
          },
          "matched_text": {
            "description": "The best-matching passage found in the source — present even when unbound, so a reviewer sees the closest evidence.",
            "type": "string"
          },
          "method": {
            "description": "How the quote matched: exact/normalized substring, fuzzy passage match, reflowed OCR reading, or unbound.",
            "enum": [
              "exact",
              "normalized",
              "fuzzy",
              "reflowed",
              "unbound"
            ],
            "type": "string"
          },
          "reading": {
            "description": "Derived body reading with page furniture removed; absent for stored text.",
            "enum": [
              "body"
            ],
            "type": "string"
          },
          "rescue": {
            "description": "OCR rescue outcome: attempted=false with code/reason, attempted=true and rescued=false (optionally ambiguous), or rescued=true with transformations, raw dropped lines and retained spans. Unknown or unattempted is never a failed rescue.",
            "oneOf": [
              {
                "properties": {
                  "attempted": {
                    "enum": [
                      false
                    ],
                    "type": "boolean"
                  },
                  "code": {
                    "enum": [
                      "not_ocr",
                      "labels_not_available",
                      "judge_unavailable",
                      "not_prepared",
                      "disabled",
                      "resource_limit"
                    ],
                    "type": "string"
                  },
                  "reason": {
                    "type": "string"
                  }
                },
                "required": [
                  "attempted",
                  "code",
                  "reason"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "ambiguous": {
                    "enum": [
                      true
                    ],
                    "type": "boolean"
                  },
                  "attempted": {
                    "enum": [
                      true
                    ],
                    "type": "boolean"
                  },
                  "rescued": {
                    "enum": [
                      false
                    ],
                    "type": "boolean"
                  }
                },
                "required": [
                  "attempted",
                  "rescued"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "attempted": {
                    "enum": [
                      true
                    ],
                    "type": "boolean"
                  },
                  "basis": {
                    "enum": [
                      "code",
                      "measured"
                    ],
                    "type": "string"
                  },
                  "calibrationKey": {
                    "type": "string"
                  },
                  "decidedAt": {
                    "format": "date-time",
                    "type": "string"
                  },
                  "droppedLines": {
                    "items": {
                      "type": "string"
                    },
                    "type": "array"
                  },
                  "model": {
                    "nullable": true,
                    "type": "string"
                  },
                  "reading": {
                    "description": "Derived matching text; display the raw preview text instead.",
                    "type": "string"
                  },
                  "rescued": {
                    "enum": [
                      true
                    ],
                    "type": "boolean"
                  },
                  "retainedSpans": {
                    "items": {
                      "properties": {
                        "fromLine": {
                          "type": "integer"
                        },
                        "page": {
                          "type": "integer"
                        },
                        "toLine": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "page",
                        "fromLine",
                        "toLine"
                      ],
                      "type": "object"
                    },
                    "type": "array"
                  },
                  "transformations": {
                    "items": {
                      "properties": {
                        "afterLine": {
                          "type": "integer"
                        },
                        "atLine": {
                          "type": "integer"
                        },
                        "kind": {
                          "enum": [
                            "dehyphenate",
                            "join",
                            "drop"
                          ],
                          "type": "string"
                        },
                        "line": {
                          "type": "string"
                        },
                        "source": {
                          "enum": [
                            "element_label",
                            "repetition",
                            "page_number",
                            "margin_band"
                          ],
                          "type": "string"
                        }
                      },
                      "required": [
                        "kind"
                      ],
                      "type": "object"
                    },
                    "type": "array"
                  }
                },
                "required": [
                  "attempted",
                  "rescued",
                  "reading",
                  "transformations",
                  "droppedLines",
                  "retainedSpans",
                  "calibrationKey",
                  "basis",
                  "model",
                  "decidedAt"
                ],
                "type": "object"
              }
            ]
          },
          "score": {
            "description": "Similarity of the best match, 0..1.",
            "type": "number"
          }
        },
        "required": [
          "grounded",
          "method"
        ],
        "type": "object"
      },
      "CitationDto": {
        "properties": {
          "author": {
            "description": "Author or domain name",
            "example": "example",
            "type": "string"
          },
          "credibility_basis": {
            "description": "Whether credibility_score rests on an actual measurement ('measured') or is a placeholder because no check has run yet ('unknown', e.g. no grounding signal or the citation has not been analysed). Absent means the same as \"measured\" for citations produced before this field existed.",
            "enum": [
              "measured",
              "unknown"
            ],
            "example": "measured",
            "type": "string"
          },
          "credibility_score": {
            "description": "Credibility/relevance score from 1-100 (higher = more credible/relevant/authoritative)",
            "example": 95,
            "type": "number"
          },
          "group_id": {
            "description": "Group ID for citations from the same domain",
            "example": "group-example-com",
            "type": "string"
          },
          "highlight_ranges": {
            "description": "Character ranges [start, end] in snippet that are most relevant to the claim",
            "example": [
              [
                10,
                25
              ],
              [
                40,
                55
              ]
            ],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "id": {
            "description": "Unique citation identifier",
            "example": "1",
            "type": "string"
          },
          "is_group_primary": {
            "description": "Whether this is the primary (highest-ranked) citation from its domain group",
            "example": true,
            "type": "boolean"
          },
          "publication_year": {
            "description": "Publication year of the cited source",
            "example": 2024,
            "type": "number"
          },
          "rank": {
            "description": "Sequential rank (1, 2, 3...) where rank 1 is the highest/best citation, based on credibility_score and verified status",
            "example": 1,
            "type": "number"
          },
          "ranking_factors": {
            "allOf": [
              {
                "$ref": "#/components/schemas/RankingFactorsDto"
              }
            ],
            "description": "Breakdown of how the ranking score was calculated"
          },
          "snippet": {
            "description": "Relevant excerpt from the source",
            "example": "This is a snippet from the article...",
            "type": "string"
          },
          "source_metadata": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SourceMetadataDto"
              }
            ],
            "description": "Additional metadata about the source"
          },
          "source_type": {
            "description": "Source type: Government Document, Dataset, Journal, Report, Trusted Media, Article, Reddit, Social Media",
            "enum": [
              "Government Document",
              "Dataset",
              "Journal",
              "Report",
              "Trusted Media",
              "Article",
              "Reddit",
              "Social Media"
            ],
            "example": "Article",
            "type": "string"
          },
          "stance": {
            "description": "Whether this citation supports or contradicts the claim (only with useEnhancedAnalysis)",
            "enum": [
              "supports",
              "contradicts",
              "partially_supports",
              "neutral",
              "inconclusive"
            ],
            "example": "supports",
            "type": "string"
          },
          "stance_confidence": {
            "description": "Confidence in the stance assessment (0-100)",
            "example": 90,
            "type": "number"
          },
          "stance_explanation": {
            "description": "Brief explanation of the stance determination",
            "example": "The source directly confirms the key claim with supporting data.",
            "type": "string"
          },
          "status": {
            "description": "Verification status of the citation",
            "example": "verified",
            "type": "string"
          },
          "title": {
            "description": "Title of the cited source",
            "example": "Example Article Title",
            "type": "string"
          },
          "url": {
            "description": "URL of the cited source",
            "example": "https://example.com/article",
            "type": "string"
          }
        },
        "required": [
          "title",
          "url",
          "snippet",
          "id",
          "author",
          "status",
          "credibility_score",
          "rank"
        ],
        "type": "object"
      },
      "CitationListResponseDto": {
        "properties": {
          "data": {
            "description": "Array of citation records",
            "example": [
              {
                "citation": "[{\"title\":\"Example\",\"url\":\"https://example.com\"}]",
                "created_at": "2024-01-01T00:00:00.000Z",
                "id": "123e4567-e89b-12d3-a456-426614174000",
                "is_active": true,
                "prompt": "Latest trends in AI",
                "thread_id": "thread-123"
              }
            ],
            "items": {
              "$ref": "#/components/schemas/CitationRecordDto"
            },
            "type": "array"
          },
          "message": {
            "description": "Response message",
            "example": "Citations fetched successfully",
            "type": "string"
          },
          "pagination": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PaginationMetaDto"
              }
            ],
            "description": "Pagination metadata"
          },
          "statusCode": {
            "description": "HTTP status code",
            "example": 200,
            "type": "number"
          }
        },
        "required": [
          "data",
          "pagination",
          "statusCode",
          "message"
        ],
        "type": "object"
      },
      "CitationRecordDto": {
        "properties": {
          "api_key_id": {
            "description": "API key ID used for this citation",
            "example": "api-key-123",
            "type": "string"
          },
          "citation": {
            "description": "Citation data (JSON string or array of CitationObject)",
            "example": "[{\"title\":\"Example\",\"url\":\"https://example.com\",\"snippet\":\"...\"}]",
            "type": "object"
          },
          "created_at": {
            "description": "Timestamp when the citation was created",
            "example": "2024-01-01T00:00:00.000Z",
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "description": "Unique identifier of the citation record",
            "example": "123e4567-e89b-12d3-a456-426614174000",
            "type": "string"
          },
          "is_active": {
            "description": "Whether the citation is active",
            "example": true,
            "type": "boolean"
          },
          "prompt": {
            "description": "The original prompt that generated this citation",
            "example": "Latest trends in AI",
            "type": "string"
          },
          "thread_id": {
            "description": "Thread ID associated with this citation",
            "example": "123e4567-e89b-12d3-a456-426614174000",
            "type": "string"
          },
          "updated_at": {
            "description": "Timestamp when the citation was last updated",
            "example": "2024-01-01T00:00:00.000Z",
            "format": "date-time",
            "type": "string"
          },
          "user_id": {
            "description": "User ID who created this citation",
            "example": "user-123",
            "type": "string"
          }
        },
        "required": [
          "id",
          "thread_id",
          "prompt"
        ],
        "type": "object"
      },
      "CitationViewDataDto": {
        "properties": {
          "citation": {
            "description": "Citation data (JSON string or array of CitationObject)",
            "example": "[{\"title\":\"Example Article\",\"url\":\"https://example.com/article\",\"snippet\":\"This is a snippet...\",\"id\":\"1\",\"author\":\"example\",\"status\":\"verified\",\"credibility_score\":95,\"rank\":1}]",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "items": {
                  "$ref": "#/components/schemas/CitationDto"
                },
                "type": "array"
              }
            ]
          },
          "generated_prompts": {
            "description": "Array of generated sub-prompts when useMultiplePrompt was enabled",
            "example": [
              "What are the latest AI trends in 2024?",
              "What are emerging AI technologies?",
              "What are AI adoption trends in industry?"
            ],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "prompt": {
            "description": "The original prompt that generated this citation",
            "example": "Latest trends in AI",
            "type": "string"
          }
        },
        "required": [
          "prompt",
          "citation"
        ],
        "type": "object"
      },
      "CitationViewResponseDto": {
        "properties": {
          "data": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CitationViewDataDto"
              }
            ],
            "description": "Citation data"
          },
          "message": {
            "description": "Response message",
            "example": "Citation fetched successfully",
            "type": "string"
          },
          "statusCode": {
            "description": "HTTP status code",
            "example": 200,
            "type": "number"
          }
        },
        "required": [
          "statusCode",
          "message",
          "data"
        ],
        "type": "object"
      },
      "ClaimGroupDto": {
        "properties": {
          "citation_count": {
            "description": "Number of citations for this claim",
            "example": 5,
            "type": "number"
          },
          "citations": {
            "description": "All citations for this claim (full objects with stance, ranking, etc.)",
            "items": {
              "$ref": "#/components/schemas/CitationDto"
            },
            "type": "array"
          },
          "claim": {
            "description": "The claim/sub-prompt text",
            "example": "What are the AI trends in healthcare?",
            "type": "string"
          },
          "claim_id": {
            "description": "Unique identifier for this claim group",
            "example": "claim-1",
            "type": "string"
          },
          "claim_index": {
            "description": "Index of the claim (1-based)",
            "example": 1,
            "type": "number"
          },
          "domain_groups": {
            "description": "Citations grouped by domain (lightweight, for deduplication UI)",
            "items": {
              "$ref": "#/components/schemas/DomainGroupDto"
            },
            "type": "array"
          },
          "stance_summary": {
            "description": "Overall stance summary based on citations",
            "enum": [
              "supported",
              "contradicted",
              "mixed",
              "unverifiable"
            ],
            "example": "supported",
            "type": "string"
          },
          "verdict": {
            "allOf": [
              {
                "$ref": "#/components/schemas/VerdictDto"
              }
            ],
            "description": "Verdict for this claim (streamed as citations complete)"
          }
        },
        "required": [
          "claim_id",
          "claim_index",
          "claim",
          "stance_summary",
          "citation_count",
          "citations"
        ],
        "type": "object"
      },
      "ClassifyRequestDto": {
        "properties": {
          "asset_id": {
            "description": "An uploaded asset id (from POST /upload). One of asset_id / asset_url is required.",
            "type": "string"
          },
          "asset_url": {
            "description": "A direct URL to the file (e.g. the consumer's own signed GCS URL). One of asset_id / asset_url is required.",
            "type": "string"
          },
          "taxonomy": {
            "description": "Taxonomy preset to classify against. Default \"vc\".",
            "enum": [
              "vc",
              "ma"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "ClassifyResponseDto": {
        "properties": {
          "basis": {
            "enum": [
              "content",
              "filename",
              "fallback"
            ],
            "type": "string"
          },
          "category": {
            "description": "The coarse category, or `unfiled` when nothing matched. Unchanged by a refusal: check `code` to learn whether the document was read at all.",
            "type": "string"
          },
          "code": {
            "description": "The stable classification of `reason`, safe to switch on. The same vocabulary and the same values as `POST /extract`. Null when nothing refused.\n\n| Code | What it means | What to do |\n|------|---------------|------------|\n| `source_too_large` | a size or expansion ceiling refused the read | `reason` carries the observed value and the limit; send a smaller file |\n| `source_encrypted` | the container is password-protected | send an unlocked copy |\n| `source_corrupt` | the container could not be opened as the format it declares | re-export the file |\n| `unsupported_format` | no reader is registered for this format | convert it |\n| `ocr_unavailable` | the page had no text layer and no vision provider is configured | configure a vision key, or send a text-layer file |\n| `partial_extraction` | some of the source was recovered and some was not | use what came back |\n| `empty_source` | the source was read and held nothing | a fact about the file, not a failure — nothing to retry |\n| `extraction_error` | our failure | retry |",
            "enum": [
              "source_too_large",
              "source_encrypted",
              "source_corrupt",
              "unsupported_format",
              "ocr_unavailable",
              "partial_extraction",
              "empty_source",
              "extraction_error"
            ],
            "nullable": true,
            "type": "string"
          },
          "confidence": {
            "description": "Heuristic signal score, not a calibrated probability.",
            "maximum": 1,
            "minimum": 0,
            "type": "number"
          },
          "confidenceAssessment": {
            "properties": {
              "basis": {
                "enum": [
                  "heuristic"
                ],
                "type": "string"
              },
              "calibrated": {
                "enum": [
                  false
                ],
                "type": "boolean"
              },
              "language": {
                "description": "Unknown; the vocabulary census is not a document language detector.",
                "nullable": true,
                "type": "string"
              },
              "languageBasis": {
                "enum": [
                  "unknown"
                ],
                "type": "string"
              },
              "perLanguageCalibration": {
                "enum": [
                  "unavailable"
                ],
                "type": "string"
              }
            },
            "required": [
              "basis",
              "calibrated",
              "language",
              "languageBasis",
              "perLanguageCalibration"
            ],
            "type": "object"
          },
          "covers": {
            "description": "The fine multi-type covers the document holds. Empty when nothing matched.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "reason": {
            "description": "Free text naming the cause, straight from whatever refused or failed — `spreadsheet_magic_mismatch:xlsx`, `spreadsheet_too_large:bytes:33554433>33554432`, `extraction_failed:File is password-protected`. Carries detail no enum can, and is NOT stable: it includes dependency error text. Display it; do not branch on it. Null when nothing refused.",
            "nullable": true,
            "type": "string"
          },
          "state": {
            "description": "How much of the source the read recovered, in one word. `complete` on a healthy read, whatever the payload beside it turned out to contain.",
            "enum": [
              "complete",
              "partial",
              "unsupported",
              "error"
            ],
            "type": "string"
          },
          "sufficiency": {
            "properties": {
              "reason": {
                "type": "string"
              },
              "status": {
                "enum": [
                  "unavailable"
                ],
                "type": "string"
              }
            },
            "required": [
              "status",
              "reason"
            ],
            "type": "object"
          }
        },
        "required": [
          "state",
          "reason",
          "code",
          "confidence",
          "basis",
          "category",
          "covers"
        ],
        "type": "object"
      },
      "ConflictSideDto": {
        "properties": {
          "citation_id": {
            "description": "Id of the citation taking this side",
            "example": "cit_3",
            "type": "string"
          },
          "quote": {
            "description": "The text this source actually carries, quoted rather than summarised, so a reader can check the disagreement themselves",
            "example": "The review concluded the figure was 3.1 billion.",
            "type": "string"
          },
          "source": {
            "description": "The source, by url where known and by domain otherwise",
            "example": "ministry.example.gov",
            "type": "string"
          }
        },
        "required": [
          "citation_id",
          "source",
          "quote"
        ],
        "type": "object"
      },
      "CorrectionDto": {
        "properties": {
          "actual": {
            "description": "What the sources actually say",
            "example": "The corrected value from sources",
            "type": "string"
          },
          "citation_ids": {
            "description": "IDs of citations that support this correction",
            "example": [
              "2"
            ],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "claimed": {
            "description": "What the user originally claimed",
            "example": "The original claimed value",
            "type": "string"
          }
        },
        "required": [
          "claimed",
          "actual",
          "citation_ids"
        ],
        "type": "object"
      },
      "CreateAskDto": {
        "properties": {
          "documentName": {
            "description": "Label for diagnostics and evidence.",
            "example": "10-K FY2025",
            "type": "string"
          },
          "documentText": {
            "description": "Document text. v1 accepts text; file ingest with page splits is a later slice.",
            "type": "string"
          },
          "hasTextLayer": {
            "description": "Set false when the caller knows the source is a scan with no text layer.",
            "type": "boolean"
          },
          "question": {
            "description": "The question to answer over the document.",
            "example": "What was net income in FY2025?",
            "type": "string"
          },
          "topK": {
            "default": 8,
            "description": "Retrieved passages fed to the drafter.",
            "type": "number"
          }
        },
        "required": [
          "question",
          "documentText"
        ],
        "type": "object"
      },
      "DomainGroupDto": {
        "properties": {
          "domain": {
            "description": "Domain that groups these citations",
            "example": "example.com",
            "type": "string"
          },
          "domain_category": {
            "description": "Category of this domain",
            "enum": [
              "government",
              "academic",
              "news",
              "organization",
              "encyclopedia",
              "social",
              "other"
            ],
            "example": "news",
            "type": "string"
          },
          "group_id": {
            "description": "Unique identifier for this domain group",
            "example": "group-example-com",
            "type": "string"
          },
          "group_stance": {
            "description": "Overall stance of citations from this domain",
            "enum": [
              "supports",
              "contradicts",
              "mixed",
              "neutral"
            ],
            "example": "supports",
            "type": "string"
          },
          "primary_citation_id": {
            "description": "ID of the primary (highest-ranked) citation from this domain",
            "example": "1",
            "type": "string"
          }
        },
        "required": [
          "group_id",
          "domain",
          "domain_category",
          "group_stance",
          "primary_citation_id"
        ],
        "type": "object"
      },
      "ExtractFiguresResponseDto": {
        "properties": {
          "code": {
            "description": "The stable classification of `reason`, safe to switch on. The same vocabulary and the same values as `POST /extract`. Null when nothing refused.\n\n| Code | What it means | What to do |\n|------|---------------|------------|\n| `source_too_large` | a size or expansion ceiling refused the read | `reason` carries the observed value and the limit; send a smaller file |\n| `source_encrypted` | the container is password-protected | send an unlocked copy |\n| `source_corrupt` | the container could not be opened as the format it declares | re-export the file |\n| `unsupported_format` | no reader is registered for this format | convert it |\n| `ocr_unavailable` | the page had no text layer and no vision provider is configured | configure a vision key, or send a text-layer file |\n| `partial_extraction` | some of the source was recovered and some was not | use what came back |\n| `empty_source` | the source was read and held nothing | a fact about the file, not a failure — nothing to retry |\n| `extraction_error` | our failure | retry |",
            "enum": [
              "source_too_large",
              "source_encrypted",
              "source_corrupt",
              "unsupported_format",
              "ocr_unavailable",
              "partial_extraction",
              "empty_source",
              "extraction_error"
            ],
            "nullable": true,
            "type": "string"
          },
          "figures": {
            "description": "Every known metric found, as `{ metric, value, unit, entity?, period?, band, bound, provenance }`. Empty both when the document held no recognised metric and when the read was refused.",
            "items": {
              "type": "object"
            },
            "type": "array"
          },
          "reason": {
            "description": "Free text naming the cause, straight from whatever refused or failed — `spreadsheet_magic_mismatch:xlsx`, `spreadsheet_too_large:bytes:33554433>33554432`, `extraction_failed:File is password-protected`. Carries detail no enum can, and is NOT stable: it includes dependency error text. Display it; do not branch on it. Null when nothing refused.",
            "nullable": true,
            "type": "string"
          },
          "state": {
            "description": "How much of the source the read recovered, in one word. `complete` on a healthy read, whatever the payload beside it turned out to contain.",
            "enum": [
              "complete",
              "partial",
              "unsupported",
              "error"
            ],
            "type": "string"
          }
        },
        "required": [
          "state",
          "reason",
          "code",
          "figures"
        ],
        "type": "object"
      },
      "ExtractPagesResponseDto": {
        "properties": {
          "chunks": {
            "description": "Anchored chunks, in document order. Empty both when the document held no text and when the read was refused — `code` is what tells those apart.",
            "items": {
              "$ref": "#/components/schemas/AnchoredChunkDto"
            },
            "type": "array"
          },
          "code": {
            "description": "The stable classification of `reason`, safe to switch on. The same vocabulary and the same values as `POST /extract`. Null when nothing refused.\n\n| Code | What it means | What to do |\n|------|---------------|------------|\n| `source_too_large` | a size or expansion ceiling refused the read | `reason` carries the observed value and the limit; send a smaller file |\n| `source_encrypted` | the container is password-protected | send an unlocked copy |\n| `source_corrupt` | the container could not be opened as the format it declares | re-export the file |\n| `unsupported_format` | no reader is registered for this format | convert it |\n| `ocr_unavailable` | the page had no text layer and no vision provider is configured | configure a vision key, or send a text-layer file |\n| `partial_extraction` | some of the source was recovered and some was not | use what came back |\n| `empty_source` | the source was read and held nothing | a fact about the file, not a failure — nothing to retry |\n| `extraction_error` | our failure | retry |",
            "enum": [
              "source_too_large",
              "source_encrypted",
              "source_corrupt",
              "unsupported_format",
              "ocr_unavailable",
              "partial_extraction",
              "empty_source",
              "extraction_error"
            ],
            "nullable": true,
            "type": "string"
          },
          "reason": {
            "description": "Free text naming the cause, straight from whatever refused or failed — `spreadsheet_magic_mismatch:xlsx`, `spreadsheet_too_large:bytes:33554433>33554432`, `extraction_failed:File is password-protected`. Carries detail no enum can, and is NOT stable: it includes dependency error text. Display it; do not branch on it. Null when nothing refused.",
            "nullable": true,
            "type": "string"
          },
          "state": {
            "description": "How much of the source the read recovered, in one word. `complete` on a healthy read, whatever the payload beside it turned out to contain.",
            "enum": [
              "complete",
              "partial",
              "unsupported",
              "error"
            ],
            "type": "string"
          }
        },
        "required": [
          "state",
          "reason",
          "code",
          "chunks"
        ],
        "type": "object"
      },
      "ExtractRequestDto": {
        "properties": {
          "asset_id": {
            "description": "An uploaded asset id (from POST /upload). One of asset_id / asset_url is required.",
            "type": "string"
          },
          "asset_url": {
            "description": "A direct URL to the file. One of asset_id / asset_url is required.",
            "type": "string"
          }
        },
        "type": "object"
      },
      "ExtractResponseDto": {
        "properties": {
          "code": {
            "description": "The stable classification of `reason`, safe to switch on. Null on a complete read.\n\n| Code | What it means | What to do |\n|------|---------------|------------|\n| `source_too_large` | a size or expansion ceiling refused the read | `reason` carries the observed value and the limit; send a smaller file |\n| `source_encrypted` | the container is password-protected | send an unlocked copy |\n| `source_corrupt` | the container could not be opened as the format it declares | re-export the file |\n| `unsupported_format` | no reader is registered for this format | convert it |\n| `ocr_unavailable` | the page had no text layer and no vision provider is configured | configure a vision key, or send a text-layer file |\n| `partial_extraction` | some of the source was recovered and some was not | use what came back; `lost[]` names the rest |\n| `empty_source` | the source was read and held nothing | a fact about the file, not a failure — nothing to retry |\n| `extraction_error` | our failure | retry |",
            "enum": [
              "source_too_large",
              "source_encrypted",
              "source_corrupt",
              "unsupported_format",
              "ocr_unavailable",
              "partial_extraction",
              "empty_source",
              "extraction_error"
            ],
            "nullable": true,
            "type": "string"
          },
          "complete": {
            "description": "Present, and only ever `false`, when the read did not recover the whole source. Absent on a complete read, so seeing it is a determination rather than a default.",
            "type": "boolean"
          },
          "extraction_method": {
            "description": "The path that produced most of this document's text.",
            "enum": [
              "text_layer",
              "ocr",
              "spreadsheet_cells",
              "none"
            ],
            "type": "string"
          },
          "format": {
            "description": "The format the bytes were read as.",
            "type": "string"
          },
          "lost": {
            "description": "The parts that were not recovered. Absent on a complete read.",
            "items": {
              "$ref": "#/components/schemas/LostPartDto"
            },
            "type": "array"
          },
          "markdown": {
            "description": "The whole document as markdown. Empty when nothing was read.",
            "type": "string"
          },
          "reason": {
            "description": "Free text naming the cause, straight from whatever refused or failed — `extraction_failed:File is password-protected`, `spreadsheet_too_large:bytes:33554433>33554432`. Carries detail no enum can (the observed value and the limit are both in it) and is NOT stable: it includes dependency error text. Display it; do not branch on it. Null on a complete read.",
            "nullable": true,
            "type": "string"
          },
          "state": {
            "description": "How much of the source the read recovered, in one word.",
            "enum": [
              "complete",
              "partial",
              "unsupported",
              "error"
            ],
            "type": "string"
          }
        },
        "required": [
          "format",
          "markdown",
          "extraction_method",
          "state",
          "reason",
          "code"
        ],
        "type": "object"
      },
      "FigureDto": {
        "properties": {
          "basis": {
            "description": "Whether the figure is actual, forecast, assumed or unknown.",
            "enum": [
              "actual",
              "forecast",
              "assumption",
              "unknown"
            ],
            "type": "string"
          },
          "currency": {
            "description": "Currency code of the normalized value.",
            "type": "string"
          },
          "decimalValue": {
            "description": "Canonical decimal text used for exact arithmetic. Without it the figure can be reported but not recomputed.",
            "example": "62.125",
            "type": "string"
          },
          "definition": {
            "description": "Definition or accounting-policy scope.",
            "type": "string"
          },
          "entity": {
            "description": "Entity scope, e.g. \"subscription\".",
            "type": "string"
          },
          "metric": {
            "description": "Metric dictionary key, e.g. \"gross_margin\".",
            "example": "gross_margin",
            "type": "string"
          },
          "observationId": {
            "description": "Immutable observation identity. Exact replay may reuse it.",
            "type": "string"
          },
          "period": {
            "description": "Period, e.g. \"FY2024\".",
            "type": "string"
          },
          "provenance": {
            "$ref": "#/components/schemas/ProvenanceDto"
          },
          "scale": {
            "description": "Scale qualifier for the already-normalized value. This does not apply another conversion.",
            "type": "string"
          },
          "unit": {
            "description": "Canonical unit.",
            "enum": [
              "percent",
              "multiple",
              "currency",
              "ratio",
              "count",
              "months"
            ],
            "type": "string"
          },
          "value": {
            "description": "Value normalized to the metric canonical unit.",
            "example": 62,
            "type": "number"
          }
        },
        "required": [
          "metric",
          "value",
          "unit",
          "provenance"
        ],
        "type": "object"
      },
      "FxRateDto": {
        "properties": {
          "applicableDate": {
            "example": "2025-12-31",
            "type": "string"
          },
          "applicablePeriod": {
            "type": "string"
          },
          "decimalRate": {
            "description": "Canonical decimal rate used by exact arithmetic.",
            "example": "1.08425",
            "type": "string"
          },
          "direction": {
            "enum": [
              "multiply",
              "divide"
            ],
            "type": "string"
          },
          "fromCurrency": {
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "provenance": {
            "$ref": "#/components/schemas/ProvenanceDto"
          },
          "rate": {
            "type": "number"
          },
          "ruleRevision": {
            "type": "string"
          },
          "toCurrency": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "fromCurrency",
          "toCurrency",
          "rate",
          "decimalRate",
          "direction",
          "applicablePeriod",
          "applicableDate",
          "ruleRevision",
          "provenance"
        ],
        "type": "object"
      },
      "GapDocDto": {
        "properties": {
          "category": {
            "type": "string"
          },
          "covers": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "filename": {
            "type": "string"
          },
          "label": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "GapsRequestDto": {
        "properties": {
          "category": {
            "description": "The category whose expected-documents checklist to compute.",
            "type": "string"
          },
          "docs": {
            "description": "The documents currently filed in that category.",
            "items": {
              "$ref": "#/components/schemas/GapDocDto"
            },
            "type": "array"
          },
          "stage": {
            "description": "Stage to tailor the checklist to.",
            "enum": [
              "early",
              "growth"
            ],
            "type": "string"
          },
          "taxonomy": {
            "description": "Taxonomy preset. Default \"vc\".",
            "enum": [
              "vc",
              "ma"
            ],
            "type": "string"
          }
        },
        "required": [
          "category",
          "docs"
        ],
        "type": "object"
      },
      "KeyFindingDto": {
        "properties": {
          "citation_ids": {
            "description": "IDs of citations that support this finding",
            "example": [
              "1",
              "3",
              "4"
            ],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "confidence": {
            "description": "Confidence in this finding (0-100)",
            "example": 92,
            "type": "number"
          },
          "finding": {
            "description": "A key fact extracted from the citations",
            "example": "The event occurred on the stated date according to official records",
            "type": "string"
          }
        },
        "required": [
          "finding",
          "citation_ids",
          "confidence"
        ],
        "type": "object"
      },
      "LostPartDto": {
        "properties": {
          "code": {
            "description": "The stable classification of this part's `reason`. Same vocabulary as the document-level `code`; the two can differ, because a document that recovered most of itself is `partial_extraction` while the one part it lost names its own cause.",
            "enum": [
              "source_too_large",
              "source_encrypted",
              "source_corrupt",
              "unsupported_format",
              "ocr_unavailable",
              "partial_extraction",
              "empty_source",
              "extraction_error"
            ],
            "nullable": true,
            "type": "string"
          },
          "index": {
            "description": "1-based page / sheet index, as on the unit that failed.",
            "type": "number"
          },
          "kind": {
            "description": "Which kind of unit was lost.",
            "enum": [
              "page",
              "sheet"
            ],
            "type": "string"
          },
          "reason": {
            "description": "Free text: what the reader said about this part.",
            "nullable": true,
            "type": "string"
          },
          "sheet": {
            "description": "Sheet units only: the tab the workbook declared.",
            "type": "string"
          },
          "state": {
            "description": "What happened to this part specifically.",
            "enum": [
              "partial",
              "unreadable",
              "error"
            ],
            "type": "string"
          }
        },
        "required": [
          "kind",
          "index",
          "state",
          "reason",
          "code"
        ],
        "type": "object"
      },
      "NamedConflictDto": {
        "properties": {
          "contradicting": {
            "$ref": "#/components/schemas/ConflictSideDto"
          },
          "supporting": {
            "$ref": "#/components/schemas/ConflictSideDto"
          }
        },
        "required": [
          "supporting",
          "contradicting"
        ],
        "type": "object"
      },
      "ObservationPrecedenceDto": {
        "properties": {
          "basis": {
            "enum": [
              "actual",
              "forecast",
              "assumption",
              "unknown"
            ],
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "definition": {
            "type": "string"
          },
          "entity": {
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "metric": {
            "type": "string"
          },
          "period": {
            "type": "string"
          },
          "provenance": {
            "$ref": "#/components/schemas/ProvenanceDto"
          },
          "reason": {
            "type": "string"
          },
          "reviewerId": {
            "type": "string"
          },
          "ruleRevision": {
            "type": "string"
          },
          "scale": {
            "type": "string"
          },
          "selectedObservationId": {
            "type": "string"
          },
          "supersededObservationIds": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "id",
          "metric",
          "selectedObservationId",
          "supersededObservationIds",
          "reviewerId",
          "reason",
          "ruleRevision",
          "entity",
          "period",
          "currency",
          "scale",
          "basis",
          "definition",
          "provenance"
        ],
        "type": "object"
      },
      "PaginationMetaDto": {
        "properties": {
          "hasNextPage": {
            "description": "Whether there is a next page",
            "example": true,
            "type": "boolean"
          },
          "hasPreviousPage": {
            "description": "Whether there is a previous page",
            "example": false,
            "type": "boolean"
          },
          "limit": {
            "description": "Number of items per page",
            "example": 20,
            "type": "number"
          },
          "page": {
            "description": "Current page number",
            "example": 1,
            "type": "number"
          },
          "total": {
            "description": "Total number of items",
            "example": 100,
            "type": "number"
          },
          "totalPages": {
            "description": "Total number of pages",
            "example": 5,
            "type": "number"
          }
        },
        "required": [
          "total",
          "page",
          "limit",
          "totalPages",
          "hasNextPage",
          "hasPreviousPage"
        ],
        "type": "object"
      },
      "ProvenanceDto": {
        "properties": {
          "assetId": {
            "description": "Asset the figure came from.",
            "type": "string"
          },
          "cell": {
            "description": "Cell reference, e.g. B4.",
            "type": "string"
          },
          "documentName": {
            "description": "Human-readable document name.",
            "type": "string"
          },
          "method": {
            "description": "How the value was extracted.",
            "enum": [
              "rule",
              "model"
            ],
            "type": "string"
          },
          "page": {
            "description": "1-based page.",
            "type": "number"
          },
          "sheet": {
            "description": "Sheet name.",
            "type": "string"
          }
        },
        "required": [
          "assetId",
          "documentName",
          "method"
        ],
        "type": "object"
      },
      "RankingFactorsDto": {
        "properties": {
          "content_relevance": {
            "description": "How well the content matches the claim being verified (0-100).",
            "example": 85,
            "type": "number"
          },
          "recency": {
            "description": "Publication freshness score (0-100). Current year = 100, decreases with age.",
            "example": 100,
            "type": "number"
          },
          "source_authority": {
            "description": "Domain reputation score (0-100). Higher scores for government, academic, major news sources.",
            "example": 90,
            "type": "number"
          }
        },
        "required": [
          "source_authority",
          "content_relevance",
          "recency"
        ],
        "type": "object"
      },
      "SearchMetadataDto": {
        "properties": {
          "analysis_model": {
            "description": "AI model used for analysis",
            "example": "claude-3-5-sonnet-v2@20241022",
            "type": "string"
          },
          "processing_time_ms": {
            "description": "Total processing time in milliseconds",
            "example": 2340,
            "type": "number"
          },
          "relevant_sources_found": {
            "description": "Number of relevant sources found and included",
            "example": 5,
            "type": "number"
          },
          "total_sources_searched": {
            "description": "Total number of sources searched",
            "example": 12,
            "type": "number"
          }
        },
        "required": [
          "total_sources_searched",
          "relevant_sources_found",
          "processing_time_ms",
          "analysis_model"
        ],
        "type": "object"
      },
      "SearchResponseDto": {
        "properties": {
          "citations": {
            "deprecated": true,
            "description": "[DEPRECATED] Use claim_groups[0].citations instead. Legacy citations array.",
            "items": {
              "$ref": "#/components/schemas/CitationDto"
            },
            "type": "array"
          },
          "claim_groups": {
            "description": "Unified tree: All data organized by claim. Each claim_group contains citations, domain_groups, and verdict.",
            "items": {
              "$ref": "#/components/schemas/ClaimGroupDto"
            },
            "type": "array"
          },
          "content": {
            "description": "Text content from agent (for non-citation responses)",
            "type": "string"
          },
          "domain_groups": {
            "deprecated": true,
            "description": "[DEPRECATED] Use claim_groups[n].domain_groups instead. Legacy domain grouping.",
            "items": {
              "$ref": "#/components/schemas/DomainGroupDto"
            },
            "type": "array"
          },
          "generated_prompts": {
            "description": "Generated sub-prompts when useMultiplePrompt is enabled",
            "example": [
              "What are the latest AI trends?",
              "What are emerging AI technologies?"
            ],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "operation_id": {
            "description": "Billing operation identifier on public v1 responses; null when no operation was recorded.",
            "nullable": true,
            "type": "string"
          },
          "search_metadata": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SearchMetadataDto"
              }
            ],
            "description": "Metadata about the search and analysis process"
          },
          "thread_id": {
            "description": "Thread ID for conversation continuity",
            "example": "123e4567-e89b-12d3-a456-426614174000",
            "type": "string"
          },
          "totalResults": {
            "description": "Total number of citations across all claims",
            "example": 5,
            "type": "number"
          },
          "usage": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BillingUsageDto"
              }
            ],
            "description": "Billing usage on public v1 responses."
          },
          "verdict": {
            "allOf": [
              {
                "$ref": "#/components/schemas/VerdictDto"
              }
            ],
            "deprecated": true,
            "description": "[DEPRECATED] Use claim_groups[n].verdict instead. Legacy overall verdict."
          }
        },
        "type": "object"
      },
      "SearchSourcesDto": {
        "properties": {
          "filters": {
            "additionalProperties": false,
            "description": "Applied to retrieval query and retained source metadata. Domain includes subdomains; false primary-source is an explicit filter. Unknown required metadata does not match.",
            "properties": {
              "domain": {
                "items": {
                  "type": "string"
                },
                "maxItems": 20,
                "minItems": 1,
                "type": "array"
              },
              "is_primary_source": {
                "type": "boolean"
              },
              "publication_year": {
                "additionalProperties": false,
                "properties": {
                  "from": {
                    "maximum": 9999,
                    "minimum": 1000,
                    "type": "integer"
                  },
                  "to": {
                    "maximum": 9999,
                    "minimum": 1000,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "source_type": {
                "items": {
                  "type": "string"
                },
                "maxItems": 20,
                "minItems": 1,
                "type": "array"
              }
            },
            "type": "object"
          },
          "limit": {
            "default": 10,
            "description": "Maximum number of sources to return (1-20)",
            "maximum": 20,
            "minimum": 1,
            "type": "number"
          },
          "query": {
            "description": "Search query to find sources for",
            "example": "climate change effects on coral reefs",
            "type": "string"
          }
        },
        "required": [
          "query"
        ],
        "type": "object"
      },
      "SourceMetadataDto": {
        "properties": {
          "domain": {
            "description": "Extracted domain from URL",
            "example": "example.gov",
            "type": "string"
          },
          "domain_category": {
            "description": "Category of the source domain",
            "enum": [
              "government",
              "academic",
              "news",
              "organization",
              "encyclopedia",
              "social",
              "other"
            ],
            "example": "government",
            "type": "string"
          },
          "is_fact_check_site": {
            "description": "Whether this is a fact-checking website (snopes, politifact, etc.)",
            "example": false,
            "type": "boolean"
          },
          "is_primary_source": {
            "description": "Whether this is a primary source (government, official, press release)",
            "example": true,
            "type": "boolean"
          }
        },
        "required": [
          "domain",
          "domain_category",
          "is_primary_source",
          "is_fact_check_site"
        ],
        "type": "object"
      },
      "SourcePreviewRequestDto": {
        "properties": {
          "asset_id": {
            "description": "Uploaded asset ID to preview (from POST /upload). Provide this OR url. Also accepts an \"asset://<id>\" citation url.",
            "example": "123e4567-e89b-12d3-a456-426614174000",
            "type": "string"
          },
          "cell": {
            "description": "A1 cell anchor for a legacy spreadsheet preview.",
            "type": "string"
          },
          "full": {
            "description": "Return the capped full sheet instead of a cell window.",
            "type": "boolean"
          },
          "highlight_terms": {
            "description": "Terms to highlight in the preview.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "page": {
            "default": 1,
            "description": "1-based page (PDF) or sheet index (spreadsheet) to preview.",
            "example": 3,
            "type": "number"
          },
          "quote": {
            "description": "The cited quote. It is bound back against the resolved source text; the response reports whether it was found (grounded) and how it matched.",
            "example": "the Eiffel Tower is 330 metres tall",
            "type": "string"
          },
          "representation_id": {
            "description": "Exact parser representation, never latest.",
            "type": "string"
          },
          "source_unit_id": {
            "description": "Exact unit within the immutable representation.",
            "type": "string"
          },
          "source_version_id": {
            "description": "Immutable source version. Requires representation_id and source_unit_id.",
            "type": "string"
          },
          "title": {
            "description": "Optional title for a web source.",
            "type": "string"
          },
          "url": {
            "description": "Web source URL to preview. Provide this OR asset_id. Returns a text-fragment deep link that scrolls to the quote.",
            "example": "https://en.wikipedia.org/wiki/Eiffel_Tower",
            "type": "string"
          }
        },
        "type": "object"
      },
      "SourcePreviewResponseDto": {
        "properties": {
          "anchor": {
            "type": "string"
          },
          "asset_id": {
            "description": "Asset ID (kind=page|grid).",
            "type": "string"
          },
          "binding": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BindBackDto"
              }
            ],
            "description": "bindBack result — whether the quote is grounded in the source."
          },
          "cells": {
            "description": "Bounded rows of cells with A1 refs, values and anchor flags.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "deep_link": {
            "description": "Deep link to the source at the cited location: url#:~:text=quote for web, asset_url#page=N for documents.",
            "type": "string"
          },
          "highlight_terms": {
            "description": "Highlight terms.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "image_base64": {
            "type": "string"
          },
          "judgment": {
            "type": "object"
          },
          "kind": {
            "description": "Preview shape.",
            "enum": [
              "web",
              "page",
              "grid",
              "unit",
              "image"
            ],
            "type": "string"
          },
          "locator": {
            "type": "object"
          },
          "mime_type": {
            "enum": [
              "application/pdf"
            ],
            "type": "string"
          },
          "page": {
            "description": "Page/sheet number (kind=page|grid).",
            "type": "number"
          },
          "quote": {
            "description": "The cited quote.",
            "type": "string"
          },
          "representation_id": {
            "type": "string"
          },
          "sheet": {
            "description": "Sheet name (kind=grid).",
            "type": "string"
          },
          "source_unit_id": {
            "type": "string"
          },
          "source_version_id": {
            "type": "string"
          },
          "state": {
            "type": "object"
          },
          "text": {
            "description": "Resolved source text (kind=page|grid).",
            "type": "string"
          },
          "title": {
            "description": "Web source title (kind=web).",
            "type": "string"
          },
          "url": {
            "description": "Web source URL (kind=web).",
            "type": "string"
          }
        },
        "required": [
          "kind",
          "deep_link",
          "binding"
        ],
        "type": "object"
      },
      "StanceBreakdownDto": {
        "properties": {
          "contradicts": {
            "description": "Number of citations that contradict the claim",
            "example": 0,
            "type": "number"
          },
          "inconclusive": {
            "description": "Number of citations that looked at the claim and could not tell. Distinct from neutral, which is a source that took no side.",
            "example": 1,
            "type": "number"
          },
          "irrelevant": {
            "description": "Number of citations judged off-topic. Excluded from every verdict denominator.",
            "example": 0,
            "type": "number"
          },
          "neutral": {
            "description": "Number of citations that took no side. This counts the neutral stance only. It previously also absorbed inconclusive and unscored citations, which are now reported in their own fields below.",
            "example": 1,
            "type": "number"
          },
          "partially_supports": {
            "description": "Number of citations that partially support the claim",
            "example": 1,
            "type": "number"
          },
          "supports": {
            "description": "Number of citations that fully support the claim",
            "example": 3,
            "type": "number"
          },
          "unknown": {
            "description": "Number of citations with no stance recorded. Never treated as agreement or as neutral: these were not scored, not scored as undecided.",
            "example": 0,
            "type": "number"
          }
        },
        "required": [
          "supports",
          "partially_supports",
          "contradicts",
          "neutral"
        ],
        "type": "object"
      },
      "VerdictDto": {
        "properties": {
          "claim": {
            "description": "The original prompt/claim that was analyzed",
            "example": "User query or claim being verified",
            "type": "string"
          },
          "confidence": {
            "description": "Confidence in the verdict (0-100)",
            "example": 88,
            "type": "number"
          },
          "conflict": {
            "allOf": [
              {
                "$ref": "#/components/schemas/NamedConflictDto"
              }
            ],
            "description": "The quoted, named pair of sources behind a \"mixed\" verdict. Null for every other verdict.",
            "nullable": true,
            "type": "object"
          },
          "corrections": {
            "description": "Corrections when the claim contains inaccuracies",
            "items": {
              "$ref": "#/components/schemas/CorrectionDto"
            },
            "type": "array"
          },
          "document_breakdown": {
            "allOf": [
              {
                "$ref": "#/components/schemas/StanceBreakdownDto"
              }
            ],
            "description": "Stance counts over every document analysed, before syndicated copies of one telling were collapsed into a single origin."
          },
          "document_count": {
            "description": "Documents analysed. Reported for transparency; origin_count is what the verdict counts.",
            "example": 5,
            "type": "number"
          },
          "insufficient_evidence": {
            "description": "True when the counts were split but no two sources could be quoted disagreeing, so the split was reported as unverifiable rather than as a controversy.",
            "example": false,
            "type": "boolean"
          },
          "key_findings": {
            "description": "Key facts extracted from citations (only with useEnhancedAnalysis)",
            "items": {
              "$ref": "#/components/schemas/KeyFindingDto"
            },
            "type": "array"
          },
          "origin_count": {
            "description": "Distinct origins behind those documents. Four outlets running one wire story are four documents and one origin, and only this number votes.",
            "example": 2,
            "type": "number"
          },
          "result": {
            "description": "Overall verdict based on citation analysis",
            "enum": [
              "supported",
              "partially_supported",
              "contradicted",
              "mixed",
              "unverifiable"
            ],
            "example": "supported",
            "type": "string"
          },
          "stance_breakdown": {
            "allOf": [
              {
                "$ref": "#/components/schemas/StanceBreakdownDto"
              }
            ],
            "description": "Breakdown of citation stances over distinct ORIGINS, which is what the verdict is computed from. For the raw per-document counts before origins were collapsed, see document_breakdown."
          },
          "summary": {
            "description": "Human-readable summary of the findings",
            "example": "Based on 4 authoritative sources, the claim appears to be well-supported by available evidence.",
            "type": "string"
          },
          "unverified_claims": {
            "description": "Parts of the claim that could not be verified by any source",
            "example": [
              "Aspect of claim that could not be verified"
            ],
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "claim",
          "result",
          "confidence",
          "summary",
          "stance_breakdown"
        ],
        "type": "object"
      },
      "VerificationStampDto": {
        "properties": {
          "band": {
            "enum": [
              "verified",
              "needs_review",
              "unverified"
            ],
            "type": "string"
          },
          "binding_method": {
            "enum": [
              "exact",
              "normalized",
              "fuzzy",
              "reflowed",
              "unbound"
            ],
            "type": "string"
          },
          "confidence": {
            "maximum": 100,
            "minimum": 0,
            "nullable": true,
            "type": "number"
          },
          "grounded": {
            "type": "boolean"
          },
          "layer": {
            "enum": [
              "deterministic",
              "bound",
              "model",
              "unbound"
            ],
            "type": "string"
          },
          "meaning": {
            "enum": [
              "not_checked",
              "faithful",
              "changed",
              "ambiguous"
            ],
            "type": "string"
          },
          "review_reason": {
            "type": "string"
          }
        },
        "required": [
          "layer",
          "band",
          "confidence",
          "grounded",
          "meaning"
        ],
        "type": "object"
      },
      "VerifyBatchRequestDto": {
        "properties": {
          "items": {
            "description": "Claims to verify, each against its source. Bound back per item; a fuzzy match is capped at needs_review, never verified.",
            "items": {
              "$ref": "#/components/schemas/BatchItemDto"
            },
            "type": "array"
          }
        },
        "required": [
          "items"
        ],
        "type": "object"
      },
      "VerifyBatchResultDto": {
        "properties": {
          "binding": {
            "$ref": "#/components/schemas/BindBackDto"
          },
          "engine": {
            "enum": [
              "context_graph"
            ],
            "type": "string"
          },
          "error": {
            "type": "string"
          },
          "feedback_token": {
            "type": "string"
          },
          "id": {
            "description": "Caller id echoed from the request.",
            "type": "string"
          },
          "judgment": {
            "additionalProperties": true,
            "type": "object"
          },
          "quote": {
            "type": "string"
          },
          "source_check": {
            "enum": [
              "read",
              "not_read"
            ],
            "type": "string"
          },
          "verification": {
            "$ref": "#/components/schemas/VerificationStampDto"
          }
        },
        "required": [
          "quote",
          "binding",
          "verification",
          "feedback_token"
        ],
        "type": "object"
      },
      "VerifyClaimDto": {
        "properties": {
          "claim": {
            "description": "The factual claim to verify",
            "example": "The Eiffel Tower is 330 meters tall",
            "type": "string"
          },
          "decompose_claim": {
            "default": false,
            "description": "Break complex claims into sub-claims and verify each independently",
            "type": "boolean"
          },
          "include_stance": {
            "default": true,
            "description": "Include stance analysis for each source (adds 1 credit)",
            "type": "boolean"
          },
          "include_verdict": {
            "default": true,
            "description": "Generate an overall verdict with confidence score (adds 1 credit)",
            "type": "boolean"
          },
          "thread_id": {
            "description": "Thread ID to continue a conversation",
            "type": "string"
          }
        },
        "required": [
          "claim"
        ],
        "type": "object"
      },
      "VerifyFeedbackRequestDto": {
        "properties": {
          "note": {
            "description": "Optional note or correction.",
            "type": "string"
          },
          "token": {
            "description": "The feedback_token from a batch result.",
            "type": "string"
          },
          "verdict": {
            "description": "The human verdict on the result.",
            "enum": [
              "correct",
              "incorrect",
              "unsure"
            ],
            "type": "string"
          }
        },
        "required": [
          "token",
          "verdict"
        ],
        "type": "object"
      }
    },
    "securitySchemes": {
      "apiKey": {
        "in": "header",
        "name": "x-api-key",
        "type": "apiKey"
      }
    }
  },
  "info": {
    "contact": {},
    "description": "Claim verification with citations bound back to their sources.",
    "title": "Webcite API",
    "version": "1.0"
  },
  "openapi": "3.0.0",
  "paths": {
    "/api/v1/accuracy": {
      "get": {
        "description": "The engine's measured accuracy against a gold-set corpus: conflict detection recall and precision, and recompute correctness. Reproducible and gated on every build — an accuracy regression cannot ship.\n\n**Cost: 1 credit.** No LLM calls; the report runs the deterministic gold-set eval on the server, so it is subject to your plan's rate limits.",
        "operationId": "ApiV1Controller_accuracy",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Corpus accuracy report"
          },
          "401": {
            "description": "Unauthorized - API key required"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "x-api-key": []
          },
          {
            "bearer": []
          }
        ],
        "summary": "Measured numeric-accuracy report",
        "tags": [
          "Public API"
        ]
      }
    },
    "/api/v1/analyze/conflicts": {
      "post": {
        "description": "Verify NUMBERS, not just text. Given figures extracted across one or more documents, the finance accuracy engine:\n\n- **recomputes** every derivable metric from its primitives (e.g. gross margin from revenue and COGS, revenue growth from two periods) and flags where the stated value does not match the computed one;\n- **detects cross-document conflicts** — figures for the same metric/entity/period that disagree beyond the metric's tolerance;\n- **flags semantic conflicts** — metrics that are each plausible alone but jointly impossible (e.g. logo retention far below 100 − churn);\n- returns a **review** flag: whether a human should look, and the concrete reasons (a conflict, a recompute mismatch, or model-read inputs). The findings are deterministic — a conflict exists or it does not — so this is a flag with reasons, not a probability score.\n\nDeterministic compute, no LLM calls. **Cost: 1 credit.** Still subject to your plan's rate limits, since the request uses server compute.",
        "operationId": "ApiV1Controller_analyzeConflicts",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AnalyzeConflictsRequestDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Conflicts, recomputations, and a review flag"
          },
          "401": {
            "description": "Unauthorized - API key required"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "x-api-key": []
          },
          {
            "bearer": []
          }
        ],
        "summary": "Analyze numeric figures for conflicts and mismatches",
        "tags": [
          "Public API"
        ]
      }
    },
    "/api/v1/analyze/document": {
      "post": {
        "description": "Document-in numeric analysis. Give an uploaded asset id; the document is downloaded, its figures are extracted, then recomputed and cross-checked. Returns the extracted figures alongside conflicts, recomputations, and a review flag.\n\n- **Spreadsheets** (xlsx/xls/csv): extracted deterministically with exact cell provenance. A cell read is exact, so these are rule reads. No LLM calls — server compute and file I/O only.\n- **PDFs**: a vision model reads the printed figures (it never computes); these are model reads, capped at needs_review and never presented as verified. Their value is cross-source, e.g. a deck figure that disagrees with the spreadsheet. **This path makes a vision-model (LLM) call per page.**\n\n**Cost: 3 credits.** The document is downloaded and parsed; PDF pages may also use a vision model. Rate-limited more strictly than compute-only endpoints.",
        "operationId": "ApiV1Controller_analyzeDocument",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AnalyzeDocumentRequestDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Extracted figures with conflicts, recomputations, and a review flag"
          },
          "400": {
            "description": "Asset not found or unsupported type"
          },
          "401": {
            "description": "Unauthorized - API key required"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "x-api-key": []
          },
          {
            "bearer": []
          }
        ],
        "summary": "Analyze a spreadsheet or PDF document for conflicts and mismatches",
        "tags": [
          "Public API"
        ]
      }
    },
    "/api/v1/ask": {
      "post": {
        "description": "Queues an ask job: the document is chunked, top passages are\nretrieved, a derivation is drafted, and every drafted number is checked\nagainst the passages (verbatim match) or recomputed from grounded operands.\nUnverified values return as null, never as claims. Poll GET ask/:id.\n\n**Credits: 5** (retrieval plus a model draft plus verification)",
        "operationId": "ApiV1Controller_ask",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAskDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "description": "Ask job queued."
          },
          "400": {
            "description": "bad_input or needs_ocr."
          },
          "401": {
            "description": "Unauthorized - API key required"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "x-api-key": []
          },
          {
            "bearer": []
          }
        ],
        "summary": "Ask a question over a document",
        "tags": [
          "Public API"
        ]
      }
    },
    "/api/v1/ask/{id}": {
      "get": {
        "description": "Job view with status (queued, running, done, failed) and,\nwhen done, the checked result or a null answer with warnings.\n\n**Cost: 0 credits** — this is a read of a result you already generated.",
        "operationId": "ApiV1Controller_askStatus",
        "parameters": [
          {
            "description": "Ask job ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ask job view."
          },
          "401": {
            "description": "Unauthorized - API key required"
          },
          "404": {
            "description": "Ask job not found."
          }
        },
        "security": [
          {
            "x-api-key": []
          },
          {
            "bearer": []
          }
        ],
        "summary": "Poll an ask job",
        "tags": [
          "Public API"
        ]
      }
    },
    "/api/v1/citations": {
      "get": {
        "description": "List your past verification results. Supports pagination and filtering by thread_id.\n\n**Cost: 1 credit.** This is a read of results you already generated. No LLM calls.",
        "operationId": "ApiV1Controller_listCitations",
        "parameters": [
          {
            "description": "Page number (starts at 1)",
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "default": 1,
              "example": 1,
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "description": "Results per page (max 50)",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "example": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "number"
            }
          },
          {
            "description": "Predefined field set to return",
            "in": "query",
            "name": "fields",
            "required": false,
            "schema": {
              "default": "all",
              "enum": [
                "minimal",
                "basic",
                "detailed",
                "with_relations",
                "all"
              ],
              "example": "basic",
              "type": "string"
            }
          },
          {
            "description": "Filter by thread ID",
            "in": "query",
            "name": "thread_id",
            "required": false,
            "schema": {
              "example": "123e4567-e89b-12d3-a456-426614174000",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CitationListResponseDto"
                }
              }
            },
            "description": "Citations retrieved"
          },
          "401": {
            "description": "Unauthorized - API key required"
          }
        },
        "security": [
          {
            "x-api-key": []
          },
          {
            "bearer": []
          }
        ],
        "summary": "List citations",
        "tags": [
          "Public API"
        ]
      }
    },
    "/api/v1/citations/source-preview": {
      "post": {
        "description": "Resolve a citation back to its exact source and render it, so you can show the evidence behind a claim.\n\n- **Web sources** (`url`): returns a text-fragment deep link (`url#:~:text=quote`) that scrolls a browser to the quote.\n- **Document sources** (`asset_id`, from POST /upload): returns the cited page's extracted text and a `asset_url#page=N` deep link. Spreadsheets return the sheet grid.\n\nEvery preview includes a **bindBack** result: whether the cited quote is actually present in the source (`grounded`) and how it matched (`exact`, `normalized`, `fuzzy`, `reflowed`, or `unbound`). A `reflowed` match used a derived OCR reading and always requires review. A citation that cannot be bound back is never reported as grounded.\n\n**Cost: 1 credit.** No LLM calls, but a web preview fetches the source URL, so this endpoint is subject to your plan's rate limits.",
        "operationId": "ApiV1Controller_sourcePreview",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SourcePreviewRequestDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SourcePreviewResponseDto"
                }
              }
            },
            "description": "Source preview with bindBack verification"
          },
          "400": {
            "description": "Provide a web url or an asset_id"
          },
          "401": {
            "description": "Unauthorized - API key required"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "x-api-key": []
          },
          {
            "bearer": []
          }
        ],
        "summary": "Preview a citation source",
        "tags": [
          "Public API"
        ]
      }
    },
    "/api/v1/citations/{id}": {
      "get": {
        "description": "Get the full details of a specific verification by its ID.\n\n**Cost: 1 credit.** This is a read of a result you already generated. No LLM calls.",
        "operationId": "ApiV1Controller_getCitation",
        "parameters": [
          {
            "description": "Citation ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CitationViewResponseDto"
                }
              }
            },
            "description": "Citation retrieved"
          },
          "401": {
            "description": "Unauthorized - API key required"
          },
          "404": {
            "description": "Citation not found"
          }
        },
        "security": [
          {
            "x-api-key": []
          },
          {
            "bearer": []
          }
        ],
        "summary": "Get citation by ID",
        "tags": [
          "Public API"
        ]
      }
    },
    "/api/v1/classify": {
      "post": {
        "description": "Deterministic, model-free document classification. Downloads an uploaded asset, extracts its content + spreadsheet sheet names, and returns a coarse **category** plus the fine multi-type **covers** it holds (a bundled workbook covers several). Works with no model configured (air-gapped safe).\n\nPass an optional `taxonomy` preset (`vc` = venture data-room, `ma` = M&A categories; default `vc`).\n\n**`unfiled` is an answer, not a failure.** A document that was read completely and matched no category gets it, so it cannot also mean \"we never read this\". `state`, `reason` and `code` — the same three fields, the same vocabulary and the same values as `POST /extract` — say whether the classifier ever saw any content. A refused, corrupt, unsupported or unreadable asset returns `unfiled` with a non-null `code`; a genuinely empty document returns `unfiled` with `code: \"empty_source\"`; a complete read returns `code: null`. `category` and `covers` are untouched by this — anything routing on them today keeps working.\n\n**Cost: 1 credit.** Deterministic (no LLM calls), but downloads + parses the asset.",
        "operationId": "ApiV1Controller_classify",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClassifyRequestDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClassifyResponseDto"
                }
              }
            },
            "description": "{ category, covers[], state, reason, code }"
          },
          "400": {
            "description": "Asset not found"
          },
          "401": {
            "description": "Unauthorized - API key required"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "x-api-key": []
          },
          {
            "bearer": []
          }
        ],
        "summary": "Classify a document into a category and covered types",
        "tags": [
          "Public API"
        ]
      }
    },
    "/api/v1/extract": {
      "post": {
        "description": "All-formats extraction (PDF, spreadsheets, docx/pptx/html/txt, …) into a normalized document: whole-doc `markdown`, per-page/sheet `units` with page/sheet provenance, and (for spreadsheets) sheet names. Deterministic-first; scanned PDFs fall back to vision OCR. Extraction never hard-fails — an unreadable asset returns empty text.\n\n**A partial read says so.** When the file opened but a part of it could not be read (a workbook sheet whose part the package is missing, a page that failed), the response carries `complete: false` and `lost[]` naming what was not recovered — sheet or page, and why. Both keys are absent from a complete read, so seeing either is a finding rather than a default, and `state` (`complete` | `partial` | `error` | `unsupported`) is the same determination in one word. `unsupported` was served but not listed here: it is the answer for a format no reader is registered for, and a consumer switching on the documented three fell through on it.\n\n**Why a read did not complete, in two fields.** `reason` is free text straight from whatever refused or failed, so it carries detail no enum can — `spreadsheet_too_large:bytes:33554433>33554432` has the observed value and the limit in it. It is not stable: it includes dependency error text such as `extraction_failed:Cannot read properties of undefined (reading 'Pages')`. `code` is the stable classification of the same refusal, and it is the one to switch on.\n\n| `code` | Meaning | Caller's move |\n|--------|---------|---------------|\n| `source_too_large` | a size or expansion ceiling refused the read | send a smaller file; `reason` has the numbers |\n| `source_encrypted` | the container is password-protected | send an unlocked copy |\n| `source_corrupt` | the container could not be opened as the format it declares | re-export the file |\n| `unsupported_format` | no reader is registered for this format | convert it |\n| `ocr_unavailable` | no text layer, and no vision provider is configured | configure a vision key, or send a text-layer file |\n| `partial_extraction` | some of the source came back and some did not | use what came back; `lost[]` names the rest |\n| `empty_source` | the source was read and held nothing | nothing to retry — this is a fact about the file |\n| `extraction_error` | our failure | retry |\n\nBoth are `null` on a complete read. Each `lost[]` entry carries its own `reason` and `code` for the part it names, which can differ from the document's: a workbook that lost one sheet of two is `partial_extraction` while the lost sheet is `source_corrupt`.\n\n**`extraction_method`** says which path produced the text, so a consumer storing this as evidence can tell a deterministic read from a model transcription:\n\n| Value | Meaning |\n|-------|---------|\n| `text_layer` | the file's own machine-readable text, read deterministically |\n| `ocr` | no text layer, so a vision model transcribed the page image |\n| `spreadsheet_cells` | cells read out of the workbook, no model and no rendering |\n| `none` | nothing readable came back (empty, unparseable, or a scan with no OCR configured) |\n\nA document can mix paths — a text-layer PDF with two scanned pages in it. The top-level `extraction_method` is then the **dominant** path (the one behind the most readable units, ties going to `ocr`); `units[].extraction_method` is the exact answer for that page or sheet.\n\n**Spreadsheet units** also carry `sheet`: the tab's native structure straight from the workbook. `sheet.merges` lists the merged ranges (`[\"A1:A2\",\"B1:C1\"]`) and `sheet.cells[].merged_range` says which range each cell belongs to, so a header spanning two columns can be rebuilt: the `text` CSV flattens it to a value plus a blank.\n\n**Cost: 1 credit.** Deterministic for text-layer documents; downloads and parses a file.",
        "operationId": "ApiV1Controller_extract",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExtractRequestDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtractResponseDto"
                }
              }
            },
            "description": "{ format, markdown, extraction_method, state, reason, code, units[{ …, extraction_method, sheet? }], sheets?[], complete?: false, lost?[{ kind, index, sheet?, state, reason, code }] }"
          },
          "400": {
            "description": "asset_url is not an allowed public web address, or neither asset_id nor asset_url was given"
          },
          "401": {
            "description": "Unauthorized - API key required"
          },
          "404": {
            "description": "The asset does not exist, or the store has no bytes for it"
          },
          "413": {
            "description": "The asset is over the 50 MB download ceiling. The body carries `bytes` (null when the reader refused mid-stream) and `max_bytes`"
          },
          "429": {
            "description": "Rate limit exceeded"
          },
          "502": {
            "description": "The asset store returned a failure. Ours, not the request — retry"
          },
          "503": {
            "description": "Private evidence storage is not configured, so an evidence:// asset cannot be read"
          },
          "504": {
            "description": "The asset download timed out. Retry"
          }
        },
        "security": [
          {
            "x-api-key": []
          },
          {
            "bearer": []
          }
        ],
        "summary": "Extract any document into normalized text + units with provenance",
        "tags": [
          "Public API"
        ]
      }
    },
    "/api/v1/extract/figures": {
      "post": {
        "description": "Reads a spreadsheet and returns each known metric as a **figure**: the value normalized to its canonical unit, plus what it means (`metric`), `unit`, optional `entity`/`period`, a confidence `band`, whether it was confirmed against the cited cell (`bound`), and full `provenance` (asset, sheet, cell, source label). This is deterministic financial-model reading: header scale (\"$M\"/\"'000\"), accounting negatives, period columns (FY2023 vs FY2024), and unit-declaration checks are all honoured, so a % is never mis-read as currency.\n\nDeterministic-first. Reads **spreadsheets** (cell-level discovery), **PDFs with a text layer** (grounded and cited by page), and **every other text-bearing format** (docx, pptx, html, txt) deterministically with no model calls. **Scanned PDF pages and image files** (png/jpg/...) are transcribed by vision OCR first, then grounded, so their numbers are still extracted (provenance marked `model`); this requires a vision key and is skipped otherwise. Accepts `asset_id` (uploaded asset) or `asset_url` (your own signed URL).\n\n**An empty `figures` says why it is empty.** `state`, `reason` and `code` — the same three fields, the same vocabulary and the same values as `POST /extract` — describe **the read that fed the figure engine**, not the figure list. A workbook that opened and yielded no known metric is `state: \"complete\"` with `code: null`: the engine measured it and found nothing to tag, which is an answer. A refusal never reached the engine and carries its cause (`source_too_large`, `source_corrupt`, `source_encrypted`, `unsupported_format`, `extraction_error`). `figures` itself is untouched.\n\n**Cost: 2 credits.** Deterministic (no LLM calls); downloads and runs the full figure engine over every sheet/page.",
        "operationId": "ApiV1Controller_extractFigures",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExtractRequestDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtractFiguresResponseDto"
                }
              }
            },
            "description": "{ figures: { metric, value, unit, entity?, period?, band, bound, provenance }[], state, reason, code }"
          },
          "400": {
            "description": "Asset not found"
          },
          "401": {
            "description": "Unauthorized - API key required"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "x-api-key": []
          },
          {
            "bearer": []
          }
        ],
        "summary": "Extract every number from a document as a tagged, source-grounded figure",
        "tags": [
          "Public API"
        ]
      }
    },
    "/api/v1/extract/pages": {
      "post": {
        "description": "The same all-formats extraction as POST /extract, sliced into retrieval-sized **chunks** that keep the anchor a citation needs: `page` (PDF), `sheet` (workbook tab), `section` (nearest preceding markdown heading), plus a `token_estimate` for packing a context budget. `ordinal` is contiguous from 0 across the whole document.\n\nUse this instead of /extract when you intend to index the text and later point a reader at where a passage came from: flat pages cannot carry a citation.\n\n**An empty `chunks` says why it is empty.** `state`, `reason` and `code` are the extraction's own determination, carried through verbatim — the same three fields, the same vocabulary and the same values as `POST /extract` over the same asset. A refusal (`source_too_large`, `source_corrupt`, `source_encrypted`, `unsupported_format`) and a document that genuinely holds nothing (`empty_source`) both return no chunks, and `code` is what tells them apart. `code` is `null` on a complete read.\n\n**Cost: 1 credit.** Same work as /extract — the chunking is in-process and makes no model call.",
        "operationId": "ApiV1Controller_extractPages",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExtractRequestDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtractPagesResponseDto"
                }
              }
            },
            "description": "{ chunks[], state, reason, code }"
          },
          "400": {
            "description": "Asset not found"
          },
          "401": {
            "description": "Unauthorized - API key required"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "x-api-key": []
          },
          {
            "bearer": []
          }
        ],
        "summary": "Extract a document into anchored chunks ready to index and cite",
        "tags": [
          "Public API"
        ]
      }
    },
    "/api/v1/gaps": {
      "post": {
        "description": "Given the documents already filed in a category, returns each expected document type flagged present/absent. An item is present when any document matches it by filename, category, or covered type. Advisory — nothing blocks the workflow. Pure and deterministic; no I/O.\n\nPass an optional `taxonomy` preset and `stage` (`early`/`growth`) to tailor the list.\n\n**Cost: 1 credit.** Pure and deterministic (no LLM calls or I/O).",
        "operationId": "ApiV1Controller_gaps",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GapsRequestDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "{ items: { name, present }[] }"
          },
          "401": {
            "description": "Unauthorized - API key required"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "x-api-key": []
          },
          {
            "bearer": []
          }
        ],
        "summary": "Compute the \"usually also here\" checklist for a category",
        "tags": [
          "Public API"
        ]
      }
    },
    "/api/v1/sources/search": {
      "post": {
        "description": "Search for authoritative sources related to a query. Returns raw citations without stance analysis or verdict.\n\n**Credits: 2** (search only)",
        "operationId": "ApiV1Controller_searchSources",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchSourcesDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponseDto"
                }
              }
            },
            "description": "Sources found"
          },
          "401": {
            "description": "Unauthorized - API key required"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "x-api-key": []
          },
          {
            "bearer": []
          }
        ],
        "summary": "Search for sources",
        "tags": [
          "Public API"
        ]
      }
    },
    "/api/v1/upload": {
      "post": {
        "description": "Upload a file to storage for use in verification context.",
        "operationId": "ApiV1Controller_uploadFile",
        "parameters": [],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "properties": {
                  "file": {
                    "description": "File to upload",
                    "format": "binary",
                    "type": "string"
                  }
                },
                "required": [
                  "file"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "File uploaded successfully"
          },
          "401": {
            "description": "Unauthorized - API key required"
          },
          "503": {
            "description": "Private evidence storage required: set EVIDENCE_STORAGE_ROOT or EVIDENCE_BUCKET_NAME (I1; never public-first)"
          }
        },
        "security": [
          {
            "x-api-key": []
          },
          {
            "bearer": []
          }
        ],
        "summary": "Upload a file",
        "tags": [
          "Public API"
        ]
      }
    },
    "/api/v1/verify": {
      "post": {
        "description": "Verify a factual claim against authoritative sources. Returns sources with stance analysis and an overall verdict.\n\n**Credit System:**\n| Operation | Credits | Description |\n|-----------|---------|-------------|\n| Search | 2 | Find sources for the claim |\n| Stance Analysis | 1 | Analyze stance of each source (set include_stance=false to skip) |\n| Verdict | 1 | Generate overall verdict (set include_verdict=false to skip) |\n\n**Full verification = 4 credits**, **Search only = 2 credits**",
        "operationId": "ApiV1Controller_verifyClaim",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyClaimDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponseDto"
                }
              }
            },
            "description": "Claim verified successfully"
          },
          "401": {
            "description": "Unauthorized - API key required"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "x-api-key": []
          },
          {
            "bearer": []
          }
        ],
        "summary": "Verify a factual claim",
        "tags": [
          "Public API"
        ]
      }
    },
    "/api/v1/verify/batch": {
      "post": {
        "description": "Bind every claim to its source in a single request. Each item is a quote plus its source (inline text, a url, or an asset). The response gives, per item, whether the quote is grounded, how it matched (exact, normalized, fuzzy, reflowed, or unbound), the best-matching passage and score even when unbound, the verification tier, and a feedback token.\n\nA fuzzy match is capped at needs_review, never verified. A reflowed match uses a derived OCR reading and is also capped at needs_review. Use this to check every citation in a document at once.\n\n**Cost: 1 credit per item.** BindBack is deterministic (no LLM calls), but a batch can be large and `url`/`asset` items fetch their source, so this endpoint is rate-limited more strictly than compute-only endpoints.",
        "operationId": "ApiV1Controller_verifyBatch",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyBatchRequestDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/VerifyBatchResultDto"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Per-item verification results"
          },
          "401": {
            "description": "Unauthorized - API key required"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "x-api-key": []
          },
          {
            "bearer": []
          }
        ],
        "summary": "Verify many claims in one call",
        "tags": [
          "Public API"
        ]
      }
    },
    "/api/v1/verify/feedback": {
      "post": {
        "description": "Accept, reject, or flag a result using the feedback_token from a batch result. The token carries the result summary, so token plus verdict is enough. Feedback is stored so corrections accumulate over time.\n\n**Cost: 1 credit.** No LLM calls; records a single row. Subject to your plan's rate limits.",
        "operationId": "ApiV1Controller_verifyFeedback",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyFeedbackRequestDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Feedback recorded"
          },
          "400": {
            "description": "Invalid feedback_token"
          },
          "401": {
            "description": "Unauthorized - API key required"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "x-api-key": []
          },
          {
            "bearer": []
          }
        ],
        "summary": "Record a verdict on a verification result",
        "tags": [
          "Public API"
        ]
      }
    },
    "/api/v1/verify/stream": {
      "post": {
        "description": "Stream verification results via Server-Sent Events. Citations stream as processed, followed by verdict.\n\n**Event Types:** citation, groups, verdict, metadata, done, error",
        "operationId": "ApiV1Controller_verifyClaimStream",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyClaimDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "SSE stream of verification results"
          },
          "401": {
            "description": "Unauthorized - API key required"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "x-api-key": []
          },
          {
            "bearer": []
          }
        ],
        "summary": "Verify a claim with streaming response",
        "tags": [
          "Public API"
        ]
      }
    },
    "/api/v2/sources/{versionId}/representations/{representationId}/prepare-ocr-rescue": {
      "post": {
        "operationId": "OcrRescueController_prepare",
        "parameters": [
          {
            "in": "path",
            "name": "versionId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "representationId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "x-api-key": []
          },
          {
            "bearer": []
          }
        ],
        "tags": [
          "Versioned evidence"
        ]
      }
    },
    "/api/v2/verify/numeric": {
      "post": {
        "operationId": "NumericClaimController_assess",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "additionalProperties": false,
                "properties": {
                  "claim": {
                    "maxLength": 10000,
                    "minLength": 1,
                    "type": "string"
                  },
                  "operands": {
                    "items": {
                      "additionalProperties": false,
                      "properties": {
                        "figure_index": {
                          "maximum": 10000,
                          "minimum": 0,
                          "type": "integer"
                        },
                        "representation_id": {
                          "type": "string"
                        },
                        "source_unit_id": {
                          "type": "string"
                        },
                        "source_version_id": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "source_version_id",
                        "representation_id",
                        "source_unit_id",
                        "figure_index"
                      ],
                      "type": "object"
                    },
                    "maxItems": 2,
                    "minItems": 1,
                    "type": "array"
                  }
                },
                "required": [
                  "claim",
                  "operands"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "arithmetic": {
                      "nullable": true,
                      "properties": {
                        "computed": {
                          "nullable": true,
                          "type": "number"
                        },
                        "reason": {
                          "type": "string"
                        },
                        "tolerance": {
                          "type": "number"
                        },
                        "verdict": {
                          "enum": [
                            "holds",
                            "fails",
                            "not_computable"
                          ],
                          "type": "string"
                        }
                      },
                      "required": [
                        "verdict",
                        "computed",
                        "tolerance",
                        "reason"
                      ],
                      "type": "object"
                    },
                    "comparability": {
                      "nullable": true,
                      "properties": {
                        "facets": {
                          "items": {
                            "properties": {
                              "decidedBy": {
                                "enum": [
                                  "code",
                                  "judge"
                                ],
                                "type": "string"
                              },
                              "facet": {
                                "enum": [
                                  "unit",
                                  "scale",
                                  "entity",
                                  "period",
                                  "basis"
                                ],
                                "type": "string"
                              },
                              "p": {
                                "nullable": true,
                                "type": "number"
                              },
                              "passed": {
                                "nullable": true,
                                "type": "boolean"
                              }
                            },
                            "type": "object"
                          },
                          "type": "array"
                        },
                        "reason": {
                          "type": "string"
                        },
                        "verdict": {
                          "enum": [
                            "comparable",
                            "not_comparable",
                            "unknown"
                          ],
                          "type": "string"
                        }
                      },
                      "required": [
                        "verdict",
                        "facets",
                        "reason"
                      ],
                      "type": "object"
                    },
                    "limitation": {
                      "type": "string"
                    },
                    "operands": {
                      "items": {
                        "properties": {
                          "binding": {
                            "enum": [
                              "retained_source_slice"
                            ],
                            "type": "string"
                          },
                          "figure": {
                            "properties": {
                              "accounting": {
                                "type": "boolean"
                              },
                              "index": {
                                "type": "integer"
                              },
                              "magnitude": {
                                "type": "number"
                              },
                              "raw": {
                                "type": "string"
                              },
                              "scale": {
                                "type": "number"
                              },
                              "sign": {
                                "enum": [
                                  -1,
                                  1
                                ],
                                "type": "integer"
                              },
                              "unit": {
                                "nullable": true,
                                "type": "string"
                              },
                              "unitClass": {
                                "enum": [
                                  "percent",
                                  "percentage_point",
                                  "basis_point",
                                  "currency",
                                  "none"
                                ],
                                "type": "string"
                              },
                              "value": {
                                "type": "number"
                              }
                            },
                            "type": "object"
                          },
                          "source": {
                            "properties": {
                              "representation_id": {
                                "type": "string"
                              },
                              "source_unit_id": {
                                "type": "string"
                              },
                              "source_version_id": {
                                "type": "string"
                              }
                            },
                            "type": "object"
                          }
                        },
                        "required": [
                          "figure",
                          "source",
                          "binding"
                        ],
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "outcome": {
                      "enum": [
                        "supported",
                        "contradicted",
                        "escalate",
                        "not_applicable"
                      ],
                      "type": "string"
                    },
                    "reason": {
                      "type": "string"
                    },
                    "scope": {
                      "enum": [
                        "arithmetic_only"
                      ],
                      "type": "string"
                    }
                  },
                  "required": [
                    "outcome",
                    "arithmetic",
                    "comparability",
                    "reason",
                    "scope",
                    "limitation",
                    "operands"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Arithmetic assessment over owned retained source figures, never full semantic verification."
          }
        },
        "security": [
          {
            "x-api-key": []
          },
          {
            "bearer": []
          }
        ],
        "tags": [
          "Verification"
        ]
      }
    }
  },
  "servers": [
    {
      "description": "Production",
      "url": "https://api.webcite.co"
    }
  ],
  "tags": []
}
