TL;DR: Every Public API request uses Authorization: Bearer clapi_... (or a Clerk JWT). On top of the token, each endpoint requires an OAuth scope (e.g. enrich:write); a missing scope returns 403 insufficient_scope. Generate keys in the portal or via /api/v1/api-keys. Up to 10 active keys per user, and key creation requires the API_ACCESS feature.
Authentication
The Cleanlist Public API accepts two credential types:
| Credential | Header | Used by |
|---|---|---|
API key (clapi_...) | Authorization: Bearer clapi_... | servers, scripts, agents, MCP |
| Clerk session JWT | Authorization: Bearer <jwt> | the portal, and the /api/v1/api-keys management endpoints |
Authorization: Bearer clapi_your_api_key_hereOAuth scopes
Authentication answers "who are you?"; scopes answer "what may you do?". Every /api/v1/public/* endpoint asserts a single scope before running. If the authenticating credential doesn't carry that scope, the request fails with 403 and code insufficient_scope.
| Scope | Grants |
|---|---|
people:read | People search + people filters |
companies:read | Company search, company filters, similar companies |
lists:read | Read lead lists and their leads |
lists:write | Create lists, add/remove leads, CSV import |
enrich:read | Poll enrichment status |
enrich:write | Run person/company/by-task/bulk enrichment |
credits:read | Credit balance + cost estimates |
export:read | CSV signed URLs + JSON export |
sync:write | Sync to CRM / sequencer |
smart_agents:read | List smart-agent runs + results |
smart_agents:write | Run a smart agent |
admin:api_keys | List keys + usage reports via the public API |
A 403 insufficient_scope error includes the required and granted scopes in details:
{
"error": {
"code": "insufficient_scope",
"problem": "This call requires the 'enrich:write' scope, which is not granted to the authenticating credential.",
"fix": "Re-issue the API key with the required scope, or sign in with an identity that has it. See /api/v1/public/whoami.",
"retryable": false,
"docs_url": "https://docs.cleanlist.ai/errors/insufficient-scope",
"request_id": "req_...",
"details": { "required_scope": "enrich:write", "granted_scopes": ["people:read", "lists:read"] }
}
}To see what your credential can do, call GET /whoami — it returns the granted scopes list.
Key format
| Property | Value |
|---|---|
| Prefix | clapi_ |
| Scope of ownership | User (and the user's organization) |
| Secret body | 48 random URL-safe bytes |
| Visibility | Shown once at creation; afterward only the prefix and last 4 are displayed |
| Limit | 10 active keys per user (MAX_API_KEYS_PER_USER) |
The full key is only returned once — at creation. Store it in a secret manager, env var, or vault. If you lose it, revoke it and generate a new one.
Generating a key
The fastest path is the portal: Settings → API Keys → Generate Key, then copy the full key immediately.
You can also create keys programmatically with your Clerk session token. These management endpoints live at /api/v1/api-keys (not /public) and authenticate with the Clerk JWT, not a clapi_ key.
Key creation is gated by the API_ACCESS feature. If your plan doesn't include it, POST /api/v1/api-keys returns a 403 feature-gate error. Listing keys is also gated; revoking is not.
POST /api/v1/api-keys
curl -X POST https://api.cleanlist.ai/api/v1/api-keys \
-H "Authorization: Bearer <clerk_session_jwt>" \
-H "Content-Type: application/json" \
-d '{
"name": "Production server",
"expires_at": "2027-01-01T00:00:00Z"
}'Request body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | A human-readable label |
expires_at | ISO-8601 datetime | No | Optional expiration; omit for a non-expiring key |
Response
{
"api_key": "clapi_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"created_at": "2026-04-06T15:00:00Z",
"expires_at": "2027-01-01T00:00:00Z"
}If you already have 10 active keys, creation returns 400 with a "Maximum active API keys reached" message — revoke one first.
Listing your keys
GET /api/v1/api-keys
Lists active and inactive keys (Clerk JWT, gated by API_ACCESS). The full secret is never returned — only the first 10 characters (key_prefix) and last 4 (key_last4).
curl https://api.cleanlist.ai/api/v1/api-keys \
-H "Authorization: Bearer <clerk_session_jwt>"[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Production server",
"key_prefix": "clapi_AbCd",
"key_last4": "wXyZ",
"created_at": "2026-04-06T15:00:00Z",
"expires_at": null,
"last_used_at": "2026-04-06T15:42:11Z",
"request_count": 1547,
"is_active": true
}
]There is also a public, clapi_-authenticated GET /api/v1/public/api-keys (scope admin:api_keys) that returns a lighter shape (key_id, key_prefix, key_last4, is_active).
Revoking keys
POST /api/v1/api-keys/{key_id}/revoke
Revoke a single key by its UUID (not feature-gated):
curl -X POST https://api.cleanlist.ai/api/v1/api-keys/550e8400-e29b-41d4-a716-446655440000/revoke \
-H "Authorization: Bearer <clerk_session_jwt>"POST /api/v1/api-keys/revoke-all
Revoke every active key for the authenticated user (useful if you suspect a leak):
curl -X POST https://api.cleanlist.ai/api/v1/api-keys/revoke-all \
-H "Authorization: Bearer <clerk_session_jwt>"{ "success": true, "message": "3 API key(s) revoked successfully" }Revoked keys cannot be reactivated — generate a new one.
Inspecting key usage
GET /api/v1/api-keys/{key_id}/requests
Pull the most recent requests made with a specific key:
curl "https://api.cleanlist.ai/api/v1/api-keys/550e8400-e29b-41d4-a716-446655440000/requests?limit=100" \
-H "Authorization: Bearer <clerk_session_jwt>"| Query param | Default | Range |
|---|---|---|
limit | 100 | 1–500 |
[
{
"id": "log-uuid",
"method": "POST",
"path": "/api/v1/public/enrichment/bulk",
"status_code": 200,
"duration_ms": 184,
"ip_address": "203.0.113.42",
"user_agent": "python-requests/2.32.0",
"created_at": "2026-04-06T15:42:11Z"
}
]Using a key
Once you have a clapi_ key, every Public API call uses the same Bearer header:
curl https://api.cleanlist.ai/api/v1/public/credits/balance \
-H "Authorization: Bearer clapi_your_api_key"Verifying a key
To confirm a key is valid (and inspect its scopes and tier) without spending credits, call GET /whoami:
curl https://api.cleanlist.ai/api/v1/public/whoami \
-H "Authorization: Bearer clapi_your_api_key"A 401 response means the key is missing, malformed, expired, or revoked.
Security best practices
- Never commit
clapi_keys to source control. - Store keys in a secret manager (AWS Secrets Manager, Doppler, 1Password, Vault, etc.).
- Use
nameandexpires_atto make keys easy to rotate and audit. - If a key leaks, immediately call
POST /api/v1/api-keys/{key_id}/revoke. - Monitor
request_countandlast_used_atto detect unexpected usage. - Prefer one key per environment (dev / staging / production) — never share keys across systems.
Common authentication errors
| Status | Code | Cause | Fix |
|---|---|---|---|
401 | invalid_token | Missing/malformed token, or revoked/expired key | Confirm header format and the key's status |
403 | insufficient_scope | Credential lacks the endpoint's required scope | Re-issue the key with the scope; check /whoami |
403 | feature gate | Plan lacks API_ACCESS (key creation) | Upgrade your plan in billing |
429 | rate_limited | Rate limit hit | Back off using Retry-After; see Errors |