{"openapi":"3.1.0","info":{"title":"ChronoVerify","description":"Verify a photo's capture time and provenance. Reads EXIF and XMP, cryptographically validates C2PA Content Credentials against the official trust lists, and runs classical pixel forensics, returning one verdict (provenance_confirmed, consistent, inconclusive, metadata_anomaly, or manipulation_indicated) with a 0 to 100 confidence. Provenance-first, not a deepfake or AI-generation detector. The public path is keyless and rate-limited; send 'Authorization: Bearer cv_live_...' to meter against a key.","version":"1.0.0"},"paths":{"/health":{"get":{"summary":"Health","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/verify":{"post":{"summary":"Verify","description":"Verify one image, by upload or by URL. Returns the verdict object.\n\nTwo paths share one endpoint:\n  - No Authorization header: the free public verifier, rate limited per IP.\n  - Authorization: Bearer cv_live_...: the metered API. The key's monthly\n    quota is spent first, then its prepaid balance at the per-image price.\n    A key with neither is refused with 402 before any work is done.\n\nThe file is processed for this check and not retained.","operationId":"verify_v1_verify_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_verify_v1_verify_post"}}}},"responses":{"200":{"description":"The verdict object.","headers":{"X-Credits-Remaining-USD":{"description":"Remaining prepaid balance in USD, on a metered (pay-as-you-go) key.","schema":{"type":"string"}},"X-Quota-Remaining":{"description":"Remaining included verifications this month, on a plan key.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyResponse"}}}},"400":{"description":"No image was provided, or the URL could not be fetched.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Out of monthly quota and prepaid credits. Add credits at /pricing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Image exceeds the size or pixel-dimension limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"415":{"description":"Unsupported or unreadable image format.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited. Retry after the Retry-After header.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"The verifier is temporarily busy or unavailable. Retry after the Retry-After header.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{},{"bearerAuth":[]}]}},"/v1/report":{"post":{"summary":"Report","description":"Generate an on-demand signed PDF report for one image.\n\nThis is the paid/forensic artifact and the most expensive path (full decode\n+ pixel forensics + PDF generation), so it REQUIRES an API key and is metered\nlike a verification. Leaving it unauthenticated would make it a free\ncost-amplification target. The free public web verifier does not call this.\nReturns application/pdf.","operationId":"report_v1_report_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_report_v1_report_post"}}},"required":true},"responses":{"200":{"description":"A signed PDF audit report (application/pdf).","content":{"application/json":{"schema":{}},"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"A signed report requires an API key, or the key is invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Out of monthly quota and prepaid credits for a signed report.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Image exceeds the size or pixel-dimension limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"415":{"description":"Unsupported or unreadable image format.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"The report builder is temporarily busy or unavailable. Retry after the Retry-After header.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"429":{"description":"Rate limit exceeded. Back off and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{},{"bearerAuth":[]}]}},"/v1/key":{"get":{"summary":"Signing Key","description":"Publish the report signing public key so signatures are verifiable.","operationId":"signing_key_v1_key_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/verify.schema.json":{"get":{"summary":"Verify Schema","description":"The JSON Schema for the POST /v1/verify response.\n\nA self-describing, versioned contract so an agent or codegen tool can fetch\nthe exact response shape (verdict enum, signal direction enum, the C2PA block,\nintegrity fingerprints) without scraping the docs. Derived from the same\nPydantic model that types /openapi.json, so it can never drift from the API.","operationId":"verify_schema_v1_verify_schema_json_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/billing/status":{"get":{"summary":"Billing Status","description":"Public probe: is self-serve credit purchase live? The pricing page uses\nthis to hide the Buy credits flow until Stripe is configured, so a buyer\nnever reaches a checkout that 503s.","operationId":"billing_status_v1_billing_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/billing/checkout":{"post":{"summary":"Billing Checkout","description":"Start a prepaid credit top-up. Returns a Stripe Checkout URL to redirect\nthe buyer to. Minimum $5; the resulting credits fund pay-as-you-go usage.","operationId":"billing_checkout_v1_billing_checkout_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopupRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/billing/session":{"get":{"summary":"Billing Session","description":"Reveal the API key for a completed Checkout Session. Idempotent: the\nwelcome page can reload and re-read the same key. The session id is the\none-time capability the buyer receives via the post-payment redirect.","operationId":"billing_session_v1_billing_session_get","parameters":[{"name":"session_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/billing/account":{"get":{"summary":"Billing Account","description":"Return the account summary for a key supplied as a Bearer token.","operationId":"billing_account_v1_billing_account_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/billing/portal":{"post":{"summary":"Billing Portal","description":"Open the Stripe customer portal for the key supplied as a Bearer token.\nThe portal lets a customer update their card, download invoices, and cancel\na subscription themselves, with no email and no sales call.","operationId":"billing_portal_v1_billing_portal_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"Body_admin_login_v1_admin_login_post":{"properties":{"token":{"type":"string","title":"Token","default":""}},"type":"object","title":"Body_admin_login_v1_admin_login_post"},"Body_report_v1_report_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["file"],"title":"Body_report_v1_report_post"},"Body_verify_v1_verify_post":{"properties":{"file":{"anyOf":[{"type":"string","contentMediaType":"application/octet-stream"},{"type":"null"}],"title":"File"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"}},"type":"object","title":"Body_verify_v1_verify_post"},"Body_waitlist_v1_waitlist_post":{"properties":{"email":{"type":"string","title":"Email"},"use_case":{"type":"string","title":"Use Case","default":""}},"type":"object","required":["email"],"title":"Body_waitlist_v1_waitlist_post"},"C2PA":{"properties":{"present":{"type":"boolean","title":"Present","description":"Whether C2PA Content Credentials were found.","default":false},"validated":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Validated","description":"True only when the signer is on a recognised C2PA trust list; null when validation did not run."},"signature_valid":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Signature Valid","description":"Whether the cryptographic signature is intact (a Trusted or Valid state)."},"trust_list_match":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Trust List Match","description":"Whether the signer is on a recognised C2PA trust list."},"validation_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Validation State","description":"The C2PA validation state: 'Trusted', 'Valid', 'Invalid', or null when absent."},"signer":{"anyOf":[{"$ref":"#/components/schemas/C2PASigner"},{"type":"null"}],"description":"Signer summary from the active manifest, when present."},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"}},"type":"object","title":"C2PA"},"C2PASigner":{"properties":{"issuer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Issuer"},"common_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Common Name"},"signed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signed At","description":"When the manifest was signed, if recorded."},"claim_generator":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Claim Generator","description":"The claim_generator (signing tool) string from the active manifest, if recorded."}},"type":"object","title":"C2PASigner"},"CaptureDevice":{"properties":{"make":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Make"},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model"},"software":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Software"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"Where the device info came from, e.g. 'exif'."}},"type":"object","title":"CaptureDevice"},"CaptureLocation":{"properties":{"present":{"type":"boolean","title":"Present","description":"Whether any location was found.","default":false},"lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lat"},"lon":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lon"},"source":{"type":"string","title":"Source","description":"Where the location came from, or 'none'.","default":"none"},"place":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Place"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"region":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Region"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"}},"type":"object","title":"CaptureLocation"},"CaptureTime":{"properties":{"value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Value","description":"ISO-8601 capture timestamp, or null if none was found."},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"Where the time came from, e.g. 'exif', 'xmp', or 'none'."},"consistent":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Consistent","description":"Whether corroborating timestamps agree, when more than one is present."}},"type":"object","title":"CaptureTime"},"ConsistencyCheck":{"properties":{"name":{"type":"string","title":"Name"},"passed":{"type":"boolean","title":"Passed"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"}},"type":"object","required":["name","passed"],"title":"ConsistencyCheck"},"ErrorResponse":{"properties":{"detail":{"type":"string","title":"Detail","description":"A human-readable error message."}},"type":"object","required":["detail"],"title":"ErrorResponse","description":"The error body returned for any non-2xx response (FastAPI's standard shape).","example":{"detail":"Out of monthly quota and credits. Add credits at /pricing or check /dashboard."}},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"Integrity":{"properties":{"sha256":{"type":"string","title":"Sha256"},"sha512":{"type":"string","title":"Sha512"},"format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Format"},"width":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Width"},"height":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Height"},"bytes":{"type":"integer","title":"Bytes"},"processed_at_utc":{"type":"string","title":"Processed At Utc"},"pipeline_version":{"type":"string","title":"Pipeline Version"},"method_version":{"type":"string","title":"Method Version"},"forensics_input":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Forensics Input","description":"Whether forensics ran on the original or a downscaled copy."},"pillow_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pillow Version"},"c2pa_validator_enabled":{"type":"boolean","title":"C2Pa Validator Enabled"}},"type":"object","required":["sha256","sha512","bytes","processed_at_utc","pipeline_version","method_version","c2pa_validator_enabled"],"title":"Integrity"},"MetadataConsistency":{"properties":{"exif_present":{"type":"boolean","title":"Exif Present"},"xmp_present":{"type":"boolean","title":"Xmp Present"},"checks":{"items":{"$ref":"#/components/schemas/ConsistencyCheck"},"type":"array","title":"Checks"},"anomalies":{"items":{"type":"string"},"type":"array","title":"Anomalies","description":"Plain-language metadata contradictions, if any."}},"type":"object","required":["exif_present","xmp_present"],"title":"MetadataConsistency"},"PixelForensics":{"properties":{"estimated_jpeg_quality":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Estimated Jpeg Quality"},"ela_mean":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ela Mean"},"ela_max":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ela Max"},"ela_localization":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ela Localization","description":"Localized error-level-analysis ratio. The manipulation gate requires this to be high alongside a corroborating noise signal."},"noise_dispersion":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Noise Dispersion"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"}},"type":"object","title":"PixelForensics"},"Signal":{"properties":{"name":{"type":"string","title":"Name"},"layer":{"type":"string","title":"Layer","description":"Which layer produced the signal: 'integrity', 'provenance', 'metadata', or 'pixel'."},"direction":{"type":"string","enum":["supports_authentic","supports_edited","neutral"],"title":"Direction","description":"Whether the signal supports an authentic capture, supports editing, or is neutral."},"weight":{"type":"number","title":"Weight"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"}},"type":"object","required":["name","layer","direction","weight"],"title":"Signal"},"TopupRequest":{"properties":{"amount_usd":{"type":"number","exclusiveMinimum":0.0,"title":"Amount Usd"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key"}},"type":"object","required":["amount_usd"],"title":"TopupRequest"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VerifyResponse":{"properties":{"schema_version":{"type":"string","title":"Schema Version","description":"Response schema version, currently 'v1'."},"request_id":{"type":"string","title":"Request Id"},"verdict":{"type":"string","enum":["provenance_confirmed","consistent","inconclusive","metadata_anomaly","manipulation_indicated"],"title":"Verdict","description":"The single fused verdict enum."},"confidence":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Confidence","description":"0 to 100 confidence in the verdict."},"headline":{"type":"string","title":"Headline","description":"One-line plain-language verdict."},"summary":{"type":"string","title":"Summary"},"capture_time":{"$ref":"#/components/schemas/CaptureTime"},"capture_location":{"$ref":"#/components/schemas/CaptureLocation"},"capture_device":{"$ref":"#/components/schemas/CaptureDevice"},"c2pa":{"$ref":"#/components/schemas/C2PA"},"metadata_consistency":{"$ref":"#/components/schemas/MetadataConsistency"},"pixel_forensics":{"$ref":"#/components/schemas/PixelForensics"},"signals":{"items":{"$ref":"#/components/schemas/Signal"},"type":"array","title":"Signals","description":"The individual signals that produced the verdict."},"limits":{"type":"string","title":"Limits","description":"Plain-language statement of what the verdict does and does not mean."},"integrity":{"$ref":"#/components/schemas/Integrity"},"processing_ms":{"type":"integer","title":"Processing Ms"}},"type":"object","required":["schema_version","request_id","verdict","confidence","headline","summary","capture_time","capture_location","capture_device","c2pa","metadata_consistency","pixel_forensics","limits","integrity","processing_ms"],"title":"VerifyResponse","description":"The verdict object returned by POST /v1/verify.","example":{"c2pa":{"note":"No Content Credentials (C2PA) found. Most images do not carry them yet.","present":false},"capture_device":{"make":"Canon","model":"EOS R6","software":"Firmware 1.8.1","source":"exif"},"capture_location":{"present":false,"source":"none"},"capture_time":{"source":"exif","value":"2026-03-14T09:21:30"},"confidence":58,"headline":"Metadata is internally consistent. No manipulation signals fired.","integrity":{"bytes":166692,"c2pa_validator_enabled":false,"forensics_input":"original","format":"JPEG","height":800,"method_version":"fusion-0.1.0","pillow_version":"11.1.0","pipeline_version":"chronoverify-pipeline-0.1.0","processed_at_utc":"2026-06-27T17:18:25Z","sha256":"1313339afab5c9ed466605db093ea60cc721e1021c3b274213d6f49dc8f0a1b2","sha512":"93a81e4ad853d774118e2cd1cc9748c08229fe44b62d41388fb4f7a01330c0de","width":1200},"limits":"ChronoVerify returns investigative triage, not proof. A clean result means the file's saved data is internally consistent, not that the scene it shows is real.","metadata_consistency":{"anomalies":[],"checks":[{"detail":"EXIF metadata is present.","name":"exif_present","passed":true}],"exif_present":true,"xmp_present":false},"pixel_forensics":{"ela_localization":1.314,"ela_max":3.667,"ela_mean":0.563,"estimated_jpeg_quality":98,"noise_dispersion":0.059,"note":"Pixel-forensic signals are probabilistic and unreliable on recompressed or screenshotted images."},"processing_ms":133,"request_id":"9c094a668aa8404caed916d3d0fa0d2f","schema_version":"v1","signals":[{"detail":"No Content Credentials (C2PA) found.","direction":"neutral","layer":"provenance","name":"c2pa_absent","weight":0.0}],"summary":"The image carries metadata that is internally consistent, and no pixel-forensic signal crossed a flag threshold. This is consistent with an unedited capture, but it is not proof.","verdict":"consistent"}},"Error":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string","description":"Human-readable error message."}}}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Your ChronoVerify API key as a bearer token (cv_live_...). Optional: omit it to use the free, rate-limited public path. Metered calls spend the key's monthly free quota first, then its prepaid balance."}}},"servers":[{"url":"https://chronoverify.com"}]}