Getting startedQuickstart

Quickstart

Create an API key and submit your first KYC request in three minutes.

This is the fastest path from "I have a Certivant account" to "a verification request is in flight." If you finish this page, you have a working integration end-to-end.

What you need

  • A Certivant organization (sign up at your tenant URL — the trial includes free credits).
  • The role Owner or Admin on that organization. Members cannot mint API keys.
  • A way to make HTTPS requests — curl, Postman, or your language's HTTP client.

1 — Create an API key

Sign in to the dashboard and open your organization.
Navigate to KYC → Developer in the left sidebar.

Click New Key, give it a label, choose Live or Test (for example a label such as staging-server), and pick the scopes you need:

  • kyc:read — read requests, documents, fraud signals
  • kyc:write — create requests, upload documents
  • kyc:submit — submit a request for automated processing
  • kyc:manage — override decisions, delete applicants (GDPR)
Click Create. The full key is shown once and starts with ck_live_ (or ck_test_ if you chose the test environment). Copy it now — Certivant only stores a hash, so you cannot recover it later. If you lose it, revoke the key and create a new one.

Treat API keys like passwords. Store them in your secrets manager, never commit them to git, and use a separate key per environment (staging vs. production). Revoke a key the moment you suspect exposure.

2 — Create a KYC request

The minimum payload only needs a type. Add applicant data inline if you have it — otherwise the applicant fills it in via a hosted session. The same end-to-end flow (create -> upload documents -> submit -> poll) in eight languages:

# 1. Create a verification request
curl -X POST https://app.certivant.com/api/v1/kyc/requests \
  -H "Authorization: Bearer ck_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "INDIVIDUAL",
    "externalRef": "user_123",
    "applicant": {
      "fullName": "Jane Doe", "email": "jane@example.com", "nationality": "US",
      "emailVerified": false, "phoneVerified": false
    }
  }'
# -> { "id": "req_...", "status": "PENDING_UPLOAD", ... }

# 2 & 3. Upload an identity document (front) + a selfie
curl -X POST https://app.certivant.com/api/v1/kyc/requests/{requestId}/documents \
  -H "Authorization: Bearer ck_live_your_key_here" \
  -F "file=@passport.jpg" -F "documentType=PASSPORT" -F "side=front"
curl -X POST https://app.certivant.com/api/v1/kyc/requests/{requestId}/documents \
  -H "Authorization: Bearer ck_live_your_key_here" \
  -F "file=@selfie.jpg" -F "documentType=SELFIE"

# 4. Submit for AI processing (email + phone must already be verified if your org requires it)
curl -X POST https://app.certivant.com/api/v1/kyc/requests/{requestId}/submit \
  -H "Authorization: Bearer ck_live_your_key_here"

# 5. Poll for the decision (or just listen for the webhook)
curl https://app.certivant.com/api/v1/kyc/requests/{requestId} \
  -H "Authorization: Bearer ck_live_your_key_here"
const BASE = "https://app.certivant.com/api/v1/kyc";
const headers = { Authorization: "Bearer ck_live_your_key_here" };

// 1. Create
const { id } = await fetch(BASE + "/requests", {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({
    type: "INDIVIDUAL",
    externalRef: "user_123",
    applicant: { fullName: "Jane Doe", email: "jane@example.com", nationality: "US" },
  }),
}).then((r) => r.json());

// 2 & 3. Upload documents
async function upload(file, documentType, side) {
  const form = new FormData();
  form.append("file", file);
  form.append("documentType", documentType);
  if (side) form.append("side", side);
  await fetch(BASE + "/requests/" + id + "/documents", { method: "POST", headers, body: form });
}
await upload(passportFile, "PASSPORT", "front");
await upload(selfieFile, "SELFIE");

// 4. Submit
await fetch(BASE + "/requests/" + id + "/submit", { method: "POST", headers });

// 5. Poll (production: listen for the webhook instead)
let result;
do {
  await new Promise((r) => setTimeout(r, 3000));
  result = await fetch(BASE + "/requests/" + id, { headers }).then((r) => r.json());
} while (result.status === "PROCESSING");
console.log(result.status, result.riskScore);
const BASE = "https://app.certivant.com/api/v1/kyc";
const headers = { Authorization: "Bearer ck_live_your_key_here" };

type KycStatus = "PENDING_UPLOAD" | "DOCUMENTS_READY" | "PROCESSING"
  | "APPROVED" | "REJECTED" | "ESCALATED" | "CANCELLED";

interface KycRequest {
  id: string;
  status: KycStatus;
  riskScore?: { overallScore: number; decision: string } | null;
}

const created: KycRequest = await fetch(BASE + "/requests", {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({
    type: "INDIVIDUAL",
    externalRef: "user_123",
    applicant: { fullName: "Jane Doe", email: "jane@example.com", nationality: "US" },
  }),
}).then((r) => r.json());

await fetch(BASE + "/requests/" + created.id + "/submit", { method: "POST", headers });
import time, requests

BASE = "https://app.certivant.com/api/v1/kyc"
headers = {"Authorization": "Bearer ck_live_your_key_here"}

# 1. Create
r = requests.post(f"{BASE}/requests", headers=headers, json={
    "type": "INDIVIDUAL",
    "externalRef": "user_123",
    "applicant": {"fullName": "Jane Doe", "email": "jane@example.com", "nationality": "US"},
})
request_id = r.json()["id"]

# 2 & 3. Upload documents
def upload(path, document_type, side=None):
    data = {"documentType": document_type}
    if side:
        data["side"] = side
    with open(path, "rb") as f:
        requests.post(f"{BASE}/requests/{request_id}/documents",
                      headers=headers, files={"file": f}, data=data)

upload("passport.jpg", "PASSPORT", "front")
upload("selfie.jpg", "SELFIE")

# 4. Submit
requests.post(f"{BASE}/requests/{request_id}/submit", headers=headers)

# 5. Poll
while True:
    result = requests.get(f"{BASE}/requests/{request_id}", headers=headers).json()
    if result["status"] != "PROCESSING":
        break
    time.sleep(3)
print(result["status"])
<?php
$BASE = "https://app.certivant.com/api/v1/kyc";
$auth = ["Authorization: Bearer ck_live_your_key_here"];

// 1. Create
$ch = curl_init("$BASE/requests");
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => array_merge($auth, ["Content-Type: application/json"]),
  CURLOPT_POSTFIELDS => json_encode([
    "type" => "INDIVIDUAL",
    "externalRef" => "user_123",
    "applicant" => ["fullName" => "Jane Doe", "email" => "jane@example.com", "nationality" => "US"],
  ]),
]);
$req = json_decode(curl_exec($ch), true);
$id = $req["id"];

// 2. Upload a document
$ch = curl_init("$BASE/requests/$id/documents");
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => $auth,
  CURLOPT_POSTFIELDS => [
    "file" => new CURLFile("passport.jpg"),
    "documentType" => "PASSPORT",
    "side" => "front",
  ],
]);
curl_exec($ch);

// 4. Submit
$ch = curl_init("$BASE/requests/$id/submit");
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => $auth]);
curl_exec($ch);
using System.Net.Http;
using System.Net.Http.Json;
using System.Net.Http.Headers;

var http = new HttpClient { BaseAddress = new Uri("https://app.certivant.com/api/v1/kyc/") };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "ck_live_your_key_here");

// 1. Create
var created = await http.PostAsJsonAsync("requests", new {
    type = "INDIVIDUAL",
    externalRef = "user_123",
    applicant = new { fullName = "Jane Doe", email = "jane@example.com", nationality = "US" }
});
var request = await created.Content.ReadFromJsonAsync<Dictionary<string, object>>();
var id = request["id"].ToString();

// 2. Upload a document
using var form = new MultipartFormDataContent();
form.Add(new ByteArrayContent(File.ReadAllBytes("passport.jpg")), "file", "passport.jpg");
form.Add(new StringContent("PASSPORT"), "documentType");
form.Add(new StringContent("front"), "side");
await http.PostAsync($"requests/{id}/documents", form);

// 4. Submit
await http.PostAsync($"requests/{id}/submit", null);
import java.net.http.*;
import java.net.URI;

var http = HttpClient.newHttpClient();
String BASE = "https://app.certivant.com/api/v1/kyc";
String auth = "Bearer ck_live_your_key_here";

// 1. Create
var createBody = """
  {"type":"INDIVIDUAL","externalRef":"user_123",
   "applicant":{"fullName":"Jane Doe","email":"jane@example.com","nationality":"US"}}""";
var created = http.send(HttpRequest.newBuilder(URI.create(BASE + "/requests"))
    .header("Authorization", auth).header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(createBody)).build(),
    HttpResponse.BodyHandlers.ofString());
// parse created.body() -> id with your JSON library (Jackson/Gson)

// 4. Submit
http.send(HttpRequest.newBuilder(URI.create(BASE + "/requests/" + id + "/submit"))
    .header("Authorization", auth).POST(HttpRequest.BodyPublishers.noBody()).build(),
    HttpResponse.BodyHandlers.ofString());
package main

import (
	"bytes"
	"net/http"
)

const base = "https://app.certivant.com/api/v1/kyc"
const auth = "Bearer ck_live_your_key_here"

func main() {
	// 1. Create
	body := []byte(`{"type":"INDIVIDUAL","externalRef":"user_123",
		"applicant":{"fullName":"Jane Doe","email":"jane@example.com","nationality":"US"}}`)
	req, _ := http.NewRequest("POST", base+"/requests", bytes.NewReader(body))
	req.Header.Set("Authorization", auth)
	req.Header.Set("Content-Type", "application/json")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
	// decode resp.Body -> { "id": "..." }

	// 4. Submit
	submit, _ := http.NewRequest("POST", base+"/requests/"+id+"/submit", nil)
	submit.Header.Set("Authorization", auth)
	http.DefaultClient.Do(submit)
}

A successful response looks like:

{
  "id": "req_01HXY...",
  "status": "PENDING_UPLOAD",
  "type": "INDIVIDUAL",
  "externalRef": "order-12345",
  "pendingDocTypes": ["GOVERNMENT_ID", "SELFIE"],
  "applicant": { "fullName": "Jane Doe", "email": "jane@example.com" },
  "createdAt": "2026-05-27T10:00:00.000Z"
}

Save the id — every subsequent call needs it.

The externalRef field is your foreign key. Use your order ID, your user ID, anything that lets you correlate the Certivant request back to a row in your own database. It's returned on every webhook event.

3 — Get the applicant a hosted session

The easiest way to collect documents is to send the applicant a short-lived URL. Certivant handles document capture, liveness, and the upload UI; you just hand them the link.

curl -X POST https://app.certivant.com/api/v1/kyc/requests/req_01HXY.../session-url \
  -H "Authorization: Bearer ck_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "expiresInHours": 72,
    "redirectUrl": "https://yourapp.example.com/onboarding/complete"
  }'

Returns:

{
  "sessionUrl": "https://app.certivant.com/s/01HZ...",
  "expiresAt": "2026-05-30T10:00:00.000Z"
}

Email it to the applicant, embed it in your own onboarding flow, or open it in an iframe — whatever fits your UX. When the applicant submits, Certivant moves the request into PROCESSING, runs the checks, and fires the webhook.

On this hosted-session path the applicant also completes email/phone verification when your organization requires it, so you don't need to pass emailVerified / phoneVerified yourself. On the server-to-server path (see Your first request), verify each required channel — or set those flags on create — before you call submit.

4 — Listen for the result

Configure a webhook endpoint at KYC → Settings → Webhooks with a URL you control. Certivant will POST to it whenever the request state changes.

{
  "event": "kyc.request.approved",
  "requestId": "req_01HXY...",
  "organizationId": "org_01HW...",
  "externalRef": "order-12345",
  "status": "APPROVED",
  "timestamp": "2026-05-27T10:04:21.000Z",
  "data": { "score": 92, "decision": "approve" }
}

Use the externalRef to find the row in your database and proceed with onboarding. See Webhooks for signature verification, retry behaviour, and the full event catalogue.

What's next