# Webcite API documentation Verify claims, inspect their sources, and extract evidence from documents. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## Make your first request Create an [API key](/api-keys), set it as `WEBCITE_API_KEY` on your server, and follow the [quickstart](/api-docs/quickstart). You can also test a claim in the [playground](/playground). The API origin is `https://api.webcite.co`. Operation paths include their version, such as `/api/v1/verify`. Do not add another `/api` prefix. ## Choose a workflow - Check a claim against the web: [Verify a claim](/api-docs/verify) returns sources, stances, and a verdict. - Check quotations against known sources: [Batch quote matching](/api-docs/batch) reports whether the text can be found. - Read a document: [Upload](/api-docs/upload), then [extract text](/api-docs/extract) or [anchored chunks](/api-docs/chunks). - Check numbers: [Compare figures](/api-docs/conflicts) or [verify arithmetic](/api-docs/numeric) with retained operands. - Connect an agent: Use the [MCP setup guide](/api-docs/mcp). ## Read results before acting A source match, a source stance, and a factual verdict answer different questions. Read the [evidence guide](/api-docs/evidence) before deciding what your application can publish. Keep partial, unknown, and failed states visible. ## Use the format you need Every topic has a **Copy page** action and a Markdown link. Agents can start with the [documentation index](/llms.txt), read the [full documentation](/llms-full.txt), or use the [public OpenAPI file](/api-docs/openapi.json). --- # Make your first API call Create a key, verify one claim, and inspect the result. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/quickstart API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## Create an API key Create a key on the [API Keys page](/api-keys). Keep it in an environment variable on your server. Never put it in frontend code or a public repository. ```bash export WEBCITE_API_KEY="YOUR_API_KEY" ``` ## Send a claim This example requests source stances and a verdict. The fixed price is 4 credits. Examples are illustrative; they do not represent a recorded live verification. ```bash curl --fail-with-body https://api.webcite.co/api/v1/verify \ -H "x-api-key: $WEBCITE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"claim":"The Eiffel Tower is 330 meters tall","include_stance":true,"include_verdict":true}' ``` ## Inspect the result Read each entry in `claim_groups`. Keep the claim with its `citations`, `domain_groups`, and `verdict`. Record `operation_id` and `usage.credits` when returned. Do not use an absent verdict as evidence of support. ```javascript for (const group of data.claim_groups ?? []) { console.log(group.claim); console.log(group.verdict ?? "No verdict returned"); for (const citation of group.citations ?? []) { console.log(citation.url, citation.verification); } } ``` ## Continue your integration The [verify reference](/api-docs/verify) lists every request field and the response schema. For a long-running request, use [streaming](/api-docs/streaming). Read [credits](/api-docs/credits) and [errors](/api-docs/errors) before adding retries. --- # Authentication Authenticate public API calls with an API key. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/authentication API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## Send the key in a header Use `x-api-key: YOUR_API_KEY` on each public API request. JSON operations also use `Content-Type: application/json`. For file uploads, let your client supply the multipart boundary. ```bash curl --fail-with-body https://api.webcite.co/api/v1/citations \ -H "x-api-key: $WEBCITE_API_KEY" ``` ## Keep account and API access separate Create and manage keys in [API Keys](/api-keys). A development key and a production key are not interchangeable. Use the documented origin for the environment that issued your key. Account, billing, admin, and context routes have separate authentication and availability rules. They are not included in this public API-key reference. Manage subscription settings from your account. ## Authenticate an MCP connector The hosted MCP endpoint is `https://api.webcite.co/mcp`. Its connector configuration uses an `Authorization: Bearer YOUR_API_KEY` header. Follow the [connect page](/connect) for the client-specific setup. --- # Credits and billing Estimate a request's cost and interpret the billing information it returns. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/credits API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## Verification prices JSON and streaming verification use the same fixed prices. Cached and uncached requests have the same price. There is no additional API operation charge. | Include stance | Include verdict | Credits per request | | --- | --- | --- | | `false` | `false` | **2 credits** | | `true` | `false` | **3 credits** | | `false` | `true` | **4 credits** | | `true` | `true` | **4 credits** (default) | A verdict requires stance analysis internally, even when the response omits stance fields. `include_numeric_assess` on JSON verification does not add a separate charge. ## Prices for every endpoint | Endpoint | Credits | | --- | --- | | [POST /api/v1/verify](/api-docs/verify) | 2 credits for search only; 3 with stance; 4 with a verdict. No additional operation charge. | | [POST /api/v1/verify/stream](/api-docs/streaming) | Same 2/3/4-credit prices as JSON verification. | | [POST /api/v1/verify/batch](/api-docs/batch) | 1 credit per item, up to 200 items. | | [POST /api/v1/verify/feedback](/api-docs/feedback) | 1 credit. | | [POST /api/v1/sources/search](/api-docs/search) | 2 credits. | | [GET /api/v1/citations](/api-docs/citations) | 1 credit. | | [GET /api/v1/citations/{id}](/api-docs/citation) | 1 credit. | | [POST /api/v1/citations/source-preview](/api-docs/source-preview) | 1 credit. | | [POST /api/v1/upload](/api-docs/upload) | 1 credit for a successful upload. | | [POST /api/v1/extract](/api-docs/extract) | 1 credit for a billable extraction outcome. | | [POST /api/v1/extract/pages](/api-docs/chunks) | 1 credit for a billable extraction outcome. | | [POST /api/v1/classify](/api-docs/classify) | 1 credit for a billable classification outcome. | | [POST /api/v1/gaps](/api-docs/gaps) | 1 credit. | | [POST /api/v1/ask](/api-docs/ask) | 5 credits to queue the work. Polling is free. | | [GET /api/v1/ask/{id}](/api-docs/ask-result) | 0 credits. | | [POST /api/v2/sources/{versionId}/representations/{representationId}/prepare-ocr-rescue](/api-docs/ocr) | Availability and metering depend on the configured OCR workflow; no fixed public credit price is specified. | | [POST /api/v1/extract/figures](/api-docs/figures) | 2 credits for a billable extraction outcome. | | [POST /api/v1/analyze/conflicts](/api-docs/conflicts) | 1 credit. | | [POST /api/v1/analyze/document](/api-docs/analyze-document) | 3 credits. | | [POST /api/v2/verify/numeric](/api-docs/numeric) | No fixed public credit price is specified for this V2 operation. | | [GET /api/v1/accuracy](/api-docs/accuracy) | 1 credit. | Document extraction and classification inspect the outcome before billing. Unsupported/error outcomes and partial outcomes without readable evidence are not charged. A useful partial result can be billable. The V2 numeric and OCR preparation operations do not publish a fixed credit price in this contract. Do not assume they have the V1 price or a universal receipt shape. ## Read billing receipts V1 object responses that use the billing wrapper include `operation_id` and `usage.credits`. `null` means the accounting value is unavailable, not that the work was free. A returned `0` is a known zero deduction. ```json {"operation_id":"EXAMPLE_OPERATION_ID","usage":{"credits":4}} ``` This is an illustrative receipt. Preserve missing and null values when you store it. Streaming sends a separate `usage` event; an `accounting_error` means accounting did not complete normally. Batch verification returns a result array. Citation list/get, feedback, and upload retain their own response shapes and do not universally add this object receipt. Free ask polling returns the job view. Do not require a top-level receipt on every endpoint. The current streaming receipt has a legacy zero fallback when the deduction value is unavailable. A zero in an SSE usage event alone does not establish a free operation. Retain the operation ID and reconcile it with your account usage. ## Manage your plan See [pricing](/#pricing) for current plan allocations and subscription prices. Manage credits and overage settings in your account. Repeating a successful request can incur another charge; store completed results and avoid automatic replay after an ambiguous timeout. --- # Evidence and review Read source grounding, meaning, and confidence as separate parts of a result. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/evidence API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## Evidence policy 13: production rollout Production rollout verified on 24 September 2026: the hosted API includes evidence policy 13 and the hosted MCP connector runs 1.9.0. This does not publish or upgrade the npm package installed by local clients. Inspect response metadata and the installed client version; saved responses retain their original policy and evidence time. Provider-generated summaries and snippets are discovery material. A redirect resolving to a publisher is not proof that its page was read. A failed read remains unavailable evidence, not a contradiction or a finding that the claim is false. Legacy records without receipts remain unknown; this update does not rewrite historical records. ## What a source match establishes `grounded` reports whether a quotation was bound back to its source. It does not establish that the source itself is correct or that the quote preserves the original meaning. Binding methods are `exact`, `normalized`, `fuzzy`, `reflowed`, and `unbound`. Fuzzy and reflowed matches stay `needs_review`. Keep `matched_text` so a reviewer can inspect the passage. ## Read the verification stamp - `layer`: `deterministic`, `bound`, `model`, or `unbound`, describing the evidence basis. - `band`: `verified`, `needs_review`, or `unverified`, describing the review state. - `confidence`: a number or null. A score is not a guarantee. - `grounded`: whether source text was matched. - `meaning`: `not_checked`, `faithful`, `changed`, or `ambiguous`. Missing assessment must not be presented as faithful. - `review_reason`: why the result needs review, when available. Use the whole stamp. Filtering only on a high score can hide a meaning problem or an unresolved match. ## OCR readings and provenance A prepared OCR reading may remove page furniture or join lines. Preserve both the original matched source text and the derived reading. Keep `transformations`, `droppedLines`, `retainedSpans`, `calibrationKey`, `basis`, `model`, and `decidedAt`. A reflowed quote remains `needs_review` even when the derived text matches exactly. [Prepare OCR evidence](/api-docs/ocr) requires retained source and representation IDs. See the batch reference for a complete illustrative OCR result. ## Read supported corrections When evidence establishes a replacement for a wrong clause, `verdict.corrections` can contain a `corrected_statement` and exact proof quotes linked to citations from the same claim group. `correction_status: not_established` means no replacement was proven. Do not invent a corrected value when none is returned. ## Read a publisher evidence receipt In policy 13, a citation can carry an optional `evidence` object with `version: 1`. It records a particular publisher read, not an independent certification of the publisher's claims. | Field | Meaning | | --- | --- | | `originalUrl`, `finalUrl` | Discovered URL and captured destination; a null destination was not established. | | `state` | `discovered`, `matched`, `read`, `unavailable`, `unsupported`, or `invalid`. `matched` identifies an exact selected passage from extracted publisher text; `read` does not establish a relevant match. | | `contentHash`, `retrievedAt` | SHA-256 identity and capture timestamp, or null if unavailable. A new fetch is a new capture. | | `extractionVersion` | `publisher-blocks-v6` for new captures; earlier extraction versions remain valid for historical receipts. | | `quoteMatch` | `exact`, `normalized`, `none`, or `not_attempted`. URL resolution and HTTP 200 alone do not establish an exact match. | | `retention`, `replayable` | Current receipts use `hash_only` and `false`. A hash does not retain the source bytes needed to replay the historical page. | | `failureReason` | Why evidence could not be used, or null. | | `capture` | Optional HTTP status, content type, extracted-text hash, and `robotsNoarchive` observation. | | `context` | Optional list of up to four separate adjacent publisher passages, at most 500 characters each. These provide context and are never joined into the selected quote. | | `dates` | Attributed publisher metadata: `value`, `precision` (`day` or `year`), `kind` (`published` or `updated`), and `basis: publisher_metadata`. An empty list means no accepted metadata date. | Receipt matching is distinct from batch quote binding, which supports additional matching methods. Keep the receipt with the exact citation and claim it describes. Publication and update dates do not establish when a legal or institutional change became effective. A year in a forecast, URL, copyright notice or generated summary is not a publication date. ## Separate source scores from verdict confidence `credibility_score: null` means unknown; `0` is a known zero. `credibility_basis` distinguishes `heuristic`, `measured`, and `unknown`. Policy 5 labels retained source-ranking estimates as heuristic. Provider grounding confidence measures attribution confidence and must not be interpreted as source credibility. Historical `measured` labels alone do not establish how a score was calculated. Verdict `confidence_basis: unknown` and `confidence_available: false` mean calibrated truth confidence is unavailable. The legacy numeric `confidence` is retained as `aggregation_score` for compatibility; `calibrated_confidence` is null. Do not display that aggregate as a probability that the claim is true. A finding's model-reported confidence, a source's relevance and the overall verdict confidence answer different questions; their percentages need not match. Read the cited passages, unresolved timing, conflicting evidence and uncovered claim scope. Unknown authority is not proof that a source is false, and an official source is not automatically decisive for every claim. For current administrative affiliation only, a matched direct official statement can take precedence over undated sources with unverified authority. The summary explains that choice and `conflict` retains the opposing quotation, even when the result is supported or contradicted. The source is not labelled stale without evidence. Conflicting official records, dated counter-evidence and fact-checks remain relevant when they concern the same period. A dated report of a past event does not by itself assert that its organizational affiliation still holds today; explicit continuity claims and retrospective challenges remain counter-evidence. ## Search provider and historical limits The current development repair keeps provider-derived discovery separate from publisher evidence. Alternate search provider implementations are not yet supported; a configuration switch alone cannot enable Tavily, Serper or Firecrawl. Unavailable or unsupported evidence remains explicit. Derived response and search caches are versioned and isolate execution scope. A cache hit does not refresh the publisher capture time. Old entries and missing historical provenance cannot be relabeled as current evidence. --- # Compound claims Ask Webcite to check the factual parts of a claim separately. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/decomposition API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## Enable decomposition Set `decompose_claim: true` on either `POST /api/v1/verify` or `POST /api/v1/verify/stream`. The default is false. ```json {"claim":"The Eiffel Tower is in Paris and is 330 meters tall","decompose_claim":true} ``` ## Keep each result with its claim Read each `claim_groups` entry independently. One supported clause does not make every clause supported. Keep source IDs and correction proof quotes within their group. Streaming can emit `decomposition`, `sub-claim-start`, and `sub-claim-result` events before the final result. --- # Response structure Keep claims, citations, verdicts, and source locations together. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/responses API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## Use claim groups The preferred verification structure is `claim_groups`. Each group contains its claim, citations, domain groups, and optional verdict. The top-level `citations`, `domain_groups`, and `verdict` fields are legacy fields in the schema. New integrations should use the grouped structure. `totalResults` counts citations across claims. `thread_id` can be used for conversation continuity. Source search shares this response schema but does not request verification analysis. ## Handle optional and unknown values An omitted field, null value, false value, and zero have different meanings. Do not replace an absent verdict with supported, absent confidence with zero, or an unknown charge with a free operation. The shared schema includes fields used by internal workflows, such as `generated_prompts`. Their presence in a response schema does not make internal request flags public parameters. ## Inspect the schemas Every endpoint page shows its request fields, declared status codes, and response schema where one exists. Expand object fields for nested definitions. The [OpenAPI file](/api-docs/openapi.json) contains the complete published schemas. Some operations do not yet publish a complete response schema. Those gaps are identified on their reference pages; illustrative examples are not substitutes for a schema. ## Read back the recorded verification Production rollout verified on 24 September 2026: the hosted API includes evidence policy 13 and the hosted MCP connector runs 1.9.0. This does not publish or upgrade the npm package installed by local clients. Inspect response metadata and the installed client version; saved responses retain their original policy and evidence time. `GET /api/v1/citations/{id}` returns a saved record under `data`, including `id`, `thread_id`, `created_at`, and `metadata` when available. Preserve actual IDs; do not invent a thread ID, request ID or result URL when absent. New stored results can include `data.metadata.final_response`. It holds the saved verification response and its evidence policy metadata. Reading it does not rerun verification. Preserve the stored verdict, source receipts and original evidence time. Historical `citation` storage may be a JSON-encoded bare source array or an object with `citations` or `claim_groups`; a missing stored verdict stays unavailable. Malformed storage is a data error, not an empty successful result. The readback metadata is additive. Numeric fields that are unknown can be null. Update client types to preserve null separately from zero and false. Keep unknown additive receipt fields so a later backend does not lose provenance when your client stores the response. --- # Work with documents Upload, extract, and retain the source location behind each piece of evidence. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/document-workflows API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## Choose the next operation 1. [Upload a file](/api-docs/upload) and retain its asset ID. 2. [Extract text](/api-docs/extract) for reading, or [extract anchored chunks](/api-docs/chunks) for retrieval. 3. Check extraction `state`, `code`, and `lost` before using the content. 4. Use [source preview](/api-docs/source-preview) or [batch quote matching](/api-docs/batch) to inspect cited evidence. For numbers, use [analyze document figures](/api-docs/analyze-document). For a question over text, send extracted text to [Ask a document](/api-docs/ask), then poll its result. ## Check extraction coverage `state` is `complete`, `partial`, `unsupported`, or `error`. A partial result can contain useful content and missing sections at the same time. `complete: false` and `lost` identify incomplete recovery when present. Branch on `code`, not on free-form `reason`: - `source_too_large`: send a smaller file. - `source_encrypted`: provide an unlocked copy. - `source_corrupt`: re-export the document. - `unsupported_format`: convert to a supported format. - `ocr_unavailable`: provide a text-layer document or use an OCR-enabled environment. - `partial_extraction`: inspect the missing parts. - `empty_source`: the source was read but held no content. - `extraction_error`: extraction failed; inspect the error before a bounded retry. ## Retain source identity Keep asset IDs, page/sheet/cell anchors, and any `source_version_id`, `representation_id`, and `source_unit_id` returned by the workflow. A mutable filename or URL is not an immutable source reference. V2 numeric verification and OCR preparation require existing retained-source identities. The public upload response does not guarantee that it supplies those identities. Do not invent IDs to bridge that gap. --- # Errors and rate limits Handle failed requests without losing evidence or repeating paid work unnecessarily. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/errors API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## Check the HTTP status Treat any documented 2xx response as HTTP success, then inspect its result. Some V1 POST handlers return 201 although the OpenAPI response is listed as 200. The examples check the full success range. - 400: invalid request or an operation-specific refusal, including insufficient credits in affected V1 flows. Read the message. - 401: missing or invalid authentication. Check the key and environment. - 403: access to the requested resource is not permitted. - 404: the requested resource is unavailable. - 413: input exceeds an accepted size. Reduce it. - 429: request limit reached. Wait for the supplied retry interval. - 5xx: a service or dependency failed. Inspect the response before retrying. After an SSE response begins, errors arrive as events even though the HTTP status was successful. An `error` event or a missing `done` is not a completed verification. ## Rate limits Default configured request limits are Free: 50 per minute and 500 per day; Builder: 100 per minute and 5,000 per day; Enterprise: 500 per minute and 50,000 per day. Deployments may override these values, and some operations use stricter limits. Read `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` when returned. A 429 body can include `retryAfter` in seconds. The published guard enforces request windows; do not rely on the old docs' concurrent-request table as an enforced guarantee. ## Use bounded retries For a 429, wait for the supplied interval. For a transient service failure, use a small retry budget with increasing delays. Do not retry validation or authentication errors unchanged. A timeout does not prove the server did no work. Save completed responses and operation IDs. JSON verification supports `Idempotency-Key` on JSON POST `/api/v1/verify` (MCP `verify_claim` uses optional `idempotency_key`). Retry the same payload and key within 24 hours; a changed payload returns 409. In-flight or uncertain accounting returns 503 and requires reconciliation. This does not guarantee idempotent streaming, batch retries, or automatic refunds. Poll an existing ask job instead of submitting the same question again. --- # API versions and scope Use the version in each operation path, even when the product release is called V2. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/versions API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## Route versions Most public operations use `/api/v1`. Numeric verification and retained OCR preparation use the `/api/v2` paths listed in this reference. Product release V2.0.0 did not move every endpoint to a new prefix. The API origin is `https://api.webcite.co`, without a trailing `/api`. Use the full path shown on each reference page. ## Public and account-scoped workflows This reference covers the API-key-facing operations in the [public OpenAPI](/api-docs/openapi.json). `/api/v2/context` workflows use account scope and separate availability controls. A deployment does not automatically enable graph retrieval, research, generation, or OCR. See the [V2.0.0 release notes](https://github.com/strategyconnect/webcite-backend/releases/tag/V2.0.0) for product release context. Feature availability still depends on the environment. --- # Connect an AI agent Use Webcite through MCP or give an agent the HTTP reference. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/mcp API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## Hosted MCP Follow [Connect to Claude](/connect) to configure a custom connector. The hosted endpoint is: ```text https://api.webcite.co/mcp ``` Use an `Authorization: Bearer YOUR_API_KEY` header as described on the connect page. The public profile exposes 21 API workflows plus `webcite_guide`. Call the guide first to select a workflow. Tool availability does not override backend feature gates. ## Local MCP Set the environment variable before starting the server: ```bash export WEBCITE_API_KEY="YOUR_API_KEY" npx -y webcite-mcp-server ``` For Claude Code, with the key available in its environment: ```bash claude mcp add webcite -- npx -y webcite-mcp-server ``` See the [package documentation](https://www.npmjs.com/package/webcite-mcp-server) for profiles and configuration. Context and evaluation tools are opt-in and have separate backend requirements. ## Upload through MCP Hosted `upload_file` takes a filename and base64 content, up to 20 MB. It cannot read a path on your computer. File paths are supported only by a local MCP server with access to that file. ## HTTP tools and automation An agent or workflow builder can use [OpenAPI](/api-docs/openapi.json) to inspect operation names, parameters, and schemas. Configure API-key authentication with header name `x-api-key`. Handle non-success status codes and preserve source IDs in later steps. For documentation context, start with [llms.txt](/llms.txt). Every page has a `.md` equivalent; [llms-full.txt](/llms-full.txt) combines the guides and reference. These formats provide direct access to the text, without requiring a JavaScript browser. ## MCP 1.9.0: evidence and saved results Production rollout verified on 24 September 2026: the hosted API includes evidence policy 13 and the hosted MCP connector runs 1.9.0. This does not publish or upgrade the npm package installed by local clients. Inspect response metadata and the installed client version; saved responses retain their original policy and evidence time. - `verify_claim` and `verify_claim_stream` return readable text plus `structuredContent` containing the complete backend response, including optional evidence and real IDs. - `verify_batch` returns `{ results: [...] }`, preserving item records, binding details and feedback tokens. - `get_citation` preserves the API `{ data: ... }` envelope and adds normalized `citations` and optional `final_response` from stored metadata (or a verdict-bearing legacy object). The snapshot preserves final evidence before credit settlement, so usage fields can differ from the final response. It does not generate a replacement verdict. - Text labels unknown and heuristic scores; `0` is displayed as zero. Generated snippets are not presented as publisher quotations. - Streaming requires a full result followed by `done`. Partial groups, missing completion, backend errors and accounting errors return `partial_result`, not raw events presented as a finished verification. Successful stream usage is preserved as `stream_usage`. Malformed stored JSON or response shapes return `invalid_api_output`. Check `isError` before consuming structured evidence. The package changes do not alter backend feature gates or prove that historical sources were read. --- # Read a verification stream Keep complete events across network chunks and detect interrupted responses. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/streaming-guide API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## Read event types The response uses `text/event-stream`, with a JSON object in each `data:` frame. Events can include `citation`, `groups`, `verdict`, `decomposition`, `sub-claim-start`, `sub-claim-result`, `metadata`, and `result`. Treat `result` as the final structured verification payload. A separate `usage` event carries `operation_id` and `usage.credits`. `done` marks successful completion. `error` reports a verification failure; `accounting_error` reports a billing failure. Do not turn an interrupted stream into a successful result. ## Keep partial frames A network chunk can end in the middle of a UTF-8 character, a JSON value, or a frame separator. Retain incomplete data until the next read. This example accepts the API's LF and CRLF framing, fails on error events, and limits buffered data to 8 MiB. Increase that client limit only when your expected results require it. ```javascript // This function is also published verbatim in the streaming guide. async function* readEvents(response) { if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); if (!response.body) throw new Error("Response body is unavailable"); const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; let completed = false; try { while (true) { const { value, done } = await reader.read(); buffer += decoder.decode(value, { stream: !done }); if (buffer.length > 8 * 1024 * 1024) throw new Error("SSE frame exceeds the client limit"); let boundary; while ((boundary = /\r?\n\r?\n/.exec(buffer))) { const frame = buffer.slice(0, boundary.index); buffer = buffer.slice(boundary.index + boundary[0].length); const data = frame .split(/\r?\n/) .filter((line) => line.startsWith("data:")) .map((line) => line.slice(5).replace(/^ /, "")) .join("\n"); if (!data) continue; const event = JSON.parse(data); if (event.type === "error" || event.type === "accounting_error") { throw new Error(event.message || event.type); } yield event; if (event.type === "done") { completed = true; return; } } if (done) break; } if (!completed) throw new Error("Stream ended without a done event"); } finally { await reader.cancel(); reader.releaseLock(); } } ``` ## Consume the stream Call this from your server with a key in its environment. Set a timeout appropriate to your request. ```javascript const response = await fetch("https://api.webcite.co/api/v1/verify/stream", { method: "POST", headers: { "x-api-key": process.env.WEBCITE_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ claim: "The Eiffel Tower is 330 meters tall" }), signal: AbortSignal.timeout(300_000), }); for await (const event of readEvents(response)) { console.log(event); } ``` --- # Verify a claim Check a factual claim against sources and return citations, source stances, and a verdict. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/verify API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## When to use it Use this when you need sources and an assessment of whether they support a claim. Set `decompose_claim: true` to check the parts of a compound claim. Both `/verify` and `/verify/stream` support decomposition. `include_numeric_assess` adds an arithmetic check of the claim text to this JSON endpoint. It does not supply source figures or certify the choice of operands. Use [numeric verification](/api-docs/numeric) for retained source operands. ## Evidence response contract Production rollout verified on 24 September 2026: the hosted API includes evidence policy 13 and the hosted MCP connector runs 1.9.0. This does not publish or upgrade the npm package installed by local clients. Inspect response metadata and the installed client version; saved responses retain their original policy and evidence time. Preserve each citation's optional `evidence` receipt and `credibility_basis`, including null scores. Verdict `confidence_available: false` means calibrated truth confidence is unavailable; `aggregation_score` preserves the legacy number for compatibility. See [publisher evidence receipts](/api-docs/evidence#source-receipts) and [score meanings](/api-docs/evidence#scores). Source search does not become a judged verification merely because it returns citations. ## Request POST /api/v1/verify 2 credits for search only; 3 with stance; 4 with a verdict. No additional operation charge. ### curl ```curl curl --fail-with-body -X POST 'https://api.webcite.co/api/v1/verify' \ -H "x-api-key: $WEBCITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "claim": "The Eiffel Tower is 330 meters tall", "include_stance": true, "include_verdict": true }' ``` ### Node.js ```javascript const response = await fetch("https://api.webcite.co/api/v1/verify", { method: "POST", headers: { "x-api-key": process.env.WEBCITE_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ "claim": "The Eiffel Tower is 330 meters tall", "include_stance": true, "include_verdict": true }), }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); console.log(await response.json()); ``` ### python ```python import os import json import requests payload = json.loads("{\n \"claim\": \"The Eiffel Tower is 330 meters tall\",\n \"include_stance\": true,\n \"include_verdict\": true\n}") response = requests.post( "https://api.webcite.co/api/v1/verify", headers={"x-api-key": os.environ["WEBCITE_API_KEY"]}, json=payload, timeout=(10, 300), ) response.raise_for_status() print(response.json()) ``` ## Response Read claim_groups for each claim and its citations, domain_groups, and verdict. Use operation_id and usage.credits to reconcile billing. Optional analysis fields may be absent; absence does not mean the claim is supported. ## Errors For 400, check the request fields and source identifiers. For 401, check your API key. For 429, wait for the retry interval. See the errors guide before retrying a billable request. ## OpenAPI operation ```json { "path": "/api/v1/verify", "method": "POST", "operation": { "description": "Verify a factual claim against authoritative sources. Returns sources with stance analysis and an overall verdict. When a wrong clause has a proven replacement, verdict.corrections contains a corrected_statement and exact proof quotes tied to citations in the same claim group. correction_status is not_established when the evidence cannot prove a replacement.\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\nThe fixed prices are:\n\n| include_stance | include_verdict | Credits |\n|----------------|-----------------|---------|\n| false | false | 2 |\n| true | false | 3 |\n| false | true | 4 |\n| true | true | 4 |\n\nVerdict generation requires stance analysis internally, so verdict-only costs 4. Cached and uncached V1 requests use the same price.", "operationId": "ApiV1Controller_verifyClaim", "parameters": [ { "description": "Optional 1-128 printable non-space ASCII key scoped to the account and verify endpoint. Reuse with the same request to recover a completed result without regenerating or charging twice. Different payload returns 409. Completed checkpoints are retained for 24 hours; missing checkpoints or unresolved accounting return 503 instead of repeating work. This header contract applies to JSON verify only.", "in": "header", "name": "Idempotency-Key", "required": false, "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "examples": { "quick": { "summary": "Quick verify", "value": { "claim": "The Eiffel Tower is 330 meters tall", "include_stance": true, "include_verdict": true } }, "with_numeric": { "summary": "Verify + numeric assess", "value": { "claim": "Revenue rose from 12.5 to 20.0, up 60%", "include_numeric_assess": true, "include_stance": true, "include_verdict": true } } }, "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" }, "409": { "description": "Idempotency-Key reused with a different verification request" }, "429": { "description": "Rate limit exceeded" }, "503": { "description": "Idempotent work in progress, saved result unavailable, or accounting unresolved; retry with the same key" } }, "security": [ { "x-api-key": [] }, { "bearer": [] } ], "summary": "Verify a factual claim", "tags": [ "Public API" ] }, "schemas": { "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_numeric_assess": { "default": false, "description": "If true, also run deterministic numeric assessment on the claim text (no extra figure operands). Skips with reason when the claim is not numeric. Default false. Does not add a separate credit charge in this release; uses the verify bill only.", "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" }, "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" }, "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", "heuristic", "unknown" ], "example": "measured", "type": "string" }, "credibility_score": { "description": "Credibility/relevance score from 1-100 (higher = more credible/relevant/authoritative)", "example": 95, "type": "number" }, "evidence": { "description": "Publisher evidence receipt v1: state, originalUrl, finalUrl, contentHash, retrievedAt, quoteMatch, typed publisher dates, retention, replayable and failureReason. Missing on legacy records means unknown provenance.", "type": "object" }, "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" }, "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" }, "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" }, "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" }, "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" }, "VerdictDto": { "properties": { "aggregation_score": { "description": "Legacy aggregate score retained for compatibility", "type": "number" }, "calibrated_confidence": { "nullable": true, "type": "number" }, "claim": { "description": "The original prompt/claim that was analyzed", "example": "User query or claim being verified", "type": "string" }, "confidence": { "description": "Legacy aggregation score (0-100); use confidence_available and calibrated_confidence for calibration status", "example": 88, "type": "number" }, "confidence_available": { "description": "True only when calibrated confidence is available", "type": "boolean" }, "confidence_basis": { "enum": [ "unknown", "heuristic", "calibrated" ], "type": "string" }, "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" }, "correction_status": { "description": "Whether a source-backed replacement statement is available", "enum": [ "available", "not_established", "conflicted", "not_needed" ], "type": "string" }, "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" }, "NamedConflictDto": { "properties": { "contradicting": { "$ref": "#/components/schemas/ConflictSideDto" }, "supporting": { "$ref": "#/components/schemas/ConflictSideDto" } }, "required": [ "supporting", "contradicting" ], "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": { "deprecated": true, "description": "Deprecated v1 alias of corrected_statement", "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" }, "corrected_statement": { "description": "A standalone replacement statement supported by the cited evidence", "example": "The CAA operates under the UAE Ministry of Higher Education and Scientific Research.", "type": "string" }, "proof": { "items": { "$ref": "#/components/schemas/CorrectionProofDto" }, "type": "array" } }, "required": [ "claimed", "corrected_statement", "citation_ids", "proof" ], "type": "object" }, "CorrectionProofDto": { "properties": { "citation_id": { "example": "caa-mohesr-2025", "type": "string" }, "quote": { "description": "Exact passage copied from the cited source excerpt", "type": "string" } }, "required": [ "citation_id", "quote" ], "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" }, "KeyFindingDto": { "properties": { "citation_ids": { "description": "IDs of citations that support this finding", "example": [ "1", "3", "4" ], "items": { "type": "string" }, "type": "array" }, "confidence": { "description": "Legacy model finding score (0-100); not calibrated truth confidence", "example": 92, "type": "number" }, "confidence_available": { "description": "True only when calibrated confidence is available", "type": "boolean" }, "confidence_basis": { "enum": [ "unknown", "heuristic", "calibrated" ], "type": "string" }, "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" }, "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" }, "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" } } } ``` --- # Stream verification Receive verification events as work progresses, then wait for the final result and completion event. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/streaming API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## When to use it Use `curl --no-buffer` to inspect the stream. See [reading SSE safely](/api-docs/streaming-guide) for a client that retains partial network frames. Use `claim`, `include_stance`, `include_verdict`, and `decompose_claim`. The shared request schema also lists `include_numeric_assess`, but the stream handler does not apply it; use the JSON verify endpoint for that option. ## MCP completion contract Production rollout verified on 24 September 2026: the hosted API includes evidence policy 13 and the hosted MCP connector runs 1.9.0. This does not publish or upgrade the npm package installed by local clients. Inspect response metadata and the installed client version; saved responses retain their original policy and evidence time. MCP 1.9.0 requires both the full result and a later `done` marker. Its `structuredContent` retains the result and a separate `stream_usage` receipt. Incomplete streams and accounting failures report `partial_result`; do not automatically replay a billable request after an ambiguous failure. ## Request POST /api/v1/verify/stream Same 2/3/4-credit prices as JSON verification. ### curl ```curl curl --no-buffer --fail-with-body -X POST 'https://api.webcite.co/api/v1/verify/stream' \ -H "x-api-key: $WEBCITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "claim": "The Eiffel Tower is 330 meters tall", "include_stance": true, "include_verdict": true }' ``` ### Node.js ```javascript const response = await fetch("https://api.webcite.co/api/v1/verify/stream", { method: "POST", headers: { "x-api-key": process.env.WEBCITE_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ "claim": "The Eiffel Tower is 330 meters tall", "include_stance": true, "include_verdict": true }), }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); // Read complete SSE frames. See the streaming guide below. console.log(response.headers.get("content-type")); ``` ### python ```python import os import json import requests payload = json.loads("{\n \"claim\": \"The Eiffel Tower is 330 meters tall\",\n \"include_stance\": true,\n \"include_verdict\": true\n}") response = requests.post( "https://api.webcite.co/api/v1/verify/stream", headers={"x-api-key": os.environ["WEBCITE_API_KEY"]}, json=payload, stream=True, timeout=(10, 300), ) response.raise_for_status() for line in response.iter_lines(decode_unicode=True): if line: print(line) ``` ## Response Content-Type is text/event-stream. Each data frame contains a JSON object with a type. Read result for the final structured response, usage for the billing receipt, and done for successful completion. error and accounting_error are separate failures. End of connection without done is not successful completion. ## Errors For 400, check the request fields and source identifiers. For 401, check your API key. For 429, wait for the retry interval. See the errors guide before retrying a billable request. ## OpenAPI operation ```json { "path": "/api/v1/verify/stream", "method": "POST", "operation": { "description": "Stream verification results via Server-Sent Events. Citations stream as processed, followed by verdict when requested.\n\nPrices match POST /verify: search-only 2 credits, stance without verdict 3 credits, and any request with verdict 4 credits. Cached and uncached V1 requests use the same price.\n\n**Event Types:** citation, groups, verdict, metadata, result, usage, done, error. A successful stream emits one usage event followed by one done event.", "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" ] }, "schemas": { "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_numeric_assess": { "default": false, "description": "If true, also run deterministic numeric assessment on the claim text (no extra figure operands). Skips with reason when the claim is not numeric. Default false. Does not add a separate credit charge in this release; uses the verify bill only.", "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" } } } ``` --- # Check quotes in a batch Check whether each quote appears in the source you provide. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/batch API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## When to use it This checks source grounding. It does not independently search for evidence that the quoted assertion is true. Provide `source_text`, `url`, or `asset_id` for each quote. For retained evidence, supply all three immutable IDs: `source_version_id`, `representation_id`, and `source_unit_id`. `fuzzy` and `reflowed` matches remain `needs_review`. Omit `meaning` when no assessment is available; the result reports `not_checked`. Caller-supplied `meaning` accepts only `changed` or `ambiguous`, never a self-certified `faithful`. ## Per-item response example Illustrative response, not a recorded API result. ```json [ { "id": "1", "quote": "reduced HbA1c by 1.2%", "binding": { "grounded": true, "method": "fuzzy", "score": 0.67, "matched_text": "A 1.2% reduction in HbA1c was observed versus placebo." }, "verification": { "layer": "bound", "band": "needs_review", "confidence": 80, "grounded": true, "meaning": "not_checked" }, "feedback_token": "eyJxIjoi..." } ] ``` ## Prepared OCR example Illustrative response, not a recorded API result. ```json [ { "id": "ocr-example", "quote": "Revenue increased by 18 percent.", "binding": { "grounded": true, "method": "reflowed", "reading": "body", "score": 1, "matched_text": "Revenue increased\nAnnual report\nby 18 percent.", "rescue": { "attempted": true, "rescued": true, "reading": "Revenue increased by 18 percent.", "transformations": [ { "kind": "drop", "atLine": 1, "line": "Annual report", "source": "element_label" }, { "kind": "join", "afterLine": 0 } ], "droppedLines": [ "Annual report" ], "retainedSpans": [ { "page": 1, "fromLine": 0, "toLine": 0 }, { "page": 1, "fromLine": 2, "toLine": 2 } ], "calibrationKey": "illustrative-prepared-artifact-key", "basis": "code", "model": null, "decidedAt": "2026-09-19T00:00:00.000Z" } }, "verification": { "layer": "bound", "band": "needs_review", "confidence": 80, "grounded": true, "meaning": "not_checked" }, "feedback_token": "illustrative-token" } ] ``` ## Request POST /api/v1/verify/batch 1 credit per item, up to 200 items. ### curl ```curl curl --fail-with-body -X POST 'https://api.webcite.co/api/v1/verify/batch' \ -H "x-api-key: $WEBCITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "items": [ { "id": "revenue-2025", "quote": "Revenue was $12 million.", "source_text": "The annual report states: Revenue was $12 million." } ] }' ``` ### Node.js ```javascript const response = await fetch("https://api.webcite.co/api/v1/verify/batch", { method: "POST", headers: { "x-api-key": process.env.WEBCITE_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ "items": [ { "id": "revenue-2025", "quote": "Revenue was $12 million.", "source_text": "The annual report states: Revenue was $12 million." } ] }), }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); console.log(await response.json()); ``` ### python ```python import os import json import requests payload = json.loads("{\n \"items\": [\n {\n \"id\": \"revenue-2025\",\n \"quote\": \"Revenue was $12 million.\",\n \"source_text\": \"The annual report states: Revenue was $12 million.\"\n }\n ]\n}") response = requests.post( "https://api.webcite.co/api/v1/verify/batch", headers={"x-api-key": os.environ["WEBCITE_API_KEY"]}, json=payload, timeout=(10, 300), ) response.raise_for_status() print(response.json()) ``` ## Response An array of results, one per item. Each result includes binding, verification, and feedback_token. This endpoint returns an array, not an object with a top-level usage receipt. ## Errors For 400, check the request fields and source identifiers. For 401, check your API key. For 429, wait for the retry interval. See the errors guide before retrying a billable request. ## OpenAPI operation ```json { "path": "/api/v1/verify/batch", "method": "POST", "operation": { "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" ] }, "schemas": { "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" }, "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" }, "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" }, "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" }, "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" } } } ``` --- # Record review feedback Record whether a reviewer accepts, rejects, or is unsure about a batch result. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/feedback API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## When to use it Pass the `feedback_token` from a batch result as `token`. Valid verdicts are `correct`, `incorrect`, and `unsure`. Feedback records a review; it is not a promise that the underlying source or model has been changed. ## Request POST /api/v1/verify/feedback 1 credit. ### curl ```curl curl --fail-with-body -X POST 'https://api.webcite.co/api/v1/verify/feedback' \ -H "x-api-key: $WEBCITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "token": "FEEDBACK_TOKEN_FROM_BATCH", "verdict": "incorrect", "note": "The quotation omits the stated reporting period." }' ``` ### Node.js ```javascript const response = await fetch("https://api.webcite.co/api/v1/verify/feedback", { method: "POST", headers: { "x-api-key": process.env.WEBCITE_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ "token": "FEEDBACK_TOKEN_FROM_BATCH", "verdict": "incorrect", "note": "The quotation omits the stated reporting period." }), }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); console.log(await response.json()); ``` ### python ```python import os import json import requests payload = json.loads("{\n \"token\": \"FEEDBACK_TOKEN_FROM_BATCH\",\n \"verdict\": \"incorrect\",\n \"note\": \"The quotation omits the stated reporting period.\"\n}") response = requests.post( "https://api.webcite.co/api/v1/verify/feedback", headers={"x-api-key": os.environ["WEBCITE_API_KEY"]}, json=payload, timeout=(10, 300), ) response.raise_for_status() print(response.json()) ``` ## Response Returns confirmation that the feedback was recorded. The public schema does not define every response field. ## Errors For 400, check the request fields and source identifiers. For 401, check your API key. For 429, wait for the retry interval. See the errors guide before retrying a billable request. ## OpenAPI operation ```json { "path": "/api/v1/verify/feedback", "method": "POST", "operation": { "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" ] }, "schemas": { "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" } } } ``` --- # Search sources Find sources for a query without requesting stance analysis or a verdict. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/search API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## When to use it `limit` is the maximum number of returned sources (1-20), not a guarantee. Filters guide retrieval and filter returned metadata. A source with unknown required metadata does not match. Filter by `source_type`, `is_primary_source`, `publication_year` (`from`, `to`), or `domain`. Domain filters include subdomains. Explicit `is_primary_source: false` is a filter, not an omitted value. ## Request POST /api/v1/sources/search 2 credits. ### curl ```curl curl --fail-with-body -X POST 'https://api.webcite.co/api/v1/sources/search' \ -H "x-api-key: $WEBCITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "query": "climate change effects on coral reefs", "limit": 10 }' ``` ### Node.js ```javascript const response = await fetch("https://api.webcite.co/api/v1/sources/search", { method: "POST", headers: { "x-api-key": process.env.WEBCITE_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ "query": "climate change effects on coral reefs", "limit": 10 }), }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); console.log(await response.json()); ``` ### python ```python import os import json import requests payload = json.loads("{\n \"query\": \"climate change effects on coral reefs\",\n \"limit\": 10\n}") response = requests.post( "https://api.webcite.co/api/v1/sources/search", headers={"x-api-key": os.environ["WEBCITE_API_KEY"]}, json=payload, timeout=(10, 300), ) response.raise_for_status() print(response.json()) ``` ## Response Returns source results using the shared search response schema. Analysis fields from verification may be absent. ## Errors For 400, check the request fields and source identifiers. For 401, check your API key. For 429, wait for the retry interval. See the errors guide before retrying a billable request. ## OpenAPI operation ```json { "path": "/api/v1/sources/search", "method": "POST", "operation": { "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" ] }, "schemas": { "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" }, "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" }, "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", "heuristic", "unknown" ], "example": "measured", "type": "string" }, "credibility_score": { "description": "Credibility/relevance score from 1-100 (higher = more credible/relevant/authoritative)", "example": 95, "type": "number" }, "evidence": { "description": "Publisher evidence receipt v1: state, originalUrl, finalUrl, contentHash, retrievedAt, quoteMatch, typed publisher dates, retention, replayable and failureReason. Missing on legacy records means unknown provenance.", "type": "object" }, "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" }, "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" }, "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" }, "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" }, "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" }, "VerdictDto": { "properties": { "aggregation_score": { "description": "Legacy aggregate score retained for compatibility", "type": "number" }, "calibrated_confidence": { "nullable": true, "type": "number" }, "claim": { "description": "The original prompt/claim that was analyzed", "example": "User query or claim being verified", "type": "string" }, "confidence": { "description": "Legacy aggregation score (0-100); use confidence_available and calibrated_confidence for calibration status", "example": 88, "type": "number" }, "confidence_available": { "description": "True only when calibrated confidence is available", "type": "boolean" }, "confidence_basis": { "enum": [ "unknown", "heuristic", "calibrated" ], "type": "string" }, "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" }, "correction_status": { "description": "Whether a source-backed replacement statement is available", "enum": [ "available", "not_established", "conflicted", "not_needed" ], "type": "string" }, "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" }, "NamedConflictDto": { "properties": { "contradicting": { "$ref": "#/components/schemas/ConflictSideDto" }, "supporting": { "$ref": "#/components/schemas/ConflictSideDto" } }, "required": [ "supporting", "contradicting" ], "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": { "deprecated": true, "description": "Deprecated v1 alias of corrected_statement", "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" }, "corrected_statement": { "description": "A standalone replacement statement supported by the cited evidence", "example": "The CAA operates under the UAE Ministry of Higher Education and Scientific Research.", "type": "string" }, "proof": { "items": { "$ref": "#/components/schemas/CorrectionProofDto" }, "type": "array" } }, "required": [ "claimed", "corrected_statement", "citation_ids", "proof" ], "type": "object" }, "CorrectionProofDto": { "properties": { "citation_id": { "example": "caa-mohesr-2025", "type": "string" }, "quote": { "description": "Exact passage copied from the cited source excerpt", "type": "string" } }, "required": [ "citation_id", "quote" ], "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" }, "KeyFindingDto": { "properties": { "citation_ids": { "description": "IDs of citations that support this finding", "example": [ "1", "3", "4" ], "items": { "type": "string" }, "type": "array" }, "confidence": { "description": "Legacy model finding score (0-100); not calibrated truth confidence", "example": 92, "type": "number" }, "confidence_available": { "description": "True only when calibrated confidence is available", "type": "boolean" }, "confidence_basis": { "enum": [ "unknown", "heuristic", "calibrated" ], "type": "string" }, "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" }, "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" }, "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" } } } ``` --- # List citations Retrieve previous verification records with pagination and optional thread filtering. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/citations API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## When to use it Start with `page=1&limit=20`. The pagination schema allows a maximum of 100, while the controller description still says 50. Use 50 or fewer for compatibility. `fields` defaults to `all`; request `basic` explicitly for a smaller response. ## Request GET /api/v1/citations 1 credit. ### curl ```curl curl --fail-with-body -X GET 'https://api.webcite.co/api/v1/citations' \ -H "x-api-key: $WEBCITE_API_KEY" ``` ### Node.js ```javascript const response = await fetch("https://api.webcite.co/api/v1/citations", { method: "GET", headers: { "x-api-key": process.env.WEBCITE_API_KEY, }, }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); console.log(await response.json()); ``` ### python ```python import os import requests response = requests.get( "https://api.webcite.co/api/v1/citations", headers={"x-api-key": os.environ["WEBCITE_API_KEY"]}, timeout=(10, 300), ) response.raise_for_status() print(response.json()) ``` ## Response Returns data and pagination metadata as defined below. Use the returned pagination values when requesting the next page. ## Errors For 400, check the request fields and source identifiers. For 401, check your API key. For 429, wait for the retry interval. See the errors guide before retrying a billable request. ## OpenAPI operation ```json { "path": "/api/v1/citations", "method": "GET", "operation": { "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" ] }, "schemas": { "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" }, "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" } } } ``` --- # Get a citation Retrieve a saved verification record by its ID. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/citation API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## When to use it Use a saved citation record ID returned by the API. A citation record ID and a source URL identify different things. ## Stored identity and conclusions Production rollout verified on 24 September 2026: the hosted API includes evidence policy 13 and the hosted MCP connector runs 1.9.0. This does not publish or upgrade the npm package installed by local clients. Inspect response metadata and the installed client version; saved responses retain their original policy and evidence time. The saved `data` record exposes `id`, `thread_id`, `created_at`, and `metadata` when available. Read `metadata.final_response` for a retained conclusion. An absent legacy conclusion remains unavailable; do not regenerate one while displaying history. See [response structure](/api-docs/responses#saved-results) for historical array/object compatibility and MCP parsing. ## Request GET /api/v1/citations/{id} 1 credit. ### curl ```curl curl --fail-with-body -X GET 'https://api.webcite.co/api/v1/citations/YOUR_ID' \ -H "x-api-key: $WEBCITE_API_KEY" ``` ### Node.js ```javascript const response = await fetch("https://api.webcite.co/api/v1/citations/YOUR_ID", { method: "GET", headers: { "x-api-key": process.env.WEBCITE_API_KEY, }, }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); console.log(await response.json()); ``` ### python ```python import os import requests response = requests.get( "https://api.webcite.co/api/v1/citations/YOUR_ID", headers={"x-api-key": os.environ["WEBCITE_API_KEY"]}, timeout=(10, 300), ) response.raise_for_status() print(response.json()) ``` ## Response Returns the saved record and its available view data. A missing record returns 404. ## Errors For 400, check the request fields and source identifiers. For 401, check your API key. For 429, wait for the retry interval. See the errors guide before retrying a billable request. ## OpenAPI operation ```json { "path": "/api/v1/citations/{id}", "method": "GET", "operation": { "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" ] }, "schemas": { "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" }, "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" } ] }, "created_at": { "description": "Original record time", "format": "date-time", "type": "string" }, "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" }, "id": { "description": "Stored citation identifier", "type": "string" }, "metadata": { "description": "Historical metadata. final_response preserves the final result under evidence_policy_version 9; absent for legacy records.", "type": "object" }, "prompt": { "description": "The original prompt that generated this citation", "example": "Latest trends in AI", "type": "string" }, "thread_id": { "description": "Stored thread identifier, when present", "type": "string" } }, "required": [ "prompt", "citation" ], "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", "heuristic", "unknown" ], "example": "measured", "type": "string" }, "credibility_score": { "description": "Credibility/relevance score from 1-100 (higher = more credible/relevant/authoritative)", "example": 95, "type": "number" }, "evidence": { "description": "Publisher evidence receipt v1: state, originalUrl, finalUrl, contentHash, retrievedAt, quoteMatch, typed publisher dates, retention, replayable and failureReason. Missing on legacy records means unknown provenance.", "type": "object" }, "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" }, "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" }, "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" } } } ``` --- # Preview source evidence Resolve a citation to its source and check whether the quoted passage can be found. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/source-preview API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## When to use it For a web page, send `url`. For a file, send `asset_id` with a 1-based `page`, or a spreadsheet `cell` and optional `full`. For immutable evidence, send `source_version_id`, `representation_id`, and `source_unit_id` together. IDs must belong to the authenticated account. A `reflowed` match uses a prepared OCR reading and remains subject to review. Preserve the original `matched_text` and the rescue provenance. See [evidence and review](/api-docs/evidence). ## Request POST /api/v1/citations/source-preview 1 credit. ### curl ```curl curl --fail-with-body -X POST 'https://api.webcite.co/api/v1/citations/source-preview' \ -H "x-api-key: $WEBCITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "url": "https://www.toureiffel.paris/en/the-monument/key-figures", "quote": "330 meters" }' ``` ### Node.js ```javascript const response = await fetch("https://api.webcite.co/api/v1/citations/source-preview", { method: "POST", headers: { "x-api-key": process.env.WEBCITE_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ "url": "https://www.toureiffel.paris/en/the-monument/key-figures", "quote": "330 meters" }), }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); console.log(await response.json()); ``` ### python ```python import os import json import requests payload = json.loads("{\n \"url\": \"https://www.toureiffel.paris/en/the-monument/key-figures\",\n \"quote\": \"330 meters\"\n}") response = requests.post( "https://api.webcite.co/api/v1/citations/source-preview", headers={"x-api-key": os.environ["WEBCITE_API_KEY"]}, json=payload, timeout=(10, 300), ) response.raise_for_status() print(response.json()) ``` ## Response Returns a source preview and binding information. Check binding.grounded, binding.method, and the matched passage before presenting the quote as source-backed. ## Errors For 400, check the request fields and source identifiers. For 401, check your API key. For 429, wait for the retry interval. See the errors guide before retrying a billable request. ## OpenAPI operation ```json { "path": "/api/v1/citations/source-preview", "method": "POST", "operation": { "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" ] }, "schemas": { "SourcePreviewRequestDto": { "properties": { "asset_id": { "description": "Uploaded asset ID to preview (from POST /upload). Provide this OR url. Also accepts an \"asset://\" 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" }, "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" } } } ``` --- # Upload a file Upload a file and keep the returned asset ID for extraction, preview, or document analysis. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/upload API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## When to use it Send `multipart/form-data` with one `file` field. Let the HTTP client set the multipart boundary. Do not send a local file path as JSON. The hosted MCP upload tool uses filename and base64 content with a 20 MB limit; that is a separate transport from this HTTP endpoint. ## Request POST /api/v1/upload 1 credit for a successful upload. ### curl ```curl curl --fail-with-body -X POST 'https://api.webcite.co/api/v1/upload' \ -H "x-api-key: $WEBCITE_API_KEY" \ -F "file=@./report.pdf" ``` ### Node.js ```javascript import { readFile } from "node:fs/promises"; const form = new FormData(); form.append("file", new Blob([await readFile("./report.pdf")]), "report.pdf"); const response = await fetch("https://api.webcite.co/api/v1/upload", { method: "POST", headers: { "x-api-key": process.env.WEBCITE_API_KEY, }, body: form, }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); console.log(await response.json()); ``` ### python ```python import os import requests with open("report.pdf", "rb") as file: response = requests.post( "https://api.webcite.co/api/v1/upload", headers={"x-api-key": os.environ["WEBCITE_API_KEY"]}, files={"file": file}, timeout=(10, 300), ) response.raise_for_status() print(response.json()) ``` ## Response Returns the storage upload result. Keep the asset ID from the response. The public OpenAPI does not yet provide a complete upload response schema. Upload billing is recorded separately and does not add a top-level usage object. ## Errors An unsuccessful upload uses a non-success HTTP status. A 503 can mean private evidence storage is unavailable. Check the response before starting extraction; an upload failure does not provide a usable asset. ## OpenAPI operation ```json { "path": "/api/v1/upload", "method": "POST", "operation": { "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" ] }, "schemas": {} } ``` --- # Extract document text Read an uploaded document as text with extraction status and source coverage. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/extract API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## When to use it Use the returned asset ID from [upload](/api-docs/upload). Text-layer extraction is attempted before OCR when supported. Scanned documents can require OCR. Check `state` before using `markdown`. Branch on the stable `code`; display `reason` as diagnostic text. See [document workflows](/api-docs/document-workflows). ## Request POST /api/v1/extract 1 credit for a billable extraction outcome. ### curl ```curl curl --fail-with-body -X POST 'https://api.webcite.co/api/v1/extract' \ -H "x-api-key: $WEBCITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "asset_id": "YOUR_ASSET_ID" }' ``` ### Node.js ```javascript const response = await fetch("https://api.webcite.co/api/v1/extract", { method: "POST", headers: { "x-api-key": process.env.WEBCITE_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ "asset_id": "YOUR_ASSET_ID" }), }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); console.log(await response.json()); ``` ### python ```python import os import json import requests payload = json.loads("{\n \"asset_id\": \"YOUR_ASSET_ID\"\n}") response = requests.post( "https://api.webcite.co/api/v1/extract", headers={"x-api-key": os.environ["WEBCITE_API_KEY"]}, json=payload, timeout=(10, 300), ) response.raise_for_status() print(response.json()) ``` ## Response Read state, code, reason, extraction_method, markdown, and any lost parts. complete: false means the whole source was not recovered. Do not convert an error or partial read into an empty successful document. ## Errors The contract lists 400, 401, 404, 413, 429, 500, 502, 503, and 504 outcomes. Inspect the response: missing assets, oversized files, storage failures, and timeouts need different remedies. Never treat a failed download as an empty document. ## OpenAPI operation ```json { "path": "/api/v1/extract", "method": "POST", "operation": { "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" ] }, "schemas": { "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" }, "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" } } } ``` --- # Extract anchored chunks Split extracted content into chunks that retain page, sheet, or section locations. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/chunks API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## When to use it Use these chunks for retrieval and citation display. Preserve the source anchors when you index the text. `token_estimate` is an estimate for context budgeting, not a provider billing count. ## Request POST /api/v1/extract/pages 1 credit for a billable extraction outcome. ### curl ```curl curl --fail-with-body -X POST 'https://api.webcite.co/api/v1/extract/pages' \ -H "x-api-key: $WEBCITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "asset_id": "YOUR_ASSET_ID" }' ``` ### Node.js ```javascript const response = await fetch("https://api.webcite.co/api/v1/extract/pages", { method: "POST", headers: { "x-api-key": process.env.WEBCITE_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ "asset_id": "YOUR_ASSET_ID" }), }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); console.log(await response.json()); ``` ### python ```python import os import json import requests payload = json.loads("{\n \"asset_id\": \"YOUR_ASSET_ID\"\n}") response = requests.post( "https://api.webcite.co/api/v1/extract/pages", headers={"x-api-key": os.environ["WEBCITE_API_KEY"]}, json=payload, timeout=(10, 300), ) response.raise_for_status() print(response.json()) ``` ## Response Returns chunks with contiguous zero-based ordinal, text, token_estimate, and available source anchors, plus extraction state. ## Errors For 400, check the request fields and source identifiers. For 401, check your API key. For 429, wait for the retry interval. See the errors guide before retrying a billable request. ## OpenAPI operation ```json { "path": "/api/v1/extract/pages", "method": "POST", "operation": { "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" ] }, "schemas": { "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" }, "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" }, "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" } } } ``` --- # Classify a document Identify a document category and the document types it covers. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/classify API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## When to use it Classification uses extracted content and spreadsheet sheet names. Check the read state before treating a category as established. It does not certify that the document meets a diligence requirement. ## Request POST /api/v1/classify 1 credit for a billable classification outcome. ### curl ```curl curl --fail-with-body -X POST 'https://api.webcite.co/api/v1/classify' \ -H "x-api-key: $WEBCITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "asset_id": "YOUR_ASSET_ID" }' ``` ### Node.js ```javascript const response = await fetch("https://api.webcite.co/api/v1/classify", { method: "POST", headers: { "x-api-key": process.env.WEBCITE_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ "asset_id": "YOUR_ASSET_ID" }), }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); console.log(await response.json()); ``` ### python ```python import os import json import requests payload = json.loads("{\n \"asset_id\": \"YOUR_ASSET_ID\"\n}") response = requests.post( "https://api.webcite.co/api/v1/classify", headers={"x-api-key": os.environ["WEBCITE_API_KEY"]}, json=payload, timeout=(10, 300), ) response.raise_for_status() print(response.json()) ``` ## Response Returns category, covers, state, reason, and code. A bundled document can cover more than one type. ## Errors For 400, check the request fields and source identifiers. For 401, check your API key. For 429, wait for the retry interval. See the errors guide before retrying a billable request. ## OpenAPI operation ```json { "path": "/api/v1/classify", "method": "POST", "operation": { "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" ] }, "schemas": { "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" } } } ``` --- # Check document coverage Compare the documents already filed with a category checklist. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/gaps API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## When to use it This is an advisory checklist. `present` means a supplied record matched the checklist; it does not establish that the contents are complete or correct. Choose `vc` or `ma` taxonomy and, where appropriate, `early` or `growth` stage. ## Request POST /api/v1/gaps 1 credit. ### curl ```curl curl --fail-with-body -X POST 'https://api.webcite.co/api/v1/gaps' \ -H "x-api-key: $WEBCITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "category": "financial", "docs": [], "taxonomy": "vc", "stage": "early" }' ``` ### Node.js ```javascript const response = await fetch("https://api.webcite.co/api/v1/gaps", { method: "POST", headers: { "x-api-key": process.env.WEBCITE_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ "category": "financial", "docs": [], "taxonomy": "vc", "stage": "early" }), }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); console.log(await response.json()); ``` ### python ```python import os import json import requests payload = json.loads("{\n \"category\": \"financial\",\n \"docs\": [],\n \"taxonomy\": \"vc\",\n \"stage\": \"early\"\n}") response = requests.post( "https://api.webcite.co/api/v1/gaps", headers={"x-api-key": os.environ["WEBCITE_API_KEY"]}, json=payload, timeout=(10, 300), ) response.raise_for_status() print(response.json()) ``` ## Response Returns items with name and present. A match can come from a filename, category, or covered document type. ## Errors For 400, check the request fields and source identifiers. For 401, check your API key. For 429, wait for the retry interval. See the errors guide before retrying a billable request. ## OpenAPI operation ```json { "path": "/api/v1/gaps", "method": "POST", "operation": { "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" ] }, "schemas": { "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" }, "GapDocDto": { "properties": { "category": { "type": "string" }, "covers": { "items": { "type": "string" }, "type": "array" }, "filename": { "type": "string" }, "label": { "type": "string" } }, "type": "object" } } } ``` --- # Ask a document Queue a question over document text and retrieve a checked answer when processing finishes. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/ask API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## When to use it This endpoint accepts `documentText`, not an asset ID. Extract the document first and check its coverage. Set `hasTextLayer: false` when the supplied source is a scan without usable text. `topK` defaults to 8 retrieved passages. ## Request POST /api/v1/ask 5 credits to queue the work. Polling is free. ### curl ```curl curl --fail-with-body -X POST 'https://api.webcite.co/api/v1/ask' \ -H "x-api-key: $WEBCITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "question": "What was revenue in FY2025?", "documentText": "Revenue for FY2025 was $12 million.", "documentName": "Example annual report" }' ``` ### Node.js ```javascript const response = await fetch("https://api.webcite.co/api/v1/ask", { method: "POST", headers: { "x-api-key": process.env.WEBCITE_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ "question": "What was revenue in FY2025?", "documentText": "Revenue for FY2025 was $12 million.", "documentName": "Example annual report" }), }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); console.log(await response.json()); ``` ### python ```python import os import json import requests payload = json.loads("{\n \"question\": \"What was revenue in FY2025?\",\n \"documentText\": \"Revenue for FY2025 was $12 million.\",\n \"documentName\": \"Example annual report\"\n}") response = requests.post( "https://api.webcite.co/api/v1/ask", headers={"x-api-key": os.environ["WEBCITE_API_KEY"]}, json=payload, timeout=(10, 300), ) response.raise_for_status() print(response.json()) ``` ## Response HTTP 202 returns an id. Poll GET /api/v1/ask/{id}. Unverified answer values remain null with warnings. ## Errors For 400, check the request fields and source identifiers. For 401, check your API key. For 429, wait for the retry interval. See the errors guide before retrying a billable request. ## OpenAPI operation ```json { "path": "/api/v1/ask", "method": "POST", "operation": { "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" ] }, "schemas": { "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" } } } ``` --- # Get a document answer Poll a queued document question for its status and checked result. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/ask-result API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## When to use it Keep the ID returned by [Ask a document](/api-docs/ask). Poll with a delay and a bounded timeout. Stop polling on `done` or `failed`. ## Request GET /api/v1/ask/{id} 0 credits. ### curl ```curl curl --fail-with-body -X GET 'https://api.webcite.co/api/v1/ask/YOUR_ID' \ -H "x-api-key: $WEBCITE_API_KEY" ``` ### Node.js ```javascript const response = await fetch("https://api.webcite.co/api/v1/ask/YOUR_ID", { method: "GET", headers: { "x-api-key": process.env.WEBCITE_API_KEY, }, }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); console.log(await response.json()); ``` ### python ```python import os import requests response = requests.get( "https://api.webcite.co/api/v1/ask/YOUR_ID", headers={"x-api-key": os.environ["WEBCITE_API_KEY"]}, timeout=(10, 300), ) response.raise_for_status() print(response.json()) ``` ## Response Status is queued, running, done, or failed. When done, inspect the result and warnings; a completed job can still have a null answer. ## Errors For 400, check the request fields and source identifiers. For 401, check your API key. For 429, wait for the retry interval. See the errors guide before retrying a billable request. ## OpenAPI operation ```json { "path": "/api/v1/ask/{id}", "method": "GET", "operation": { "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" ] }, "schemas": {} } ``` --- # Prepare OCR evidence Prepare a retained OCR representation for later quote matching. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/ocr API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## When to use it Requires an existing, account-owned source version and parser representation. An upload asset ID is not a replacement for either ID. This endpoint does not ingest a new document. Preparation can be disabled for the deployment. A prepared artifact lets batch verification or source preview attempt a derived body reading; it does not turn an OCR match into a verified quotation. [OCR provenance example](/api-docs/evidence#ocr-readings). ## Request POST /api/v2/sources/{versionId}/representations/{representationId}/prepare-ocr-rescue Availability and metering depend on the configured OCR workflow; no fixed public credit price is specified. ### curl ```curl curl --fail-with-body -X POST 'https://api.webcite.co/api/v2/sources/YOUR_VERSIONID/representations/YOUR_REPRESENTATIONID/prepare-ocr-rescue' \ -H "x-api-key: $WEBCITE_API_KEY" ``` ### Node.js ```javascript const response = await fetch("https://api.webcite.co/api/v2/sources/YOUR_VERSIONID/representations/YOUR_REPRESENTATIONID/prepare-ocr-rescue", { method: "POST", headers: { "x-api-key": process.env.WEBCITE_API_KEY, }, }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); console.log(await response.json()); ``` ### python ```python import os import requests response = requests.post( "https://api.webcite.co/api/v2/sources/YOUR_VERSIONID/representations/YOUR_REPRESENTATIONID/prepare-ocr-rescue", headers={"x-api-key": os.environ["WEBCITE_API_KEY"]}, timeout=(10, 300), ) response.raise_for_status() print(response.json()) ``` ## Response Returns preparation metadata, or attempted: false with code disabled or not_ocr. The current public OpenAPI has no complete response schema for this operation. ## Errors For 400, check the request fields and source identifiers. For 401, check your API key. For 429, wait for the retry interval. See the errors guide before retrying a billable request. ## OpenAPI operation ```json { "path": "/api/v2/sources/{versionId}/representations/{representationId}/prepare-ocr-rescue", "method": "POST", "operation": { "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" ] }, "schemas": {} } ``` --- # Extract figures Read document numbers as figures with units, reporting context, and source locations. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/figures API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## When to use it Keep the source label, sheet, cell, and other provenance with each figure. A missing number is unknown, not zero. `scale` describes an already normalized value; do not apply the scale again. ## Request POST /api/v1/extract/figures 2 credits for a billable extraction outcome. ### curl ```curl curl --fail-with-body -X POST 'https://api.webcite.co/api/v1/extract/figures' \ -H "x-api-key: $WEBCITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "asset_id": "YOUR_ASSET_ID" }' ``` ### Node.js ```javascript const response = await fetch("https://api.webcite.co/api/v1/extract/figures", { method: "POST", headers: { "x-api-key": process.env.WEBCITE_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ "asset_id": "YOUR_ASSET_ID" }), }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); console.log(await response.json()); ``` ### python ```python import os import json import requests payload = json.loads("{\n \"asset_id\": \"YOUR_ASSET_ID\"\n}") response = requests.post( "https://api.webcite.co/api/v1/extract/figures", headers={"x-api-key": os.environ["WEBCITE_API_KEY"]}, json=payload, timeout=(10, 300), ) response.raise_for_status() print(response.json()) ``` ## Response Returns figures with available metric, value, unit, period, entity, basis, and provenance fields. Preserve decimalValue when supplied for exact arithmetic. ## Errors For 400, check the request fields and source identifiers. For 401, check your API key. For 429, wait for the retry interval. See the errors guide before retrying a billable request. ## OpenAPI operation ```json { "path": "/api/v1/extract/figures", "method": "POST", "operation": { "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" ] }, "schemas": { "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" }, "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" } } } ``` --- # Compare figures Recompute and compare figures you already extracted. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/conflicts API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## When to use it Compare figures only when entity, period, unit, currency, scale, basis, and definition are compatible. Include source-linked `fxRates` for currency conversion and reviewed `precedenceRules` only where justified. A missing comparison is not a clean result. ## Request POST /api/v1/analyze/conflicts 1 credit. ### curl ```curl curl --fail-with-body -X POST 'https://api.webcite.co/api/v1/analyze/conflicts' \ -H "x-api-key: $WEBCITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "figures": [ { "metric": "revenue", "value": 12, "unit": "currency", "currency": "USD", "period": "FY2025", "decimalValue": "12", "provenance": { "assetId": "YOUR_ASSET_ID", "sheet": "Income statement", "cell": "B2", "documentName": "Example annual report", "method": "rule" } } ] }' ``` ### Node.js ```javascript const response = await fetch("https://api.webcite.co/api/v1/analyze/conflicts", { method: "POST", headers: { "x-api-key": process.env.WEBCITE_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ "figures": [ { "metric": "revenue", "value": 12, "unit": "currency", "currency": "USD", "period": "FY2025", "decimalValue": "12", "provenance": { "assetId": "YOUR_ASSET_ID", "sheet": "Income statement", "cell": "B2", "documentName": "Example annual report", "method": "rule" } } ] }), }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); console.log(await response.json()); ``` ### python ```python import os import json import requests payload = json.loads("{\n \"figures\": [\n {\n \"metric\": \"revenue\",\n \"value\": 12,\n \"unit\": \"currency\",\n \"currency\": \"USD\",\n \"period\": \"FY2025\",\n \"decimalValue\": \"12\",\n \"provenance\": {\n \"assetId\": \"YOUR_ASSET_ID\",\n \"sheet\": \"Income statement\",\n \"cell\": \"B2\",\n \"documentName\": \"Example annual report\",\n \"method\": \"rule\"\n }\n }\n ]\n}") response = requests.post( "https://api.webcite.co/api/v1/analyze/conflicts", headers={"x-api-key": os.environ["WEBCITE_API_KEY"]}, json=payload, timeout=(10, 300), ) response.raise_for_status() print(response.json()) ``` ## Response Returns conflicts, recomputations, and review information. The current public schema describes the request figures but does not define the complete response. ## Errors For 400, check the request fields and source identifiers. For 401, check your API key. For 429, wait for the retry interval. See the errors guide before retrying a billable request. ## OpenAPI operation ```json { "path": "/api/v1/analyze/conflicts", "method": "POST", "operation": { "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" ] }, "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" }, "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" }, "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" }, "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" }, "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" } } } ``` --- # Analyze document figures Extract numbers from a spreadsheet or PDF, then recompute and compare them. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/analyze-document API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## When to use it Use this when your starting point is a file. Use [Compare figures](/api-docs/conflicts) when you already have structured figures. The public OpenAPI does not yet define a complete response schema. ## Request POST /api/v1/analyze/document 3 credits. ### curl ```curl curl --fail-with-body -X POST 'https://api.webcite.co/api/v1/analyze/document' \ -H "x-api-key: $WEBCITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "asset_id": "YOUR_ASSET_ID" }' ``` ### Node.js ```javascript const response = await fetch("https://api.webcite.co/api/v1/analyze/document", { method: "POST", headers: { "x-api-key": process.env.WEBCITE_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ "asset_id": "YOUR_ASSET_ID" }), }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); console.log(await response.json()); ``` ### python ```python import os import json import requests payload = json.loads("{\n \"asset_id\": \"YOUR_ASSET_ID\"\n}") response = requests.post( "https://api.webcite.co/api/v1/analyze/document", headers={"x-api-key": os.environ["WEBCITE_API_KEY"]}, json=payload, timeout=(10, 300), ) response.raise_for_status() print(response.json()) ``` ## Response Returns extracted figures alongside conflicts, recomputations, and review information. Inspect extraction coverage as well as numeric findings. ## Errors For 400, check the request fields and source identifiers. For 401, check your API key. For 429, wait for the retry interval. See the errors guide before retrying a billable request. ## OpenAPI operation ```json { "path": "/api/v1/analyze/document", "method": "POST", "operation": { "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" ] }, "schemas": { "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" } } } ``` --- # Verify numeric arithmetic Check a numeric claim against figures selected from retained source evidence. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/numeric API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## When to use it Supply one or two operands using immutable IDs already returned by a retained-source workflow. `figure_index` is zero-based within the selected source unit. Inspect the source text before choosing it. Do not substitute an upload asset ID or invent a source version. `escalate`, `not_applicable`, null arithmetic, or unknown comparability are unresolved states. Do not translate them into supported. ## Request POST /api/v2/verify/numeric No fixed public credit price is specified for this V2 operation. ### curl ```curl curl --fail-with-body -X POST 'https://api.webcite.co/api/v2/verify/numeric' \ -H "x-api-key: $WEBCITE_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "claim": "Revenue rose from 12.5 to 20.0, up 60%", "operands": [ { "source_version_id": "YOUR_SOURCE_VERSION_ID", "representation_id": "YOUR_REPRESENTATION_ID", "source_unit_id": "YOUR_SOURCE_UNIT_ID", "figure_index": 0 }, { "source_version_id": "YOUR_SOURCE_VERSION_ID", "representation_id": "YOUR_REPRESENTATION_ID", "source_unit_id": "YOUR_SOURCE_UNIT_ID", "figure_index": 1 } ] }' ``` ### Node.js ```javascript const response = await fetch("https://api.webcite.co/api/v2/verify/numeric", { method: "POST", headers: { "x-api-key": process.env.WEBCITE_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ "claim": "Revenue rose from 12.5 to 20.0, up 60%", "operands": [ { "source_version_id": "YOUR_SOURCE_VERSION_ID", "representation_id": "YOUR_REPRESENTATION_ID", "source_unit_id": "YOUR_SOURCE_UNIT_ID", "figure_index": 0 }, { "source_version_id": "YOUR_SOURCE_VERSION_ID", "representation_id": "YOUR_REPRESENTATION_ID", "source_unit_id": "YOUR_SOURCE_UNIT_ID", "figure_index": 1 } ] }), }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); console.log(await response.json()); ``` ### python ```python import os import json import requests payload = json.loads("{\n \"claim\": \"Revenue rose from 12.5 to 20.0, up 60%\",\n \"operands\": [\n {\n \"source_version_id\": \"YOUR_SOURCE_VERSION_ID\",\n \"representation_id\": \"YOUR_REPRESENTATION_ID\",\n \"source_unit_id\": \"YOUR_SOURCE_UNIT_ID\",\n \"figure_index\": 0\n },\n {\n \"source_version_id\": \"YOUR_SOURCE_VERSION_ID\",\n \"representation_id\": \"YOUR_REPRESENTATION_ID\",\n \"source_unit_id\": \"YOUR_SOURCE_UNIT_ID\",\n \"figure_index\": 1\n }\n ]\n}") response = requests.post( "https://api.webcite.co/api/v2/verify/numeric", headers={"x-api-key": os.environ["WEBCITE_API_KEY"]}, json=payload, timeout=(10, 300), ) response.raise_for_status() print(response.json()) ``` ## Response HTTP 201 returns outcome, arithmetic, comparability, reason, scope, limitation, and operands. scope is arithmetic_only. Figure selection and the full semantic claim are not certified. ## Errors 400 includes invalid claim or operand fields, an unavailable figure index, or ambiguous source numbers. Authentication and source ownership checks also apply. Do not retry with fabricated operand values. ## OpenAPI operation ```json { "path": "/api/v2/verify/numeric", "method": "POST", "operation": { "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" ] }, "schemas": {} } ``` --- # Read accuracy measurements Retrieve the numeric engine's measured results on its evaluation corpus. Documentation index: https://webcite.co/llms.txt Canonical page: https://webcite.co/api-docs/accuracy API origin: https://api.webcite.co Authentication: x-api-key header. Keep keys on your server. ## When to use it These measurements describe a specific evaluation corpus. They are not a guarantee of accuracy for your documents. Keep the corpus context with any reported score. ## Request GET /api/v1/accuracy 1 credit. ### curl ```curl curl --fail-with-body -X GET 'https://api.webcite.co/api/v1/accuracy' \ -H "x-api-key: $WEBCITE_API_KEY" ``` ### Node.js ```javascript const response = await fetch("https://api.webcite.co/api/v1/accuracy", { method: "GET", headers: { "x-api-key": process.env.WEBCITE_API_KEY, }, }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); console.log(await response.json()); ``` ### python ```python import os import requests response = requests.get( "https://api.webcite.co/api/v1/accuracy", headers={"x-api-key": os.environ["WEBCITE_API_KEY"]}, timeout=(10, 300), ) response.raise_for_status() print(response.json()) ``` ## Response Returns corpus measurements for conflict detection and recomputation. The public contract does not define all response fields. ## Errors For 400, check the request fields and source identifiers. For 401, check your API key. For 429, wait for the retry interval. See the errors guide before retrying a billable request. ## OpenAPI operation ```json { "path": "/api/v1/accuracy", "method": "GET", "operation": { "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" ] }, "schemas": {} } ```