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:

bash
curl -X POST https://liveliness.neetix.in/v1/signup \
  -H 'Content-Type: application/json' \
  -d '{"email":"you@company.com"}'
json
{
  "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:

bash
Authorization: Bearer lv_test_...
Build against the test key

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:

verdictMeaningWhat to do
approvedChecks passedContinue the journey
reviewSomething needs a humanQueue it; do not auto-reject
declinedA check failed hardStop

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:

json
{
  "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.

Note what `is_same_face` says here

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.

Scores are 0–100

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:

CallCredits
POST /v1/liveness1
POST /v1/age1
POST /v1/face/face-match2
POST /v1/session/{id}/verify2
POST /v1/workflow/run3
POST /v1/stream/start5

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.

Handle 402 before you go live

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.