Verifact API Reference
Integrate C2PA photo verification into your claims management system. All enterprise endpoints are under /api/v1/ and require a scoped API key. Portal staff endpoints use session cookies at /api/portal/ and /api/admin/.
Authentication
Every enterprise API request must include an API key in the Authorization header:
Authorization: Bearer tm_grow_AbCdEf1234567890...API keys are issued by Verifact per tenant. Each key has a set of scopes that determine which endpoints it can call. Keys are hashed with SHA-256 before storage — the raw key is shown only once at issuance.
# Test your key
curl https://yourdomain.co.in/api/v1/usage \
-H "Authorization: Bearer tm_grow_AbCdEf..."Scopes
Every key is issued with a subset of scopes. A request to an endpoint whose scope is not on the key returns HTTP 403.
verifyclaims:readclaims:writephotos:writereportjobsadminRate Limits
Rate limits are enforced per API key using a sliding 24-hour window. When exceeded, the response is HTTP 429 with a Retry-After header.
Endpoints
POST/api/v1/verifyverifyGET/api/v1/claimsclaims:readPOST/api/v1/claimsclaims:writeGET/api/v1/claims/:idclaims:readPATCH/api/v1/claims/:idclaims:writePOST/api/v1/photosphotos:writePOST/api/v1/vault/decryptverifyGET/api/v1/report/:idreportGET/api/v1/jobsjobsGET/api/v1/usageclaims:readPOST /api/v1/verify
Verify a photo's C2PA manifest and extract all forensic metadata. Accepts a file upload or a remote URL.
# Upload a file
curl -X POST https://yourdomain.co.in/api/v1/verify \
-H "Authorization: Bearer tm_grow_..." \
-F "photo=@claim_photo.jpg"
# Or verify a remote URL
curl -X POST https://yourdomain.co.in/api/v1/verify \
-H "Authorization: Bearer tm_grow_..." \
-H "Content-Type: application/json" \
-d '{"url":"https://storage.example.com/photo.jpg"}'// Response 200
{
"status": "verified", // "verified" | "tampered" | "unverified"
"assertions": 6,
"claimBinding": { // null if neither v2 vault nor legacy v1 claim_ref is available
"claimNumber": "OIC-2026-BH-048291",
"policyNumber": "OIC-MOT-PNA-2024-9921",
"surveyorName": "Amit Singh",
"surveyorId": "u4",
"insurerCode": "ORIENTAL",
"expiresAt": "2026-05-02T00:00:00Z",
"source": "online_job_queue"
},
"claimBindingSource": "vault_decrypted", // "vault_decrypted" | "legacy_claim_ref" | "vault_invalid" | "none"
"vault": { // present for v2 vault photos
"present": true,
"captureId": "uuid",
"keyId": "tm-pilot-key-1",
"algorithm": "RSA-OAEP-256/AES-256-GCM",
"decrypted": true
},
"gps": {
"lat": 25.6464,
"lon": 87.1344,
"accuracy": 33,
"satellites": 12
},
"device": {
"model": "motorola edge 40",
"attestationType": "StrongBox",
"rooted": false,
"emulator": false,
"osVersion": "13"
},
"ntpSynced": true,
"alerts": [],
"_meta": {
"apiVersion": "v1",
"tenant": "ORIENTAL",
"processedAt": "2026-05-03T10:14:22Z"
}
}GET /api/v1/claims
List claims for the calling tenant. All results are automatically scoped to the tenant — cross-tenant access is impossible.
GET /api/v1/claims?status=pending&page=1&limit=50
Query params:
status pending | under_review | approved | rejected | siu_flagged
surveyorId filter by assigned surveyor
page default 1
limit default 50, max 200// Response 200
{
"data": [
{
"id": "c1a2b3c4",
"claimNumber": "OIC-2026-BH-048291",
"policyNumber": "OIC-MOT-PNA-2024-9921",
"claimType": "motor",
"status": "pending",
"district": "Patna",
"surveyorId": "u4",
"surveyorName": "Amit Singh",
"riskBand": "low",
"riskScore": 0.18,
"siuFlagged": false,
"photoCount": 1,
"verifiedPhotos": 1,
"submittedAt": "2026-04-25T08:00:00Z",
"updatedAt": "2026-04-25T08:00:00Z"
}
],
"meta": { "total": 47, "page": 1, "limit": 50, "pages": 1 }
}PATCH /api/v1/claims/:id — Billing trigger
Update claim status. When status changes from pending to under_review for the first time, a claim_set_billed usage event is written at the tenant's pricePerClaim. Subsequent status changes are free.
PATCH /api/v1/claims/c1a2b3c4
Content-Type: application/json
{ "status": "under_review" } // ← billing meter fires here, once only// Response 200
{
"id": "c1a2b3c4",
"status": "under_review",
"updatedAt": "2026-05-03T10:14:22Z"
}Billing Model
Verifact uses a per-verified-claim-set model. The billing unit is one claim_set_billed event, written when an adjuster first opens a claim for review (status pending → under_review). This happens whether the adjuster uses the web portal or calls PATCH /api/v1/claims/:id.
claim_submitted₹0New claim created. Free — tracks volume.photo_verified₹0Photo uploaded and manifest checked. Free.claim_set_billedpricePerClaimTHE BILLING UNIT. Fires once per claim on first review.api_verify10% of priceEach call to POST /v1/verify.api_report₹0GET /v1/report — free, tracks usage.At month-end, Verifact generates an invoice from all unbilled claim_set_billed events and marks them billed. You can view unbilled events and current totals at any time via GET /api/v1/usage.
Error Codes
401Missing or invalid API key403Key exists but scope is insufficient, or tenant suspended404Resource not found, or belongs to a different tenant429Rate limit exceeded — see Retry-After header400Missing required field or invalid value — see error.hint500Server error — retry with exponential backoffIntegration Examples
Python — verify a photo and attach to claim
import requests
BASE = "https://yourdomain.co.in/api/v1"
HEADERS = {"Authorization": "Bearer tm_grow_AbCdEf..."}
# 1. Verify the photo
with open("claim_photo.jpg", "rb") as f:
verify = requests.post(f"{BASE}/verify", headers=HEADERS,
files={"photo": f}).json()
print(verify["status"]) # "verified"
print(verify["claimBinding"]) # {"claimNumber": "OIC-2026-BH-048291", ...}
print(verify["gps"]["lat"]) # 25.6464
# 2. Create a claim
claim = requests.post(f"{BASE}/claims", headers=HEADERS, json={
"claimNumber": "OIC-2026-BH-048291",
"policyNumber": "OIC-MOT-PNA-2024-9921",
"claimType": "motor",
"district": "Patna",
"surveyorId": "u4",
"surveyorName": "Amit Singh",
"description": "Vehicle rear-end collision near Bailey Road.",
}).json()["claim"]
# 3. Upload the photo (billing NOT triggered yet)
with open("claim_photo.jpg", "rb") as f:
requests.post(f"{BASE}/photos", headers=HEADERS,
data={"claimId": claim["id"]},
files={"photo": f})
# 4. Set to under_review — THIS triggers billing (₹30)
requests.patch(f"{BASE}/claims/{claim['id']}", headers=HEADERS,
json={"status": "under_review"})
# 5. Get the investigation report
report = requests.get(f"{BASE}/report/{claim['id']}", headers=HEADERS).json()
print(report["verdict"])
print(report["summary"]["pilotScore"]) # 0-100Node.js — check this month's usage
const BASE = "https://yourdomain.co.in/api/v1";
const KEY = "tm_grow_AbCdEf...";
const usage = await fetch(`${BASE}/usage`, {
headers: { Authorization: `Bearer ${KEY}` },
}).then(r => r.json());
console.log(usage.summary.byEvent["claim_set_billed"]);
// { count: 42, amount: 1260 } → 42 claims × ₹30 = ₹1,260
console.log(usage.unbilled.amount); // ₹1,260 unbilled this month