
# Get verification status

```
GET /api/kyc/status/:verificationId
```

Returns the **minimal** lifecycle state of a verification. `status` is the merged
top-line (`not_started`, `in_progress`, `processing`, `in_review`,
`awaiting_resubmission`, `approved`, `declined`, `abandoned`, `expired`, `error`)
and `checkStatus` is what the checks themselves found (`pending`, `verified`,
`failed`, `not_found`, `error`). They differ when a person overrode the automated
result. See [Status and checkStatus](https://trust.myaza.co/documentation/webhooks/markdown#status-and-checkstatus).

This endpoint is **publishable-safe**: it can be
called with the `pk_` key that ships in your SDK, so it deliberately returns **no
PII, no match scores, and no result data**. To read the full result + extracted
biodata, use [Get verification result](https://trust.myaza.co/documentation/api-verification-result/markdown) with a **secret**
(`sk_`) key from your backend.

[Webhooks](https://trust.myaza.co/documentation/webhooks/markdown) are the recommended way to learn about completion without polling.

**Authentication:** `Authorization: Bearer pk_…` or `sk_…` (required). A verification is only visible to the organisation that created it.

## Request

```bash
curl "https://trust.myaza.app/api/kyc/status/ver_01j9..." \
  -H "Authorization: Bearer $MYAZA_PUBLISHABLE_KEY"
```

## Responses

Every response includes `verificationId`, `status`, `createdAt`, your
`externalUserId` (so a publishable key can tell which user an id belongs to;
your `metadata` stays on the [secret-key result](https://trust.myaza.co/documentation/api-verification-result/markdown)), and
the workflow attribution pair `workflowId` / `workflowVersion` (both `null` when
the SDK was configured with plain props instead of a [workflow](https://trust.myaza.co/documentation/workflows/markdown)), and
`attempt` with its `submittedAt`. A verification you
[send back or verify again](https://trust.myaza.co/documentation/api-verify-again/markdown) keeps its id: when the applicant
resubmits, `attempt` counts up and `submittedAt` and `completedAt` describe their
latest attempt.

### Why a verification is still `processing`

`processing` means "no answer yet", and it covers both the eight seconds we
spend reading a document and the eight days an [address presence
watch](https://trust.myaza.co/documentation/address-intelligence/markdown#presence-verification) runs. Those read identically
on a row, so **`waitingOn`** says which:

| `waitingOn` | The decision is held on |
|---|---|
| `null` | Nothing. The checks are simply still running. |
| `"screening"` | A sanctions, PEP or adverse-media screen. |
| `"key_people"` | The directors and owners on a business application finishing their own verifications. |
| `"address_presence"` | A presence watch confirming the person lives at the address. Days, not seconds. |

It is only ever set while a decision run is genuinely parked, and it clears the
moment the run continues. Values are add-only: treat one you do not recognise
as a check you have not learned about yet, rather than mapping it to a known
one.

### `processing` / `approved`

```json
{
  "verificationId": "ver_01j9...",
  "status": "approved",
  "checkStatus": "verified",
  "externalUserId": "user_42",
  "workflowId": "wf_AbC123dEf456",
  "workflowVersion": 3,
  "attempt": 1,
  "createdAt": "2026-04-27T12:00:00.000Z",
  "submittedAt": "2026-04-27T12:00:00.000Z",
  "completedAt": "2026-04-27T12:00:05.000Z"
}
```

> `workflowVersion` is the published version that **actually ran**. Publishing a
> workflow overwrites its live configuration in place, so keep the pair if you need
> to explain later exactly which rules a submission went through. The workflow's
> human-readable name is on the [full result](https://trust.myaza.co/documentation/api-verification-result/markdown); this
> endpoint stays minimal.

> An `approved` status confirms the person was accepted, but carries no identity
> data. Fetch the [full result](https://trust.myaza.co/documentation/api-verification-result/markdown) with a secret key to read
> the biodata and facial-match score.

> `approved` with `checkStatus: "failed"` is not a contradiction. It means a person
> reviewed the verification and accepted it despite what the checks found, and
> `reason`/`reasonCode` below say what they accepted it despite. Branch on `status`;
> store `checkStatus` alongside it.

### `declined` / `error`

`declined` means the person was not accepted, either because the checks did not pass or because a reviewer declined them. `error` means a system problem occurred on our side and you were not charged. Both include a human-readable `reason` and a stable `reasonCode` you can branch on. See [failure reason codes](https://trust.myaza.co/documentation/verifications/markdown#failure-reason-codes).

A `reason` is present whenever the checks did not pass, even on a verification a reviewer later approved.

```json
{
  "verificationId": "ver_01j9...",
  "status": "declined",
  "checkStatus": "failed",
  "reason": "The selfie does not match the photo on the government record for this ID. Take it again in good light, looking straight at the camera. If it still does not match, check that the ID number entered is the right one.",
  "reasonCode": "selfie_mismatch",
  "createdAt": "2026-04-27T12:00:00.000Z",
  "completedAt": "2026-04-27T12:00:05.000Z"
}
```

### `declined` with `identity_not_found`

An ID number the government database does not hold is a decline like any other; the `reasonCode` is what tells it apart from a failed face match. There is no separate `not_found` status.

```json
{
  "verificationId": "ver_01j9...",
  "status": "declined",
  "checkStatus": "not_found",
  "reason": "The BVN number was not found in the government database.",
  "reasonCode": "identity_not_found",
  "createdAt": "2026-04-27T12:00:00.000Z",
  "completedAt": "2026-04-27T12:00:05.000Z"
}
```

## Errors

| Status | Body | Cause |
|---|---|---|
| `404` | `{ "error": "Verification not found" }` | Unknown ID, or it belongs to another organisation. |
| `401` | `{ "error": "Invalid API key" }` | Auth failed. |

## Polling guidance

If you must poll, do so with backoff (e.g. every 2–3 seconds, widening over time) and stop once `status` is no longer `pending`. Prefer [webhooks](https://trust.myaza.co/documentation/webhooks/markdown) for production. Once complete, fetch the [full result](https://trust.myaza.co/documentation/api-verification-result/markdown) from your backend.
