Request data with Signa ID
Ask a Signa ID owner to release verified identity data to your business, then exchange the code on your server.
A data release lets your business ask a person for verified data from their Signa ID. The person approves the request in a popup on your page, and your server then gets the data from Blaaiz. The person does not upload documents again.
Read the Signa ID overview first.
Before you start
- Signa ID data release must be enabled for your business. Contact your account manager.
- Your OAuth credentials must carry the
signa-id:releasescope. - Register a
kyc_urlwebhook to get thesigna_id.grant.revokedevent. See Signa webhooks. - Install the Signa web SDK on your page.
No scope bundle contains signa-id:release, full-access included. Select
the scope by name when you create or rotate your credentials. The release
endpoints accept only an OAuth access token. A call with an API key fails
with 403.
Flow
Step 1: Create a release request
On your server, create the request:
curl -X POST https://api-prod.blaaiz.com/api/external/signa-id/release-requests \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"idempotency_key": "release-user_10482-2026-10-05",
"purpose": "Open your trading account",
"scopes": ["identity", "id_document", "document_images"],
"origin": "https://yourapp.com",
"reference": "user_10482"
}'| Field | Required | Description |
|---|---|---|
idempotency_key | Yes | Your key for this request, 1 to 191 characters. |
purpose | Yes | Why you ask for the data, 3 to 200 characters. The person sees this text before they approve. |
scopes | Yes | The data blocks that you ask for. One to four different values from the table below. |
origin | Yes | The exact origin of the page that opens the popup, such as https://yourapp.com. |
reference | No | Your own reference for the person, 1 to 191 characters. If you omit it, Blaaiz uses the idempotency_key. |
| Scope | Data block |
|---|---|
identity | The first, middle and last name, the date of birth, and the nationality. |
id_document | The type, number, issuing country, issue date and expiry date of the identity document. |
address | The verified address, with the country. |
document_images | The images of the identity document. |
Ask only for the scopes that you need. The person sees each item before they approve.
origin must be the value of window.location.origin on your page: https, a lower-case host, and no path, query or default port. The popup sends the result only to this origin. In the sandbox, http://localhost with a port is also accepted.
{
"message": "Signa ID release request created.",
"data": {
"id": "b84c0427-231c-44c2-b397-e528a775962f",
"status": "PENDING",
"purpose": "Open your trading account",
"scopes": ["document_images", "id_document", "identity"],
"origin": "https://yourapp.com",
"reference": "user_10482",
"expires_at": "2026-10-05T10:44:22.000Z",
"released_at": null,
"exchanged_at": null,
"access": null,
"created_at": "2026-10-05T10:14:22.000Z",
"request_token": "EHoPu_CY9bReov_nzsaGcPeex9QqDGx2AQ72MQbFPMo",
"release_url": "https://id.blaaiz.com/r/EHoPu_CY9bReov_nzsaGcPeex9QqDGx2AQ72MQbFPMo"
}
}Store data.id against your own record of the person. Send data.request_token to your page.
The request expires 30 minutes after the create. Blaaiz sends no webhook when a request expires. A release request does not count against your open verification session cap.
release_url works only when the web SDK opens it. Do not send it to the
person by email or SMS. Use signa.requestData() on your page.
Idempotency
- A repeat call with the same
idempotency_keyand the same values returns the same release request. - A repeat call with the same key and different values fails with
422. - Verification sessions and release requests share one set of keys. A key that a verification session uses fails with
422on a release request.
Step 2: Open the popup
Call signa.requestData() from the click handler of a button. A browser blocks a popup that does not come from a user action.
import { createSigna } from '@blaaiz/signa-web-sdk';
const signa = createSigna({ environment: 'production' });
button.onclick = async () => {
try {
const { releaseId, code } = await signa.requestData({ requestToken });
// Send releaseId and code to your server at once.
} catch (error) {
// error.code is 'closed', 'blocked' or 'in_progress'.
}
};In the popup, the person:
- Signs in to Signa ID with an email code or a passkey.
- Reviews what you ask for: your business name, the host of your page, your
purpose, the data, and the access period of 30 days. - Selects Share, or Decline.
- Takes a new selfie. Blaaiz checks that a live person is present and that the face matches the verified identity.
After the selfie, the popup sends releaseId and code to your page and closes.
When requestData() rejects, read error.code:
error.code | Cause |
|---|---|
closed | The popup closed with no release. |
blocked | The browser blocked the popup. Call requestData() from a click handler. |
in_progress | Another Signa popup is open on the page. |
The page cannot know why the popup closed with no release. The person can decline, their Signa ID can be under review, or their Signa ID cannot cover your request. All of these reject with closed.
To try again, read the release on your server. While its status is PENDING, call requestData() again with the same token. For any other status, create a new release request.
A requestToken that is not 43 base64url characters rejects at once. That error has no code.
Step 3: Exchange the code
Send code to your server at once. The code works one time only, for 5 minutes, and only for your business.
curl -X POST https://api-prod.blaaiz.com/api/external/signa-id/releases/exchange \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"code": "EthamT0KfpkGpItLjuYmLuo8-_PfnkFV1sVo7yVNz70"
}'{
"message": "Signa ID data released.",
"data": {
"release": {
"id": "b84c0427-231c-44c2-b397-e528a775962f",
"status": "EXCHANGED",
"purpose": "Open your trading account",
"scopes": ["document_images", "id_document", "identity"],
"origin": "https://yourapp.com",
"reference": "user_10482",
"expires_at": "2026-10-05T10:44:22.000Z",
"released_at": "2026-10-05T10:19:47.000Z",
"exchanged_at": "2026-10-05T10:20:03.000Z",
"access": {
"status": "ACTIVE",
"expires_at": "2026-11-04T10:19:47.000Z"
},
"created_at": "2026-10-05T10:14:22.000Z"
},
"data": {
"verification": {
"status": "VERIFIED",
"verified_at": "2026-09-12T14:03:51.000Z",
"released_at": "2026-10-05T10:19:47.000Z",
"liveness": "PASSED",
"face_match": "PASSED"
},
"identity": {
"first_name": "Amara",
"middle_name": null,
"last_name": "Okafor",
"date_of_birth": "1993-04-17",
"nationality": "NGA"
},
"id_document": {
"type": "PASSPORT",
"number": "A05729314",
"issuing_country": "NGA",
"issue_date": "2021-06-02",
"expiry_date": "2031-06-01"
},
"document_images": [
{
"id": "doc:901",
"document_type": "PASSPORT",
"document_side": "FRONT_SIDE",
"content_type": "image/jpeg",
"available": true,
"unavailable_reason": null
}
]
}
}
}Check that data.release.id is the release that you created for this person
in step 1. Do not trust a releaseId that your page sends to your server.
data.verificationis always present. It tells you when the person verified, and that the liveness check and the face match passed.- A data block is present only for a scope that you asked for. A block is
nullwhen Blaaiz holds no value for it. id_document.numbercan benullwhen Blaaiz cannot read it at the time of the call. Read the release again later.
If the code expires before the exchange, the release stays RELEASED and you cannot read the data. Create a new release request.
The exchange fails with 422 and the message The code is invalid or has expired. when the code is wrong, used, expired, or for a different business.
Step 4: Read the data again
For 30 days after the release, you can read the release and its data again:
curl -X GET https://api-prod.blaaiz.com/api/external/signa-id/releases/b84c0427-231c-44c2-b397-e528a775962f \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"The response has the same shape as the exchange response. data.data is null before the exchange, and when access.status is not ACTIVE.
Step 5: Download the document images
When the release includes document_images, get a download URL for one image with its id:
curl -X GET https://api-prod.blaaiz.com/api/external/signa-id/releases/b84c0427-231c-44c2-b397-e528a775962f/documents/doc:901 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"{
"message": "Document URL generated.",
"data": {
"url": "https://storage.example.com/releases/b84c0427/doc-901.jpg?expires=900&signature=EXAMPLE",
"content_type": "image/jpeg",
"expires_at": "2026-10-05T10:35:03.000Z"
}
}Send an HTTP GET to data.url with no Authorization header. The URL is valid for 15 minutes. Download the image at once, and do not store or share the URL.
The call returns 404 when the release has no document_images scope, when you did not exchange the code, or when access.status is not ACTIVE.
Statuses
status | Meaning |
|---|---|
PENDING | The person did not finish in the popup yet. |
RELEASED | The person approved, and Blaaiz issued the code. |
EXCHANGED | Your server exchanged the code. |
DECLINED | The person declined. |
EXPIRED | The request expired before the person finished. |
access is null before the release. After the release, access.status tells you if you can read the data:
access.status | Meaning |
|---|---|
ACTIVE | You can read the data until access.expires_at. |
REVOKED | The person or Blaaiz stopped your access. |
EXPIRED | The 30-day access period ended. |
UNAVAILABLE | Blaaiz cannot release the data at this time. |
When access stops
When the person or Blaaiz stops your access, Blaaiz sends signa_id.grant.revoked to your kyc_url:
{
"event": "signa_id.grant.revoked",
"data": {
"release_id": "b84c0427-231c-44c2-b397-e528a775962f",
"revoked_at": "2026-10-12T08:41:16.000Z"
}
}Blaaiz sends no webhook when the 30-day access period ends, or when access becomes UNAVAILABLE. Read access.status before you use the data.
Limits
| Endpoint | Limit for each business |
|---|---|
| Create a release request | 30 requests each minute |
| Exchange a release code | 30 requests each minute |
| Get a release document URL | 30 requests each minute and 600 each hour, shared with the session document URL endpoint |
Above a limit, the call returns 429 with a Retry-After header.
Security
- Call the release endpoints from your server only. Every response carries
Cache-Control: no-store. - Send the
request_tokenonly to the page that opens the popup. Do not put it in a URL or a log. - Do not send the OAuth access token of your server to a browser.
- Do not send the
Cross-Origin-Opener-Policy: same-originheader on the page. Usesame-origin-allow-popups, or send no header. - Do not write the released data or the download URLs to your logs.