Deze vertaling is machinaal gegenereerd en wacht op beoordeling.Overschakelen naar Engels
Op deze pagina

Self-hosted identity verification (direct API)

This guide is for a backend engineer who wants to start identity verifications from a server without a link and without the page Noxtica hosts. Your server calls the direct API, receives a verdict, reason codes and a verification id, and keeps them next to your own customer record.

Read the limits first. The direct route is a smaller surface than hosted sessions, and one thing does not work today: it gives your own screens no way to capture a document and have it checked. The section “Capture in your own application” says exactly why and what to use instead.

What it is

Availability: Controlled availability. Identity verification is provisioned per tenant by your account team, and the account team confirms which of the routes below apply to your tenant. See the product page for the overview and the limits of the result.

The direct API is a set of server-to-server routes under https://collect.noxtica.com/v1/kyc/. Every call uses your server key as a bearer token. What it provides today:

  • Admission and result. POST /v1/kyc/verify mints a single-use nonce (action: "mint"), then runs one verification with that nonce (action: "verify"). The response carries a verdict, a verification level, reason codes and a verificationId.
  • Safe retries. A repeated call with the same Idempotency-Key returns the stored response again instead of running a second verification.
  • Re-check a known subject. POST /v1/kyc/reverify asks whether a subject is already verified, to at least a level you name, using your tenant’s device registry.
  • Bulk admission. POST /v1/kyc/verify/batch admits up to 500 rows from a JSON or CSV manifest, with status, results and cancel routes.
  • Evidence for a verification that already exists. POST /v1/kyc/evidence/handle and POST /v1/kyc/evidence/finalize upload sealed evidence for an admitted verification. A proof-of-address cross-match builds on that upload.
  • An event when a verification finishes. kyc.verification.completed, described in Identity verification events and webhooks.

Results are automated signals. You decide what each outcome means for your customer, and a person in your organisation should review any adverse decision before it affects someone.

Capture in your own application

Documents are captured on the page Noxtica hosts. The direct API does not replace that page, for two reasons you can check against the routes above:

  • A verify request carries data only. It does not accept images, audio or other media, and the request body is limited to 32 KiB.
  • An evidence handle is issued only for a verification that already exists in your tenant. The verify call creates that verification and returns its id in the same request that runs the check, so your application has no moment in which to upload a capture and then run the document check on it.

Until a route for this exists, use hosted identity verification when the customer must capture a document, or send an invitation from Backoffice (Invitations and hosted verification settings). This section is where a future route for capture in your own application will be documented; nothing on this page promises one.

Plan your code for what the current document check returns. It hands its result to a person for review and reports the reason code provider_manual_review, so expect unverified together with that reason rather than an automatic verified. Handle every verdict.

Prerequisites

  • The module is provisioned for your tenant. Until it is, every route below returns the plain 404 with the body V1 endpoint not found. Two switches have to be on (a platform switch and your tenant’s module), and the response is identical whichever one is off, so a 404 means “ask your account team”, not “retry”.
  • A server key with the identity permission. In Backoffice open API & SDK → Server API Keys and create a key with “Verify identity documents (KYC)” ticked. A new key does not carry it by default. The secret is shown once; keep it in your server secret store and never in a browser or an app.
  • For evidence routes only: raw evidence storage armed for your tenant and a data residency (TR, EU or US) set for it. Ask your account team. A request whose jurisdiction does not match your tenant’s residency gets the same plain 404.
  • A recorded legal basis where your organisation is required to have one. Without it the verify route answers 403 kyc_legal_basis_receipt_required. An authorised person records it in Backoffice → Identity Verification → KYC Invitations → Settings.

Steps

1. Mint an admission nonce

The nonce is single-use, tied to your tenant and valid for 300 seconds. Mint it as close to the verify call as you can.

curl -sS -X POST https://collect.noxtica.com/v1/kyc/verify \
  -H "Authorization: Bearer $NOXTICA_SERVER_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "action": "mint" }'
{ "ok": true, "nonce": "<nonce>", "ttlSeconds": 300 }

2. Run the verification

Send the nonce within its lifetime, with an Idempotency-Key header of 8 to 128 characters. A missing or malformed key is rejected with 400 invalid_request before anything runs.

curl -sS -X POST https://collect.noxtica.com/v1/kyc/verify \
  -H "Authorization: Bearer $NOXTICA_SERVER_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "action": "verify",
    "nonce": "<nonce>",
    "id_type": "passport",
    "purpose": "identity_verification"
  }'
import { randomUUID } from 'node:crypto';

const base = 'https://collect.noxtica.com';
const headers = {
  Authorization: `Bearer ${process.env.NOXTICA_SERVER_KEY}`,
  'Content-Type': 'application/json',
};

const mint = await fetch(`${base}/v1/kyc/verify`, { method: 'POST', headers, body: JSON.stringify({ action: 'mint' }) });
if (!mint.ok) throw new Error(`mint failed: ${mint.status}`);
const { nonce } = await mint.json();

const idempotencyKey = randomUUID(); // store it with the customer; reuse it only to retry this same request
const verify = await fetch(`${base}/v1/kyc/verify`, {
  method: 'POST',
  headers: { ...headers, 'Idempotency-Key': idempotencyKey },
  body: JSON.stringify({ action: 'verify', nonce, id_type: 'passport', purpose: 'identity_verification' }),
});
const result = await verify.json();
if (!verify.ok) throw new Error(`verify failed: ${verify.status} ${result.error}`);
// Store result.verificationId with your customer record. Read the verdict below.
FieldRequiredNotes
actionNomint or verify. When you leave it out the request is treated as verify.
nonceYesFrom the mint call. Used once. A missing nonce is rejected with 400 missing_nonce.
id_type (or idType)YesThe document type your customer presents. A missing value is rejected with 400 invalid_request.
purposeNoDefaults to identity_verification. Other values come from a closed list; an unknown value can be rejected with 400 invalid_request.
payloadNoData only, never media. What a payload may contain depends on the configured check; leave it out unless your account team specifies one.
deviceContextNotrue adds an advisory deviceTrust block to the response.

A request that includes an attachDeviceTrust field, in any shape, is rejected with 400: device trust cannot be asserted by the caller.

3. Read and store the result

A verdict is always returned with status 200. A fault on Noxtica’s side comes back as "verdict": "error", never as a subject statement and never as a 5xx.

{
  "ok": true,
  "verdict": "unverified",
  "verificationLevel": "partial",
  "reasonCodes": ["provider_manual_review"],
  "verificationId": "<verification-id>",
  "requestId": "<request-id>"
}
FieldMeaning
verdictverified, unverified or error. Act on verified as an approval only after your own checks; treat everything else as not approved.
verificationLevelverified, partial, unverified or inconclusive.
reasonCodesClosed-list codes that explain the outcome. Branch on codes, never on text, and ignore codes you do not know.
verificationIdIdentifies the verification. Find it in Backoffice under Identity Verification → KYC Verifications.
requestIdQuote it when you contact support.

A tenant policy that blocks or challenges can turn a provider verified into unverified and add policy_blocked or policy_challenge_required to the reason codes.

A repeated request with the same Idempotency-Key returns the stored response byte for byte, with no second verification. A different body under a key you already used is refused with 409 idempotency_conflict. A request still running under that key answers 409 with the reason code request_in_progress; wait and retry with the same key.

4. Choose how you learn about later changes

POST /v1/kyc/verify answers when the check ends, so you do not need to poll for the result of that call. A verification that reaches a final state also sends a signed event, kyc.verification.completed. Register an endpoint under Integrations → Webhooks and verify the signature as described in Identity verification events and webhooks. Treat the event as a notification: compare it with the verification you stored before you act. A person’s review decision is visible in Backoffice under Identity Verification → KYC Review Queue and Identity Verification → KYC Verifications.

5. Re-check a subject you verified before (optional)

POST /v1/kyc/reverify answers one question: is this subject already verified, in your tenant’s device registry, to at least the level you ask for. It needs the same key permission and a device registry module that your account team enables, it takes the opaque subject identifier your records use for the customer, and it always returns 200.

curl -sS -X POST https://collect.noxtica.com/v1/kyc/reverify \
  -H "Authorization: Bearer $NOXTICA_SERVER_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "subjectId": "cust_example", "requiredLevel": "verified" }'
{ "ok": true, "verified": true, "verificationLevel": "verified", "requestId": "<request-id>" }

A missing subjectId or an unknown requiredLevel is rejected with 400 invalid_request.

6. Admit rows in bulk (optional)

POST /v1/kyc/verify/batch accepts a JSON manifest ({ "items": [{ "idType": "passport" }] }) or a CSV with an id_type column, up to 500 rows and 256 KiB, with a required Idempotency-Key header. It answers 202 with a batchId and one status per row. Read progress with GET /v1/kyc/verify/batch/{batchId}/status and per-row outcomes with GET /v1/kyc/verify/batch/{batchId}/results, and stop work that has not started with POST /v1/kyc/verify/batch/{batchId}/cancel. The field-level reference is in the identity API reference.

7. Attach evidence to an admitted verification (optional)

Evidence is uploaded as sealed ciphertext: POST /v1/kyc/evidence/handle returns a short-lived handle and the keys to seal with, and POST /v1/kyc/evidence/finalize redeems it. Your code does the sealing, so plan time for it and read the identity API reference before you start. The handle is valid for 15 minutes. Evidence kinds are a closed list, and kinds that the platform derives itself are refused with 400 invalid_request.

Verify it works

  • The mint call returns "ok": true and a nonce, and the verify call returns status 200 with a verificationId.
  • Repeating the verify call with the same Idempotency-Key and body returns the same response.
  • The verification appears in Backoffice under Identity Verification → KYC Verifications with the verificationId you stored.
  • If you registered an endpoint, your handler receives kyc.verification.completed with a valid signature.

Troubleshooting

SymptomCauseFix
404 with V1 endpoint not foundThe module is not provisioned for your tenant, the path is wrong, or an evidence request named a residency other than your tenant’s.Check the path, then ask your account team. Do not retry in a loop.
401 invalid_api_keyThe key is missing, malformed, unknown or revoked.Check the header and that the key is a current sk_prod_ key.
403 with API key does not have write:identity scopeThe key lacks the identity permission.Create a key with “Verify identity documents (KYC)” ticked.
403 kyc_legal_basis_receipt_requiredYour organisation has no recorded legal basis.Record it in Backoffice, then retry.
403 tenant_inactiveThe tenant is suspended.Contact your account team.
400 missing_nonceThe verify body has no nonce.Mint a nonce and send it.
400 invalid_requestA missing Idempotency-Key or id_type, a malformed purpose, an attachDeviceTrust field, or evidence of a kind you may not upload.Fix the request.
Verdict unverified with provider_manual_reviewThe check handed the result to a person.A reviewer decides in Backoffice under Identity Verification → KYC Review Queue.
Verdict errorA fault on the verification side, not a statement about the subject.Retry with a new nonce and a new Idempotency-Key.
402 spend_cap_reachedYour organisation’s spending limit is reached.Contact your account team.
409 idempotency_conflictThe key was used with a different request.Use a new key for a new request.
413 body_too_large or 415 invalid_requestThe body is over the limit or not JSON.Send a small JSON body with Content-Type: application/json.
429 Rate limit exceededMore than 10 calls in 60 seconds with one key on these routes.Back off and retry. Honour Retry-After when present.
503 kyc_verify_unavailableA temporary fault.Retry after the Retry-After delay.

The shared error envelope and the rate-limit rules are described in Errors and Authentication.