Get started
Four things decide how you integrate. Learn them once here and every platform guide after this is mechanical.
Get a key
Signup is self-serve and needs no auth. One call returns two keys:
curl -X POST https://liveliness.neetix.in/v1/signup \
-H 'Content-Type: application/json' \
-d '{"email":"you@company.com"}'{
"status": "success",
"api_key": "lv_...",
"test_api_key": "lv_test_...",
"credits": 1000,
"monthly_quota": 500
}Both keys are shown once. Persist them before you close the terminal.
Every request authenticates the same way, on every endpoint and every platform:
Authorization: Bearer lv_test_...Test keys are free and unmetered — calls never deduct credits, and
GET /v1/billing/balance reports metered: false. They also get their own
isolated client_id, so gallery enrollments, replay state and audit logs
never mix with live data. Every inference response carries "mode": "test" or
"mode": "live", so you can assert in a test which key actually ran.
Read the verdict, not the score
Every verification response carries a verdict. It is the only field your
business logic should branch on:
verdict | Meaning | What to do |
|---|---|---|
approved | Checks passed | Continue the journey |
review | Something needs a human | Queue it; do not auto-reject |
declined | A check failed hard | Stop |
Raw scores (same_face_confidence, liveness_score, cosine_similarity) are
returned for your audit trail and for tuning. They are not thresholds you have
to pick — the verdict already applied them.
Treat warnings[] as the reason code
Alongside the verdict, warnings[] explains why. A review with
LIVENESS_UNAVAILABLE is a very different operational problem from a review
with MASK_DETECTED, and only the warning tells you which.
This is a real response — a printed photo of a genuine customer's face, held up to the camera:
{
"verdict": "declined",
"warnings": [
{
"code": "LIVENESS_FACE_ATTACK",
"severity": "decline",
"description": "Presentation attack suspected (print/replay/mask)."
}
],
"result": {
"is_same_face": true,
"same_face_confidence": 99.9,
"liveness_score": 2.43,
"liveness_status": "spoof",
"spoof_detected": true
}
}Each warning carries its own severity — "review" or "decline" — so you
do not have to maintain your own list of which codes are fatal.
It says true. It is the same face — it is a photograph of the right
person. A face match on its own cannot tell you anyone was present; only the
liveliness result can. This is the single most common way a face-match
integration ends up approving an attacker, and it is why verdict exists:
it already combined the two.
Log the array. When a verification is disputed months later, this is the field that answers the question.
liveness_score and same_face_confidence are percentages, and responses
include the threshold they were compared against. A score is not a fraction.
Know what each call costs
Billing is prepaid credits, deducted per successful call. The common ones:
| Call | Credits |
|---|---|
POST /v1/liveness | 1 |
POST /v1/age | 1 |
POST /v1/face/face-match | 2 |
POST /v1/session/{id}/verify | 2 |
POST /v1/workflow/run | 3 |
POST /v1/stream/start | 5 |
An exhausted balance returns HTTP 402 with
message_code: "insufficient_credits". Every SDK surfaces it as a typed error
rather than a generic failure — in TypeScript, err.isInsufficientCredits.
It is the one error that arrives with no warning and stops every verification at once. Decide now whether a 402 blocks the journey or queues it for review.
Next
Pick your platform and make the first real call. The REST guide is the right one for server-to-server work; the rest wrap the same endpoints.