Subject Management
A subject is the person being verified. The subject record holds the details the verification pipeline scores a submitted document against — name, date of birth, licence number, address — and it is the parent record every submission, message and erasure request hangs off.
Identiwise has not launched. This page describes the API as it is built today; the endpoints are not open to the public yet, and the hostnames in the examples do not resolve.
Auth: Authorization: Bearer <system token>.
Base URL: https://{tenant}.identiwise.com/api/v1 — the tenant is resolved from the
Host header, so the hostname you call is the tenancy boundary.
Roles: admin and superuser for every route on this page.
Identifiers
Subject ids are UUID strings (UUIDv7, time-ordered), for example
0198c2f4-6b31-7c9a-8d55-2f9a1c7b40e1. Submission, token, preset and workflow ids are the
same shape. Only system users and API tokens use integer ids.
external_uid is your own identifier for the person. It is optional and unique within your
tenant, and it works as a lookup key in exactly one place: GET /admin/review?externalUid=….
Every other route addresses the subject by its Identiwise UUID — the router matches only the
UUID shape — so store the subject_id we return at creation. Without it you cannot mint a
token for the subject, read, update or erase them, or fetch a photo.
Submissions carry their own external_uid and verification_id, which you set on the upload
that creates them; GET /admin/review?type=…&identifier=external&value=… looks a submission
up by that key.
1. List subjects
Endpoint: GET /api/v1/admin/subjects
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | int | 100 | Rows to return. Values above 1000 are clamped to 1000. |
offset | int | 0 | Pagination offset. Negative values are treated as 0. |
includeDeletionRequests | bool | false | Attach each subject's erasure requests as deletion_requests. |
deletionRequestStatus | string | — | Narrows that attachment to pending or completed. |
Rows come back newest first.
- cURL
- PHP
- Response
curl -s "https://acme.identiwise.com/api/v1/admin/subjects?limit=10&includeDeletionRequests=1" \
-H "Authorization: Bearer $IDENTIWISE_TOKEN"
$response = $client->get('https://acme.identiwise.com/api/v1/admin/subjects', [
'headers' => ['Authorization' => 'Bearer ' . $token],
'query' => ['limit' => 10, 'includeDeletionRequests' => 1],
]);
$subjects = json_decode((string) $response->getBody(), true);
[
{
"id": "0198c2f4-6b31-7c9a-8d55-2f9a1c7b40e1",
"external_uid": "usr_55920",
"first_name": "Jane",
"last_name": "Doe",
"date_of_birth": "1994-03-11",
"photo_url": null,
"subject_created_at": "2026-09-01 10:14:02",
"deletion_requests": [
{
"request_id": "0198c31a-77b2-7f04-9c10-6d2ab3e1f550",
"status": "pending"
}
]
}
]
deletion_requests is present only when you asked for it, and is an empty array for a
subject with none.
2. Create a subject
Endpoint: POST /api/v1/admin/subjects
Create the subject first, then mint an ephemeral token for them.
Body
| Field | Type | Required | Description |
|---|---|---|---|
first_name | string | Yes | |
last_name | string | Yes | |
external_uid | string | No | Your own id. Must be unused within the tenant. |
dob / date_of_birth | string | No | YYYY-MM-DD. An empty string is stored as null. |
middle_name | string | No | |
email | string | No | |
license_number | string | No | Scored against the document's OCR text. |
license_state | string | No | |
license_issue_date | string | No | YYYY-MM-DD. |
license_expiry_date | string | No | YYYY-MM-DD. |
addr_street, addr_city, addr_state, addr_zip, addr_country | string | No | addr_street is scored against the OCR text. |
photo_url | string | No |
The fields marked as scored are the ground truth the OCR step compares the document against — the more of them you supply, the more the automated check can decide on its own.
- cURL
- PHP
- Response
curl -s -X POST "https://acme.identiwise.com/api/v1/admin/subjects" \
-H "Authorization: Bearer $IDENTIWISE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"external_uid": "usr_55920",
"first_name": "Jane",
"last_name": "Doe",
"dob": "1994-03-11",
"license_number": "D1234567",
"addr_street": "123 Main St"
}'
$response = $client->post('https://acme.identiwise.com/api/v1/admin/subjects', [
'headers' => ['Authorization' => 'Bearer ' . $token],
'json' => [
'external_uid' => 'usr_55920',
'first_name' => 'Jane',
'last_name' => 'Doe',
'dob' => '1994-03-11',
'license_number' => 'D1234567',
'addr_street' => '123 Main St',
],
]);
{
"subject_id": "0198c2f4-6b31-7c9a-8d55-2f9a1c7b40e1",
"status": "created"
}
| Status | When |
|---|---|
201 | Created. |
400 | Body is not valid JSON, or first_name / last_name is missing. |
409 | Another subject in this tenant already uses that external_uid. |
3. Get one subject
Endpoint: GET /api/v1/admin/subjects/{id}
| Parameter | Type | Description |
|---|---|---|
include | string | status adds an aggregate verification state block. |
- cURL
- PHP
- Response
curl -s "https://acme.identiwise.com/api/v1/admin/subjects/0198c2f4-6b31-7c9a-8d55-2f9a1c7b40e1?include=status" \
-H "Authorization: Bearer $IDENTIWISE_TOKEN"
$response = $client->get('https://acme.identiwise.com/api/v1/admin/subjects/' . $subjectId, [
'headers' => ['Authorization' => 'Bearer ' . $token],
'query' => ['include' => 'status'],
]);
{
"id": "0198c2f4-6b31-7c9a-8d55-2f9a1c7b40e1",
"external_uid": "usr_55920",
"first_name": "Jane",
"last_name": "Doe",
"photo_url": null,
"date_of_birth": "1994-03-11",
"created_at": "2026-09-01 10:14:02",
"middle_name": null,
"license_number": "D1234567",
"license_state": null,
"license_issue_date": null,
"license_expiry_date": null,
"addr_street": "123 Main St",
"addr_city": null,
"addr_state": null,
"addr_zip": null,
"addr_country": null,
"email": "jane@example.com",
"status": {
"license_status": "approved",
"selfie_status": "pending",
"license_expiry_date": null,
"has_pending_reupload": false,
"need_student_reply": false,
"approved_licenses_count": 1,
"rejected_licenses_count": 0,
"approved_selfies_count": 0,
"rejected_selfies_count": 0
}
}
license_status and selfie_status are rolled up from that subject's submissions and are
one of none, approved, rejected, pending or mixed. A subject id that is malformed,
belongs to another tenant, or has been erased answers the same 404 — the API never
confirms the existence of another tenant's record.
4. Update a subject
Endpoint: PATCH /api/v1/admin/subjects/{id}
Send only the fields you want to change; the accepted set is the same as create. Both writes (the subject row and its detail row) happen in one transaction, so a partial update is never left behind.
- cURL
- PHP
- Response
curl -s -X PATCH "https://acme.identiwise.com/api/v1/admin/subjects/0198c2f4-6b31-7c9a-8d55-2f9a1c7b40e1" \
-H "Authorization: Bearer $IDENTIWISE_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "license_number": "D7654321", "addr_city": "Austin" }'
$client->patch('https://acme.identiwise.com/api/v1/admin/subjects/' . $subjectId, [
'headers' => ['Authorization' => 'Bearer ' . $token],
'json' => [
'license_number' => 'D7654321',
'addr_city' => 'Austin',
],
]);
{ "status": "updated" }
| Status | When |
|---|---|
200 | Applied. |
404 | No such subject in this tenant — including when an erasure removed it while the update was in flight. The write is rolled back, never partly applied. |
503 | Temporary database contention; nothing was written. Retry (Retry-After is set). |
Every accepted change writes an audit-trail entry naming the fields that were written — field names only, never values.
5. Request erasure (Article 17)
Endpoint: POST /api/v1/admin/subjects/{id}/request-deletion
Puts the subject in the privacy queue instead of erasing immediately, so a human confirms
the erasure. The data subject can lodge the same request themselves with their ephemeral
token via POST /subject/request-deletion. The hosted verification UI does not render a
control for this today — surface it from your own client if you need to offer one.
Body
| Field | Type | Required | Description |
|---|---|---|---|
confirmation | bool | No | Defaults to true; send false only to abandon the request (that answers 400). |
reason | string | No | Free text, maximum 4096 bytes. |
The route is idempotent per subject. One pending request per subject: a repeat while a
request is still outstanding answers 200 with the id of the request that already exists
and writes nothing new. A request that has been completed does not block a later one.
- 201 — request lodged
- 200 — one already pending
{
"status": "Deletion request logged",
"subject_id": "0198c2f4-6b31-7c9a-8d55-2f9a1c7b40e1",
"request_id": "0198c31a-77b2-7f04-9c10-6d2ab3e1f550",
"reason": "Customer asked for their data to be removed",
"confirmed": true
}
{
"status": "Deletion request already pending",
"subject_id": "0198c2f4-6b31-7c9a-8d55-2f9a1c7b40e1",
"request_id": "0198c31a-77b2-7f04-9c10-6d2ab3e1f550",
"confirmed": true
}
| Status | When |
|---|---|
201 | Request lodged. |
200 | A request for this subject was already outstanding; its id is returned. |
400 | confirmation sent as false, or reason is not a string / exceeds 4096 bytes. |
404 | No such subject in this tenant. |
Pending requests are visible on the subject list with
?includeDeletionRequests=1.
6. Erase a subject
Endpoint: DELETE /api/v1/admin/subjects/{id}
This erases the subject: every stored image is deleted from object storage and every database record for that person is removed in the same transaction. There is no undo.
Optional reason is read from the JSON request body. (The OpenAPI document expresses it as
a query parameter because OpenAPI 3.0 forbids a request body on DELETE.)
The erasure runs as one transaction: it collects every object key the subject has ever had —
current submissions and superseded attempts alike — deletes those objects, then removes the
rows. If object storage cannot be reached, nothing in the database is changed and the call
answers 503, so a retry finishes the job rather than leaving records pointing at deleted
files. A subject_deletion_requests row is marked completed and the erasure is recorded on
the audit trail, so the fact of the deletion survives the data.
- cURL
- PHP
- Response
curl -s -X DELETE "https://acme.identiwise.com/api/v1/admin/subjects/0198c2f4-6b31-7c9a-8d55-2f9a1c7b40e1" \
-H "Authorization: Bearer $IDENTIWISE_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "reason": "Erasure request 2026-09-04" }'
$client->delete('https://acme.identiwise.com/api/v1/admin/subjects/' . $subjectId, [
'headers' => ['Authorization' => 'Bearer ' . $token],
'json' => ['reason' => 'Erasure request 2026-09-04'],
]);
{
"message": "verification subject ID 0198c2f4-6b31-7c9a-8d55-2f9a1c7b40e1 deleted",
"requestId": "0198c31a-77b2-7f04-9c10-6d2ab3e1f550"
}
| Status | When |
|---|---|
200 | Erased. requestId joins this erasure to its privacy-queue row. |
404 | No such subject in this tenant, or a concurrent erasure finished first. |
503 | The erasure was interrupted; no database record was changed and objects already removed stay removed. Retry the request. |
500 | Unexpected failure. The transaction is rolled back. |
Individual submissions can be erased the same way through the generic route
DELETE /api/v1/{subjects|licenses|selfies}/{id}, which removes one record and its stored
image rather than the whole person.
Error semantics on this page
| Status | Meaning |
|---|---|
400 | The request is malformed. Fix it and retry. |
401 | Missing, revoked, expired or suspended bearer token. |
403 | Authenticated, but the role does not allow this route, or the tenant's IP allow-list does not include the caller. |
404 | Not found in this tenant. Malformed, foreign and erased ids are indistinguishable by design. |
409 | A uniqueness conflict — today, a duplicate external_uid. |
429 | The tenant's monthly request quota is spent. The body carries the limit. |
503 | Temporary: database contention or an unreachable dependency. Honour Retry-After and retry. |
Error bodies are a stable envelope — {"error": "..."} — and never carry exception,
SQL or infrastructure detail.