API keys

Every VerifyAI request authenticates with a vai_… API key in the X-API-Key header. This page covers managing those keys: what a key looks like, how to create and revoke them, and the one rule that trips people up — the full key is shown exactly once.

For how keys are used on a request, see Authentication.

The key object

A key is scoped to a single customer account. The server stores only a SHA-256 hash of the key — never the plaintext — so a key that's lost can only be replaced, never recovered.

json
{
  "id": "key_4f9a2c",
  "name": "Production backend",
  "key_prefix": "vai_live_8x9k…",
  "scope": "full",
  "active": true,
  "customer_id": "cus_forest1",
  "rate_limit_per_minute": 100,
  "rate_limit_per_hour": 6000,
  "last_used_at": "2026-06-01T11:58:02Z",
  "created_at": "2026-05-12T14:30:00Z",
  "expires_at": null
}

Fields

| Field | Type | Description | | ----------------------- | -------- | ------------------------------------------------------------------------------------ | | id | string | Stable key identifier, prefix key_. Use this to revoke. Not the secret. | | name | string | Human label you set at creation (e.g. Production backend, iOS app). | | key_prefix | string | The first few characters of the key, for identifying it in lists. Never the full key.| | scope | string | full or verify_only. See Scopes. | | active | boolean | false once revoked. Inactive keys return 401 Invalid API key. | | customer_id | string? | The customer this key acts on behalf of. See Customers.| | rate_limit_per_minute | number | Per-key request cap per minute. Defaults to 100. | | rate_limit_per_hour | number | Per-key request cap per hour. Defaults to 6000. | | last_used_at | string? | ISO 8601 timestamp of the most recent authenticated request. null if never used. | | created_at | ISO 8601 | When the key was created. | | expires_at | string? | Optional expiry. null means the key never expires until revoked. |

The full key is shown once, at creation

Create returns the complete vai_… secret a single time. We store only a hash, so we cannot show it again. Copy it into your secret manager immediately. If you lose it, revoke the key and create a new one.

Managing keys in the dashboard

The fastest path is the dashboard, which is the supported way to author keys today:

  1. Open Dashboard → Settings → API keys.
  2. Click Create key, give it a name, and pick a scope.
  3. Copy the full vai_… value from the one-time reveal into your secret manager.
  4. To retire a key, click Revoke next to it. Revocation takes effect immediately — in-flight requests using that key start returning 401.

Sandbox keys are issued automatically when you sign up; their usage is free and never bills your account.

REST contract

The endpoints below expose the same operations programmatically. They are account-scoped — a key can only manage keys belonging to its own customer — and they authenticate exactly like every other VerifyAI request.

Availability

Programmatic key management is rolling out behind the same auth as the rest of the API. If a call below returns 404, manage keys from Dashboard → Settings → API keys in the meantime — the request and response shapes match what ships.

Authentication

| Header | Required | Value | | ----------- | -------- | ------------------------------------------------------ | | X-API-Key | Yes | A vai_… key with full scope. verify_only keys cannot manage keys. |

List keys

text
GET /api/v1/api-keys

Returns every key on your account, newest first. The plaintext secret is never included — only id, key_prefix, and metadata.

curl https://verify.switchlabs.dev/api/v1/api-keys \
-H "X-API-Key: vai_your_api_key"
json
{
  "data": [
    {
      "id": "key_4f9a2c",
      "name": "Production backend",
      "key_prefix": "vai_live_8x9k…",
      "scope": "full",
      "active": true,
      "rate_limit_per_minute": 100,
      "rate_limit_per_hour": 6000,
      "last_used_at": "2026-06-01T11:58:02Z",
      "created_at": "2026-05-12T14:30:00Z",
      "expires_at": null
    }
  ]
}

Create a key

text
POST /api/v1/api-keys

Creates a new key on your account. The response is the only place the full secret appears.

Body parameters

| Parameter | Type | Required | Description | | ------------ | ------ | -------- | --------------------------------------------------------------------------- | | name | string | Yes | A label to identify the key later (e.g. iOS app). | | scope | string | No | full (default) or verify_only. See Scopes. | | expires_at | string | No | ISO 8601 expiry. Omit for a non-expiring key. |

curl -X POST https://verify.switchlabs.dev/api/v1/api-keys \
-H "X-API-Key: vai_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "name": "iOS app", "scope": "verify_only" }'
json
{
  "id": "key_7b1d8e",
  "name": "iOS app",
  "key": "vai_live_3q5w8r1t6y2u4i9o0a3s8x9k2m4n7p3q",
  "key_prefix": "vai_live_3q5w…",
  "scope": "verify_only",
  "active": true,
  "rate_limit_per_minute": 100,
  "rate_limit_per_hour": 6000,
  "created_at": "2026-06-01T12:00:00Z",
  "expires_at": null
}

The key field is present only on this create response. Every other endpoint returns key_prefix instead.

Revoke a key

text
DELETE /api/v1/api-keys/:id

Revokes a key by its key_ id. Revocation is immediate and permanent — the key flips to active: false and all future requests with it return 401. There is no un-revoke; create a fresh key instead.

curl -X DELETE https://verify.switchlabs.dev/api/v1/api-keys/key_7b1d8e \
-H "X-API-Key: vai_your_api_key"
json
{ "id": "key_7b1d8e", "active": false, "revoked_at": "2026-06-01T12:05:00Z" }

Scopes

A key's scope limits what it can do. Use the narrowest scope that works:

| Scope | Can do | | ------------- | ------------------------------------------------------------------- | | full | Everything — verify, read history, and manage keys/customers/webhooks. | | verify_only | Submit photos to /v1/verify only. Cannot list, manage keys, or delete. |

Use verify-only for mobile binaries

A key embedded in a mobile app is semi-public — anyone who unpacks the binary can read it. Scope embedded keys to verify_only so a leaked key can submit photos but cannot enumerate your verifications or touch your other keys. Rotate after each major app release.

Rate limits

Each key carries its own rate_limit_per_minute (default 100) and rate_limit_per_hour (default 6000). When a key exceeds either window, requests return 429 with a Retry-After header indicating how many seconds to wait. Limits are enforced per key, so splitting traffic across keys gives each its own budget. Contact support to raise the defaults on a key.

Errors

| Status | Reason | | ------ | ------------------------------------------------------------------- | | 400 | Missing name, or an invalid scope / expires_at value. | | 401 | Missing or invalid API key. | | 403 | The authenticating key is verify_only and cannot manage keys. | | 404 | No key with that id on this account. | | 429 | Rate limit — check the Retry-After header. | | 503 | Authentication service unavailable. Retry. |

What's next

Get in Touch

Questions about pricing, integrations, or custom deployments? We'd love to hear from you.