Errors, limits and best practices

Error format, codes, request limits and how to deal with Meta's rate limits.

🇧🇷 Ler em português

Error format

{
  "error": true,
  "code": "validation_failed",
  "user_message": "O nome da campanha é obrigatório",
  "request_id": "0b6f…",
  "errors": { "campaign_name": "O nome da campanha é obrigatório" }
}

user_message is always in Brazilian Portuguese and can be shown to the end user (the example reads "The campaign name is required"). Branch your code on code and the HTTP status, never on the message text. Internal details (database, Meta) are never returned — they stay in the logs, linked to the request_id/log_id.

HTTPcodeMeaning
400invalid_request, invalid_json, selection_missingMalformed body (the field is named in user_message and in issues)
401unauthorizedNo credential, revoked/expired key or invalid token
403insufficient_scopeMissing permission (see required_scope)
403selection_denied, forbiddenArchived client, account from another BM/disabled/inactive on Meta, another user's resource
404not_found, campaign_not_found, post_not_found, client_not_found, client_archivedResource does not exist (or the client is archived)
409execution_running, conflict, ambiguous_clientRun already in progress / retry already done / more than one client matches the name (use the id)
422validation_failedBusiness rules — see errors per field
422audiences_unavailableCustom audience the ad account cannot use
429rate_limitedMedia Ops API limit (see Retry-After)
429meta_rate_limitedMeta throttled the ad account (see retry_after_seconds)
502upstream_error, meta_errorMeta or Google Drive failure

API limits

Per API key (or OAuth app), per minute:

ClassLimitExamples
Read300GET, lookups
Write60Regular POST/PATCH/DELETE
Heavy10POST /campaigns, /campaigns/validate, /audiences/engagement, syncs

Responses carry x-ratelimit-limit and x-ratelimit-remaining. On a 429, wait Retry-After seconds.

Meta limits

Meta rate-limits calls per ad account (code 17 and similar). During a creation run, the server waits and resends on its own — you will see progress.throttle.paused = true. An item is marked as an error only after 30 minutes of waiting.

For direct lookups (/meta/*), a block becomes 429 meta_rate_limited with retry_after_seconds.

Best practices

  • Validate with POST /campaigns/validate before creating.
  • Keep the job_id. If your automation goes down, resume by polling /executions/{job_id}/progress — the run keeps going on the server.
  • Do not resend POST /campaigns to "try again": use POST /executions/{job_id}/retry, which reuses what was already created.
  • Use keys with the minimum permissions and an expiration date.
  • Send your own x-request-id if you want to correlate with your logs; it is echoed back in the response.

Did this page help you?