> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cr3dentials.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Verifications

> Prepare a verification on your server, and fetch the verifications your app started with the mobile SDK.

A verification is started in your app, by the [mobile SDK](https://docs.cr3dentials.xyz/sdk/overview.md)'s `Zktls.verify()`, and its results reach your server by [webhook](https://docs.cr3dentials.xyz/essentials/webhooks.md#verification-events). Your server doesn't need to call anything to start one. Two endpoints help when it wants to take part:

| Endpoint | Use it to |
| - | - |
| `POST /partner/verification-tokens` | Decide on your server which platforms are verified, and with which reference. The app then calls `Zktls.verify(token:)` |
| `GET /partner/verifications/{id}` | Read a verification and its results at any time, for example to reconcile with your webhooks |

Both authenticate with your Partner API key in the `x-api-key` header, like every Partner API call. Keep the key on your server. See [Authentication](https://docs.cr3dentials.xyz/authentication.md).

> [!NOTE]
>
> These two endpoints answer with the object itself, not inside the `{ "status", "data" }` envelope of the session endpoints.

## Key environments

Each Partner API key has an environment, Development or Production, which you choose when you create it in the [Partner Portal](https://docs.cr3dentials.xyz/guides/partner-portal.md). The environment decides which verifications the key works with:

| | Development API key | Production API key |
| - | - | - |
| Creates tokens for | Your test app key, `zk_test_…` | Your live app key, `zk_live_…` |
| Fetches | Test verifications, `"test": true` | Live verifications, `"test": false` |

A token request whose app key doesn't match the API key's environment is refused with `403`. A fetch of a verification from the other environment answers `404`.

## Create a verification token

```
POST /partner/verification-tokens
```

```bash theme={null}
curl -X POST https://app.cr3dentials.xyz/v1/partner/verification-tokens \
  -H "x-api-key: $CR3D_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "appKey": "zk_test_…",
    "platforms": ["zktls-demo"],
    "reference": "order_8f3k2"
  }'
```

**appKey** `string` *required*

The app key of the app that will run the verification: your test key or your live key. The app must be your team's, and the key must match your API key's [environment](#key-environments).

**platforms** `string[]` *required*

The platform ids to verify, in the order the user should see them: 1 to 10 ids, each listed once.

**reference** `string` *required*

Your reference. It comes back unchanged in every webhook for the verification. 1 to 128 characters of `A-Z`, `a-z`, `0-9`, `.`, `_` and `-`, with no personal data. See [The reference](https://docs.cr3dentials.xyz/sdk/overview.md#the-reference).

The response, `201 Created`:

```json theme={null}
{
  "token": "vt_3q2-7wGkTq0bX9aJ1mZt5yQeR8uVnC4dLhP6sFoWiYk",
  "expiresAt": "2026-10-23T12:15:00.000Z"
}
```

**token** `string`

`vt_` followed by 43 base64url characters. Single use.

**expiresAt** `string`

When the token expires, 15 minutes after it was made, in ISO 8601.

Pass the token to your app over your own authenticated API, and have the app start the verification straight away:

```swift Swift theme={null}
let outcome = try await Zktls.verify(token: token)
```

```kotlin Kotlin theme={null}
val outcome = Zktls.verify(activity, token)
```

The verification then runs as if the app had passed the platforms and the reference itself. A token that is unknown, already used or expired is refused in the app with `token_invalid`. The app's other refusals, such as `platform_unavailable`, are in [Errors](https://docs.cr3dentials.xyz/sdk/errors.md).

| Status | Code | When |
| - | - | - |
| `400` | `request_invalid`, `platforms_invalid` or `reference_invalid` | The body is invalid: a field is missing, of the wrong type, or unknown; or the platforms or the reference break the rules in [Errors](https://docs.cr3dentials.xyz/sdk/errors.md) |
| `401` | | The API key is missing or invalid |
| `403` | `environment_mismatch` | The app key doesn't match your API key's environment: a live key with a Development API key, or a test key with a Production API key |
| `404` | `not_found` | No app of your team has this app key |
| `429` | | Too many requests. See [Rate limits](https://docs.cr3dentials.xyz/essentials/rate-limits.md) |
| `503` | `unavailable` | We couldn't make the token just now. Try again |

A refusal with a code answers `{ "code", "message" }`.

## Get a verification

```
GET /partner/verifications/{id}
```

```bash theme={null}
curl https://app.cr3dentials.xyz/v1/partner/verifications/7d0f3c52-9a41-4b8e-b6f2-2f1d9c6e0a17 \
  -H "x-api-key: $CR3D_API_KEY"
```

**id** `string` *required*

The `verificationId`, from a webhook or from the app's `VerifyOutcome`.

The response, `200 OK`:

```json theme={null}
{
  "verificationId": "7d0f3c52-9a41-4b8e-b6f2-2f1d9c6e0a17",
  "test": true,
  "reference": "order_8f3k2",
  "createdAt": "2026-10-23T12:00:00.000Z",
  "finishedAt": "2026-10-23T12:00:09.000Z",
  "platforms": [
    {
      "platform": "zktls-demo",
      "runId": "c3a5e0d4-1f7b-4e29-8d6a-5b0c2e9f4a81",
      "status": "done",
      "complete": true,
      "result": {
        "evidence": "attested",
        "transfer": { "amount_minor": "000000012500", "currency": "SGD", "status": "completed", "reference": "REF-7C21A94E0B3F" }
      },
      "claim": "AQAAAG…",
      "error": null,
      "finishedAt": "2026-10-23T12:00:08.000Z"
    }
  ]
}
```

**verificationId** `string`

The verification.

**test** `boolean`

`true` for a verification started with a test key.

**reference** `string`

Your reference.

**createdAt** `string`

When the app started the verification, in ISO 8601.

**finishedAt** `string | null`

When every platform had ended and `verification.finished` was queued for your webhooks. `null` until then.

**platforms** `object[]`

One entry per platform, in the order the app passed them.

**platform entry**

**platform** `string`

The platform id.

**runId** `string`

This platform's run, as in its `platform.finished` webhook.

**status** `string`

`pending` until the run has ended and its `platform.finished` webhook is on its way: the user is on it, or its result is on the way or still being checked. Then `done` or `failed`. A platform the user never started reads `not_started` once the app, or the 2-hour expiry, ends it.

**complete** `boolean | null`

`true` when the platform's read was complete, `false` when a size cap ended it first. `null` unless `status` is `done`.

**result** `object | null`

The platform's attested figures, as in `platform.finished`. `null` unless `status` is `done`.

**claim** `string | null`

The attester's signed claim, in base64, or `null`.

**error** `object | null`

When the run failed, `{"code": "…", "message": "…"}`.

**finishedAt** `string | null`

When the run ended, in ISO 8601.

| Status | Code | When |
| - | - | - |
| `401` | | The API key is missing or invalid |
| `404` | `not_found` | No such verification for your team, or it doesn't match your API key's environment |
| `429` | | Too many requests. See [Rate limits](https://docs.cr3dentials.xyz/essentials/rate-limits.md) |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.