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

# REST API

> Use the Verilock API directly with cURL or any HTTP client.

## Base URL

All API requests use the following base URL:

```
https://verilock.io/api/v1
```

## Authentication

Include your API key in the `Authorization` header as a Bearer token:

```bash theme={null}
Authorization: Bearer qi_live_...
Content-Type: application/json
```

<Warning>
  **Keep your API key secret.** Never expose it in client-side code, mobile apps, or public repositories. All API calls must originate from your backend server.
</Warning>

| Key Prefix | Environment                                     |
| ---------- | ----------------------------------------------- |
| `qi_live_` | Production -- real verifications, charges apply |
| `qi_test_` | Sandbox -- simulated results, free to use       |

***

## Create a Verification Session

```bash cURL theme={null}
curl -X POST https://verilock.io/api/v1/sessions \
  -H "Authorization: Bearer qi_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "profile_id": "01234567-89ab-cdef-0123-456789abcdef",
    "applicant_email": "john@example.com",
    "external_id": "user_12345",
    "redirect_url": "https://example.com/callback",
    "metadata": {
      "plan": "premium"
    }
  }'
```

**Response:**

```json 201 Created theme={null}
{
  "id": "ses_abc123",
  "status": "created",
  "session_token": "tok_abc123",
  "session_url": "https://verilock.io/verify/tok_abc123",
  "expires_at": "2026-03-18T12:00:00Z"
}
```

Use `session_token` to initialize the Web or Flutter SDK. Use `session_url` for redirect-based flows.

***

## Upload a Document (Multipart)

```bash cURL theme={null}
curl -X POST https://verilock.io/api/v1/sessions/ses_abc123/documents \
  -H "Authorization: Bearer qi_live_..." \
  -F "file=@/path/to/id_front.jpg" \
  -F "side=front" \
  -F "document_type=passport"
```

**Response:**

```json 200 OK theme={null}
{
  "id": "doc_xyz789",
  "session_id": "ses_abc123",
  "side": "front",
  "document_type": "passport",
  "status": "uploaded",
  "created_at": "2026-03-17T12:01:00Z"
}
```

| Field           | Required | Values                                                       |
| --------------- | -------- | ------------------------------------------------------------ |
| `file`          | Yes      | JPG, PNG, or PDF (max 10MB)                                  |
| `side`          | Yes      | `front` or `back`                                            |
| `document_type` | No       | `passport`, `id_card`, `driving_license`, `residence_permit` |

Upload a selfie with the same pattern:

```bash cURL theme={null}
curl -X POST https://verilock.io/api/v1/sessions/ses_abc123/selfie \
  -H "Authorization: Bearer qi_live_..." \
  -F "file=@/path/to/selfie.jpg"
```

***

## AML Screening

Screen a person against global sanctions lists, PEP databases, and watchlists.

```bash cURL theme={null}
curl -X POST https://verilock.io/api/v1/aml/screen \
  -H "Authorization: Bearer qi_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "John Doe",
    "date_of_birth": "1990-01-15",
    "nationality": "US"
  }'
```

**Response:**

```json 200 OK theme={null}
{
  "id": "aml_abc123",
  "status": "completed",
  "risk_level": "low",
  "matches": [],
  "screened_lists": ["ofac_sdn", "un_sanctions", "eu_sanctions", "pep_global"],
  "created_at": "2026-03-17T12:02:00Z"
}
```

Retrieve a previous screening:

```bash theme={null}
curl https://verilock.io/api/v1/aml/screen/aml_abc123 \
  -H "Authorization: Bearer qi_live_..."
```

***

## Transaction Screening

Screen transactions for AML/CFT risk indicators.

```bash cURL theme={null}
curl -X POST https://verilock.io/api/v1/transactions/screen \
  -H "Authorization: Bearer qi_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "transaction_ref": "TX-001",
    "transaction_type": "wire_transfer",
    "sender_name": "Alice Smith",
    "sender_country": "US",
    "receiver_name": "Bob Johnson",
    "receiver_country": "NG",
    "amount": 5000,
    "currency": "USD"
  }'
```

**Response:**

```json 200 OK theme={null}
{
  "id": "txs_abc123",
  "status": "completed",
  "risk_level": "medium",
  "risk_score": 62,
  "flags": ["cross_border", "high_risk_jurisdiction"],
  "transaction_ref": "TX-001",
  "created_at": "2026-03-17T12:03:00Z"
}
```

### Batch screening

Screen up to 100 transactions in a single request:

```bash cURL theme={null}
curl -X POST https://verilock.io/api/v1/transactions/screen/batch \
  -H "Authorization: Bearer qi_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "transactions": [
      { "transaction_ref": "TX-002", "sender_name": "Alice", "receiver_name": "Charlie", "amount": 200, "currency": "EUR" },
      { "transaction_ref": "TX-003", "sender_name": "Alice", "receiver_name": "Diana", "amount": 15000, "currency": "USD" }
    ]
  }'
```

***

## Wallet Screening

Screen crypto wallet addresses against known illicit activity databases.

```bash cURL theme={null}
curl -X POST https://verilock.io/api/v1/wallet/screen \
  -H "Authorization: Bearer qi_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD08",
    "network": "ethereum"
  }'
```

**Response:**

```json 200 OK theme={null}
{
  "id": "wlt_abc123",
  "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD08",
  "network": "ethereum",
  "risk_level": "low",
  "risk_score": 12,
  "flags": [],
  "labels": [],
  "created_at": "2026-03-17T12:04:00Z"
}
```

Supported networks: `ethereum`, `bitcoin`, `polygon`, `bsc`, `solana`, `tron`, `arbitrum`, `optimism`.

***

## Error Responses

All errors follow a consistent JSON structure:

```json theme={null}
{
  "error": "error_code",
  "message": "Human-readable description of the problem.",
  "details": null
}
```

| Status | Error Code             | Description                                                            |
| ------ | ---------------------- | ---------------------------------------------------------------------- |
| `401`  | `authentication_error` | Missing or invalid API key                                             |
| `402`  | `insufficient_credits` | Account has no remaining credits for this operation                    |
| `404`  | `not_found`            | The requested resource does not exist                                  |
| `409`  | `session_completed`    | Session already has a final decision                                   |
| `410`  | `session_expired`      | Session token has expired                                              |
| `422`  | `validation_error`     | Request body failed validation. Check `details` for field-level errors |
| `429`  | `rate_limited`         | Too many requests. Retry after the `Retry-After` header value          |
| `500`  | `server_error`         | Internal error on our end. Retry with backoff                          |

### Validation error example

```json 422 Unprocessable Entity theme={null}
{
  "error": "validation_error",
  "message": "The given data was invalid.",
  "details": {
    "name": ["The name field is required."],
    "amount": ["The amount must be a number."]
  }
}
```

***

## Rate Limiting

Every response includes rate-limit headers:

| Header                  | Description                                               |
| ----------------------- | --------------------------------------------------------- |
| `X-RateLimit-Limit`     | Maximum requests per minute                               |
| `X-RateLimit-Remaining` | Requests remaining in the current window                  |
| `Retry-After`           | Seconds until the window resets (only on `429` responses) |

<Info>
  Default rate limit is **120 requests/minute** per API key. Contact us if you need higher limits for production workloads.
</Info>

When you receive a `429`, wait for the number of seconds specified in `Retry-After` before retrying. Our SDKs handle this automatically with exponential backoff.

***

## Pagination

List endpoints return paginated results using a `data` / `meta` pattern:

```json theme={null}
{
  "data": [
    { "id": "ses_abc123", "status": "completed", "..." : "..." },
    { "id": "ses_def456", "status": "pending", "..." : "..." }
  ],
  "meta": {
    "current_page": 1,
    "per_page": 20,
    "total": 142,
    "last_page": 8
  }
}
```

| Parameter  | Default | Description              |
| ---------- | ------- | ------------------------ |
| `page`     | `1`     | Page number              |
| `per_page` | `20`    | Items per page (max 100) |

```bash theme={null}
curl "https://verilock.io/api/v1/sessions?page=2&per_page=50&status=completed" \
  -H "Authorization: Bearer qi_live_..."
```

***

## Webhook Verification

When you configure webhooks, Verilock signs every payload with HMAC-SHA256 so you can verify authenticity.

Each webhook request includes a `X-Verilock-Signature` header:

```
X-Verilock-Signature: sha256=a1b2c3d4e5f6...
```

### Verify the signature

<CodeGroup>
  ```javascript Node.js theme={null}
  const crypto = require('crypto');

  function verifyWebhook(payload, signature, secret) {
    const expected = 'sha256=' + crypto
      .createHmac('sha256', secret)
      .update(payload, 'utf8')
      .digest('hex');

    return crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expected),
    );
  }

  // In your webhook handler:
  const isValid = verifyWebhook(rawBody, req.headers['x-verilock-signature'], webhookSecret);
  ```

  ```python Python theme={null}
  import hmac
  import hashlib

  def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
      expected = 'sha256=' + hmac.new(
          secret.encode(), payload, hashlib.sha256
      ).hexdigest()
      return hmac.compare_digest(signature, expected)
  ```

  ```php PHP theme={null}
  function verifyWebhook(string $payload, string $signature, string $secret): bool {
      $expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
      return hash_equals($expected, $signature);
  }
  ```
</CodeGroup>

<Warning>
  Always use constant-time comparison (`timingSafeEqual`, `compare_digest`, `hash_equals`) to prevent timing attacks.
</Warning>

### Webhook payload structure

```json theme={null}
{
  "event": "session.completed",
  "data": {
    "id": "ses_abc123",
    "status": "completed",
    "decision": "approved",
    "risk_score": 15,
    "completed_at": "2026-03-17T12:05:00Z"
  },
  "created_at": "2026-03-17T12:05:01Z"
}
```

Common event types: `session.completed`, `session.expired`, `aml.completed`, `transaction.screened`, `wallet.screened`.
