Errors

One error envelope, one stable code per failure, and a matching HTTP status.

Every error uses the same envelope:

{
  "error": {
    "code": "invalid_request",
    "message": "Body must be JSON.",
    "request_id": "req_8fK2mQ"
  }
}
  • code is a stable identifier. Branch on it.
  • message is human-readable and safe to log, but do not branch on it.
  • request_id matches the X-Request-Id response header. Paste the JSON into a support request and it is enough to find the call.

The HTTP status is derived from code, never inferred from the message.

Codes

StatuscodeMeaning
400invalid_requestThe query or body failed the operation's schema, the body was not a JSON object, an Idempotency-Key was too long, or a key was reused with a different body.
401unauthorizedMissing, invalid, revoked or expired key, or an OAuth token sent to REST.
403forbiddenThe key is valid but short on scope or the approval grant. The body carries required_scope or required_grant, plus key_scope. See Authentication.
404not_foundNo such resource in this workspace.
409conflictA request with this Idempotency-Key is still running.
422proposal_rejectedWell-formed, but refused by the product's rules or by Meta. message says why.
429rate_limitedToo many requests. See Rate limits.
502upstream_errorAn upstream provider was unreachable or errored.
500internalAn unexpected server error. The message is fixed and never contains internal detail. Quote the request_id.

What a 422 usually means

A tool that refuses returns 422 proposal_rejected with a message that says why. The common causes are:

  • A launch refused before upload: an ad name that does not match id__locale__format__persona__angle__concept__variant, or a form ad set mixed with a website destination.
  • Meta refused the call, for example a missing permission or a throttled quota.
  • The free trial has ended. Launching, turning ads on and changing live ads are off until a plan is active. The message links to Settings → Billing. Pausing, reading and everything else still work.
  • A frozen record: a second Conclusion on a Test, or a supported or rejected Conclusion that contradicts the latest Observation without an overrideReason.

A 422 is a policy outcome, not a bug. The request was well-formed and was refused on purpose, so handle it separately from 400.

Some operations answer 404 not_found for a missing row instead of 422: reading or updating a creative, reading its recipe, reading a brief, a report view, a Test or a learning, and updating a copy line. Each operation in the reference lists a 404 when it can return one.

Retrying

  • 429: wait for Retry-After and retry.
  • 500, 502 or a timeout: retry with the same Idempotency-Key. See Idempotency.
  • 400, 401, 403, 404, 422: fix the request. Retrying unchanged will fail again.

On this page