The error format, every error code the API returns, what each means and what to do about it.
Every error has the same shape:
{
"success": false,
"error": "cpm must be greater than 0",
"code": "VALIDATION_ERROR",
"param": "campaigns[2].cpm",
"request_id": "6f1c2a7e-3b0e-4f39-9a52-0c8d7f1e2b44"
}
| Field | Always | Meaning |
|---|
error | yes | A sentence a person can read. Show it; don’t parse it — wording can improve. |
code | yes on all v1 errors | Stable, machine-readable. Branch on this. |
param | on input errors | The field that was wrong, as a path: cpm, campaigns[2].channels[0], account_id. |
request_id | on most errors | Same as the X-Request-Id response header. Quote it to support. |
Some errors add context: min_cpm and currency when Telegram refuses a bid as too low, missing with UNKNOWN_AD_IDS, limit with QUEUE_FULL.
HTTP status codes
| Status | Meaning | Retry? |
|---|
| 200 | Done | — |
| 202 | Accepted, running in the background — poll the task | — |
| 400 | The request is wrong (field, value, missing account_id) — or Telegram refused the change | No — fix the request |
| 401 | No key, bad key, or the key reaches no cabinet | No |
| 403 | The key or plan isn’t allowed to do this | No |
| 404 | Not found among the cabinets this key reaches | No |
| 409 | Conflict with the current state (cabinet locked, Telegram session expired, same request in progress) | Sometimes — see the code |
| 413 | File too large | No |
| 422 | Idempotency-Key reused for a different request | No — use a new key |
| 429 | Rate limit or queue full | Yes, after Retry-After seconds |
| 500 | Our bug — already logged | Read the state first, then retry with backoff |
| 503 | Temporarily unavailable | Yes, after Retry-After |
Codes
Authentication and access
| Code | HTTP | What to do |
|---|
API_KEY_MISSING | 401 | Send X-API-Key. |
API_KEY_INVALID | 401 | The key is wrong, revoked or expired. Create a new one. |
NO_ACCOUNTS | 401 | The key reaches no active cabinet right now (cabinet deactivated, access removed). |
FORBIDDEN | 403 | This key type isn’t allowed for its owner. |
READ_ONLY_KEY | 403 | Turn on Manage campaigns for the key in the panel. |
WRITE_NOT_ALLOWED | 403 | This key type (issued by Adsly for partners) never writes. |
VIEW_ONLY_ACCESS | 403 | Your access to this cabinet is Viewer. Ask the agency owner for Manager. |
FEATURE_NOT_AVAILABLE | 403 | The plan doesn’t include this action (the response names the feature). |
SUBSCRIPTION_REQUIRED | 403 | The cabinet’s owner has no active subscription. |
ACCOUNT_INACTIVE | 403 | The cabinet is deactivated. |
Finding things
| Code | HTTP | What to do |
|---|
ACCOUNT_ID_REQUIRED | 400 | The key reaches several cabinets (or this ad_id exists in several) — pass account_id. |
ACCOUNT_NOT_FOUND | 404 | That account_id isn’t reachable with this key. Check GET /v1/account/info. |
CAMPAIGN_NOT_FOUND | 404 | No such campaign in the key’s cabinets (or it is deleted). |
TASK_NOT_FOUND | 404 | No such task for this key’s owner. |
NOT_FOUND | 404 | No stats for this campaign and period. |
UNKNOWN_AD_IDS | 400 | Some ad_ids in a bulk call aren’t campaigns of that cabinet; missing lists them. |
| Code | HTTP | What to do |
|---|
VALIDATION_ERROR | 400 | param names the field; error says what’s wrong. Unknown fields are rejected too — check spelling. |
UNSUPPORTED_MEDIA | 400 | Upload JPG, PNG, GIF, WEBP or MP4 in the file field. |
FILE_TOO_LARGE | 413 | Upload a smaller file (10 MB at most). |
Telegram and cabinet state
| Code | HTTP | What to do |
|---|
PLATFORM_REJECTED | 400 | Telegram refused the change; error carries its reason. With a low bid you also get param: "cpm", min_cpm and currency. |
BUDGET_UNCHANGED | 400 | Telegram accepted the top-up but the campaign’s budget didn’t move. Check the cabinet balance (GET /v1/account/info). |
INSUFFICIENT_AD_BALANCE | 400 | You asked to withdraw more than the campaign has. Withdraw less, or use withdraw_all. |
CABINET_AUTH_EXPIRED | 409 | The cabinet’s Telegram session expired. The owner reconnects it in the panel; then retry. |
CABINET_LOCKED | 409 | The cabinet is under automatic management; manual changes are blocked. |
Limits and retries
| Code | HTTP | What to do |
|---|
RATE_LIMITED | 429 | Wait Retry-After seconds. See Rate limits. |
QUEUE_FULL | 429 | The cabinet already has 5 operations queued. Wait for a task to finish, then retry (same Idempotency-Key is fine). |
IDEMPOTENCY_KEY_REQUIRED | 400 | Add an Idempotency-Key header. |
IDEMPOTENCY_KEY_INVALID | 400 | Use 8–255 characters; a UUID is ideal. |
IDEMPOTENCY_KEY_REUSED | 422 | That key was used for a different request — new operation, new key. |
IDEMPOTENCY_IN_PROGRESS | 409 | The first request with this key is still running. Retry in a few seconds. |
IDEMPOTENCY_UNAVAILABLE | 503 | Nothing was done. Retry with the same key. |
INGEST_DISABLED | 403 | Turn on Receive postbacks for the key (Conversions). |
INTERNAL_ERROR | 500 | Our side. Check the state with a GET before retrying a write; contact @adsly_pro with the request_id if it persists. |
Retrying safely
- 4xx — don’t retry unchanged; fix the request. (A 4xx never consumes your
Idempotency-Key, so you can retry the fixed request with the same key.)
- 429 / 503 — retry after
Retry-After.
- 5xx after a write — the change may or may not have reached Telegram. Retrying with the same
Idempotency-Key returns the same 5xx (by design — it prevents a double action). GET the campaign to see what happened, then decide; if you retry, use a new key.
- Network timeout on a write — retry with the same
Idempotency-Key. If the first attempt went through you get its result; if it didn’t, the operation runs now.