---
title: Create a process
description: POST /v1/processes — the common fields, the validation limits, the flow fields and the 201 response with the Human Token and the intelligence.
canonical: https://developer.unico.io/human-plugin/api-reference/create-process
locale: en
generated_by: markdown-export
---

# Create a process

Start a process and get the Human Token and the person's intelligence.

`POST /v1/processes`

Starts a process with one of the [flows](#flows) and answers with `id`, `state` and the result in `data`: the Human Token and the person's [intelligence](/human-plugin/get-started/intelligence), `data.ndocs`.

## Before you call

- Human Plugin is running and `GET /health/ready` answers `200`.
- Your access key is enabled for the [flow](#flows) you use.
- If you send a document, its `type` is in the [attribute catalog](/human-plugin/additional-resources/attribute-catalog).

## Flows

The `flow` field chooses what the process does. Human Plugin runs these flows, in two integration models:

- **API Only**: your back-end sends the selfie in `imageBase64`, and the create answers with the result.
- **Human Token Journey**: the person takes the selfie in the capture journey and returns to your `callbackUri`. The create answers with a `token` and a `link`, and you read the result with [Get a process](/human-plugin/api-reference/get-process).

| Flow | Description | Possible result | Integration components required |
| --- | --- | --- | --- |
| `api+humantoken` | Receives a selfie via your back-end and generates the Human Token, a unique face identifier. The answer is final: no capture, no second call. | `PROCESS_STATE_FINISHED`, `PROCESS_STATE_FAILED` | API |
| `humantoken` | Captures a selfie in the capture journey, with a liveness check, and generates the Human Token. The create returns the `token` and the `link`; read the result when the capture ends. | `PROCESS_STATE_CREATED`, `PROCESS_STATE_FINISHED`, `PROCESS_STATE_FAILED` | API, Web |
| `humantoken+revalidation` | Captures a selfie and compares the face with the Human Token you expect. Returns whether it is the same person or not, for example before a sensitive action. | `PROCESS_STATE_CREATED`, `PROCESS_STATE_FINISHED`, `PROCESS_STATE_FAILED` | API, Web |

`clientId`, `correlationId` and `credential` are the same in every flow.

## Request

```json
{
  "flow": "api+humantoken",
  "clientId": "client-a",
  "correlationId": "onboarding-001",
  "credential": { "type": "DUI_TYPE_NG_BVN", "value": "<document-number>" },
  "imageBase64": "<base64 selfie>"
}
```

### Common fields

| Field | Required | Limit (bytes) | Notes |
| --- | --- | --- | --- |
| `flow` | Yes | 64 | One of the [flows](#flows) |
| `clientId` | Yes | 128 | Your id for the client. The intelligence counts distinct values |
| `correlationId` | Yes | 128 | Your reference for this process. **Unique**: one that another process already has, whatever its state, is refused with `409` |
| `credential` | Yes, but may be empty | | The key must be present. See below |
| `credential.type` | With a document | | A code of the attribute catalog, exact and case-sensitive |
| `credential.value` | With a document | 64 | The document number |

`flow`, `clientId` and `correlationId` must be strings, without control characters and without leading or trailing whitespace. Invalid values are rejected, never rewritten.

### The `credential` field

- **With a document:** an object with exactly `type` and `value`, both strings, sent together.
- **Without a document:** `null` or `{}`. The process still counts as a transaction and as a client in the intelligence.
- **Omitted, or an object with empty strings:** rejected with `400 MISSING_CREDENTIAL_TYPE`. This catches a misspelled key before anything is processed.
- Any other key inside `credential` is rejected.

### Flow fields

What each flow adds to the common fields:

| Field | Flow | Required | Value |
| --- | --- | --- | --- |
| `imageBase64` | `api+humantoken` | Yes | The selfie in base64. PNG, JPEG or WebP, up to 800 KB |
| `callbackUri` | `humantoken`, `humantoken+revalidation` | Yes | Where the person returns when the capture ends: a page of yours, or your app's URL scheme |
| `references` | `humantoken+revalidation` | Yes | One item with the Human Token you expect, as in the example above |

Human Plugin does not store these fields, and a misspelled optional field may be ignored without an error.

Use a frontal, well-lit selfie of one face with `api+humantoken`. A value that is not an image is refused, and the process is stored as failed: see [Flow refusals](#flow-refusals).

With `humantoken+revalidation`, the result has `humanTokenMatch` instead of `humanToken`:

- `"true"`: it is the same person. The process counts for that person, and `ndocs` describes them.
- Anything else, or a capture that is not live: it is not confirmed. The process counts for no one, and there is no `ndocs`.

`PROCESS_STATE_FINISHED` is answered in both cases: read `humanTokenMatch`. See [Get a process](/human-plugin/api-reference/get-process#finished-and-failed-processes).

A few names are reserved by Human Plugin and refused with `400 INVALID_REQUEST`, such as `clientReference`. So is another spelling of Human Plugin's own fields, such as `client_id`, `correlation_id` or `Flow`: only `flow`, `clientId`, `correlationId` and `credential` are read, spelled exactly like that.

### Body rules

- One JSON object, at most **2 MiB** (about enough for a selfie in base64). A larger body is refused with `400 REQUEST_TOO_LARGE`.
- Nothing may follow the JSON object.

## Response

`201 Created`, with `Location: /v1/processes/<id>`.

```json
{
  "id": "b063041c-7529-4e70-a2b4-c71c00f19801",
  "state": "PROCESS_STATE_FINISHED",
  "data": {
    "humanToken": { "value": "6d3d71…", "status": "validated" },
    "ndocs": {
      "distinctClientsCount": 2,
      "transactionsCount": 3,
      "firstSeen": "2026-09-08",
      "lastSeen": "2026-11-03",
      "credentials": [
        { "type": "DUI_TYPE_NG_BVN", "countryCode": "NGA", "distinctCredentialCount": 1 }
      ]
    }
  }
}
```

- `id` is Human Plugin's UUID. Each accepted request creates a new one.
- `state` is the local status. See [lifecycle](/human-plugin/api-reference/process-lifecycle).
- `humantoken` and `humantoken+revalidation` answer `PROCESS_STATE_CREATED` with a `token` and a `link`. These are returned **once** and are never stored: keep them. See [Human Token Journey](/human-plugin/integrations/human-token-journey) and the capture guide for your platform.
- `data.ndocs` is the person's intelligence. It can be absent: see [Intelligence](/human-plugin/get-started/intelligence).

<a id="result"></a>

### The result in `data`

A finished process has these fields in `data`. The Journey flows return them when you [read the process](/human-plugin/api-reference/get-process) after the capture.

| Field | When | Value |
| --- | --- | --- |
| `humanToken` | `api+humantoken`, `humantoken` | Who the person is: `value` is the Human Token, and `status` is `validated` when it was generated |
| `humanTokenMatch` | `humantoken+revalidation` | `"true"` when it is the same person as the Human Token in `references` |
| `liveness` | Flows that check liveness, such as the Journey flows | `value` is `live` when the capture came from a live person, or `not_live` when it did not; `status` is `validated` when the check ran |
| `ndocs` | When there is intelligence for the person | The person's [intelligence](/human-plugin/get-started/intelligence) |

A Journey result looks like this:

```json
{
  "id": "b063041c-7529-4e70-a2b4-c71c00f19801",
  "state": "PROCESS_STATE_FINISHED",
  "data": {
    "humanToken": { "value": "6d3d71…", "status": "validated" },
    "liveness": { "value": "live", "status": "validated" },
    "ndocs": { "distinctClientsCount": 1, "transactionsCount": 1, "firstSeen": "2026-11-05", "lastSeen": "2026-11-05", "credentials": [] }
  }
}
```

Check `liveness` before you trust the result: with `not_live`, treat the capture as failed and ask the person to try again. A process with `not_live` does not count toward anyone's intelligence.

## Errors

| Status | `message` | Meaning |
| --- | --- | --- |
| 400 | `INVALID_REQUEST`, `REQUEST_TOO_LARGE` | Not one JSON object, a reserved field, a wrong type, or a body over 2 MiB |
| 400 | `MISSING_<FIELD>`, `INVALID_<FIELD>` | A required field is missing or malformed, such as `MISSING_CLIENT_ID` |
| 401 | Empty (`""`) | Unico refused your access key: it is missing, wrong or no longer valid. The process is stored as failed. Check `HUMAN_API_ACCESS_KEY` |
| 409 | `CORRELATION_ID_ALREADY_USED` | Another process already has the `correlationId`. Nothing is stored or sent |
| 422 | `ATTRIBUTE_TYPE_NOT_SUPPORTED` | `credential.type` is not in the catalog. Nothing is stored or sent |
| 4xx | The flow's `message` | The flow refused the request, for example an invalid image. See [Flow refusals](#flow-refusals) |
| 502 | `UPSTREAM_UNAVAILABLE` | The process could not be confirmed in time |
| 503 | `DATABASE_UNAVAILABLE` | The database failed. Nothing was processed |
| 500 | `INTERNAL` | Unexpected error |

### Flow refusals

When a flow refuses the request, the create answers `4xx` with the reason in `message`, in the usual [envelope](/human-plugin/api-reference/errors-and-retries). The most common:

| `message` | Cause |
| --- | --- |
| `invalid flow` | `flow` is not a flow enabled for your account |
| `INVALID_IMAGE` (`400`) | `api+humantoken` without `imageBase64` |
| `INVALID_REQUEST` (`400`) | `imageBase64` is not an image in base64 |
| `IMAGE_TOO_LARGE` | The image is over 800 KB |
| `UNSUPPORTED_IMAGE_FORMAT` | The image is not PNG, JPEG or WebP |
| `PERMISSION_DENIED` (`403`) | Your access key is not enabled for this flow |

A refused process is stored as failed. Correct the request and create a new process with a new `correlationId`.

Read [Errors and retries](/human-plugin/api-reference/errors-and-retries) before adding retry logic: a new attempt needs a new `correlationId`.

## Next

[Get a process](/human-plugin/api-reference/get-process)