Authentication
The X-API-Key header, panel scoping, and what each failure code means.
Send your key on every request:
X-API-Key: YOUR_API_KEYThat is the whole scheme for API clients. There are no bearer tokens and no request signing.
Two other schemes exist but are not available to you: OAuth 2.1, used only by the
MCP server, and an internal X-Service-Token used by our own
services calling each other.
Header only, by design
The API does not accept a key as a query parameter, even though one old error message mentions it. Keys in URLs end up in access logs, proxy logs and browser history.
Keys are scoped to one panel
A key authenticates you as a specific CRM panel. Passing a key that is valid but
belongs to a different panel than the {crm_id} in the path is rejected the
same way an invalid key is — there is no cross-panel access.
Likewise, {of_user_id} must be an account connected to your panel:
{ "error": "Account not found or does not belong to this CRM panel" }Most routes answer that with a 403, and it means the same thing whether the
account does not exist or simply is not yours.
A handful answer the same condition with 404 {"error": "Account not found"}
instead — DELETE /accounts/{of_user_id}, the /proxy read and update, the
/polling read, and /fansly-credentials. Treat 403 and 404 on an account
path as the same thing: not yours.
Unscoped routes
These seven carry no {crm_id} and are served by
https://api.theonlyapi.com — not theonlyapi.com, which does not route them.
"Unscoped" is not the same as "open": most still require something.
| Route | What it needs |
|---|---|
GET /health | nothing |
POST /api/auth/login | nothing (the credentials are the auth) |
POST /api/auth/start-email-verification | nothing |
POST /api/auth/verify-email | nothing |
GET /api/whoami | an API key — it resolves a key to its panel, it is just not scoped by one |
POST /api/auth/register | a captcha — see below |
POST /api/crm/register | a captcha — see below |
See Base URL & panels for why the host differs.
Account creation is captcha-gated
The two registration routes require either a Cloudflare Turnstile token
(captcha_token in the body, or an X-Captcha-Token header) or an internal
service token. Without one they return:
{ "success": false, "code": "CAPTCHA_REQUIRED",
"error": "Captcha verification is required to create an account.", "reason": "missing_token" }Sign up through the website, which solves the captcha for you — see Get an API key. There is no need to call either route yourself.
Failure codes
| Code | Body | What it means |
|---|---|---|
403 | {"error": "Invalid API key"} | Key is unknown, revoked, or belongs to another panel |
401 | {"error": "Missing X-API-Key header…"} | No key sent — note 401, while an invalid key is 403 |
403 | {"error": "Account not found or does not belong to this CRM panel"} | of_user_id is not connected to your panel |
403 | {"error": "…"} | A secondary key attempted a primary-key-only operation |
429 | {"error": "Rate limit exceeded", "retry_after": "1000 per 1 minute"} | Per-minute limit. retry_after echoes the limit that fired, not a number of seconds — use the Retry-After header for the delay |
429 | {"error": "API call limit reached", "plan": …, "limit": N} | Monthly quota exhausted |
403 | {"code": "WRITES_DISABLED"} | The account has allow_of_write_actions off — see Connect an account |
403 | {"code": "SLOT_LIMIT"} | Connecting an account beyond your plan's slots |
403 | {"code": "CAPTCHA_REQUIRED"} | A registration route called without a captcha |
404 | {"error": "Account not found"} | Same as the 403 above, on the few routes noted earlier |
501 | {"code": "platform_not_supported", "platform": …, "feature": …} | The operation does not exist for that account's platform — see OnlyFans & Fansly |
Authentication failures are deliberately uniform
Invalid, revoked and wrong-panel keys all return the same 403. Key comparison
is constant-time, and unknown keys still run a dummy comparison, so response
timing does not reveal whether a key exists.