Verification States
Verification States
POST /v1/verify returns a status field that is always one of the 16 codes below. Verbitas never returns the strings real, fake, authentic, genuine, verified-truth, or confirmed-deepfake.
Adding a new state code requires an RFC in docs/rfcs/. The enum is enforced by both the Pydantic model (backend) and the Zod schema (MCP/SDK).
State codes
| Code | Confidence | Meaning |
|---|---|---|
verified_manifest_and_watermark_match | High (≥ 0.95) | C2PA signature valid, signer trusted, watermark decoded and matches manifest record |
verified_manifest_intact | High (≥ 0.92) | C2PA signature valid, signer trusted, manifest not tampered — watermark not required by recipe |
anchor_confirmed | Supplemental | Manifest digest found in a confirmed anchor batch (OTS or Arbitrum) |
watermark_only | Medium (0.75–0.90) | Watermark decoded and maps to a manifest record; no embedded C2PA manifest in file |
fingerprint_match_only | Medium (0.70–0.85) | Perceptual hash matched a known asset; no manifest or watermark in submitted file |
multiple_candidates | Low | Soft-binding lookup returned more than one match; human review required |
partial_provenance | Low-medium | Some signals present; at least one required signal is missing or failed |
no_provenance | None | No manifest, watermark, or fingerprint match found |
manifest_tampered | N/A | Manifest is present but the claim digest does not match the asset content |
revoked | N/A | Signing certificate has been revoked (OCSP response: revoked) |
expired | N/A | Signing certificate was not valid at the time of the sign operation |
trust_list_miss | N/A | Manifest present and signature valid, but signer is not on the configured trust list |
ocsp_unavailable | Warning | OCSP responder unreachable; result is downgraded (dev only — prod returns this as a hard failure state) |
ingredient_chain_broken | N/A | Recipe requires full ingredient chain; one or more parent manifests cannot be retrieved or verified |
anchor_not_found | Supplemental | Manifest digest not yet found in any anchor batch (may be pending if anchor is recent) |
error | N/A | Verification process failed due to an internal error; see request_id for triage |
What each signal contributes
Each verification call aggregates up to five independent signals:
| Signal | Source | Weight in confidence |
|---|---|---|
| C2PA manifest present | Embedded JUMBF or sidecar | High |
| C2PA signature valid | X.509 chain verification | High |
| Signer on trust list | Trust list lookup | High |
OCSP status good | OCSP responder | Medium-High |
| Watermark decoded + matches | Watermark decoder + soft-binding index | Medium |
| pHash / fingerprint match | Soft-binding index | Low-Medium |
| Anchor match | Batch anchor database | Supplemental |
The confidence score is a weighted aggregate of available signals, not a simple average. The aggregation weights are documented in docs/VERIFICATION-STATES.md.
What verification proves vs. does not prove
Every verification response includes:
{ "status": "verified_manifest_intact", "confidence": 0.97, "developer_explanation": "C2PA signature verified against trusted certificate. Timestamp valid. OCSP: good.", "user_explanation": "Provenance verified — this file carries a valid origin record.", "proves": [ "The C2PA manifest in this file was signed by a certificate on the Verbitas trust list.", "The manifest has not been modified since it was signed.", "The signing certificate was valid and not revoked at sign time." ], "does_not_prove": [ "The assertions in the manifest are accurate (they were supplied by the API caller).", "The content is semantically truthful or unedited in all respects.", "The file is legally admissible as evidence in any jurisdiction.", "The file cannot be re-shared with its manifest intact." ]}The proves and does_not_prove fields are mandatory. No verification result is emitted without both.
State transitions
┌─────────────────┐ ┌────▶│ verified_* │ (C2PA valid + optional watermark/anchor) │ └─────────────────┘ │sign ─────────┤ ┌─────────────────┐ │────▶│ partial_provenance│ (some signals pass, some fail) │ └─────────────────┘ │ └────▶│ no_provenance │ (nothing found) └─────────────────┘
manifest present but: - claim hash mismatch → manifest_tampered - cert revoked → revoked - cert expired → expired - signer not on list → trust_list_miss - ingredient missing → ingredient_chain_brokenUsing states in your application
Do not check for "real" or "authentic" — those strings will never appear. Pattern-match on the enum values:
result = client.verify(asset_id="a_01j...")
if result.status == "verified_manifest_and_watermark_match": # Highest confidence: proceedelif result.status in ("verified_manifest_intact", "watermark_only"): # Acceptable: log and proceed with noteelif result.status == "manifest_tampered": # Hard failure: quarantine assetelif result.status == "no_provenance": # No record found: treat per your policyelse: # Degraded or error states log.warning("verification_state=%s asset_id=%s", result.status, asset_id)See the Reference: Verification States page for the complete normative table including transition rules.