Public API
Errors
Every error response shares the Meta Graph API body shape, so existing Cloud API clients keep their error handlers intact when migrating.
Body shape
{
"error": {
"message": "Invalid or expired API key",
"type": "OAuthException",
"code": 190,
"error_subcode": 460,
"error_data": { "details": "..." },
"fbtrace_id": "abc123"
}
}message— human-readable description (English)type—OAuthExceptionfor auth-class errors,GraphMethodExceptionfor everything elsecode— machine-readable error code (see table below)error_subcode— optional finer-grained codeerror_data— optional structured payload (e.g. for recipient-blacklist rejections)fbtrace_id— echoes the request'sX-Request-Idheader when present, for cross-system correlation
HTTP status drives client behavior
Treat the HTTP status as the source of truth for retry / backoff decisions; the Meta error
code is for observability and human troubleshooting. Multiple Meta codes can map to the same HTTP status.Common codes
| Code | Type | HTTP | Meaning |
|---|---|---|---|
| 190 | OAuthException | 401 | Invalid token — key is wrong, revoked, expired, or HMAC failed |
| 200 | OAuthException | 403 | Permission denied — key valid but missing scope, IP rejected, etc. |
| 4 | OAuthException | 429 | Rate limit exceeded — backoff using the Retry-After header |
| 100 | GraphMethodException | 400 / 404 / 409 | Parameter error, resource not found, or conflict (e.g. tag limit reached) |
| 131000 | GraphMethodException | 400 / 409 / 422 / 500 | Generic send / processing failure. HTTP status disambiguates: 422 = blacklist hit, 409 = idempotency in-flight or resource limit (e.g. contact tag cap), 500 = downstream Meta or transient. |
| 131026 | GraphMethodException | 400 | 24-hour customer service window expired — send via a template instead |
Retry guidance
- 5xx and 429 are retryable. Use exponential backoff (e.g. 1s, 2s, 4s, 8s) capped at 30 seconds. Combine with an Idempotency-Key to make retries safe for write endpoints.
- 4xx (excluding 429) is notretryable — the request is malformed or the resource doesn't exist. Fix the request before retrying.
- Network errors (TLS, DNS, connection reset) before a status is received are retryable with the same idempotency key.
The recipient_blacklisted error
When you send with ?filter_blacklist=true and the target is on the tenant blacklist, the API returns 422 with a Meta-style body whose error_data.reason is recipient_blacklisted. No message row is created and Meta is not called. Treat this as a final state, not a retry target.
{
"error": {
"message": "Recipient is on the tenant blacklist",
"type": "GraphMethodException",
"code": 131000,
"error_data": { "reason": "recipient_blacklisted" }
}
}