Skip to content

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

CodeConfidenceMeaning
verified_manifest_and_watermark_matchHigh (≥ 0.95)C2PA signature valid, signer trusted, watermark decoded and matches manifest record
verified_manifest_intactHigh (≥ 0.92)C2PA signature valid, signer trusted, manifest not tampered — watermark not required by recipe
anchor_confirmedSupplementalManifest digest found in a confirmed anchor batch (OTS or Arbitrum)
watermark_onlyMedium (0.75–0.90)Watermark decoded and maps to a manifest record; no embedded C2PA manifest in file
fingerprint_match_onlyMedium (0.70–0.85)Perceptual hash matched a known asset; no manifest or watermark in submitted file
multiple_candidatesLowSoft-binding lookup returned more than one match; human review required
partial_provenanceLow-mediumSome signals present; at least one required signal is missing or failed
no_provenanceNoneNo manifest, watermark, or fingerprint match found
manifest_tamperedN/AManifest is present but the claim digest does not match the asset content
revokedN/ASigning certificate has been revoked (OCSP response: revoked)
expiredN/ASigning certificate was not valid at the time of the sign operation
trust_list_missN/AManifest present and signature valid, but signer is not on the configured trust list
ocsp_unavailableWarningOCSP responder unreachable; result is downgraded (dev only — prod returns this as a hard failure state)
ingredient_chain_brokenN/ARecipe requires full ingredient chain; one or more parent manifests cannot be retrieved or verified
anchor_not_foundSupplementalManifest digest not yet found in any anchor batch (may be pending if anchor is recent)
errorN/AVerification 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:

SignalSourceWeight in confidence
C2PA manifest presentEmbedded JUMBF or sidecarHigh
C2PA signature validX.509 chain verificationHigh
Signer on trust listTrust list lookupHigh
OCSP status goodOCSP responderMedium-High
Watermark decoded + matchesWatermark decoder + soft-binding indexMedium
pHash / fingerprint matchSoft-binding indexLow-Medium
Anchor matchBatch anchor databaseSupplemental

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_broken

Using 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: proceed
elif result.status in ("verified_manifest_intact", "watermark_only"):
# Acceptable: log and proceed with note
elif result.status == "manifest_tampered":
# Hard failure: quarantine asset
elif result.status == "no_provenance":
# No record found: treat per your policy
else:
# 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.