> ## Documentation Index > Fetch the complete documentation index at: https://docs.blaaiz.com/llms.txt > Use this file to discover all available pages before exploring further. # Headless verification quickstart Upload identity documents and proof of address from your own server, then submit the session for review. This quickstart uses the `HEADLESS` fulfilment mode. You already hold the document images, so you upload them from your server. Blaaiz never contacts the person you verify. A headless session asks for `DOCUMENTS`, `PROOF_OF_ADDRESS`, or both. If you need a live selfie, use the [hosted quickstart](/docs/signa/quickstart-hosted) instead. Read [Signa overview](/docs/signa/overview) first for the session model. ## Before you start - Signa must be enabled for your business. - Your OAuth credentials must carry the `compliance-kyc:create` and `compliance-kyc:read` scopes. - Your `kyc_url` webhook must be registered. See [Signa webhooks](/docs/signa/webhooks). The examples use the production base URL `https://api-prod.blaaiz.com`. For development, use `https://api-dev.blaaiz.com`. ## Flow ```mermaid sequenceDiagram participant You as Your Server participant Blaaiz participant S3 as Blaaiz Storage You->>Blaaiz: POST /compliance/kyc/sessions Blaaiz-->>You: session id, status AWAITING_INPUT You->>Blaaiz: POST /sessions/{id}/documents/upload-url Blaaiz-->>You: url, file_name, headers You->>S3: PUT the document bytes You->>Blaaiz: POST /sessions/{id}/documents Blaaiz-->>You: steps.documents SUBMITTED You->>Blaaiz: POST /sessions/{id}/submit Blaaiz-->>You: status IN_REVIEW Blaaiz->>You: merchant.kyc.session.completed webhook ``` ## Step 1: Create the session ```bash curl -X POST https://api-prod.blaaiz.com/api/external/compliance/kyc/sessions \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "customer_reference": "user_10482", "idempotency_key": "kyc-user_10482-2026-08-29", "requirements": ["DOCUMENTS"], "applicant": { "first_name": "Amara", "last_name": "Okafor", "dob": "1993-04-17", "country": "NGA" } }' ``` A `["DOCUMENTS"]` set supports `HEADLESS` only, so you can omit `fulfilment_mode`. Send `"fulfilment_mode": "HEADLESS"` if you prefer to be explicit. ```json { "message": "Verification session created successfully.", "data": { "id": "9f2c7b41-6d3e-4c8a-9a20-1e6f0b5d7c33", "business_id": "9d4c4ec5-572d-49de-a362-f01ed09f2b1b", "customer_reference": "user_10482", "requirements": ["DOCUMENTS"], "fulfilment_mode": "HEADLESS", "status": "AWAITING_INPUT", "steps": { "documents": "PENDING", "selfie": null, "proof_of_address": null }, "rejection": null, "redirect_url": null, "expires_at": "2026-08-30T09:14:22.000Z", "completed_at": null, "created_at": "2026-08-29T09:14:22.000Z", "verification_link": null, "link_expires_at": null } } ``` `steps.selfie` is `null` because the requirement set has no `SELFIE`. The session must be `AWAITING_INPUT` before it accepts a document. If `status` is `CREATED`, repeat the create call with the same `idempotency_key`. ## Step 2: Get an upload URL Do not send document bytes in the request body. A request body larger than about 8 KB is rejected at the network edge, before the API sees it. Use the upload URL for every real document. ```bash curl -X POST https://api-prod.blaaiz.com/api/external/compliance/kyc/sessions/9f2c7b41-6d3e-4c8a-9a20-1e6f0b5d7c33/documents/upload-url \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "file_name": "passport front.jpg", "id_doc_type": "PASSPORT" }' ``` ```json { "message": "Verification session document upload URL generated successfully.", "data": { "url": "https://storage.example.com/uploads/4a7f1c92e0_passport%20front.jpg?X-Amz-Expires=300&X-Amz-Signature=EXAMPLE", "file_name": "4a7f1c92e0_passport front.jpg", "headers": {} } } ``` | Field | Description | | --- | --- | | `url` | The presigned URL you upload the bytes to. It is valid for 5 minutes. | | `file_name` | The name Blaaiz gave the staged file. Send this exact value in step 4. | | `headers` | Headers you must send with the upload. The object can be empty. | Your `file_name` must use letters, numbers, spaces, dashes, or underscores, and must end in `.jpg`, `.jpeg`, `.png`, `.webp`, or `.pdf`. Blaaiz adds a random prefix and returns the result in `data.file_name`. ## Step 3: Upload the bytes Send the file to the `url` with an HTTP `PUT`. Add every header from the `headers` object. ```bash curl -X PUT "PRESIGNED_URL_FROM_STEP_2" \ --upload-file ./passport-front.jpg ``` A staged file must be 5 MB or smaller. Blaaiz rejects a larger file when you register it in step 4. ## Step 4: Register the document This call tells Blaaiz which staged file belongs to the session, and what the file is. ```bash curl -X POST https://api-prod.blaaiz.com/api/external/compliance/kyc/sessions/9f2c7b41-6d3e-4c8a-9a20-1e6f0b5d7c33/documents \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "filename": "passport-front.jpg", "content_type": "image/jpeg", "id_doc_type": "PASSPORT", "country": "NGA", "file_name": "4a7f1c92e0_passport front.jpg" }' ``` | Field | Description | | --- | --- | | `filename` | The name Blaaiz stores the document under. | | `content_type` | One of `image/jpeg`, `image/png`, `image/webp`, `application/pdf`. | | `id_doc_type` | One of `PASSPORT`, `ID_CARD`, `DRIVERS`, `RESIDENCE_PERMIT`, `UTILITY_BILL`, `BANK_STATEMENT`, `SELFIE`. `UTILITY_BILL` and `BANK_STATEMENT` satisfy the proof-of-address step. Every other type satisfies the documents step. | | `country` | The country of the document, as an ISO 3166-1 alpha-3 code. | | `file_name` | The `data.file_name` value from step 2. | The response is the session, with `steps.documents` set to `SUBMITTED`: ```json { "message": "Verification session document uploaded successfully.", "data": { "id": "9f2c7b41-6d3e-4c8a-9a20-1e6f0b5d7c33", "business_id": "9d4c4ec5-572d-49de-a362-f01ed09f2b1b", "customer_reference": "user_10482", "requirements": ["DOCUMENTS"], "fulfilment_mode": "HEADLESS", "status": "AWAITING_INPUT", "steps": { "documents": "SUBMITTED", "selfie": null, "proof_of_address": null }, "rejection": null, "redirect_url": null, "expires_at": "2026-08-30T09:14:22.000Z", "completed_at": null, "created_at": "2026-08-29T09:14:22.000Z" } } ``` To send more than one document, repeat steps 2 to 4 for each file. Give each file its own `id_doc_type`. ### Proof of address When the requirement set includes `PROOF_OF_ADDRESS`, upload the proof of address the same way. Repeat steps 2 to 4, and set `id_doc_type` to the type of document you hold. | `id_doc_type` | The document | | --- | --- | | `UTILITY_BILL` | A utility bill. | | `BANK_STATEMENT` | A bank statement. | A photo and a PDF are both accepted, so send `image/jpeg`, `image/png`, `image/webp`, or `application/pdf`. ```json { "filename": "bank-statement.pdf", "content_type": "application/pdf", "id_doc_type": "BANK_STATEMENT", "country": "NGA", "file_name": "b1e8d3f704_bank statement.pdf" } ``` On a proof of address, `country` means the country of the address **on the document**. It is not the nationality of the person, and it is not the country of the identity document. A person can hold a passport of one country and a bank statement of another. The upload sets `steps.proof_of_address` to `SUBMITTED`: ```json { "steps": { "documents": "SUBMITTED", "selfie": null, "proof_of_address": "SUBMITTED" } } ``` `steps.documents` is `null` on a `["PROOF_OF_ADDRESS"]` session, because that set asks for no identity document. Upload the proof of address only, then submit. ### Small files A document under about 8 KB can travel in the request body. Replace `file_name` with `content_base64` and send the base64 of the file. Send one of the two fields, never both. ```json { "filename": "passport-front.jpg", "content_type": "image/jpeg", "id_doc_type": "PASSPORT", "country": "NGA", "content_base64": "/9j/4AAQSkZJRgABAQAAAQABAAD..." } ``` Real identity documents are larger than the edge limit. Use the upload URL for them. ## Step 5: Submit the session for review ```bash curl -X POST https://api-prod.blaaiz.com/api/external/compliance/kyc/sessions/9f2c7b41-6d3e-4c8a-9a20-1e6f0b5d7c33/submit \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" ``` The session moves to `IN_REVIEW`: ```json { "message": "Verification session submitted successfully.", "data": { "id": "9f2c7b41-6d3e-4c8a-9a20-1e6f0b5d7c33", "business_id": "9d4c4ec5-572d-49de-a362-f01ed09f2b1b", "customer_reference": "user_10482", "requirements": ["DOCUMENTS"], "fulfilment_mode": "HEADLESS", "status": "IN_REVIEW", "steps": { "documents": "SUBMITTED", "selfie": null, "proof_of_address": null }, "rejection": null, "redirect_url": null, "expires_at": "2026-08-30T09:14:22.000Z", "completed_at": null, "created_at": "2026-08-29T09:14:22.000Z" } } ``` Blaaiz refuses the submit in three cases: - A step of the set is still `PENDING`. Upload what that step asks for first. A `null` step never blocks the submit. - The session already left `AWAITING_INPUT`. You cannot submit twice. - The session is `HOSTED`. The person completes that session on the verification link. Review starts only when every step of the set is `SUBMITTED`. On a `["DOCUMENTS", "PROOF_OF_ADDRESS"]` session, that means both uploads must land before you call submit. An `IN_REVIEW` session no longer expires, and it accepts no more documents. ## Step 6: Wait for the webhook ```json { "event": "merchant.kyc.session.completed", "data": { "session_id": "9f2c7b41-6d3e-4c8a-9a20-1e6f0b5d7c33", "business_id": "9d4c4ec5-572d-49de-a362-f01ed09f2b1b", "customer_reference": "user_10482", "requirements": ["DOCUMENTS"], "result": "REJECTED", "rejection_reason": "The document image is too blurred to read.", "rejection_type": "RETRYABLE", "completed_at": "2026-08-29T09:48:11.000Z" } } ``` A `RETRYABLE` rejection means the person can try again. Create a new session with a new `idempotency_key` and upload clearer images. A `FINAL` rejection means you must not retry. Verify the `x-blaaiz-signature` header before you trust the payload. See [Signa webhooks](/docs/signa/webhooks). ## Read the session data After the verdict, you can read the applicant details that Blaaiz read from the documents, and download the images. See [Read session data](/docs/signa/session-data).