Error Handling
Error envelope, stable codes, and HTTP status codes returned by the Zernio API
Error Response Format
Every non-2xx response returns a flat JSON envelope modeled on Stripe's error shape:
{
"error": "budgetAmount is required",
"type": "invalid_request_error",
"code": "missing_required_field",
"param": "budgetAmount",
"docUrl": "https://docs.zernio.com/guides/error-handling"
}| Field | Stable | Description |
|---|---|---|
error | No | Human-readable message. Reworded freely between releases. Never branch client logic on this. |
type | Yes | High-level category (invalid_request_error, authentication_error, permission_error, not_found, rate_limit_error, platform_error, api_error). |
code | Yes | Machine-readable code (e.g. missing_required_field, ads_connection_required). Use this for programmatic handling. |
param | Yes | Field or query parameter at fault, when applicable. Dotted path for nested fields (e.g. images.square). |
docUrl | Yes | Link to error-specific documentation when available. |
platform | Yes | Set on platform_error. One of meta, google, tiktok, linkedin, pinterest, twitter. |
platformError | Yes | Set on platform_error. The raw upstream payload returned by the platform, forwarded unchanged for inspection. |
Backward-compatible. The top-level error string is preserved so existing code that reads response.error as a string still works. New fields (type, code, param, ...) are added as top-level siblings, not under a nested details object.
Stability contract
typeandcodevalues are stable once shipped. Build retries, alerting, and i18n against them.- The
errormessage can change freely. Treat it as display-only. - New codes may be added at any time. Removing a code is a breaking change.
Error Types
| Type | Default Status | When It Happens |
|---|---|---|
invalid_request_error | 400 / 422 | Missing required fields, wrong types, mutually exclusive fields, invalid JSON, unmet preconditions. |
authentication_error | 401 | Missing or invalid API key. |
permission_error | 403 | Valid key but feature requires a plan upgrade or add-on (e.g. Ads add-on). |
not_found | 404 | Resource doesn't exist or isn't accessible under this API key. |
rate_limit_error | 429 | Request rate limit exceeded. See rate limits. |
platform_error | 502 (or upstream 4xx) | Upstream social platform (Meta, Google, TikTok, LinkedIn, Pinterest, X) rejected the request. See "Platform Errors" below. |
api_error | 500 | Unexpected server-side error. Safe to retry with backoff. |
Common Error Codes
| Code | Type | Meaning |
|---|---|---|
missing_required_field | invalid_request_error | A required field is missing from the body or query. param points at the field. |
invalid_field_value | invalid_request_error | A field has the wrong type, format, or enum value. param points at the field. |
mutually_exclusive_fields | invalid_request_error | Two incompatible fields were both provided (e.g. creatives[] + adSetId). |
invalid_json_body | invalid_request_error | Body could not be parsed as JSON. |
missing_credentials | authentication_error | No Authorization header. |
invalid_credentials | authentication_error | The API key is invalid, revoked, or expired. |
ads_addon_required | permission_error | The caller does not have the Ads add-on. |
feature_not_available | permission_error | The caller's subscription tier does not include this feature. |
account_not_found | not_found | The referenced account is unknown or not accessible. |
ad_not_found | not_found | The referenced ad is unknown or not accessible. |
post_not_found | not_found | The referenced post is unknown or not accessible. |
audience_not_found | not_found | The referenced audience is unknown or not accessible. |
linked_account_required | invalid_request_error | The connected account is missing a required linked account (e.g. Instagram Ads requires a linked Facebook account). |
ads_connection_required | invalid_request_error | The platform's ads integration isn't connected (e.g. X Ads, TikTok Ads, Google Ads). Call the relevant /v1/connect/*/ads flow. |
instagram_business_account_unresolved | invalid_request_error | The Instagram Business Account ID couldn't be resolved from the linked Page. The user must connect their Instagram to the Page in Meta Business Settings. |
missing_square_image | invalid_request_error | Google Display requires both images.landscape and images.square; only one was sent. |
ad_not_commentable | invalid_request_error | The ad exists but its creative format does not expose a commentable underlying post (e.g. Story ads, Dynamic Product Ads). Returned by GET /v1/ads/{adId}/comments. |
rate_limited | rate_limit_error | Request rate limit exceeded on this endpoint. |
platform_api_error | platform_error | Upstream platform rejected the call. Inspect platform + platformError. |
internal_error | api_error | Unexpected server-side error. |
Platform Errors
When an upstream platform (Meta, Google, TikTok, LinkedIn, Pinterest, X) rejects a request, Zernio surfaces a platform_error envelope with the original payload passed through for inspection.
{
"error": "Google rejected the ad: NOT_ENOUGH_SQUARE_MARKETING_IMAGE_ASSET",
"type": "platform_error",
"code": "platform_api_error",
"platform": "google",
"platformError": {
"code": 3,
"message": "Request contains an invalid argument.",
"details": [ { "errors": [ { "errorCode": { "assetError": "NOT_ENOUGH_SQUARE_MARKETING_IMAGE_ASSET" }, "message": "Too few." } ] } ]
}
}platform_errorreturns the upstream 4xx status when the platform indicated bad input, or502 Bad Gatewaywhen the platform returned 5xx / didn't indicate a status.- Inspect
platformErrorfor platform-specific error codes you may want to map for retries or end-user messages. platformidentifies which upstream surfaced the error, so one handler can branch by integration.
Post Publishing Failures
When a scheduled post fails to publish to one or more platforms, the post status reflects the outcome:
| Post Status | Meaning |
|---|---|
published | All platforms published successfully. |
partial | Some platforms published, others failed. |
failed | All platforms failed to publish. |
Each platform entry in the post has its own status and error fields:
{
"post": {
"status": "partial",
"platforms": [
{ "platform": "twitter", "status": "published", "platformPostUrl": "https://twitter.com/..." },
{ "platform": "instagram", "status": "failed", "error": "Media processing failed: video too short for Reels" }
]
}
}Common publishing errors
| Error | Cause | Fix |
|---|---|---|
| Token expired | OAuth token needs refresh | Check account health and reconnect. |
| Rate limited by platform | Too many posts to this platform | Wait and retry, or space out posts. |
| Media processing failed | File format/size not supported by platform | Check platform requirements. |
| Duplicate content | Platform rejected identical content | Modify the content slightly. |
| Permissions missing | Account lacks required permissions | Reconnect with proper scopes. |
Retrying failed posts
For failed or partial posts, use the retry endpoint:
const { post } = await zernio.posts.retryPost('post_123');result = client.posts.retry("post_123")curl -X POST "https://zernio.com/api/v1/posts/post_123/retry" \
-H "Authorization: Bearer YOUR_API_KEY"Only the failed platforms are retried; already-published platforms are skipped.
Account Health
Proactively check if your connected accounts are healthy before publishing:
const health = await zernio.accounts.getAccountHealth();health = client.accounts.get_health()curl "https://zernio.com/api/v1/accounts/health" \
-H "Authorization: Bearer YOUR_API_KEY"The health check endpoint returns token validity, permissions status, and recommendations for each account.
Webhook Reliability
If you use webhooks to track post status:
- Webhooks are delivered at least once (you may receive duplicates); dedupe by event id.
- Failed deliveries are retried with exponential backoff.
- You can view delivery logs via the webhook logs endpoint.
Best Practices
- Branch on
typeandcode, never onerrortext. - Handle
429by respecting theRetry-Afterheader. - Treat
platform_errorupstream 4xx as caller-fixable (bad input forwarded from the platform) and 5xx / 502 as transient. - Monitor account health periodically to catch token expirations early.
- Use webhooks instead of polling for post status updates.
- Log errors with full context, include the request body and the entire response envelope (including
platformError) for debugging.