SigFollow
Sign in

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 OAuthException for auth-class errors, GraphMethodException for everything else
  • code — machine-readable error code (see table below)
  • error_subcode — optional finer-grained code
  • error_data — optional structured payload (e.g. for recipient-blacklist rejections)
  • fbtrace_id — echoes the request's X-Request-Id header 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

CodeTypeHTTPMeaning
190OAuthException401Invalid token — key is wrong, revoked, expired, HMAC failed, or the path resource does not exist / is outside the key's organizations
200OAuthException403Permission denied — key valid but missing scope, IP rejected, or the requested organization is outside the key's authorization
4OAuthException429Rate limit exceeded — backoff using the Retry-After header
100GraphMethodException400 / 404 / 409Parameter error, resource not found, or conflict (e.g. tag limit reached)
131000GraphMethodException400 / 409 / 422 / 500Generic 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.
131026GraphMethodException40024-hour customer service window expired — send via a template instead

Organization targeting errors

Admin keys (sflo_admin_) add three failure modes that organization keys never hit:

  • 400 — missing organization. The endpoint has no :phoneNumberId / :wabaId to derive from and no X-SigFollow-Tenant-Id header was sent.
  • 400 — contradicting organization. The header names one organization while the path resource belongs to another. Fix whichever is wrong; SigFollow will not guess.
  • 403 — organization not accessible. The organization exists but is outside this key's authorization. Call GET /v1/tenantsto see the current set—it may have changed if the issuing administrator's permissions changed.
Unauthorized path resources return 401, not 403
When the organization is derived from :phoneNumberId or :wabaId, a resource outside your authorization returns the same 401as a resource that does not exist. This is deliberate: Meta's IDs are enumerable, and a distinguishable response would turn any key into a probe for other customers' phone numbers. Do not treat this 401 as "bad token"—check the resource ID first.

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" }
  }
}