Enterprise API · v1

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/.

Base URL: https://yourdomain.co.in ·  All requests over HTTPS  ·  Responses: JSON

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.

Scope
Purpose
Endpoints
verify
Verify a photo's C2PA manifest and extract all forensic metadata
POST /v1/verify
claims:read
Read claim lists and claim detail including photos and notes
GET /v1/claims, /v1/claims/:id, /v1/usage
claims:write
Create new claims and update claim status (triggers billing)
POST /v1/claims, PATCH /v1/claims/:id
photos:write
Upload signed photos to a claim
POST /v1/photos
report
Generate full investigation report for a claim
GET /v1/report/:id
jobs
Fetch surveyor job queue (used by Android/iOS app)
GET /v1/jobs
admin
All scopes — Verifact staff only
All /v1/* routes

Rate 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.

Tier
Limit/24h
Notes
pilot
1,000
For 90-day pilots
growth
5,000
Standard commercial
enterprise
Custom
Negotiated per contract — no hard cap

Endpoints

Path
Scope
Meter
POST
/api/v1/verify
Verify a photo's C2PA manifest
verify
₹0.1×pricePerClaim
GET
/api/v1/claims
List tenant's claims (paginated)
claims:read
free
POST
/api/v1/claims
Create a new claim
claims:write
free
GET
/api/v1/claims/:id
Full claim detail with photos
claims:read
free
PATCH
/api/v1/claims/:id
Update status — charges on pending→under_review
claims:write
pricePerClaim
POST
/api/v1/photos
Upload a signed photo to a claim
photos:write
free
POST
/api/v1/vault/decrypt
Decrypt and validate Verifact vault (v2)
verify
free
GET
/api/v1/report/:id
Investigation report JSON
report
free
GET
/api/v1/jobs
Surveyor job queue
jobs
free
GET
/api/v1/usage
Usage summary and unbilled events
claims:read
free

POST /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.

Event types and prices
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 key
403Key exists but scope is insufficient, or tenant suspended
404Resource not found, or belongs to a different tenant
429Rate limit exceeded — see Retry-After header
400Missing required field or invalid value — see error.hint
500Server error — retry with exponential backoff

Integration 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-100

Node.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

Further Reading

OpenAPI 3.1 Spec (machine-readable JSON) Interactive Swagger UI C2PA Technical Specification Content Credentials Verify (Adobe) IRDAI Innovation Sandbox c2pa-rs (Rust reference implementation)