Overview
Kaluta KYC is an identity verification API. Your backend creates a session per end user, then your frontend opens that session in one of three ways:
- Embedded widget runs inline on your site in a modal. No redirect.
- Full page redirect hands the user to a hosted page and back.
- Mobile handoff shows a QR code on desktop. Your user finishes on their phone.
The final decision and extracted data arrive on your server through a signed webhook.
Setup
- Create a business account. You get 5 free verifications to start.
- Open Dashboard → API Keys and generate a key. The full key is shown once. Store it as a secret on your server.
- Configure at least one webhook in Dashboard → Webhooks pointing to your backend.
Keep funds on your account from Dashboard → Billing. Pay as you go at $1.00 per completed verification.
Quickstart
From zero to first verification in three steps.
1. Create a session (server side)
curl -X POST https://kalutakyc.com/v1/sessions \
-H "X-API-Key: klt_live_xxx" \
-H "Content-Type: application/json" \
-d '{ "external_id": "user_42" }'
# → { "session_id": "abc123",
# "verification_url": "https://kalutakyc.com/verify/abc123",
# "expires_at": "2026-06-02T00:00:00Z" }2. Open the verifier (client side)
<script src="https://kalutakyc.com/embed.js"></script>
<button onclick="open()">Verify my identity</button>
<script>
function open() {
KalutaKYC.open({
url: "https://kalutakyc.com/verify/abc123",
onComplete: (r) => console.log(r.status, r.score),
});
}
</script>3. Handle the webhook (server side)
app.post("/hooks/kaluta", (req, res) => {
if (!isSignatureValid(req)) return res.status(401).end();
if (req.body.event === "session.approved") {
// unlock the user account
}
res.sendStatus(200);
});Authentication
All server to server requests authenticate with your secret API key in theX-API-Key header. Dashboard endpoints use a JWT inAuthorization: Bearer. Keys start withklt_ and are bcrypt hashed in storage. The raw value is shown only at creation time.
X-API-Key: klt_live_xxxxxxxxxxxxxxxxxxxxxxxx
Never ship your API key to the browser
Always proxy session creation through your own backend. Anything visible in DevTools is public.Create a session
Call this from your backend when a user needs to be verified.
POST /v1/sessions
X-API-Key: klt_live_xxx
{
"external_id": "user_42",
"redirect_url": "https://yourapp.com/kyc/done",
"webhook_url": "https://yourapp.com/hooks/kyc",
"metadata": { "plan": "premium" },
"first_name": "Jean Baptiste",
"last_name": "Kouame",
"date_of_birth": "1992-03-15",
"country": "CI"
}{
"session_id": "abc123",
"verification_url": "https://kalutakyc.com/verify/abc123",
"expires_at": "2026-06-02T00:00:00Z"
}Expected identity — strongly recommended
Provide the identity you already know (first_name, last_name, date_of_birth, country). Our AI extracts the same fields from the ID document and cross-checks them: any mismatch on a field you supplied causes an automatic rejection, with the failing field named in the webhook. The country field accepts an ISO code or a plain country name ("CI", "CIV", and "Côte d'Ivoire" are all equivalent) and is matched against both the issuing country and the holder's nationality on the document.
Document type is detected automatically
You no longer need to senddocument_type. The user can pick "Detect automatically" and our vision AI recognizes passports, national IDs, driver licenses, and residence permits from the photo. You can still pass an explicit type to lock the flow to one document kind.Sessions expire 24 hours after creation. Billing: one charge covers a block of attempts (3 by default) — see Retries below.
Retries
When a verification is rejected, the end user can immediately start again — the rejection screen shows a retry button that spawns a fresh session inheriting your external_id, webhook, redirect URL, and expected identity. You can also trigger it server side:
POST /v1/sessions/{rejected_session_id}/retry
# → { "session_id": "def456", "verification_url": "...", "retry_index": 1 }Retries are unlimited. You are charged once per block of attempts (default: 1 charge covers 3 attempts — attempt 4 opens a new paid block, and so on). Each new session in the chain fires its own session.created webhook and carries metadata._retry_index.
Retrieve a session
Full session with extracted identity, scores, and short lived signed image URLs.
GET /v1/sessions/{id}
Authorization: Bearer <jwt>Session lifecycle
A session moves through these states. Use webhook events to track them.
| Status | Meaning |
|---|---|
created | Session created. User has not started yet. |
document_submitted | Document uploaded. AI extraction and cross-checks ran. |
face_submitted | Selfie uploaded and face match ran. |
approved | Automated decision: approved. Webhooks fired. |
rejected | Automated decision: rejected, with a plain-language reason. Webhook fired. |
expired | 24 hour window elapsed without completion. |
Decisions are fully automated — no manual review step. A session is approved when the combined score reaches the threshold and every hard check passes (face matches the document, extracted identity matches what you supplied, liveness if enabled). Otherwise it is rejected immediately with the weakest signal named in rejection_reason, and the user can retry.
Verification steps configuration
Document scan + face match always run. Three optional steps are toggled per business in Dashboard → Settings → Verification steps: Proof of address (the user photographs a utility bill or bank statement; we extract the holder name and address and match them against the ID) and Liveness check (anti-spoofing on the selfie), and Document security check (hologram verification, see above). Settings are snapshotted onto each session at creation, so in-flight sessions keep the flow they started with.
Embedded widget
Run the flow inline on your website in a secure modal. Your users never leave your page.
<script src="https://kalutakyc.com/embed.js"></script>
<button id="verify-btn">Verify my identity</button>
<script>
document.getElementById("verify-btn").addEventListener("click", () => {
KalutaKYC.open({
url: "https://kalutakyc.com/verify/abc123",
onReady: () => console.log("widget loaded"),
onStep: (s) => console.log("step:", s),
onComplete: (r) => console.log(r.status, r.score),
onClose: () => console.log("widget closed"),
});
});
</script>KalutaKYC.open options
| Option | Type | Description |
|---|---|---|
| url | string | The verification_url returned by POST /v1/sessions. |
| sessionId | string | Alternative to url. Combined with baseUrl. |
| baseUrl | string | Override the app host. Defaults to https://kalutakyc.com. |
| onReady | () => void | Fires when the widget iframe has loaded. |
| onStep | (step: string) => void | Fires on every step change. |
| onComplete | ({status, score}) => void | Fires when the final result is known. Always treat the webhook as source of truth. |
| onClose | (result?) => void | Fires when the modal closes. |
Redirect flow
Prefer a full page hand off? Send the user to theverification_url. When they finish, they are returned to yourredirect_url.
window.location.href = verificationUrl; // or KalutaKYC.redirect(verificationUrl);
Callback parameters
The verification outcome is appended to your redirect URL as query parameters, so your page can react immediately:
https://yourapp.com/kyc/done ?kyc_session_id=abc123 &kyc_status=approved // approved | rejected &kyc_external_id=user_42 // your own reference, if you set one
Never trust the URL alone
Query parameters can be edited by the user. Treat them as a UX hint only — update your UI optimistically, but confirm the real result server side via the webhook payload (which carries the full extracted identity and scores) or aGET /v1/sessions/{id} call. Personal data (names, birth dates) is intentionally never placed in the URL.Mobile handoff
Desktop users often have low quality webcams or no rear camera. The verification flow includes a built in handoff. On the welcome screen and in every camera error fallback, the user can click Continue on my phone. A QR code appears. They scan it with their phone camera and finish the document and selfie there.
No integration work needed. The desktop page polls the session status and automatically advances to the final screen when the phone is done.
Document security check
Optional anti forgery step. The user tilts their ID in front of the camera while we capture a burst of frames, then we analyse how the surface reacts to the light. Genuine identity documents carry optically variable devices — holograms, kinegrams, colour shifting ink — whose highlights travel across the card and change hue as the angle changes. A photocopy reflects flatly and a phone screen adds its own artefacts, so neither can fake the effect.
What it blocks
Photocopies and laser prints, photos of an ID displayed on another screen, and cards whose security overlay was removed or reprinted. It runs locally on our servers, so it adds no per check cost and no third party sees your users' documents.Turning it on
Enable Document security check under Dashboard → Settings → Verification steps, or set the flag through the API:
PUT /v1/auth/me
Authorization: Bearer <jwt>
{ "require_document_video": true }As with every step toggle, the setting is snapshotted onto each session at creation, so sessions already in flight keep the flow they started with.
Signals we compute
| Signal | What it measures |
|---|---|
highlight_travel | How far the bright reflection moves between frames. A hologram sweeps across the card, printed glare barely moves. |
iridescence | Hue instability inside the highlight. Diffraction throws different wavelengths at different angles; paper glare stays white. |
highlight_area_variation | How much the lit area swells and shrinks as the card turns. |
screen_replay_likelihood | Periodic energy in the frequency domain, the moire signature of filming a display. |
The four signals blend into doc_video_score (0-100), returned on the session and in webhook payloads alongside the raw values. A session is rejected outright when the document shows no optically variable behaviour at all.
Uploading the capture yourself
Building your own UI? Post the frames, or a short video file, to the session. Send at least four frames covering a slow tilt.
POST /v1/sessions/{id}/document-video
Content-Type: multipart/form-data
files=@frame_01.jpg
files=@frame_02.jpg
files=@frame_03.jpg
...
# or a single clip: files=@tilt.mp4Duplicate document detection
Independently of the security check, every document we read is fingerprinted from its number, surname and date of birth. If the same physical document later appears in a verification for a different person, the new session is rejected and carries duplicate_of_session_id pointing at the original approval. The same person re-verifying with the same ID is never penalised.
Business verification (KYB)
KYB verifies a company rather than a person. You create a session, the company representative uploads registration documents on a hosted page, and our vision AI extracts the legal identity of the entity: legal name, registration number, incorporation date, registered address, company status and directors. The decision is automatic, exactly like KYC.
KYB and KYC compose naturally: verify the company, verify its legal representative, then link the two by passing the representative's KYC session id when creating the KYB session.
Accepted documents
| Type | Notes |
|---|---|
certificate_of_incorporation | Founding document. Identifies the company on its own. |
registration_extract | RCCM, Kbis, Companies House extract. Identifies the company on its own. |
articles_of_association | Statutes or bylaws. Strengthens the file. |
proof_of_address | Recent bill or lease for the registered office. |
ownership_structure | Shareholding chart or list of beneficial owners. |
tax_certificate | Tax identification or good standing certificate. |
At least one of the two first types is required to submit a file. PDFs and photos are both accepted, up to 20 MB per document.
Create a KYB session
POST /v1/kyb/sessions
X-API-Key: klt_live_xxx
{
"external_id": "company_42",
"redirect_url": "https://yourapp.com/kyb/done",
"webhook_url": "https://yourapp.com/hooks/kyb",
"metadata": { "tier": "enterprise" },
"legal_name": "ACME TRADING SARL",
"registration_number": "CI-ABJ-2019-B-12345",
"country": "CI",
"representative_session_id": "abc123"
}{
"session_id": "kyb_abc123",
"verification_url": "https://kalutakyc.com/kyb/kyb_abc123",
"expires_at": "2026-08-11T00:00:00Z"
}Send the representative to verification_url. As with KYC, any legal_name, registration_number or country you supply is cross-checked against the documents, and a mismatch rejects the file automatically.
KYB sessions last 7 days
Companies often need to dig up paperwork, so KYB links live far longer than the 24 hour KYC window. One verification is billed at session creation.Upload documents from your own UI
Prefer to build your own upload experience? Post directly to the session:
POST /v1/kyb/sessions/{id}/documents
Content-Type: multipart/form-data
doc_type=registration_extract
file=@extract.pdf
# then, when the representative is done:
POST /v1/kyb/sessions/{id}/submitLifecycle and scoring
| Status | Meaning |
|---|---|
created | Session created. No documents uploaded yet. |
documents_submitted | A registration document was read and the company identified. |
approved | Automated decision: approved. Webhooks fired. |
rejected | Automated decision: rejected, with a plain language reason. |
expired | The 7 day window elapsed without submission. |
The company score blends document quality (55%) and the match against your declared data (45%). A file is approved from 50 and above. Three rules reject immediately regardless of the score:
- The register reports the company as
dissolvedorsuspended. - The legal name or registration number does not match what you supplied.
- No registration document could be read.
KYB webhooks
KYB emits its own event family. Subscribe to them like any other event.
kyb.createdkyb.documents_submittedkyb.approvedkyb.verifiedkyb.rejectedkyb.expired
{
"event": "kyb.verified",
"timestamp": "2026-08-05T10:14:02Z",
"session": {
"id": "kyb_abc123",
"external_id": "company_42",
"status": "approved",
"company": {
"legal_name": "ACME TRADING SARL",
"trading_name": "Acme",
"registration_number": "CI-ABJ-2019-B-12345",
"tax_number": "1943827K",
"incorporation_date": "2019-06-14",
"company_type": "SARL",
"country": "CIV",
"registered_address": "Rue du Commerce, Plateau, Abidjan",
"company_status": "active",
"directors": [{ "name": "Jean Baptiste Kouame", "role": "Gerant" }]
},
"overall_score": 84,
"risk_level": "low",
"scores": { "document": 88, "data_match": 100 },
"representative_session_id": "abc123",
"ai_extraction": { }
}
}The redirect back to your app carries kyb_session_id, kyb_status and kyb_external_id, following the same rules as the KYC callback: treat them as a UX hint and confirm server side.
Next.js (App Router)
Server action creates the session. Client component opens the widget.
// app/actions/verify.ts
"use server";
export async function createVerificationSession(externalId: string) {
const res = await fetch("https://kalutakyc.com/v1/sessions", {
method: "POST",
headers: {
"X-API-Key": process.env.KALUTA_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
external_id: externalId,
redirect_url: `${process.env.NEXT_PUBLIC_APP_URL}/kyc/done`,
}),
cache: "no-store",
});
if (!res.ok) throw new Error("Failed to create session");
return res.json() as Promise<{ session_id: string; verification_url: string }>;
}// app/kyc/page.tsx
"use client";
import Script from "next/script";
import { useState } from "react";
import { createVerificationSession } from "@/app/actions/verify";
declare global { interface Window { KalutaKYC: any } }
export default function KycPage() {
const [status, setStatus] = useState("");
const start = async () => {
const { verification_url } = await createVerificationSession("user_42");
window.KalutaKYC.open({
url: verification_url,
onComplete: (r: any) => setStatus(`${r.status} (score ${r.score})`),
});
};
return (<>
<Script src="https://kalutakyc.com/embed.js" strategy="afterInteractive" />
<button onClick={start} className="bg-blue-600 text-white px-5 py-3 rounded-lg">
Verify my identity
</button>
<p>{status}</p>
</>);
}PHP and HTML
<?php // create-session.php
header("Content-Type: application/json");
$ch = curl_init("https://kalutakyc.com/v1/sessions");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-API-Key: " . getenv("KALUTA_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"external_id" => $_POST["user_id"] ?? "anon",
"redirect_url" => "https://example.com/kyc/done",
]),
]);
$resp = curl_exec($ch);
http_response_code(curl_getinfo($ch, CURLINFO_HTTP_CODE));
echo $resp;<!-- verify.html -->
<button id="go">Verify my identity</button>
<script src="https://kalutakyc.com/embed.js"></script>
<script>
document.getElementById("go").addEventListener("click", async () => {
const r = await fetch("/create-session.php", { method: "POST" });
const { verification_url } = await r.json();
KalutaKYC.open({
url: verification_url,
onComplete: (result) => alert("Status: " + result.status),
});
});
</script>Node.js Express
import express from "express";
import crypto from "node:crypto";
const app = express();
app.post("/api/create-session", express.json(), async (req, res) => {
const r = await fetch("https://kalutakyc.com/v1/sessions", {
method: "POST",
headers: {
"X-API-Key": process.env.KALUTA_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ external_id: req.body.userId }),
});
res.status(r.status).json(await r.json());
});
app.post("/hooks/kaluta", express.raw({ type: "application/json" }), (req, res) => {
const raw = req.body;
const sig = req.get("x-kaluta-signature") || "";
const [ts, v1] = sig.split(",").map(p => p.split("=")[1]);
const expected = crypto
.createHmac("sha256", process.env.KALUTA_WEBHOOK_SECRET)
.update(`${ts}.${raw}`).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))) {
return res.sendStatus(401);
}
const { event } = JSON.parse(raw.toString());
if (event === "session.approved") { /* unlock account */ }
res.sendStatus(200);
});Python FastAPI
import os, hmac, hashlib, httpx
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
API_KEY = os.environ["KALUTA_API_KEY"]
SECRET = os.environ["KALUTA_WEBHOOK_SECRET"].encode()
@app.post("/api/create-session")
async def create_session(payload: dict):
async with httpx.AsyncClient() as client:
r = await client.post(
"https://kalutakyc.com/v1/sessions",
headers={"X-API-Key": API_KEY},
json={"external_id": payload["user_id"]},
)
return r.json()
@app.post("/hooks/kaluta")
async def webhook(request: Request):
raw = await request.body()
sig = request.headers.get("x-kaluta-signature", "")
ts, v1 = (p.split("=")[1] for p in sig.split(","))
expected = hmac.new(SECRET, f"{ts}.".encode() + raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(v1, expected):
raise HTTPException(status_code=401)
return {"ok": True}Python Django
# views.py
import os, json, hmac, hashlib, requests
from django.http import JsonResponse, HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
API_KEY = os.environ["KALUTA_API_KEY"]
SECRET = os.environ["KALUTA_WEBHOOK_SECRET"].encode()
@require_POST
def create_session(request):
body = json.loads(request.body)
r = requests.post(
"https://kalutakyc.com/v1/sessions",
headers={"X-API-Key": API_KEY},
json={"external_id": body["user_id"]},
timeout=10,
)
return JsonResponse(r.json(), status=r.status_code)
@csrf_exempt
@require_POST
def kaluta_webhook(request):
raw = request.body
sig = request.headers.get("X-Kaluta-Signature", "")
ts, v1 = (p.split("=")[1] for p in sig.split(","))
expected = hmac.new(SECRET, f"{ts}.".encode() + raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(v1, expected):
return HttpResponse(status=401)
return HttpResponse("ok")PHP Laravel
// routes/api.php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\{Http, Route};
Route::post("/create-session", function (Request $r) {
$resp = Http::withHeaders(["X-API-Key" => config("services.kaluta.key")])
->post("https://kalutakyc.com/v1/sessions", [
"external_id" => $r->input("user_id"),
]);
return response()->json($resp->json(), $resp->status());
});
Route::post("/hooks/kaluta", function (Request $r) {
$raw = $r->getContent();
$sig = $r->header("X-Kaluta-Signature", "");
[$ts, $v1] = array_map(fn($p) => explode("=", $p)[1], explode(",", $sig));
$expected = hash_hmac("sha256", "$ts.$raw", config("services.kaluta.webhook_secret"));
abort_unless(hash_equals($expected, $v1), 401);
return response("ok");
})->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);Plain HTML
Never call POST /v1/sessions from the browser
That would expose your secret API key to every visitor. Always proxy through a backend.<button id="go">Verify my identity</button>
<p id="result"></p>
<script src="https://kalutakyc.com/embed.js"></script>
<script>
document.getElementById("go").onclick = async () => {
const res = await fetch("/api/create-session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ user_id: "user_42" }),
});
const { verification_url } = await res.json();
KalutaKYC.open({
url: verification_url,
onComplete: (r) => {
document.getElementById("result").textContent =
"Final status: " + r.status + " (score " + r.score + ")";
},
});
};
</script>Webhooks
Configure endpoints in Dashboard → Webhooks. Every event is signed with HMAC SHA256 in theX-Kaluta-Signature header. Always verify the signature before trusting the payload.
Signature format
The header uses a Stripe like scheme so receivers can detect replays.
X-Kaluta-Signature: t=1722070800,v1=8f4b3a...
To verify: recompute HMAC over t.raw_body and compare with v1. Reject if t is older than 5 minutes.
Events
session.createdsession.document_submittedsession.face_submittedsession.approvedsession.verifiedsession.rejectedsession.expired
session.verified is an alias of session.approved — it fires on every successful identity confirmation, whether automatic or triggered by our operations team. Subscribe to it if you only care about "this person is who they claim to be".
Payload
Terminal events carry the full result — extracted identity, every sub-score, and the raw AI extraction — so your server can update its records from the webhook alone, without a follow-up API call:
{
"event": "session.verified",
"timestamp": "2026-08-03T14:23:11Z",
"session": {
"id": "abc123",
"external_id": "user_42",
"status": "approved",
"identity": {
"first_name": "Jean Baptiste",
"last_name": "Kouame",
"date_of_birth": "1992-03-15",
"gender": "M",
"nationality": "CIV",
"document_type": "national_id",
"document_number": "CI004587219",
"document_expiry": "2030-08-22"
},
"overall_score": 87,
"risk_level": "low",
"rejection_reason": null,
"scores": { "document": 92, "face": 88, "liveness": null, "poa": null },
"checks_required": { "liveness": false, "proof_of_address": false },
"ai_extraction": { /* raw model output: MRZ, confidence, tampering_signs, ... */ },
"proof_of_address": null,
"attempt_count": 1,
"created_at": "2026-08-03T14:20:05Z",
"completed_at": "2026-08-03T14:23:11Z"
}
}Idempotency
We retry failed deliveries with exponential backoff. Return a 2xx within 10 seconds and make your handler idempotent.Result schema
A completed session looks like this.
{
"id": "abc123",
"external_id": "user_42",
"status": "approved",
"document_type": "passport",
"first_name": "Ada",
"last_name": "Lovelace",
"date_of_birth": "1815-12-10",
"nationality": "GBR",
"document_number": "P12345678",
"document_expiry": "2030-01-01",
"document_score": 92,
"face_score": 96,
"liveness_score": 90,
"overall_score": 93,
"risk_level": "low",
"extracted_data": { /* full OCR/MRZ payload */ },
"checks": { "mrz_detected": true, "face_match": true, "liveness": true },
"metadata": { "plan": "premium" },
"created_at": "2026-06-01T12:00:00Z",
"completed_at": "2026-06-01T12:01:42Z"
}Scoring
Overall score is a weighted blend. Weights adapt to which steps your business enabled:
- Document — image quality plus AI extraction confidence and tampering analysis.
- Face match — selfie vs document photo, computed by a deep face recognition model.
- Liveness — anti-spoofing, only when enabled in your settings.
- Proof of address — document quality, holder name match, and recency, only when enabled.
Base weights are document 55% + face 45%; enabling liveness shifts to 40/35/25, and proof of address reserves a further 20%.
Decision: 60 and up = approved · below 60 = rejected. Independently of the score, a failed face match, an identity mismatch against the values you supplied, or a failed liveness check rejects immediately.
Errors
The API returns standard HTTP status codes with a JSON body.
{ "detail": "Invalid API key" }| Status | Meaning |
|---|---|
| 400 | Bad request. Required field missing or invalid. |
| 401 | Invalid API key or expired JWT. |
| 402 | Insufficient balance. Top up to continue. |
| 403 | Action not allowed on this resource. |
| 404 | Resource not found. |
| 409 | Conflict, for example session already approved or rejected. |
| 410 | Session expired. |
| 413 | Uploaded file is too large (max 20 MB). |
| 429 | Rate limited. Retry after a short backoff. |
| 500 | Server error. Retry. Contact support if persistent. |
Testing
Two easy ways to try the flow without writing code.
- The embed demo page lets you paste any
verification_urland open the inline widget with live event logs. - Replay a webhook delivery from Dashboard → Webhooks → Endpoint → Deliveries.
New accounts receive 5 free verifications so you can run a real end to end test without committing to billing.
API reference
Every endpoint at a glance.
| Method | Endpoint | Description |
|---|---|---|
| POST | /v1/sessions | Create a verification session |
| GET | /v1/sessions | List your sessions |
| GET | /v1/sessions/{id} | Retrieve a session with results |
| POST | /v1/sessions/{id}/document-video | Upload the tilt capture for the security check |
| POST | /v1/sessions/{id}/retry | Spawn a fresh session after a rejection |
| GET | /v1/sessions/{id}/public | Public read |
| POST | /v1/kyb/sessions | Create a KYB (company) session |
| GET | /v1/kyb/sessions | List your KYB sessions |
| GET | /v1/kyb/sessions/{id} | Retrieve a KYB session |
| POST | /v1/kyb/sessions/{id}/documents | Upload a company document |
| POST | /v1/kyb/sessions/{id}/submit | Submit the company file for a decision |
| PUT | /v1/auth/me | Update profile and verification step toggles |
| GET | /v1/api-keys | List API keys |
| POST | /v1/api-keys | Create a new API key |
| DELETE | /v1/api-keys/{id} | Revoke a key |
| GET | /v1/webhooks | List webhook endpoints |
| POST | /v1/webhooks | Create a webhook endpoint |
| DELETE | /v1/webhooks/{id} | Delete a webhook endpoint |
| GET | /v1/billing/summary | Account balance and usage |
| POST | /v1/billing/topup | Add credits to your balance |
Need a hand?
Email support@kalutakyc.com for integration questions, security disclosures, or a DPA request.