
# Get verification result

```
GET /api/kyc/verifications/:id
```

Returns the **full** result of a verification, including the extracted biodata, ID
number, and facial-match score. This is the backend counterpart to the minimal
[status endpoint](https://trust.myaza.co/documentation/api-verification-status/markdown).

**Authentication:** `Authorization: Bearer sk_…`, a **secret key only**. Calling
this with a publishable (`pk_`) key returns `403 secret_key_required`. The
verification is scoped to the key's organisation **and** environment (a sandbox
secret key cannot read a production verification).

> **Never call this from client code.** Identity data must only be fetched from
> your backend with a secret key.

## Request

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

## Responses

Every response includes `verificationId`, `status`, `country`, `idType`, `createdAt`,
and `workflow`, which [workflow](https://trust.myaza.co/documentation/workflows/markdown) was responsible for the submission.

### `approved`

```json
{
  "verificationId": "ver_01j9...",
  "status": "approved",
  "checkStatus": "verified",
  "externalUserId": "user_42",
  "metadata": { "loanId": "loan_20191" },
  "country": "NG",
  "idType": "bvn",
  "createdAt": "2026-04-27T12:00:00.000Z",
  "completedAt": "2026-04-27T12:00:05.000Z",
  "workflow": {
    "id": "wf_AbC123dEf456",
    "name": "Standard KYC",
    "version": 3
  },
  "result": {
    "idNumber": "12345678901",
    "idNumberMasked": "1234•••901",
    "firstName": "JOHN",
    "lastName": "DOE",
    "middleName": "A",
    "dateOfBirth": "1990-01-01",
    "gender": "Male",
    "dataMatch": true,
    "facialMatch": { "match": true, "confidence": 85 },
    "facialMatchSource": "gov_record"
  }
}
```

`facialMatch` is `null` when no liveness/selfie check was part of the flow. `facialMatchSource` says which photo the selfie was compared with: `gov_record`, `chip`, or `document` when the workflow [allows the photo printed on the document](https://trust.myaza.co/documentation/workflows/markdown#matching-the-selfie-to-the-photo-on-a-document) and no stronger photo existed.

`workflow` is `null` when the SDK was configured with plain props instead of a
workflow. Its `version` is the published version that **actually ran**: publishing
overwrites a workflow's live configuration in place, so `id` alone stops describing
what happened after your next publish. Inspect the exact configuration a version
used under **Workflows → Version history**.

### `failed` / `error` / `not_found`

Carries the human-readable `reason` and stable `reasonCode` (same values as the
[status endpoint](https://trust.myaza.co/documentation/api-verification-status/markdown) and [webhooks](https://trust.myaza.co/documentation/webhooks/markdown)).

```json
{
  "verificationId": "ver_01j9...",
  "status": "declined",
  "checkStatus": "failed",
  "externalUserId": "user_42",
  "metadata": { "loanId": "loan_20191" },
  "country": "NG",
  "idType": "bvn",
  "createdAt": "2026-04-27T12:00:00.000Z",
  "completedAt": "2026-04-27T12:00:05.000Z",
  "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"
}
```

### After a decision

When the verification has been decided, by a reviewer in your dashboard, by your backend through [`POST /verifications/:id/review`](https://trust.myaza.co/documentation/api-review-verification/markdown) or by your workflow, `review` holds the current decision a person made and `statusHistory` lists every change, newest first:

```json
{
  "verificationId": "ver_01j9...",
  "status": "declined",
  "checkStatus": "verified",
  "review": {
    "decision": "DECLINED",
    "reviewedAt": "2026-04-28T09:14:00.000Z",
    "reviewedById": "api_key:ak_01j9..."
  },
  "statusHistory": [
    {
      "status": "declined",
      "previousStatus": "in_review",
      "source": "api",
      "changedBy": { "type": "api_key", "id": "ak_01j9..." },
      "changedAt": "2026-04-28T09:14:00.000Z",
      "decision": "DECLINED",
      "outcome": null,
      "runId": null,
      "resubmitSteps": []
    },
    {
      "status": "in_review",
      "previousStatus": "processing",
      "source": "workflow",
      "changedBy": { "type": "workflow", "id": "wf_AbC123dEf456" },
      "changedAt": "2026-04-27T12:00:07.000Z",
      "decision": null,
      "outcome": "review",
      "runId": "wfr_01j9...",
      "resubmitSteps": []
    }
  ]
}
```

Each entry has the same fields as one [`verification.status_updated`](https://trust.myaza.co/documentation/webhooks/markdown#status-changes) webhook, so what you store from the webhook and what you read here agree. The reviewer's internal note is never included.

### Attempts

A verification you [send back or verify again](https://trust.myaza.co/documentation/api-verify-again/markdown) keeps its id. Once the applicant resubmits, the result describes their latest attempt, `attempt` counts up, and each earlier attempt is listed under `attempts`, oldest first:

```json
{
  "verificationId": "ver_01j9...",
  "status": "approved",
  "attempt": 2,
  "submittedAt": "2026-04-29T10:02:00.000Z",
  "completedAt": "2026-04-29T10:02:06.000Z",
  "attempts": [
    {
      "attempt": 1,
      "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.",
      "reasonCode": "selfie_mismatch",
      "assuranceLevel": null,
      "decision": "RESUBMISSION",
      "resubmitSteps": ["liveness"],
      "submittedAt": "2026-04-27T12:00:00.000Z",
      "completedAt": "2026-04-27T12:00:05.000Z",
      "resubmittedAt": "2026-04-29T10:02:00.000Z"
    }
  ]
}
```

An earlier attempt carries what it concluded and why, never its biodata or media. The current result is the one to act on, and `statusHistory` still lists every decision across all attempts.

## Result fields

| Field | Type | Description |
|---|---|---|
| `externalUserId` | string \| null | Your reference for the subject, exactly as supplied: the top-level `externalUserId` on the submission (or the legacy `userId` / `metadata.userId`), or the session it was minted with. |
| `metadata` | object \| null | Your customer metadata, echoed unchanged. |
| `workflow` | object \| null | `{ id, name, version }`: the workflow that drove the submission, its current name, and the published version that ran. `null` for prop-configured SDK mounts. |
| `review` | object \| null | The current decision a person or your backend made: `{ decision, reviewedAt, reviewedById }`. `reviewedById` is a user id, or `api_key:<id>` for a decision made through the API. `null` when nobody has decided. |
| `statusHistory` | array | Every change to `status`, newest first, whoever made it. Empty until something decides the verification. See [After a decision](#after-a-decision). |
| `attempt` | number | Which attempt this result describes. `1` until the verification is sent back and the applicant resubmits. |
| `submittedAt` | string | When this attempt was submitted. `completedAt` is timed from it. |
| `attempts` | array | The earlier attempts, oldest first. Empty until the verification is redone. See [Attempts](#attempts). |
| `result.idNumber` | string \| null | The plaintext ID number. |
| `result.idNumberMasked` | string | The ID number, masked (e.g. `1234•••901`). |
| `result.firstName` | string \| null | Given name from the source record. |
| `result.lastName` | string \| null | Family name from the source record. |
| `result.middleName` | string \| null | Middle name, if available. |
| `result.dateOfBirth` | string \| null | Date of birth (`YYYY-MM-DD`). |
| `result.gender` | string \| null | Gender from the source record. |
| `result.dataMatch` | boolean \| null | Whether submitted biodata matched the source record. |
| `result.facialMatch` | object \| null | `{ match: boolean, confidence: number }`, or `null` if no facial check ran. |
| `result.facialMatchSource` | string \| null | Which photo the selfie was compared with: `gov_record`, `chip` or `document` (the photo printed on the ID, weaker evidence). `null` if no facial check ran. |
| `result.facialMatchSkipped` | string \| null | Why no facial check ran when one was expected: `no_face_on_document` when the photo printed on the document was the only photo to compare with and no face could be read on it (your workflow kept the verification for review). `null` otherwise. |

## Captured media

The images and videos captured during the flow are fetched per-kind from
`GET /api/kyc/verifications/:id/media/:kind` (`selfie`, `document-front`,
`document-back`, `liveness-video`, `document-front-video`, `document-back-video`,
and `document-portrait`, the portrait cut from the document when the selfie was matched to it),
also **secret key only**. These same URLs are delivered in the `media` object of
verification [webhooks](https://trust.myaza.co/documentation/webhooks/markdown).

## Errors

| Status | Body | Cause |
|---|---|---|
| `403` | `{ "error": "secret_key_required" }` | A publishable (`pk_`) key was used. Use a secret key. |
| `404` | `{ "error": "Verification not found" }` | Unknown ID, wrong environment, or another organisation's verification. |
| `401` | `{ "error": "Invalid API key" }` | Auth failed. |
