Create a project
Sign up and name your project. Every new project starts with free credits, no card and no approval.
Face, documents, MRZ, NFC, AML, cheques and cards. One key, 69 endpoints, seven official SDKs.
Sign up and name your project. Every new project starts with free credits, no card and no approval.
Portal → API Keys. Test keys start with sk_test_ and are sent straight as a bearer token.
Run the snippet below. It reads a passport's machine-readable zone and checks every check digit.
curl -X POST https://verisec.mmose.me/v1/mrz/parse \
-H "Authorization: Bearer sk_test_your_key" \
-H "Content-Type: application/json" \
-d '{
"lines": [
"P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<",
"L898902C36UTO7408122F1204159ZE184226B<<<<<10"
]
}'curl -X POST https://verisec.mmose.me/v1/face/verify \
-H "Authorization: Bearer $VERISEC_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"image1\": \"$(base64 -w0 selfie.jpg)\",
\"image2\": \"$(base64 -w0 id-photo.jpg)\",
\"threshold\": 0.8
}"{
"ok": true,
"request_id": "req_cd1d47ea5bf34b7a",
"latency_ms": 2362,
"data": {
"similarity": 0.9984,
"score": 0.2399,
"match": true,
"threshold": 0.75,
"compared": [
{ "image": "image1", "roi": [182, 241, 173, 220] },
{ "image": "image2", "roi": [940, 245, 176, 216] }
]
}
}One per language, version 1.0.0, generated from the API spec: typed requests and answers, images from files, safe retries that never charge a call twice, and webhook verification.
pip install verisecfrom verisec import VeriSec
client = VeriSec()
client.face.verify(image1="a.jpg", image2="b.jpg")npm install @veriseclabs/nodeimport { VeriSec } from "@veriseclabs/node";
const client = new VeriSec();
await client.bin.lookup("424242");go get github.com/mosesmmoisebidth/verisec-goimport "github.com/mosesmmoisebidth/verisec-go"
client, _ := verisec.New(key)
client.Bin.Lookup(ctx, "424242")implementation("io.github.mosesmmoisebidth:verisec-java:1.0.0")import dev.verisec.VeriSec;
VeriSec client = VeriSec.builder().build();
client.bin().lookup("424242");dotnet add package VeriSecusing VeriSec;
var client = new VeriSecClient();
await client.Bin.LookupAsync("424242");composer require verisec/verisec-phpuse VeriSec\Client;
$client = new Client($key);
$client->bin->lookup('424242');gem install verisecrequire "verisec"
client = VeriSec::Client.new
client.bin.lookup("424242")On-device capture with quality checks before an image leaves the handset. In development.
sk_test_…A test key is sent straight as Authorization: Bearer. It runs against the real engines at https://verisec.mmose.me, with your free credits and no live data.
Every endpoint is available. Refused calls are never charged.
ak_live_…A live key is paired with a client id and secret. Your SDK exchanges them at POST /v2/oauth/token for a short-lived bearer token and renews it for you; the long-lived key is never sent on a request.
Per-key rate limits, an optional spend cap, and an IP allow-list.
Keys are created in the console, scoped per project, and shown once. Each call is charged in credits only when it succeeds; a refused call (no credits, no permission, rate limited) is free.
A test key runs against the real engines for free. Use the Playground to try any endpoint with sample images, or send one of these card numbers to the card APIs.
4242 4242 4242 4242Reads cleanly, Luhn valid5555 5555 5555 4444Reads cleanly, Luhn valid3782 822463 1000515-digit PAN handling6011 1111 1111 1117Reads cleanly4000 0056 0000 0008UK issuer, debit4111 1111 1111 1111Classic Visa test BINThe Playground runs inside the portal and asks you to sign in first.
| Event | Description |
|---|---|
| verification.completed | A verification finished and a decision is available. |
| verification.needs_review | A verification landed in the manual review queue. |
| verification.declined | A verification was declined by the workflow rules. |
| aml.hit | A screening returned one or more scored hits. |
| credits.low | Remaining credits fell below your configured threshold. |
{
"type": "verification.completed",
"timestamp": "2026-10-17T14:02:15.221Z",
"environment": "test",
"data": {
"verification_id": "ver_7Kq2W9xLm3Ab",
"reference": "u_10422",
"decision": "approved",
"trust_seal": { "score": 94 },
"checks": {
"document_authenticity": "pass",
"face_match": "pass",
"liveness": "pass",
"aml_screening": "clear"
}
}
}Deliveries are signed as Standard Webhooks: webhook-id, webhook-timestamp and webhook-signature. Your SDK verifies them in one call; here it is by hand. Pass the raw body before parsing, and reject a stale timestamp.
import crypto from "node:crypto";
// Headers: webhook-id, webhook-timestamp, webhook-signature: "v1,<base64 HMAC>"
export function verify(rawBody, headers, secret) {
const id = headers["webhook-id"];
const ts = headers["webhook-timestamp"];
const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
const expected = crypto
.createHmac("sha256", key)
.update(`${id}.${ts}.${rawBody}`)
.digest("base64");
const ok = headers["webhook-signature"]
.split(" ")
.some((part) => {
const [, value] = part.split(",");
return value && crypto.timingSafeEqual(Buffer.from(value), Buffer.from(expected));
});
const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
return ok && fresh;
}Compare a live selfie to the portrait on a document, with a threshold you set.
Read guide →Classify ~1,400 document types, read every field, and check for tampering.
Read guide →Run a sanctions and PEP screening, then read the scored hits.
Read guide →Read the card, validate Luhn, and resolve the BIN to a network and issuer.
Read guide →Register an endpoint, verify the Standard Webhooks signature, and read the event.
Read guide →/v1/face/verifyCompare two faces (1:1) and decide on a threshold./v1/face/matchMatch faces across images and return every pair./v1/face/searchSearch a gallery (1:N) for the closest faces./v1/livenessPassive liveness from one image./v1/face/attributesAge, expression, glasses, mask and more./v1/age/verifyEstimate age and check a minimum.A KYC answer comes in an envelope { ok, request_id, latency_ms, data, warnings, error } and the SDKs return its data. A Financial answer is an object with an object type. A field with no value is left out, never null.
200The call ran. Check ok in the body: a result is in data.400A field is missing or not valid, see error.code and error.param.401Missing, malformed, revoked or expired key.402Not enough credits, or the key hit its spend cap. Nothing was charged.403The key may not call this API, or live access isn't approved.404Nothing was found.429Rate limited, retry after the Retry-After header. Not charged.5xxOur side. The call did not complete and was not charged.{
"ok": false,
"request_id": "req_9Xb2LpQ4",
"error": {
"code": "document_image_unreadable",
"message": "The front image could not be read. Send a JPEG or PNG under 12 MB.",
"param": "image_front"
}
}The API is versioned in its path: KYC under /v1, Financial under /v2. New fields are added without breaking the ones already there, and a field is never removed from a version, so your integration keeps working. There is no version header to set.
First public release: 69 endpoints across Face, Documents, MRZ, NFC, AML, Contact & risk, Cheques, Cards and BIN, with seven official server SDKs (Python, Node, Go, Java, .NET, PHP, Ruby) generated from the same contract.