API Reference
All endpoints live under the base URL:
https://verify.nexlolabs.net/api
All requests and responses are JSON. Errors follow the shape described under Error codes.
Public endpoints
These endpoints are called by the widget or your backend. They do not require a session.
Create a challenge
POST /api/challenge/create
Creates a new challenge for the given site key.
{
"siteKey": "nlx_site_YOUR_SITE_KEY",
"type": "checkbox",
"difficulty": "normal",
"theme": "auto"
}
| Field | Type | Required | Description |
|---|---|---|---|
siteKey | string | yes | Public site key. |
type | string | no | checkbox, image_select, puzzle, math, code, shapes, vowels, invisible. Default checkbox. |
difficulty | string | no | easy, normal, hard. Falls back to the site default. |
theme | string | no | auto, light, dark. |
Response 200:
{
"success": true,
"challengeId": "clx9abc123",
"type": "image_select",
"expires": "2026-08-06T12:00:00.000Z",
"data": { "target": "A", "tiles": [...] }
}
Solve a challenge
POST /api/challenge/solve
Submits the answer and, on success, returns a signed token.
{
"challengeId": "clx9abc123",
"answer": "A,B,C",
"behavior": { "elapsedMs": 4321, "moveCount": 47, "mouseEntropy": 0.83 }
}
| Field | Type | Required | Description |
|---|---|---|---|
challengeId | string | yes | From create. |
answer | string | yes | The user's answer. |
behavior | object | no | Behavioral snapshot. |
Response 200:
{
"success": true,
"token": "v1.eyJ...sig",
"expires": "2026-08-06T12:02:00.000Z"
}
A challenge allows 3 attempts before it is marked failed.
Verify a token (server-side)
POST /api/challenge/verify
Exchange a token for the verification result. Use your site secret here — never in the browser.
{
"token": "v1.eyJ...",
"secret": "nlx_secret_YOUR_SITE_SECRET"
}
| Field | Type | Required | Description |
|---|---|---|---|
token | string | yes | Token from solve. |
secret | string | yes | Site secret (backend only). |
Response 200:
{
"success": true,
"score": 0.97,
"challengeId": "clx9abc123",
"hostname": "example.com",
"expires": "2026-08-06T12:02:00.000Z"
}
| Field | Description |
|---|---|
success | true when token is valid, unused, unexpired. |
score | Risk score 0 (bot) – 1 (human). |
challengeId | The challenge that produced the token. |
hostname | The origin the widget ran on (already whitelist-checked). |
expires | Token expiry timestamp. |
Health
GET /api/health
Returns { status: "ok", uptime, version, checks: { database, redis } }. A liveness-only variant GET /api/health/live always returns 200 once the process is up.
Authenticated endpoints (dashboard session)
All of the following require your dashboard session cookie. The browser automatically sends the CSRF token (X-CSRF-Token, obtained from GET /api/auth/csrf).
Auth
| Method | Path | Description |
|---|---|---|
| POST | /api/auth/register | Create an account (email, password). |
| POST | /api/auth/login | Login (email, password). Returns requiresTwoFactor if 2FA is on. |
| POST | /api/auth/2fa/verify | Complete login with a TOTP code. |
| POST | /api/auth/refresh | Refresh the session. |
| POST | /api/auth/logout | End the session. |
| GET | /api/auth/csrf | Get the CSRF token. |
| POST | /api/auth/password/reset-request | Request a password reset. |
| POST | /api/auth/password/reset | Set a new password with the reset token. |
| GET | /api/auth/me | Current user. |
| PATCH | /api/auth/profile | Update profile. |
| PATCH | /api/auth/password | Change password (requires current password). |
| GET | /api/auth/2fa/setup | Start 2FA setup; returns a TOTP secret + otpauth URL. |
| POST | /api/auth/2fa/enable | Enable 2FA (verify a code first). |
| POST | /api/auth/2fa/disable | Disable 2FA. |
Sites
| Method | Path | Description |
|---|---|---|
| POST | /api/site/create | Create a site (returns siteKey, siteSecret once). |
| POST | /api/site/delete | Delete a site. |
| GET | /api/site/list | List your sites. |
| POST | /api/site/domains | Register a domain for a site. |
| DELETE | /api/site/domains | Remove a domain. |
| POST | /api/site/secret/rotate | Rotate a site secret. |
API keys
API keys (nlx_live_...) can be used instead of a session for the read endpoints. Auth via Authorization: Bearer nlx_live_....
| Scope | Endpoints |
|---|---|
statistics:read | GET /api/statistics |
verifications:read | GET /api/verifications |
| Method | Path | Description |
|---|---|---|
| GET | /api/keys | List your API keys (never the full key). |
| POST | /api/keys | Create an API key ({ name, scopes }); the key value is shown once. |
| POST | /api/keys/delete | Revoke an API key. |
Statistics
| Method | Path | Description |
|---|---|---|
| GET | /api/statistics | Aggregated stats (?range=day|week|month|all, ?siteId=). |
| GET | /api/verifications | Recent verification history (?limit=, ?offset=). |
Admin endpoints
Prefix /api/admin. Requires the ADMIN role. Accessible surface:
GET /api/admin/overview— platform stats, top sites, recent verifications.- User management:
GET/PATCH/DELETE /api/admin/users. - Site management:
GET/DELETE /api/admin/sites,POST /api/admin/sites/:id/suspend. - Domain blocklist:
GET /api/admin/domains/blocked,POST /api/admin/domains/block,DELETE /api/admin/domains/block. - Key blocklist:
GET /api/admin/keys/blocked,POST /api/admin/keys/block,DELETE /api/admin/keys/block. - API-key management:
GET/DELETE /api/admin/api-keys. - Challenges:
GET /api/admin/challenges,POST /api/admin/challenges/:id/revoke. - CSV export:
GET /api/admin/export/verifications— downloads verification data as averifications.csvattachment (content-disposition). - Logs:
GET /api/admin/logs. - Maintenance:
POST /api/admin/challenges/expire-stale.
Rate limits
| Route | Limit | Window |
|---|---|---|
/api/auth/* | 10 requests/ip | 15 min |
/api/challenge/create | 30/ip/device | 60 s |
/api/challenge/solve | 60/ip | 60 s |
/api/challenge/verify | 120/ip/secret | 60 s |
Exceeding a limit returns 429 with code RATE_LIMITED.