{"openapi":"3.1.0","info":{"title":"Verifact Enterprise API","version":"1.0.0","description":"REST API for insurers and SIU integrations to submit claims, upload signed photos,\nverify C2PA manifests, and pull investigation reports.\n\n**Authentication** — include your API key as a Bearer token on every request:\n```\nAuthorization: Bearer tm_pilot_AbCdEf123456...\n```\n\n**Scopes** — each key is issued with one or more scopes. Attempting a route without\nthe required scope returns HTTP 403 with the missing scope in the error body.\n\n| Scope | Grants access to |\n|---|---|\n| `verify` | POST /v1/verify |\n| `claims:read` | GET /v1/claims, GET /v1/claims/{id}, GET /v1/usage |\n| `claims:write` | POST /v1/claims, PATCH /v1/claims/{id} |\n| `photos:write` | POST /v1/photos |\n| `report` | GET /v1/report/{id} |\n| `jobs` | GET /v1/jobs |\n| `admin` | All scopes (Verifact staff only) |\n\n**Rate limiting** — sliding 24-hour window. Default 5,000 requests/day per key.\nThe `X-RateLimit-Remaining` header shows requests left in the current window.\n\n**Billing** — charges are recorded per event:\n- `api_verify` call → 10% of your `pricePerClaim`\n- `claim_set_billed` (PATCH pending→under_review) → 100% of `pricePerClaim`\n- All other calls are free and track volume only.","contact":{"name":"Verifact API Support","email":"api@verifact.co.in"},"license":{"name":"Proprietary — Verifact Technologies Pvt Ltd"}},"servers":[{"url":"https://staging.verifact.co.in","description":"Production"}],"tags":[{"name":"Verification","description":"C2PA photo authenticity checks"},{"name":"Claims","description":"Claim lifecycle management"},{"name":"Photos","description":"Signed photo upload + download"},{"name":"Reports","description":"Investigation report generation"},{"name":"Jobs","description":"Surveyor job queue sync"},{"name":"Users","description":"Surveyor / adjuster / SIU user provisioning"},{"name":"Usage","description":"Billing and usage data"},{"name":"Plan","description":"Commercial plan terms"},{"name":"AttestationKey","description":"Signed RSA public-key manifest for the Verifact Android client"}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key — tm_<tier>_<prefix>","description":"Obtain an API key from your Verifact account manager. Never share it."}},"schemas":{"Meta":{"type":"object","description":"API response metadata always included in successful responses.","properties":{"apiVersion":{"type":"string","example":"v1"},"tenant":{"type":"string","example":"ORIENTAL"},"keyPrefix":{"type":"string","example":"tm_pilot_Ab"},"processedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message"},"code":{"type":"string","description":"Machine-readable error code, when applicable","example":"MONTHLY_CLAIM_CAP_EXCEEDED"},"scope":{"type":"string","description":"Missing scope, present on 403 responses"}},"required":["error"]},"User":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string","format":"email"},"name":{"type":"string"},"role":{"type":"string","enum":["surveyor","adjuster","siu","tenant_admin"]},"district":{"type":"string","nullable":true},"surveyorLicense":{"type":"string","nullable":true,"description":"Populated for surveyors only"},"active":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","email","name","role","active","createdAt"]},"ClaimSummary":{"type":"object","properties":{"id":{"type":"string"},"claimNumber":{"type":"string"},"policyNumber":{"type":"string"},"claimType":{"type":"string","enum":["motor","property","crop","health","other"]},"status":{"type":"string","enum":["pending","under_review","approved","rejected","siu_flagged"]},"district":{"type":"string"},"surveyorId":{"type":"string"},"surveyorName":{"type":"string"},"riskBand":{"type":"string","enum":["low","review","high","very_high"],"nullable":true},"riskScore":{"type":"number","nullable":true},"estimatedAmount":{"type":"number","nullable":true,"description":"INR"},"settledAmount":{"type":"number","nullable":true,"description":"INR"},"siuFlagged":{"type":"boolean"},"photoCount":{"type":"integer"},"verifiedPhotos":{"type":"integer"},"submittedAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"ClaimDetail":{"allOf":[{"$ref":"#/components/schemas/ClaimSummary"},{"type":"object","properties":{"description":{"type":"string"},"adjusterId":{"type":"string","nullable":true},"siuReason":{"type":"string","nullable":true},"noteCount":{"type":"integer"},"photos":{"type":"array","items":{"$ref":"#/components/schemas/PhotoSummary"}}}}]},"PhotoSummary":{"type":"object","properties":{"id":{"type":"string"},"filename":{"type":"string"},"verifyStatus":{"type":"string","enum":["pending","verified","tampered","unverified","ai_generated","third_party"]},"capturedAt":{"type":"string","format":"date-time"},"uploadedAt":{"type":"string","format":"date-time"},"gpsLat":{"type":"number","nullable":true},"gpsLon":{"type":"number","nullable":true},"gpsAccuracy":{"type":"number","nullable":true,"description":"Metres"},"deviceModel":{"type":"string","nullable":true},"attestationType":{"type":"string","nullable":true,"enum":["StrongBox","TEE","SecureEnclave","software","none"]},"ntpSynced":{"type":"boolean","nullable":true},"riskScore":{"type":"number","nullable":true,"minimum":0,"maximum":1}}},"VerifyResult":{"type":"object","properties":{"status":{"type":"string","enum":["verified","tampered","unverified"],"description":"`verified` = valid C2PA manifest; `tampered` = manifest present but bytes changed; `unverified` = no manifest found."},"assertions":{"type":"integer","description":"Number of C2PA assertions found in the manifest"},"claimBinding":{"type":"object","nullable":true,"description":"Verifact claim binding embedded in the manifest.","properties":{"claimNumber":{"type":"string"},"policyNumber":{"type":"string"},"surveyorName":{"type":"string"},"surveyorId":{"type":"string"},"insurerCode":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"},"source":{"type":"string","enum":["manifest","vault_decrypted","none"]}}},"gps":{"type":"object","nullable":true,"properties":{"lat":{"type":"number"},"lon":{"type":"number"},"accuracy":{"type":"number","description":"Metres"},"satellites":{"type":"integer","nullable":true}}},"device":{"type":"object","nullable":true,"properties":{"model":{"type":"string"},"attestationType":{"type":"string"},"rooted":{"type":"boolean","nullable":true},"emulator":{"type":"boolean","nullable":true},"osVersion":{"type":"string","nullable":true}}},"ntpSynced":{"type":"boolean","nullable":true},"alerts":{"type":"array","items":{"type":"string"},"description":"Integrity warnings and errors from manifest analysis"},"_meta":{"$ref":"#/components/schemas/Meta"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"API key lacks the required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found, or belongs to a different tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"24-hour rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the window resets"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"CapExceeded":{"description":"Monthly claim cap reached — contact Verifact billing","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"cap":{"type":"integer"}}}]}}}}}},"security":[{"BearerAuth":[]}],"paths":{"/api/v1/verify":{"post":{"tags":["Verification"],"summary":"Verify a photo's C2PA manifest","operationId":"verifyPhoto","description":"Parses and validates the C2PA (Content Credentials) manifest embedded in a photo.\nReturns the verification status, GPS metadata, device attestation, and any integrity alerts.\n\n**Billing**: each successful call meters one `api_verify` event at 10% of your `pricePerClaim`.\n\n**Two input formats are accepted:**\n- `multipart/form-data` with a `photo` file field\n- `application/json` with a `{ \"url\": \"https://...\" }` body (max 10 s fetch timeout)","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["photo"],"properties":{"photo":{"type":"string","format":"binary","description":"JPEG or HEIC photo file"}}}},"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"Publicly accessible photo URL (fetched server-side, max 10 s)"}}}}}},"responses":{"200":{"description":"Verification complete (even if the photo is tampered — check `status` field)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyResult"}}}},"400":{"description":"Missing file / URL or unsupported Content-Type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/triage":{"post":{"tags":["Verification"],"summary":"Risk-score an inbound claimant photo","operationId":"triagePhoto","description":"Risk-scores a CLAIMANT-submitted photo (not a Verifact capture) on intake and routes\nhigh-risk sets to the SIU queue. Signals: provenance (incl. opening the vault when the\nimage is itself a Verifact capture — flags it if sealed for a DIFFERENT claim), EXIF\nmetadata anomalies, cross-claim perceptual-hash duplicate match, and AI-likelihood.\n\n**This is a risk score, not a verdict.** No signal auto-rejects a claim; a human reviews\nflagged items. **Billing**: meters a free `claim_triage` event.","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["photo"],"properties":{"photo":{"type":"string","format":"binary","description":"Claimant-submitted photo"},"claimId":{"type":"string","description":"Claim this photo is being submitted to (optional)"}}}}}},"responses":{"200":{"description":"Triage complete — risk score + explainable signals","content":{"application/json":{"schema":{"type":"object","properties":{"riskScore":{"type":"number"},"riskBand":{"type":"string","enum":["low","review","high","very_high"]},"routedTo":{"type":"string","enum":["pass","review","siu"]},"signals":{"type":"array","items":{"type":"object"}},"disclaimer":{"type":"string"}}}}}},"400":{"description":"Missing photo","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/triage/{id}":{"get":{"tags":["Verification"],"summary":"Fetch a stored triage result","operationId":"getTriageResult","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Triage result","content":{"application/json":{"schema":{"type":"object","properties":{"riskScore":{"type":"number"},"riskBand":{"type":"string","enum":["low","review","high","very_high"]},"routedTo":{"type":"string","enum":["pass","review","siu"]},"signals":{"type":"array","items":{"type":"object"}},"disclaimer":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/claims":{"get":{"tags":["Claims"],"summary":"List claims","operationId":"listClaims","description":"Returns paginated claims scoped to the calling tenant. A key from ORIENTAL sees only ORIENTAL claims.","parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["pending","under_review","approved","rejected","siu_flagged"]},"description":"Filter by status"},{"name":"surveyorId","in":"query","schema":{"type":"string"},"description":"Filter to a single surveyor"},{"name":"updatedSince","in":"query","schema":{"type":"string","format":"date-time"},"description":"ISO-8601 timestamp — return only claims whose updatedAt is strictly greater than this. Polling primitive: pass the previous response's max updatedAt to fetch just the delta."},{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"limit","in":"query","schema":{"type":"integer","default":50,"maximum":200}}],"responses":{"200":{"description":"Paginated claim list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ClaimSummary"}},"meta":{"type":"object","properties":{"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer"},"pages":{"type":"integer"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Claims"],"summary":"Create a claim","operationId":"createClaim","description":"Creates a new claim in `pending` status. Free — no billing charge at creation.\nThe billing charge (`claim_set_billed`) is triggered when you first move the claim to `under_review` via PATCH.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["claimNumber","policyNumber","claimType","district","surveyorId"],"properties":{"claimNumber":{"type":"string","description":"Insurer's internal claim reference"},"policyNumber":{"type":"string"},"claimType":{"type":"string","enum":["motor","property","crop","health","other"]},"district":{"type":"string","description":"Indian district where the loss occurred"},"surveyorId":{"type":"string","description":"Verifact user ID of the assigned surveyor. Must already exist in your tenant (create via POST /api/v1/users) and have role='surveyor' and active=true. Returns 404 if not found, 422 if role/active checks fail."},"surveyorName":{"type":"string","description":"Deprecated — server uses the authoritative name from the surveyor record; this field is accepted for back-compat but ignored."},"description":{"type":"string"},"estimatedAmount":{"type":"number","description":"Estimated loss in INR"},"adjusterId":{"type":"string","description":"Verifact user ID of the adjuster (optional)"}}}}}},"responses":{"201":{"description":"Claim created","content":{"application/json":{"schema":{"type":"object","properties":{"claim":{"$ref":"#/components/schemas/ClaimDetail"}}}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/claims/{id}":{"get":{"tags":["Claims"],"summary":"Get claim detail","operationId":"getClaim","description":"Returns full claim detail including all photo metadata. Cross-tenant lookups return 404.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Verifact claim ID (not the insurer's claimNumber)"}],"responses":{"200":{"description":"Claim detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimDetail"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"patch":{"tags":["Claims"],"summary":"Update claim status","operationId":"updateClaimStatus","description":"Updates the status of a claim. Valid transitions:\n- `pending` → `under_review` — **triggers billing** at your `pricePerClaim` rate\n- `under_review` → `approved` | `rejected` — free\n- Any status → `siu_flagged` — free (use the portal for SIU workflow)\n\nReturns HTTP 402 if the tenant's monthly claim cap is reached.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["pending","under_review","approved","rejected"]},"settledAmount":{"type":"number","description":"Final settled amount in INR (set when approving)"}}}}}},"responses":{"200":{"description":"Status updated","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string"},"updatedAt":{"type":"string","format":"date-time"}}}}}},"400":{"description":"Invalid status value","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/CapExceeded"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/photos":{"post":{"tags":["Photos"],"summary":"Upload a signed photo","operationId":"uploadPhoto","description":"Uploads a C2PA-signed photo and attaches it to a claim.\nThe photo is stored and a DB record is created with `verifyStatus: unverified`.\nRun `POST /api/v1/verify` on the same photo to get full manifest analysis.\n\nProvide either `claimId` (Verifact ID) or `claimNumber` (your reference).","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["photo"],"properties":{"photo":{"type":"string","format":"binary","description":"C2PA-signed JPEG or HEIC"},"claimId":{"type":"string","description":"Verifact claim ID (use this or claimNumber)"},"claimNumber":{"type":"string","description":"Insurer's claim reference (use this or claimId)"}}}}}},"responses":{"201":{"description":"Photo uploaded and attached to claim","content":{"application/json":{"schema":{"type":"object","properties":{"photoId":{"type":"string"},"claimId":{"type":"string"},"claimNumber":{"type":"string"},"filename":{"type":"string"},"fileHash":{"type":"string","description":"SHA-256 of the uploaded bytes"},"verifyStatus":{"type":"string","example":"unverified"},"uploadedAt":{"type":"string","format":"date-time"},"hint":{"type":"string"}}}}}},"400":{"description":"Missing file or claim reference","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Claim not found or belongs to a different tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/report/{id}":{"get":{"tags":["Reports"],"summary":"Get investigation report","operationId":"getReport","description":"Returns a structured investigation report for a claim: per-photo analysis, fraud flags,\na pilot score (0–100), and a plain-language verdict string.\nFree — does not trigger any billing event.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Verifact claim ID"}],"responses":{"200":{"description":"Investigation report","content":{"application/json":{"schema":{"type":"object","properties":{"generatedAt":{"type":"string","format":"date-time"},"claim":{"type":"object","properties":{"id":{"type":"string"},"claimNumber":{"type":"string"},"policyNumber":{"type":"string"},"claimType":{"type":"string"},"status":{"type":"string"},"district":{"type":"string"},"surveyorName":{"type":"string"},"insurerCode":{"type":"string"},"submittedAt":{"type":"string","format":"date-time"},"siuFlagged":{"type":"boolean"},"siuReason":{"type":"string","nullable":true}}},"summary":{"type":"object","properties":{"totalPhotos":{"type":"integer"},"verified":{"type":"integer"},"tampered":{"type":"integer"},"unverified":{"type":"integer"},"withGps":{"type":"integer"},"hwAttested":{"type":"integer"},"ntpSynced":{"type":"integer"},"riskBand":{"type":"string","enum":["low","review","high","very_high"]},"pilotScore":{"type":"integer","minimum":0,"maximum":100,"description":"Verifact composite integrity score"},"fraudIndicators":{"type":"array","items":{"type":"string"}}}},"verdict":{"type":"string","description":"Plain-language summary for adjuster/SIU use"},"photos":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"filename":{"type":"string"},"verifyStatus":{"type":"string"},"capturedAt":{"type":"string","format":"date-time"},"uploadedAt":{"type":"string","format":"date-time"},"gps":{"type":"object","nullable":true,"properties":{"lat":{"type":"number"},"lon":{"type":"number"},"accuracy":{"type":"number"}}},"device":{"type":"object","properties":{"model":{"type":"string","nullable":true},"attestationType":{"type":"string","nullable":true},"hwAttested":{"type":"boolean"},"ntpSynced":{"type":"boolean","nullable":true}}},"riskScore":{"type":"number","nullable":true},"flags":{"type":"array","items":{"type":"string"},"description":"Per-photo integrity issues"}}}},"_meta":{"$ref":"#/components/schemas/Meta"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/jobs":{"get":{"tags":["Jobs"],"summary":"Get surveyor job queue","operationId":"getSurveyorJobs","description":"Returns the active job queue for a surveyor: all claims in `pending` or `under_review`\nstatus assigned to that surveyor. Used by the Verifact Android/iOS app to sync\noffline before going into the field.","parameters":[{"name":"surveyorId","in":"query","required":true,"schema":{"type":"string"},"description":"Verifact user ID of the surveyor"}],"responses":{"200":{"description":"Job queue","content":{"application/json":{"schema":{"type":"object","properties":{"surveyorId":{"type":"string"},"tenant":{"type":"string"},"syncedAt":{"type":"string","format":"date-time"},"jobs":{"type":"array","items":{"type":"object","properties":{"jobId":{"type":"string"},"claimId":{"type":"string"},"claimNumber":{"type":"string"},"policyNumber":{"type":"string"},"claimType":{"type":"string"},"district":{"type":"string"},"description":{"type":"string"},"insurerCode":{"type":"string"},"assignedAt":{"type":"string","format":"date-time"},"expiresAt":{"type":"string","format":"date-time"},"status":{"type":"string"},"photosSubmitted":{"type":"integer"},"gpsLat":{"type":"number","nullable":true},"gpsLon":{"type":"number","nullable":true}}}}}}}}},"400":{"description":"`surveyorId` query param missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/usage":{"get":{"tags":["Usage"],"summary":"Get usage and billing summary","operationId":"getUsage","description":"Returns the calling tenant's metered usage and unbilled events for a date range.\nDefault range is the current calendar month.\nUse this to reconcile your own billing records with Verifact's meter.","parameters":[{"name":"from","in":"query","schema":{"type":"string","format":"date"},"description":"Start date (ISO 8601, default: start of current month)"},{"name":"to","in":"query","schema":{"type":"string","format":"date"},"description":"End date (ISO 8601, default: today)"}],"responses":{"200":{"description":"Usage summary","content":{"application/json":{"schema":{"type":"object","properties":{"tenant":{"type":"object","properties":{"insurerCode":{"type":"string"},"tier":{"type":"string"},"pricePerClaim":{"type":"number","description":"INR per billed claim set"}}},"period":{"type":"object","properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"}}},"summary":{"type":"object","properties":{"tenantId":{"type":"string"},"totalEvents":{"type":"integer"},"unbilledEvents":{"type":"integer"},"totalAmount":{"type":"number","description":"INR"},"unbilledAmount":{"type":"number","description":"INR"},"byEvent":{"type":"object","additionalProperties":{"type":"object","properties":{"count":{"type":"integer"},"amount":{"type":"number"}}}}}},"unbilled":{"type":"object","properties":{"count":{"type":"integer"},"amount":{"type":"number"},"events":{"type":"array","maxItems":20,"items":{"type":"object","properties":{"id":{"type":"string"},"event":{"type":"string"},"claimNumber":{"type":"string","nullable":true},"unitPrice":{"type":"number"},"quantity":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"}}}}}},"_meta":{"$ref":"#/components/schemas/Meta"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/photos/{id}/download":{"get":{"tags":["Photos"],"summary":"Download a photo's bytes","operationId":"downloadPhoto","description":"Streams the photo bytes from storage through an authed proxy.\nThe raw storage URL is never returned in any other endpoint — every\nbyte fetch goes through this route so each download produces one\nUsageEvent (`photo_downloaded`) and one ApiRequestLog row.\n\nReturns the same `Content-Type` as the upload; `Content-Disposition`\nis set to `attachment` with a safe filename. Supports conditional\nGET via `If-None-Match` against the returned `ETag`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Verifact photo ID"}],"responses":{"200":{"description":"Photo bytes","content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/heic":{"schema":{"type":"string","format":"binary"}},"image/png":{"schema":{"type":"string","format":"binary"}}},"headers":{"ETag":{"schema":{"type":"string"}},"Content-Length":{"schema":{"type":"string"}},"Content-Disposition":{"schema":{"type":"string"}}}},"304":{"description":"Not modified (If-None-Match matched ETag)"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"Upstream storage unavailable"}}}},"/api/v1/plan":{"get":{"tags":["Plan"],"summary":"Get this tenant's commercial plan","operationId":"getPlan","description":"Returns billing mode, price per claim, the live monthly cap snapshot\n(used/remaining/blocked), billing status, annual-plan window, and\nthe billing cycle string.\n\nPairs with `/api/v1/usage`: this endpoint is the **contract**,\n`/usage` is the **consumption**.","responses":{"200":{"description":"Plan","content":{"application/json":{"schema":{"type":"object","properties":{"billingMode":{"type":"string","enum":["per_claim","per_year"]},"pricePerClaim":{"type":"number","description":"INR per claim_set_billed event"},"monthlyCap":{"type":"object","properties":{"mode":{"type":"string","enum":["hard","soft","alert"]},"limit":{"type":["integer","null"],"description":"null = unlimited"},"used":{"type":"integer"},"remaining":{"type":["integer","null"]},"overCap":{"type":"boolean"},"blocked":{"type":"boolean","description":"true only when mode=hard and overCap=true"}}},"billingStatus":{"type":"string","enum":["current","due","grace","suspended"]},"billingGraceDays":{"type":"integer"},"annualPlan":{"type":"object","properties":{"active":{"type":"boolean"},"start":{"type":["string","null"],"format":"date-time"},"end":{"type":["string","null"],"format":"date-time"}}},"billingCycle":{"type":"string"},"tenant":{"type":"object","properties":{"insurerCode":{"type":"string"},"name":{"type":"string"},"tier":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/users":{"get":{"tags":["Users"],"summary":"List users in your tenant","operationId":"listUsers","description":"Returns users belonging to the calling tenant. Filters allow\nnarrowing by role and active state — e.g. populate a 'choose\nsurveyor' dropdown before calling POST /api/v1/claims.","parameters":[{"name":"role","in":"query","schema":{"type":"string","enum":["surveyor","adjuster","siu","tenant_admin"]},"description":"Filter to a single role"},{"name":"active","in":"query","schema":{"type":"boolean","default":true},"description":"Set to false to include deactivated users"}],"responses":{"200":{"description":"User list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/User"}},"meta":{"type":"object","properties":{"total":{"type":"integer"},"tenant":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"tags":["Users"],"summary":"Create a surveyor / adjuster / siu user","operationId":"createUser","description":"Provisions a new user under the calling tenant. The created user\nreceives a welcome email with a one-time temporary password.\n\n**Scope:** `users:write`.\n\n**Allowed roles via API:** `surveyor`, `adjuster`, `siu`.\n`tenant_admin` and `superadmin` are managed via the portal only —\nthey are sales/onboarding actions, not programmatic.\n\n**Idempotency:** none. Email uniqueness is enforced by 409 Conflict.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","name","role"],"properties":{"email":{"type":"string","format":"email"},"name":{"type":"string"},"role":{"type":"string","enum":["surveyor","adjuster","siu"]},"district":{"type":"string"},"surveyorLicense":{"type":"string","description":"Required when role=surveyor (IRDAI licence number)"}}}}}},"responses":{"201":{"description":"User created","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/User"},{"type":"object","properties":{"temporaryPassword":{"type":"string","description":"One-time password — also delivered via welcome email. Capture it here if you want to surface it through your own channel; the API will not return it again."},"welcomeEmailSent":{"type":"boolean"}}}]}}}},"400":{"description":"Validation error"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Email already in use"},"422":{"description":"Role or licence validation failed"}}}},"/api/v1/users/{id}":{"get":{"tags":["Users"],"summary":"Get a user","operationId":"getUser","description":"Tenant-bounded: returns 404 if the user is in a different tenant.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"User","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"User not found in this tenant"}}},"patch":{"tags":["Users"],"summary":"Update or deactivate a user","operationId":"updateUser","description":"Updates a user record. Allowed fields: name, district, surveyorLicense, active.\n**Email and role are immutable via API** — change them via the portal.\nHard-delete is not supported; set `active: false` instead so claim\nhistory (surveyorId references) stays intact.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"district":{"type":"string"},"surveyorLicense":{"type":"string"},"active":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"400":{"description":"No updatable fields provided"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"User not found in this tenant"}}}},"/api/v1/attestation-key":{"get":{"tags":["AttestationKey"],"summary":"Get the current signed attestation-key manifest","operationId":"getAttestationKey","description":"Public endpoint (no API key required) — the Ed25519 signature on the\nmanifest is the source of trust. The Verifact Android client fetches\nthis on cold start and every 6h, verifying against the ROOT_TRUST_PUBLIC_KEY\nburned into the APK at build time. Used so attestation-key rotation\ndoes not require an APK release.","security":[],"responses":{"200":{"description":"Signed manifest","content":{"application/json":{"schema":{"type":"object","required":["kid","publicKey","alg","issuedAt","signature","signedBy"],"properties":{"kid":{"type":"string","description":"Key ID — used by the server's key registry to dispatch decrypts"},"publicKey":{"type":"string","description":"RSA public key, PEM-encoded SPKI"},"alg":{"type":"string","example":"RSA-OAEP-SHA256"},"issuedAt":{"type":"string","format":"date-time"},"expiresAt":{"type":"string","format":"date-time","description":"Guidance only — server still accepts manifests past this date if not yet superseded"},"signature":{"type":"string","description":"Base64 Ed25519 signature over canonical-form of the signed fields"},"signedBy":{"type":"string","description":"sha256: fingerprint of the root trust public key"}}}}}},"502":{"description":"Upstream c2pa-node-service unreachable"},"503":{"description":"Manifest not provisioned (server side)"}}}}}}