---
title: "citecheck.completed"
method: POST
path: "citecheck.completed"
---

# citecheck.completed

`POST citecheck.completed` (webhook)

Fires once when a citation check (POST /citecheck) ends with every citation checked or not checkable. The body is CitecheckWebhookPayload: switch on `event` and dedupe on `event_id`.

## Payload

- CitecheckWebhookPayload: Body of ``citecheck.completed`` / ``citecheck.failed`` / ``citecheck.cancelled``. ``event_id`` is stable per (check, event): dedupe on it. Version ``2026-05-13`` also sends ``task_id``, an internal delivery identity that is not pollable on ``/verify/status``.
  - `event` 'citecheck.completed' | 'citecheck.failed' | 'citecheck.cancelled', required
  - `event_id` string, required
  - `citecheck_id` string, required
  - `status` 'completed' | 'failed' | 'cancelled', required
  - `citecheck` CitecheckOut, required
    - `citecheck_id` string, required
    - `status` 'queued' | 'checking' | 'completed' | 'failed' | 'cancelled', required: cancelled: you cancelled it (POST /citechecks/{citecheck_id}/cancel). Only citations it checked before that are charged.
    - `outcome` 'clean' | 'issues_found' | 'incomplete' | 'unchecked', nullable, required: Null until terminal. incomplete: a citation could not be checked for a reason of ours.
    - `created_at` string, required
    - `completed_at` string, nullable, required
    - `language` string, required: The language the reasoning is written in, as the review envelope echoes it.
    - `poll_after_seconds` integer, nullable, required
    - `policy` CitecheckPolicyOut, required
      - `max_citations` integer, nullable, required: How many citations the check selects: for text, the limit (default 20); for pairs, the number of pairs checked.
    - `summary` CitecheckSummaryOut, required
      - `citations_found` integer, nullable: Citations with a URL or DOI in the text, or the pairs sent; null until read.
      - `citations_selected` integer, nullable
      - `citation_limit` integer, nullable
      - `citation_limit_exceeded` boolean, nullable: True when citations_found is more than citation_limit: the rest are in more_citations, found and not checked. False when every citation found was selected, exactly citation_limit included. Null until the text is read.
      - `citation_checks` ReviewCitationCountsOut: Three disjoint counts; once every check has ended they add up to citations_selected.
        - `checked` integer, required: A finding other than unchecked.
        - `unchecked` integer, required: Could not be checked, for a reason of the page or the draft.
        - `failed` integer, required: No finding, for a reason of ours.
      - `citation_issues` integer
      - `citation_limit_reached` boolean, nullable: Sent only to callers on `2026-05-13`; use `citation_limit_exceeded`.
    - `credits` ReviewCreditsOut, required
      - `charged` integer, required: Net credits this review cost the account, read from the ledger. Authoritative once no deep check is running.
    - `citations` ReviewCitationOut[]
      - `index` integer, required
      - `reference` string, nullable, required
      - `cited_url` string, nullable, required
      - `doi` string, nullable, required
      - `statement` string, nullable, required
      - `quotes` string[]
      - `position` PositionOut, required
        - `start` integer, nullable, required: Where the passage starts in `text` as you sent it, in Unicode code points (not UTF-16 units or bytes). Half-open with `end`. `null` when `text` was a URL: the page is not returned, so there is nothing to index, and `text` carries the passage instead.
        - `end` integer, nullable, required: Where the passage ends, exclusive; `null` as for `start`.
        - `text` string, nullable, required: The passage itself, as it appears in the text. `null` on a citation position, whose row already carries the statement.
      - `result` ReviewCitationResultOut, required
        - `finding` 'doi_not_found' | 'page_not_found' | 'contradicted' | 'quote_not_in_source' | 'not_in_source' | 'partly_supported' | 'metadata_mismatch' | 'supported' | 'unchecked', required
        - `source` 'doi' | 'page' | 'support' | 'quote' | 'metadata', nullable, required
        - `is_issue` boolean, required: True for every finding except partly_supported, supported and unchecked. partly_supported is reported, with its snippet, but is not an issue.
      - `check` ReviewCitationCheckOut, required
        - `status` 'pending' | 'running' | 'completed' | 'failed', required
        - `page_read` 'full' | 'partial' | 'not_found' | 'none', nullable: What reading the source gave: full, partial (cut short, a teaser or an abstract alone), not_found, none.
        - `page_title` string, nullable
        - `page_published_date` string, nullable
        - `page_language` string, nullable: The language of the page that was read, as an ISO 639-1 code (e.g. `en`, `uk`); null when unknown.
        - `source_url` string, nullable: Where the text was actually read from.
        - `source_version` 'published' | 'accepted' | 'submitted', nullable
        - `support` 'supported' | 'partly_supported' | 'contradicted' | 'not_in_source' | 'unchecked', nullable
        - `snippet` string, nullable: The passage from the source the support answer rests on, checked against the page text.
        - `rationale` string, nullable: The reviewer's sentence: reasoning, not a checked source.
        - `quote` 'matched' | 'not_in_source' | 'unchecked', nullable
        - `missing_quote` string, nullable: The first of quotes the source does not contain; null unless quote is not_in_source.
        - `doi_registered` boolean, nullable
        - `metadata` 'consistent' | 'mismatch' | 'unchecked', nullable
        - `metadata_differences` ReviewMetadataDifferenceOut[]
          - `field` 'title' | 'authors' | 'year' | 'journal', required
          - `cited` string, nullable
          - `registered` string, nullable
        - `registered` ReviewRegisteredOut
          - `title` string, nullable
          - `authors` string[]
          - `year` integer, nullable
          - `journal` string, nullable
        - `unchecked_reason` string, nullable: Why the finding is unchecked; an extensible string.
        - `hint` string, nullable
        - `failure` FailureOut: Why an item failed, and what to do next. The same block everywhere an item fails: a verification, an /assess row, a review, a review's claim rows and citation checks. ``code`` is what failed, ``detail`` one sentence on it (the same sentence for the same code on every endpoint), ``hint`` what to send next (null when there is nothing to change), ``failure_class`` why, ``retryable`` whether sending the same request again can succeed, ``docs_url`` where that class is explained. API version ``2026-05-13`` sends each endpoint's earlier fields instead (flat ``error`` / ``failure_reason`` on a verification, ``error_code`` / ``hint`` on an /assess row, ``failure.failure_reason`` on a review).
          - `code` string, required: What failed, one vocabulary everywhere: `no_checkable_claim`, `page_unreadable`, `upstream_unavailable`, `internal`, `cancelled`, `no_citations`, `insufficient_credits` (a verification only ever says the first, the third, the fourth and `cancelled`). Branch on `retryable` for whether to resend, and on the input codes for what to say; fall through on a value you do not know. `no_checkable_claim`: no claim in the input could be checked against public evidence (an opinion, a forecast, a claim about a private person, a statement whose only source is unidentified; on a review, also claims found that could not be traced back to the text); `hint` says which. `page_unreadable`: the link /review was sent could not be read. Version `2026-05-13` spells `no_checkable_claim` as `no_claim` (/assess, /review) or `not_a_claim` (/extract, a verification), and keeps the earlier causes as codes (`timeout`, `framing_failed`, `assessment_failed`, `citation_check_failed`, `invalid_input`).
          - `detail` string, required: The cause, in one sentence (out of time, no row could be assessed, ...); the same sentence for the same cause everywhere.
          - `hint` string, nullable: What to send next; null when there is nothing to change.
          - `failure_class` 'upstream_unavailable' | 'invalid_input' | 'cancelled' | 'internal', required
          - `retryable` boolean, required
          - `docs_url` string, nullable, required
    - `citation_issues` ReviewCitationIssueOut[]: Citations whose source does not say what the draft says, the most serious first; partly_supported rows are not issues and are only in citations[].
      - `citation_index` integer, required
      - `reference` string, nullable, required
      - `cited_url` string, nullable, required
      - `doi` string, nullable, required
      - `statement` string, nullable, required
      - `quotes` string[]
      - `position` PositionOut, required
        - `start` integer, nullable, required: Where the passage starts in `text` as you sent it, in Unicode code points (not UTF-16 units or bytes). Half-open with `end`. `null` when `text` was a URL: the page is not returned, so there is nothing to index, and `text` carries the passage instead.
        - `end` integer, nullable, required: Where the passage ends, exclusive; `null` as for `start`.
        - `text` string, nullable, required: The passage itself, as it appears in the text. `null` on a citation position, whose row already carries the statement.
      - `finding` 'doi_not_found' | 'page_not_found' | 'contradicted' | 'quote_not_in_source' | 'not_in_source' | 'partly_supported' | 'metadata_mismatch' | 'supported' | 'unchecked', required
      - `source` 'doi' | 'page' | 'support' | 'quote' | 'metadata', nullable, required
      - `snippet` string, nullable, required: Set only when source is support.
      - `rationale` string, nullable, required: Set only when source is support.
      - `metadata_differences` ReviewMetadataDifferenceOut[]
        - `field` 'title' | 'authors' | 'year' | 'journal', required
        - `cited` string, nullable
        - `registered` string, nullable
      - `missing_quote` string, nullable: Set only when finding is quote_not_in_source.
      - `page_title` string, nullable, required
      - `failure` FailureOut, required: Why an item failed, and what to do next. The same block everywhere an item fails: a verification, an /assess row, a review, a review's claim rows and citation checks. ``code`` is what failed, ``detail`` one sentence on it (the same sentence for the same code on every endpoint), ``hint`` what to send next (null when there is nothing to change), ``failure_class`` why, ``retryable`` whether sending the same request again can succeed, ``docs_url`` where that class is explained. API version ``2026-05-13`` sends each endpoint's earlier fields instead (flat ``error`` / ``failure_reason`` on a verification, ``error_code`` / ``hint`` on an /assess row, ``failure.failure_reason`` on a review).
        - `code` string, required: What failed, one vocabulary everywhere: `no_checkable_claim`, `page_unreadable`, `upstream_unavailable`, `internal`, `cancelled`, `no_citations`, `insufficient_credits` (a verification only ever says the first, the third, the fourth and `cancelled`). Branch on `retryable` for whether to resend, and on the input codes for what to say; fall through on a value you do not know. `no_checkable_claim`: no claim in the input could be checked against public evidence (an opinion, a forecast, a claim about a private person, a statement whose only source is unidentified; on a review, also claims found that could not be traced back to the text); `hint` says which. `page_unreadable`: the link /review was sent could not be read. Version `2026-05-13` spells `no_checkable_claim` as `no_claim` (/assess, /review) or `not_a_claim` (/extract, a verification), and keeps the earlier causes as codes (`timeout`, `framing_failed`, `assessment_failed`, `citation_check_failed`, `invalid_input`).
        - `detail` string, required: The cause, in one sentence (out of time, no row could be assessed, ...); the same sentence for the same cause everywhere.
        - `hint` string, nullable: What to send next; null when there is nothing to change.
        - `failure_class` 'upstream_unavailable' | 'invalid_input' | 'cancelled' | 'internal', required
        - `retryable` boolean, required
        - `docs_url` string, nullable, required
    - `citation_failures` ReviewCitationFailedRowOut[]
      - `citation_index` integer, required
      - `reference` string, nullable, required
      - `cited_url` string, nullable, required
      - `doi` string, nullable, required
      - `failure` FailureOut, required: Why an item failed, and what to do next. The same block everywhere an item fails: a verification, an /assess row, a review, a review's claim rows and citation checks. ``code`` is what failed, ``detail`` one sentence on it (the same sentence for the same code on every endpoint), ``hint`` what to send next (null when there is nothing to change), ``failure_class`` why, ``retryable`` whether sending the same request again can succeed, ``docs_url`` where that class is explained. API version ``2026-05-13`` sends each endpoint's earlier fields instead (flat ``error`` / ``failure_reason`` on a verification, ``error_code`` / ``hint`` on an /assess row, ``failure.failure_reason`` on a review).
        - `code` string, required: What failed, one vocabulary everywhere: `no_checkable_claim`, `page_unreadable`, `upstream_unavailable`, `internal`, `cancelled`, `no_citations`, `insufficient_credits` (a verification only ever says the first, the third, the fourth and `cancelled`). Branch on `retryable` for whether to resend, and on the input codes for what to say; fall through on a value you do not know. `no_checkable_claim`: no claim in the input could be checked against public evidence (an opinion, a forecast, a claim about a private person, a statement whose only source is unidentified; on a review, also claims found that could not be traced back to the text); `hint` says which. `page_unreadable`: the link /review was sent could not be read. Version `2026-05-13` spells `no_checkable_claim` as `no_claim` (/assess, /review) or `not_a_claim` (/extract, a verification), and keeps the earlier causes as codes (`timeout`, `framing_failed`, `assessment_failed`, `citation_check_failed`, `invalid_input`).
        - `detail` string, required: The cause, in one sentence (out of time, no row could be assessed, ...); the same sentence for the same cause everywhere.
        - `hint` string, nullable: What to send next; null when there is nothing to change.
        - `failure_class` 'upstream_unavailable' | 'invalid_input' | 'cancelled' | 'internal', required
        - `retryable` boolean, required
        - `docs_url` string, nullable, required
    - `more_citations` ReviewMoreCitationOut[], nullable: Citations found in the text past the ones selected (up to 100): found but not checked; send them in a later request to check them. Null until the text is read; [] for pairs.
      - `index` integer, required
      - `reference` string, nullable
      - `cited_url` string, nullable
      - `doi` string, nullable
      - `sentence` string, required: The draft sentence around the citation, at most 1,000 characters.
      - `position` PositionOut
        - `start` integer, nullable, required: Where the passage starts in `text` as you sent it, in Unicode code points (not UTF-16 units or bytes). Half-open with `end`. `null` when `text` was a URL: the page is not returned, so there is nothing to index, and `text` carries the passage instead.
        - `end` integer, nullable, required: Where the passage ends, exclusive; `null` as for `start`.
        - `text` string, nullable, required: The passage itself, as it appears in the text. `null` on a citation position, whose row already carries the statement.
    - `failure` FailureOut, required: Why an item failed, and what to do next. The same block everywhere an item fails: a verification, an /assess row, a review, a review's claim rows and citation checks. ``code`` is what failed, ``detail`` one sentence on it (the same sentence for the same code on every endpoint), ``hint`` what to send next (null when there is nothing to change), ``failure_class`` why, ``retryable`` whether sending the same request again can succeed, ``docs_url`` where that class is explained. API version ``2026-05-13`` sends each endpoint's earlier fields instead (flat ``error`` / ``failure_reason`` on a verification, ``error_code`` / ``hint`` on an /assess row, ``failure.failure_reason`` on a review).
      - `code` string, required: What failed, one vocabulary everywhere: `no_checkable_claim`, `page_unreadable`, `upstream_unavailable`, `internal`, `cancelled`, `no_citations`, `insufficient_credits` (a verification only ever says the first, the third, the fourth and `cancelled`). Branch on `retryable` for whether to resend, and on the input codes for what to say; fall through on a value you do not know. `no_checkable_claim`: no claim in the input could be checked against public evidence (an opinion, a forecast, a claim about a private person, a statement whose only source is unidentified; on a review, also claims found that could not be traced back to the text); `hint` says which. `page_unreadable`: the link /review was sent could not be read. Version `2026-05-13` spells `no_checkable_claim` as `no_claim` (/assess, /review) or `not_a_claim` (/extract, a verification), and keeps the earlier causes as codes (`timeout`, `framing_failed`, `assessment_failed`, `citation_check_failed`, `invalid_input`).
      - `detail` string, required: The cause, in one sentence (out of time, no row could be assessed, ...); the same sentence for the same cause everywhere.
      - `hint` string, nullable: What to send next; null when there is nothing to change.
      - `failure_class` 'upstream_unavailable' | 'invalid_input' | 'cancelled' | 'internal', required
      - `retryable` boolean, required
      - `docs_url` string, nullable, required
  - `attempt` integer, required
  - `delivered_at` string, required

## Acknowledgement `2XX`

Your endpoint should return 2xx within 5 seconds. Non-2xx triggers retries at 10s / 60s / 600s (3 retries after the initial delivery, 4 attempts total).

---

[API](https://skmtc.dev/lenz/apis/lenz-ai-fact-checking-api.md) · [All operations](https://skmtc.dev/lenz/apis/lenz-ai-fact-checking-api/llms.txt) · [OpenAPI document](https://skmtc.dev/lenz/apis/lenz-ai-fact-checking-api/revisions/6150947324bf?raw)
