Responses & errors
Two envelopes, the real error shapes, and one wrapper that will confuse you if you test without a key.
Two envelopes
Which envelope you get depends on which surface you are on.
Data is a sibling key alongside success, named after the resource:
{
"success": true,
"list": [ … ],
"total": 412,
"limit": 100,
"offset": 0,
"hasMore": true
}Not universal
This is the convention, not a guarantee. Some CRM routes return a bare object or
a bare array with no success field. Do not write a client that requires
success to be present on every CRM response — check the individual endpoint
page.
Error shapes
There is no single error envelope. In practice you will see three shapes:
// Most handlers — a bare error string
{ "error": "Invalid API key" }
// Validation failures
{ "error": "limit must be at most 500" }
// Uncaught server errors
{ "error": "Internal server error", "correlation_id": "a1b2c3d4" }Some routes additionally include success: false, and some include a machine
code:
// Mass messaging — the one route using an UPPERCASE code
{ "success": false, "code": "PLATFORM_NOT_SUPPORTED", "error": "mass messaging is OnlyFans-only for now" }
// Every other 501 — lowercase, with `platform` and `feature`
{ "success": false, "code": "platform_not_supported",
"error": "\"referrals\" is not available for fansly accounts yet.",
"platform": "fansly", "feature": "referrals" }Compare that code case-insensitively — branching on the uppercase spelling alone misses every 501 except mass DMs.
Write your error handling defensively
Read error as the human message, and treat success, code and
correlation_id as optional. Do not branch on success being present.
Rate limiting and quota errors carry extra fields:
{ "error": "Rate limit exceeded", "retry_after": "1000 per 1 minute" }
{ "error": "API call limit reached", "plan": "only-api-free", "limit": 1000, "used": 1000, "upgrade_url": "…" }
// 403, not 429
{ "error": "Account limit reached (10). Buy a slot to add another account.",
"code": "SLOT_LIMIT", "plan": "only-api-free",
"accounts_used": 10, "accounts_limit": 10, "slot_purchase_url": "…" }500 responses are deliberately opaque — the full traceback is logged
server-side only, so SQL fragments, file paths, proxy credentials and session
tokens never reflect back to you. Quote the correlation_id when reporting one.
Status codes
| Code | Meaning |
|---|---|
200 | Success |
202 | Async job accepted — poll its status route. See Async jobs |
400 | Validation error, or a missing required header |
401 | No X-API-Key sent, bad dashboard credentials, or a session that could not be re-established (relogin_failed) |
403 | Invalid key, wrong panel, primary-key-only route, write actions disabled, or the account slot limit |
404 | Unknown job or, on a few account routes, an account that is not yours |
409 | An async job of that kind is already running for this account |
429 | Per-minute rate limit or monthly quota |
500 | Server error — includes a correlation_id |
501 | The operation is not supported for that account's platform |
The _notice wrapper means you are on a decoy path
Some URLs on this domain are honeypot bait rather than API routes — notably
/api/health, /api/v1/*, /api/internal/*, and paths like /.env and
/.git/config. Those return procedurally-generated fake data with a _notice
string prepended (and a top-level array reshaped into {_notice, data: […]}), plus
X-Honeypot-* and X-AI-Notice headers.
If you see `_notice`, you are not talking to the API
The decoy is selected by path, not by authentication. GET /api/health is bait
and reports a fabricated version and service list; the real health check is
GET /health on api.theonlyapi.com.
The real API is panel-scoped: https://theonlyapi.com/api/crm/{crm_id}/…. A missing
or wrong key there gives you a plain 401/403 JSON error with no _notice.
There is no User-Agent check on the API. Testing with curl needs no -A
flag — the User-Agent only appears echoed inside a decoy notice, and never changes
which response you get.
Other headers
Every response carries Cache-Control: no-store plus a standard security header
set (HSTS, X-Content-Type-Options: nosniff, X-Frame-Options: DENY,
Referrer-Policy). Rate-limit state is exposed via X-RateLimit-* headers.
CORS is restricted for /api/*; allowed request headers are Content-Type,
X-API-Key, X-Proxy and Authorization. If you are calling from a browser
origin, proxy through your own backend instead.