Audience: developers. API keys need a Pro or Enterprise plan (legacy Pro and Scale plans included) or an AppSumo Tier 5, 6 or 7 license, and they never work during a trial. Support agent: confirm the user's plan grants API access before giving API advice.
TL;DR: Every API v2 request uses Authorization: Bearer clapi_... (or a Clerk session JWT). On top of the token, each endpoint checks one OAuth scope (for example enrich:write); a missing scope returns 403 insufficient_scope. Create keys under Settings → API keys → Create API key. Up to 10 active keys per user per organization.
Authentication
Cleanlist API v2 accepts two credential types:
| Credential | Header | Used by |
|---|---|---|
API key (clapi_...) | Authorization: Bearer clapi_... | servers, scripts, your own agents |
| Clerk session JWT | Authorization: Bearer <jwt> | the Cleanlist app, and the /api/v1/api-keys management endpoints |
Authorization: Bearer clapi_your_api_key_hereThe Cleanlist MCP for Claude doesn't use an API key: it signs in with your Cleanlist account through OAuth and carries the same scopes.
Who can create API keys
| Plan | API keys |
|---|---|
| Pro, Enterprise | Yes |
| Legacy Pro, legacy Scale (shown as Enterprise) | Yes |
| AppSumo Tiers 5, 6 and 7 | Yes |
| Free, Starter (current or legacy) | No |
| AppSumo Tiers 1 to 4 | No |
| Any trial, including the Pro trial | No |
On a plan without API access, the API keys page shows an upgrade card ("The Cleanlist API.") instead of the create form.
OAuth scopes
Authentication answers "who are you?"; scopes answer "what may you do?". Every /api/v2 endpoint checks one scope before it runs. If your credential doesn't carry it, the request fails with 403 and code insufficient_scope.
| Scope | Grants |
|---|---|
people:read | People Finder, people filters, compile, find-at-companies, find-people status |
people:write | Reserved; no endpoint checks it today |
companies:read | Company search, company filters, similar companies |
companies:write | Reserved; no endpoint checks it today |
lists:read | Read lead lists and their leads |
lists:write | Create, update and delete lists, add and remove leads, CSV import, find-people jobs |
enrich:read | Poll enrichment status |
enrich:write | Run person, company, by-task and bulk enrichment |
smart_agents:read | List smart-agent runs and read results |
smart_agents:write | Run a smart agent |
sync:write | Sync a list to a CRM or sequencer |
export:read | CSV signed URLs and JSON export |
credits:read | Credit balance and cost estimates |
admin:api_keys | List keys and read usage reports through the API |
crm:read | Read CRM records through Cleanlist (used by the Cleanlist MCP) |
Scopes are granted per user, per organization. Every clapi_ key you create carries the scopes your user has in the organization the key was created in, and GET /api-keys reports those scopes on each key. You can't create a key with a narrower scope set than your user has. Users who were granted the full set before crm:read existed receive crm:read automatically.
The five legacy v1-only endpoints (/api/v1/public/auth/validate-key, /enrich/bulk, /enrich/status, /webhooks/deliveries, POST /folders) don't check scopes. Every v2 endpoint does.
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": "Either re-issue the API key with the required scope, or sign in with an identity that has it. The full list of granted scopes is visible via /api/v2/whoami.",
"retryable": false,
"docs_url": "https://docs.cleanlist.ai/errors/insufficient-scope",
"request_id": "req_...",
"details": { "required_scope": "enrich:write", "granted_scopes": ["lists:read", "people:read"] }
}
}To see what your credential can do, call GET /whoami; it returns the granted scopes list.
Key format
| Property | Value |
|---|---|
| Prefix | clapi_ |
| Ownership | The user who created it, in the organization it was created in |
| Secret body | 48 random URL-safe bytes |
| Visibility | Shown once at creation; afterwards only the prefix and last 4 characters are displayed |
| Limit | 10 active keys per user per organization |
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 create a new one.
Creating a key
The fastest path is the app:
- Go to Settings → API keys.
- Under Create API key, enter a Name (for example "CI integration") and, if you want, an Expiry.
- Click Create API key and copy the key shown under New API key.
Your keys are listed under Your API keys with their status and request count.
You can also create keys programmatically with your Clerk session token. These management endpoints live at /api/v1/api-keys (not under /public or /api/v2) and authenticate with the Clerk session JWT, not a clapi_ key.
Creating and listing keys need API access on your plan (see Who can create API keys). Without it, POST /api/v1/api-keys returns 403 with code PERMISSION_DENIED, the message "This feature requires the Pro plan or higher." and details.error: "feature_gated". Revoking keys and reading a key's request log are not gated.
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 in this organization, creation returns 400 with "Maximum active API keys reached (10). Revoke an existing key before creating a new one."
Listing your keys
GET /api/v1/api-keys
Lists your active and inactive keys in the current organization (Clerk session JWT, needs API access on the plan). 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 clapi_-authenticated GET /api/v2/api-keys (scope admin:api_keys) that returns the same per-key fields (key_id, key_prefix, key_last4, name, scopes, created_at, last_used_at, is_active).
Revoking keys
In the app, open Settings → API keys, find the key under Your API keys and click Revoke, or click Revoke all.
POST /api/v1/api-keys/{key_id}/revoke
Revoke a single key by its UUID:
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 can't be reactivated; create a new one.
Inspecting key usage
In the app, click View requests on a key under Your API keys.
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 to 500 |
[
{
"id": "log-uuid",
"method": "POST",
"path": "/api/v2/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 API v2 call uses the same Bearer header:
curl https://api.cleanlist.ai/api/v2/credits/balance \
-H "Authorization: Bearer clapi_your_api_key"With an SDK: you don't build this header yourself. Pass your key to the client
(Cleanlist(access_token="clapi_…"), or the CLEANLIST_API_KEY env var) and every call is
authenticated. See the SDKs.
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/v2/whoami \
-H "Authorization: Bearer clapi_your_api_key"A 401 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 and similar).
- Use a Name and an Expiry to make keys easy to rotate and audit.
- If a key leaks, revoke it at once (Revoke in the app, or
POST /api/v1/api-keys/{key_id}/revoke). - Watch the request count and View requests log for unexpected use.
- Use one key per environment (dev, staging, production), and never share keys across systems.
Common authentication errors
| Status | Code | Cause | Fix |
|---|---|---|---|
401 | AUTHENTICATION_INVALID | Missing, malformed, expired or revoked key ("Invalid or expired API key", or "Not authenticated" when the header is missing) | Check the header format and the key's status under Your API keys |
403 | insufficient_scope | Credential lacks the endpoint's scope | Check /whoami; if a scope you need is missing, contact support@cleanlist.ai |
403 | PERMISSION_DENIED (details.error: feature_gated) | Plan has no API access (key creation or listing) | Move to Pro or Enterprise under Settings → Plans & billing |
403 | PERMISSION_DENIED | Key used while a trial is active ("Public API access is not included during the trial...") | Use the app or the MCP during the trial, or start a paid plan |
429 | RATE_LIMITED | Rate limit hit | Back off using Retry-After; see Errors |
Authentication, plan and rate-limit errors use the generic error shape (code + message); see Errors & Rate Limits.