{"openapi":"3.1.0","info":{"title":"VerifNer API","version":"1.0.0","description":"Identity verification for Nigerien passports and national identity cards. One call, a reasoned verdict: five checks with evidence, a score, VERIFIED at 0.90 and above, REVIEW from 0.60, REJECTED below."},"servers":[{"url":"https://api.verifner.com"}],"security":[{"apiKey":[]}],"tags":[{"name":"Verify","description":"Server-to-server verification with the secret key."},{"name":"Sessions","description":"Browser flows: your server opens a session, the web SDK uploads with a client token."},{"name":"Webhooks","description":"verification.completed, signed with HMAC-SHA256, retried with backoff."}],"paths":{"/v1/verify":{"post":{"tags":["Verify"],"summary":"Verify a document, and optionally a selfie, synchronously","description":"Send an `Idempotency-Key` header to make retries safe: the same key returns the original result with `Idempotent-Replayed: true`. Test keys (`vn_test_`) run the full pipeline and never store images.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["front"],"properties":{"front":{"type":"string","format":"binary","description":"Document front (passport data page). JPEG, PNG or WebP, up to 12 MB."},"back":{"type":"string","format":"binary","description":"CNI back. Omit for a passport."},"selfie":{"type":"string","format":"binary","description":"Live photo of the holder. Omit to skip the face check."}}}},"application/json":{"schema":{"type":"object","required":["front"],"properties":{"front":{"type":"string","description":"base64"},"back":{"type":"string"},"selfie":{"type":"string"}}}}}},"responses":{"200":{"description":"Replayed from the idempotency key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationResult"}}}},"201":{"description":"Verdict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationResult"}}}},"400":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, malformed or revoked key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`unreadable` (blurred front), `not_an_image`, or `extraction_failed`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit: 120 requests per minute per key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/verifications/{id}":{"get":{"tags":["Verify"],"summary":"Read a verification","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^vrf_"}}],"responses":{"200":{"description":"The result, plus `final_verdict` once a human reviewed it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationResult"}}}},"404":{"description":"Not found in your organisation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/verifications/{id}/images/{kind}":{"get":{"tags":["Verify"],"summary":"Get a short-lived URL to a stored image","description":"Live keys only; the URL is valid 5 minutes and every issue and view is an audit event. 410 when the image was never stored or has been purged after the retention window.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"kind","in":"path","required":true,"schema":{"type":"string","enum":["front","back","selfie"]}}],"responses":{"200":{"description":"Signed URL","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"},"expires_at":{"type":"string","format":"date-time"}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"Image gone","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/sessions":{"post":{"tags":["Sessions"],"summary":"Open a session for the web SDK","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string","maxLength":200,"description":"Your identifier for this user or flow; echoed back, never interpreted."},"document_type":{"type":"string","enum":["cni","passport"],"description":"Ask for this document only; omitted, the person chooses."},"ttl_seconds":{"type":"integer","minimum":60,"maximum":3600,"default":900}}}}}},"responses":{"201":{"description":"Session with its single-use client token","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Session"},{"type":"object","properties":{"client_token":{"type":"string","description":"Give this to the browser. Shown once."},"url":{"type":"string","description":"Hosted capture page for this session (https://verify.verifner.com/#…). Send it to the person to verify instead of embedding the SDK; the token is in the fragment. Single use, expires with the session."}}}]}}}},"401":{"description":"Bad key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/sessions/{id}":{"get":{"tags":["Sessions"],"summary":"Read a session and its outcome","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^ses_"}}],"responses":{"200":{"description":"Session","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Session"},{"type":"object","properties":{"result":{"type":"object","nullable":true,"properties":{"verdict":{"$ref":"#/components/schemas/Verdict"},"final_verdict":{"$ref":"#/components/schemas/Verdict","nullable":true},"score":{"type":"number"},"document_type":{"type":"string"}}}}}]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/sessions/{id}/verify":{"post":{"tags":["Sessions"],"summary":"Upload from the browser with the client token","description":"Authenticate with `Authorization: Bearer <client_token>`. CORS is open. A 422 leaves the session usable; a 201 completes it.","security":[{"clientToken":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["front"],"properties":{"front":{"type":"string","format":"binary","description":"Document front (passport data page). JPEG, PNG or WebP, up to 12 MB."},"back":{"type":"string","format":"binary","description":"CNI back. Omit for a passport."},"selfie":{"type":"string","format":"binary","description":"Live photo of the holder. Omit to skip the face check."}}}},"application/json":{"schema":{"type":"object","required":["front"],"properties":{"front":{"type":"string","description":"base64"},"back":{"type":"string"},"selfie":{"type":"string"}}}}}},"responses":{"201":{"description":"Verdict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationResult"}}}},"401":{"description":"Bad token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`session_completed` or `session_expired`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Unreadable capture","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks":{"post":{"tags":["Webhooks"],"summary":"Register an endpoint","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"https only (http for localhost)"},"description":{"type":"string","maxLength":200}}}}}},"responses":{"201":{"description":"Endpoint with its signing secret, shown once","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/WebhookEndpoint"},{"type":"object","properties":{"secret":{"type":"string"}}}]}}}}}},"get":{"tags":["Webhooks"],"summary":"List endpoints","responses":{"200":{"description":"Endpoints","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"}}}}}}}}}},"/v1/webhooks/{id}":{"delete":{"tags":["Webhooks"],"summary":"Remove an endpoint","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Removed"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/{id}/deliveries":{"get":{"tags":["Webhooks"],"summary":"Last 50 deliveries with their state and attempts","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deliveries"}}}}},"webhooks":{"verification.completed":{"post":{"summary":"Sent after every verdict, and again after a human review with `final_verdict` set","description":"Headers: `VerifNer-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, \"<t>.<raw body>\")>`, `VerifNer-Event`, `VerifNer-Delivery`. Reject timestamps older than 5 minutes. Retried 8 times over about four hours until a 2xx.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Any 2xx acknowledges"}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"`vn_live_…` or `vn_test_…`, from the console."},"clientToken":{"type":"http","scheme":"bearer","description":"`vn_ct_…` from POST /v1/sessions."}},"schemas":{"Verdict":{"$schema":"http://json-schema.org/draft-07/schema#","type":"string","enum":["verified","review","rejected"]},"Check":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string","enum":["mrz_checksum","expiry","viz_mrz_consistency","tamper","face_match"]},"status":{"type":"string","enum":["pass","warn","fail","skipped"]},"score":{"anyOf":[{"type":"number","minimum":0,"maximum":1},{"type":"null"}]},"evidence":{"type":"object","properties":{"fr":{"type":"string"},"en":{"type":"string"}},"required":["fr","en"],"additionalProperties":false}},"required":["name","status","score","evidence"],"additionalProperties":false},"VerificationResult":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"id":{"type":"string","pattern":"^vrf_[a-z0-9]{6,}$"},"status":{"type":"string","enum":["VERIFIED","REVIEW","REJECTED"]},"verdict":{"type":"string","enum":["verified","review","rejected"]},"score":{"type":"number","minimum":0,"maximum":1},"document_type":{"type":"string","enum":["passport","cni","unknown"]},"document":{"type":"object","properties":{"type":{"type":"string","enum":["passport","cni","unknown"]},"country":{"type":["string","null"]},"fields":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"mrz":{"type":"object","properties":{"lines":{"type":"array","items":{"type":"string"}},"valid":{"type":"boolean"}},"required":["lines","valid"],"additionalProperties":false},"legibility":{"type":["number","null"]}},"required":["type"],"additionalProperties":false},"fields":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"face_match":{"anyOf":[{"type":"object","properties":{"similarity":{"type":["number","null"]},"matched":{"type":"boolean"},"faces_in_document":{"type":"number"},"faces_in_selfie":{"type":"number"}},"required":["similarity","matched"],"additionalProperties":false},{"type":"null"}]},"checks":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","enum":["mrz_checksum","expiry","viz_mrz_consistency","tamper","face_match"]},"status":{"type":"string","enum":["pass","warn","fail","skipped"]},"score":{"anyOf":[{"type":"number","minimum":0,"maximum":1},{"type":"null"}]},"evidence":{"type":"object","properties":{"fr":{"type":"string"},"en":{"type":"string"}},"required":["fr","en"],"additionalProperties":false}},"required":["name","status","score","evidence"],"additionalProperties":false}},"timings":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},"latency_ms":{"type":"integer","minimum":0,"maximum":9007199254740991},"created_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["id","verdict","score","document_type","checks","latency_ms","created_at"],"additionalProperties":false,"description":"Five checks, each with bilingual evidence. `final_verdict` appears on reads once a reviewer decided."},"Session":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"id":{"type":"string"},"mode":{"type":"string","enum":["live","test"]},"status":{"type":"string","enum":["pending","completed","expired"]},"reference":{"type":["string","null"]},"document_type":{"anyOf":[{"type":"string","enum":["cni","passport"]},{"type":"null"}],"description":"The one document asked for; null lets the person choose."},"verification_id":{"type":["string","null"]},"expires_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"created_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["id","mode","status","reference","document_type","verification_id","expires_at","created_at"],"additionalProperties":false},"WebhookEndpoint":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"description":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["id","url","description","created_at"],"additionalProperties":false},"WebhookEvent":{"type":"object","properties":{"id":{"type":"string","description":"evt_…, the same for every delivery of one event"},"type":{"type":"string","enum":["verification.completed"]},"created_at":{"type":"string","format":"date-time"},"data":{"allOf":[{"$ref":"#/components/schemas/VerificationResult"},{"type":"object","properties":{"mode":{"type":"string","enum":["live","test"]},"final_verdict":{"$ref":"#/components/schemas/Verdict","nullable":true}}}]}}},"Error":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["error"],"additionalProperties":false}}}}