# Errors Source: https://docs.native.no/native-api/errors > **Preview.** The Native API is not live yet. This page describes the planned interface; names and fields can change before access opens. How a Native API tool call fails: the error envelope, when to retry, and every error code with the fields that come with it. > **Not live for partners yet.** Partner access to the Native API is not live yet. Partner sign-in with client credentials is in development, so a partner backend cannot call these tools today. Codes without a marker are in the gateway that serves signed-in Native users today. Codes marked **New, in development** arrive with the tools and fields that raise them. Every way a Native tool can fail comes with a code. This page lists the codes a partner backend can meet, and what to do about each. Branch on the code. Never parse the message. **Native does not retry a failed publish on its own.** A destination that failed stays failed until you act. ## Where a failure shows up - **The tool refused the call.** The result has `isError: true` and carries the [error envelope](https://docs.native.no/native-api/errors#error-envelope). - **Part of the call failed.** Some tools succeed as a whole and report failures per destination or per post. See [failures inside a result](https://docs.native.no/native-api/errors#failures-inside-a-result). - **The request never reached a tool.** A token problem or the edge rate limit answers with an HTTP status and no envelope. See [errors outside the envelope](https://docs.native.no/native-api/errors#outside-the-envelope). - **An argument broke the input schema.** The MCP SDK rejects it before the tool runs, with its own validation error instead of the envelope. Treat that as a bug in your call. ## The error envelope When a tool refuses a call, the result has `isError: true`, and `structuredContent` is `{ "data": }`. `content[0].text` holds the same envelope as a JSON string. structuredContent.data: ```json { "ok": false, "error": { "code": "schedule_preflight_failed", "message": "A sentence Native wrote for people", "retryable": false, "blockedDestinations": [ { "platform": "INSTAGRAM", "code": "caption_too_long", "characterCount": 2410, "characterLimit": 2200 } ] } } ``` Envelope: - `ok` (boolean, required): Always `false` on a failure. - `error` (object, required): What went wrong. - `code` (string, required): The failure, as a stable code. Branch on this. - `message` (string, required): A sentence Native wrote for people. It can change at any time, so show it or log it, but never parse it. - `retryable` (boolean, required): Whether the same call, unchanged, could succeed. `false` means something has to change first. `retryable` answers one question: could the same call, sent again unchanged, succeed? It does not say the call did nothing. After a timeout, read the current state before you retry. See [retrying](https://docs.native.no/native-api/errors#retrying). ## Structured fields Some codes carry extra fields beside `code`, `message` and `retryable`. They sit on the same `error` object. Each one is named with the codes that use it. Destination names in these fields are uppercase `Platform` enum names, such as `INSTAGRAM`. Fields on error: - `invalidFields` (string[], optional): The input fields Native refused, as paths such as `reddit.subreddit` or `platforms.0`. On `bad_request`. New, in development: also on `font_not_found` and `logo_unreadable`, which each refuse one argument. - `fieldErrors` (object[], optional): On `bad_request`: a sentence for each refused field, where Native wrote one. `invalidFields` still names every refused field, including any with no sentence here. - `field` (string, required): The field path. - `message` (string, required): Why it was refused. - `blockedDestinations` (object[], optional): On `schedule_preflight_failed`, and in a `reschedule_posts` entry: every destination that will not publish the post as it stands. - `platform` (string, required): The destination. - `code` (string, required; one of: `caption_too_long`, `media_required`, `image_aspect_invalid`, `too_many_images`, `video_required`, `video_not_supported`, `post_empty`, `reddit_subreddit_required`, `reddit_subreddit_not_found`, `reddit_flair_required`, `reddit_title_too_long`): The rule it breaks. See [pre-flight codes](https://docs.native.no/native-api/errors#preflight-codes). - `characterCount` (integer, optional): With `caption_too_long`: the caption’s length. With `reddit_title_too_long`: the title’s length. - `characterLimit` (integer, optional): With `caption_too_long` or `reddit_title_too_long`: the destination’s limit. - `imageCount` (integer, optional): With `too_many_images`: how many pictures the post has. - `imageLimit` (integer, optional): With `too_many_images`: how many the destination takes. - `refusedDestinations` (object[], optional): On `schedule_failed`: every destination the provider refused. - `platform` (string, required): The destination. - `code` (string, required): Why it was refused. See [codes per destination](https://docs.native.no/native-api/errors#destination-codes). - `scheduledDestinations` (string[], optional): On `schedule_failed`: the destinations that hold a booking even though another one refused. - `lockedDestinations` (object[], optional): On `already_scheduled` and `post_already_scheduled`: every destination a publishing provider is holding. The `already_scheduled` that `reject_post` returns carries none, so read each `platformPostId` from `get_post_status`. - `platform` (string, required): The destination. - `platformPostId` (string, required): The id `unschedule_platform` takes to withdraw it. One call per entry. - `publishedPlatforms` (string[], optional): On `post_already_published` and `already_published`: the destinations that are live. - `unavailablePlatforms` (string[], optional): On `platform_not_connected`: the destinations the brand has no live connection to. - `connectedPlatforms` (string[], optional): On `platform_not_connected`: everything the brand can publish to now. It may be empty. - `unsupportedPlatforms` (string[], optional): On `platform_not_supported`: the destinations Native does not publish to at all. - `supportedPlatforms` (string[], optional): On `platform_not_supported`: the destinations this brand could publish to. - `supportedChannels` (string[], optional): New, in development: on `platform_not_supported` from `create_connect_link`, the channels Native can open a headless link for, in lowercase. - `overLimitPlatforms` (string[], optional): On `caption_over_limit`: the destinations whose limit the caption exceeds. - `characterCount` (integer, optional): On `caption_over_limit`: the length of the caption you sent. - `characterLimit` (integer, optional): On `caption_over_limit`: the limit the caption has to fit, the shortest among the destinations that refused it. - `resetDate` (ISO 8601 string, optional): On `usage_limit_exceeded`: when the workspace’s AI budget refills. - `jobId` (string, optional): On `image_generation_in_progress`: the render already running, for `get_generation_job`. Present when Native can say which render it is. - `websiteRefusal` (string, optional; one of: `BLOCKED_DOMAIN`, `URL_NOT_REACHABLE`, `URL_UNSAFE`, `URL_NOT_ALLOWED`): New, in development: on `invalid_website_url`, why the website was refused. See [website refusals](https://docs.native.no/native-api/errors#website-refusals). ## Error codes These are the codes a whole call can fail with, grouped by what they are about. The `retryable` column is the value the envelope carries. ### Codes any tool can return | Code | `retryable` | What it means | | --- | --- | --- | | `bad_request` | `false` | Invalid input. `invalidFields` names the refused fields, and `fieldErrors` says what is wrong with each where Native wrote a sentence. Fix those and call again. | | `not_found` | `false` | The id is wrong, or it is not yours. | | `forbidden` | `false` | This principal is not allowed to do this. | | `subscription_required` | `false` | The workspace has no active plan. | | `writes_disabled` | `false` | Writes are switched off on this deployment. Only tools that change data return it. | | `internal_error` | `true` | Native failed on its side. Retry shortly. | | `upstream_timeout` | `true` | Native did not answer within 120 seconds. The work may still be running, so read the current state before you retry. | | `upstream_unavailable` | `true` | Native could not be reached. Retry with backoff. | | `upstream_identity_failed` | `true` | The gateway could not authenticate itself to Native. Retry with backoff. | | `identity_provider_unavailable` | `true` | The gateway could not reach the identity provider to check your token, so the call was never authorised. You do not need a new token. Retry with backoff. | | `upstream_429` | `true` | Native is rate limiting. Back off, then retry. | | `upstream_` for another 4xx | `false` | Native refused the call with a code the gateway does not recognise, for example `upstream_409`. Change the call before you send it again. | | `upstream_` for a 5xx | `true` | Native failed with a code the gateway does not recognise. Retry with backoff. | ### Partner access | Code | `retryable` | What it means | | --- | --- | --- | | `partner_only` | `false` | New, in development: a partner-only tool was called without a partner principal. Nothing changed. | | `partner_not_found` | `false` | New, in development: the token is valid, but Native has no partner configured for its client id. Contact Native. Retrying will not help. | | `partner_downstream_auth_failed` | `true` | New, in development: the gateway could not get access to Native for your partner client. Your token is not the problem, so keep it. Retry with backoff. | ### Profiles | Code | `retryable` | What it means | | --- | --- | --- | | `profile_not_ready` | `true` | New, in development: a generating tool was called on a profile whose `create_profile` build is `building` or `failed`. That covers `create_posts`, `generate_caption`, `generate_image`, `regenerate_post_image`, ad generation, and `create_draft` when it is sent a `prompt` or `includeImage: true`. Poll `get_profile` until `onboarding.status` is `ready`. If it is `failed`, retry the build first: call `create_profile` again with the same `externalId`. | | `invalid_website_url` | `false` | New, in development: `create_profile` refused the website before it created anything. `websiteRefusal` says why. | | `stripe_billed_workspace` | `false` | New, in development: `deactivate_profile` or `delete_profile` was called on a workspace that pays for its own Native subscription. That subscription has to be cancelled in Native. | ### Branding | Code | `retryable` | What it means | | --- | --- | --- | | `font_not_found` | `false` | New, in development: `update_brand_style` named a font that is neither in the brand’s library nor a Google Fonts family. No role changed. `invalidFields` names `fonts.heading` or `fonts.body`. | | `logo_unreadable` | `false` | New, in development: the file given to `upload_brand_logo` is not a usable logo. It did not read as an image at least 16 px on each side, could not be fetched, or failed the upload scan. Nothing was saved. `invalidFields` names the argument. | | `source_not_https` | `false` | New, in development: `upload_brand_logo` was given a `sourceUrl` that is not https. Nothing was fetched. Send the same address as https. | | `source_not_public` | `false` | New, in development: `upload_brand_logo` was given a `sourceUrl` that points at a private or internal address. Nothing was fetched. Use an address anyone on the internet can reach. | | `design_style_unavailable` | `false` | New, in development: the brand is not on Design style, or its Design style cannot be read, so colour roles and fonts were not set. Palette colours can still be added on their own. | | `design_style_busy` | `true` | New, in development: another Design style save landed at the same moment, or Native was still setting up. Palette colours and library fonts the call added stay. Call again with the same arguments in a few seconds. | ### Connecting accounts | Code | `retryable` | What it means | | --- | --- | --- | | `platform_not_connected` | `false` | The brand has no live connection to a destination the call named or, for scheduling and publishing, no connected channel at all. `unavailablePlatforms` and `connectedPlatforms` say which. Drop those destinations, or have the client connect the account. | | `platform_not_supported` | `false` | Native does not publish to a destination the call named, however the brand is connected. `unsupportedPlatforms` and `supportedPlatforms` list both sides. New, in development: from `create_connect_link`, `supportedChannels` lists the channels it can open instead. | | `connect_session_expired` | `false` | New, in development: the `connectToken` expired or its link died. Mint a fresh link with `create_connect_link`. | | `headless_connect_unavailable` | `false` | New, in development: Native could not mint a headless link for this brand. Call `create_connect_link` without `platform` and `returnUrl` for a link to Native’s connect page. | ### Posts and generation | Code | `retryable` | What it means | | --- | --- | --- | | `usage_limit_exceeded` | `false` | The workspace’s AI budget is used up. Nothing AI-backed succeeds until `resetDate`. | | `ai_generation_failed` | `true` | AI generation failed. Retry. | | `media_required` | `false` | Every destination on the post is a story, and the post has no media. Attach a picture or video, or add a feed destination. | | `caption_over_limit` | `false` | The caption is longer than every destination on the post allows, so nothing was written. `characterCount`, `characterLimit` and `overLimitPlatforms` say by how much and where. | | `reddit_options_required` | `false` | A `REDDIT` destination needs `reddit: { subreddit }`, and none was sent. Send it, or drop `REDDIT`. | | `image_generation_in_progress` | `false` | A render is already running for this post. Poll it with `get_generation_job`, using `jobId` when the error carries one. Do not start another. | | `template_rendering` | `false` | New, in development: the workspace renders posts from design templates. Use `generate_image` instead of `regenerate_post_image`, and do not pass `saveToLibrary`. These pages do not cover `generate_image` yet: `tools/list` has its schema. | | `post_already_published`, `already_published` | `false` | Some or all of the post is live, so it cannot be changed. `publishedPlatforms` lists what is live. Take those down with `unpublish_post`, or make a new post. | One refusal has no code of its own in the gateway today. When `update_post` tries to remove a destination that the provider is holding, the refusal arrives as `upstream_409`. Withdraw that destination with `unschedule_platform` first. New, in development: this refusal arrives as `platform_scheduled`, with `retryable: false`. The fix is the same: withdraw the destination with `unschedule_platform`, then remove it. ### Approval The approval gate runs before `schedule_post` and `publish_post_now`, and refuses the whole call. `reschedule_posts` runs it for each entry and reports a refusal in that entry’s `moves[].error`. `post_content_changed` comes from `approve_post` and `reject_post`. [Approval](https://docs.native.no/native-api/approval) explains the content digest behind these codes. | Code | `retryable` | What it means | | --- | --- | --- | | `approval_required` | `false` | New, in development: the workspace requires approval and there is none on record. Partner workspaces require it by default. Record one with `approve_post`. | | `approval_stale` | `false` | The content changed after it was approved. Read the post again, have it reviewed again, and approve it again. | | `approval_rejected` | `false` | The latest decision is a rejection. Editing the post does not clear it. Only a new `approve_post` does. | | `design_changed_after_approval` | `false` | New, in development: on a workspace that requires approval, the post’s design changed after it was approved, or publishing would change the approved pictures with a design re-render or a media repair. Nothing was sent. Read it again with `view_post`, have it reviewed again, and approve it again. | | `post_content_changed` | `false` | The post changed between `view_post` and the decision. Read it again and review it again. Never resend with a newer `contentDigest` the reviewer did not see. | ### Scheduling and publishing | Code | `retryable` | What it means | | --- | --- | --- | | `schedule_preflight_failed` | `false` | Native checked the post before it contacted any platform. Nothing was booked and the post is untouched. `blockedDestinations` names each problem. See [pre-flight codes](https://docs.native.no/native-api/errors#preflight-codes). | | `schedule_failed` | Varies | At least one destination was refused at the provider, and others may have booked. `refusedDestinations` and `scheduledDestinations` say which. See [when a schedule partly fails](https://docs.native.no/native-api/errors#schedule-failed). | | `already_scheduled`, `post_already_scheduled` | `false` | A publishing provider holds the post, so its schedule or its media cannot change this way. `lockedDestinations` lists each held destination. Withdraw each one with `unschedule_platform`, one call per entry. From `reject_post`, this code comes without `lockedDestinations`: read each destination’s `platformPostId` from `get_post_status`. | | `ayrshare_error` | `true` | The publishing provider refused or failed the request. Read `get_post_status` for the reason before you retry. | ## When a schedule partly fails Scheduling runs per destination underneath, so `schedule_failed` can leave some destinations booked. `scheduledDestinations` lists them, and `refusedDestinations` lists the ones that refused, each with a [code per destination](https://docs.native.no/native-api/errors#destination-codes). The envelope’s `retryable` is `false` when any refusing destination has `media_invalid`, `channel_not_connected`, `channel_needs_reauth` or `content_rejected`. It is `true` otherwise. A retry books only the destinations still outstanding. One exception: when the error carries neither `refusedDestinations` nor `scheduledDestinations`, Native could not say which destination refused, and `retryable` is `false`. Read `get_post_status` for the reason before you change anything. structuredContent.data: ```json { "ok": false, "error": { "code": "schedule_failed", "message": "A sentence Native wrote for people", "retryable": true, "refusedDestinations": [{ "platform": "INSTAGRAM", "code": "provider_unavailable" }], "scheduledDestinations": ["FACEBOOK"] } } ``` Here Facebook is booked and Instagram was refused because the provider was unavailable. Call `schedule_post` again unchanged, and it books only Instagram. ## Failures inside a result Some tools succeed as a whole and report what failed inside the result. Each failure there carries a `code` and a `message`, and all but the dry-run entries carry `retryable` too. - `publish_post_now`: `publishResults[].reason`, one per destination. Only `published` means a destination is live. - `reschedule_posts`: `moves[].error`, one per entry, with the same codes as `schedule_post`. The other entries still ran. - `get_post_batch_status`: `items[].error`, one per post. Item codes are `usage_limit_exceeded`, `ai_generation_failed`, `platform_not_connected`, `platform_not_supported`, `reddit_options_required`, `media_required`, `bad_request` and `internal_error`. - `get_post_status`: `platformResults[].error`, one per destination. - `get_generation_job`: `error`, when a render failed. - New, in development: `schedule_post` and `publish_post_now` with `dryRun: true` return `blocked[]`, what would stop each destination, with `platform`, `code` and `message`. Its codes include the pre-flight codes, `channel_not_connected` and `channel_needs_reauth`, and for `publish_post_now` the skip reasons too. Other codes can appear, such as `brand_inactive`. ### Codes per destination These codes describe why one destination failed. They appear in `platformResults[].error.code`, `publishResults[].reason.code` and `refusedDestinations[].code`. | Code | `retryable` | What to do | | --- | --- | --- | | `channel_not_connected` | `false` | Send the client a link from `create_connect_link`, then retry. | | `channel_needs_reauth` | `false` | The client has to reconnect the account. Send a link from `create_connect_link`, then retry. | | `permission_denied` | `false` | The account lacks a permission, for example a Page role. Reconnect with the right account. | | `rate_limited` | `true` | The platform is rate limiting. Retry later with backoff. | | `provider_timeout` | `true` | Check `get_post_status` first, because it may have gone through. Then retry. | | `provider_unavailable` | `true` | Retry later. | | `media_required` | `false` | Attach or generate media. | | `media_invalid` | `false` | Replace the image or video. The platform refused its aspect ratio, size, format or duration. | | `content_rejected` | `false` | The platform refused the content, for example on policy or length. Edit it, have it approved again, then retry. | | `duplicate_content` | `false` | The platform sees the post as a duplicate. Change the content, or drop the destination. | | `validation_failed` | `false` | Fix what the message names. Do not retry unchanged. | | `unknown_error` | `false` | Do not retry blindly: it may have published. Check `get_post_status`, and contact Native support if it is still unclear. | ### Pre-flight codes Before `schedule_post` contacts any platform, Native checks each destination against that platform’s rules. A destination that fails a rule appears in `blockedDestinations[]` with one of these codes. | Code | What it means | | --- | --- | | `caption_too_long` | The destination’s caption is over its limit. `characterCount` and `characterLimit` say by how much. | | `media_required` | The destination publishes nothing without a picture or a video. | | `image_aspect_invalid` | A picture is outside Instagram’s feed band of 0.75 to 1.91. | | `too_many_images` | The post has more pictures than the destination takes. `imageCount` and `imageLimit` say by how many. | | `video_required` | The destination takes exactly one video and no pictures, as YouTube does. | | `video_not_supported` | The destination publishes no video at all. | | `post_empty` | The post has neither a caption nor a picture. | | `reddit_subreddit_required` | A Reddit destination has no subreddit. | | `reddit_subreddit_not_found` | Reddit does not know the subreddit. | | `reddit_flair_required` | The subreddit requires a flair and the post has none. The flair is chosen in the Native app. | | `reddit_title_too_long` | The Reddit title is over Reddit’s limit. `characterCount` and `characterLimit` can say by how much. | Fix every destination named, or leave those destinations out with `platforms`, before you call again. Unchanged, the call fails the same way. ### Publish results `publish_post_now` answers with `publishResults[]`, one per destination. A destination that did not publish carries a `reason`: | Code | Status | What it means | | --- | --- | --- | | `publishing_in_progress` | `pending` | The platform accepted it and is finishing in the background. Instagram does this routinely. Do not retry. Poll `get_post_status`. | | `already_published` | `skipped` | Already live. Publishing again would duplicate it. | | `held_by_provider` | `skipped` | Booked for a scheduled time. Withdraw it with `unschedule_platform` first if it should go out now. | | `stuck_at_provider` | `skipped` | The provider took it and never reported back. Retrying could duplicate it. Contact Native support. | | `story_partially_published` | `skipped` | Some story frames are live. A retry would duplicate them. | | `not_requested` | `skipped` | Not named in `platforms`. | | A [code per destination](https://docs.native.no/native-api/errors#destination-codes) | `failed` | The destination failed. Follow its `retryable`. | ### Image render codes When an image render fails, `get_generation_job` reports `status: "failed"` and an `error` with one of these codes. To retry a failed render, call `regenerate_post_image` again. Never do that while the job is still running. | Code | `retryable` | What it means | | --- | --- | --- | | `content_rejected` | `false` | The image provider refused the request, for example on content policy. Change the prompt. | | `quota_exhausted` | `false` | The image provider refused the render on quota or billing. | | `provider_timeout` | `true` | The image provider timed out. | | `provider_unavailable` | `true` | The image provider was unavailable or rate limiting. | | `generation_failed` | `true` | The render failed for another reason. | ## Build and connect failures Two flows report failure outside a tool result: a profile build that runs in the background, and an account connection that ends in the client’s browser. Both are new and in development. ### Profile build failures New, in development: when a `create_profile` build fails, `get_profile` reports `onboarding.status: "failed"` and a code in `onboarding.error`: `website_unreachable`, `analysis_failed`, `enrichment_failed`, `build_not_queued` or `build_interrupted`. Treat the list as open: more codes can appear. `create_profile` answers `status: "failed"` only when it could not start the build, in practice with `build_not_queued`. To retry a failed build, call `create_profile` again with the same `externalId`. The build restarts where it stopped and the call answers `building`, so read the code from `get_profile` before you retry. ### Website refusals New, in development: when `create_profile` refuses the website with `invalid_website_url`, nothing is created and `websiteRefusal` says why: | `websiteRefusal` | What it means | | --- | --- | | `BLOCKED_DOMAIN` | A social media or platform address, not the client’s own website. | | `URL_NOT_REACHABLE` | The address does not resolve, or refused the connection. | | `URL_UNSAFE` | The address is flagged as harmful. | | `URL_NOT_ALLOWED` | The address points at a private address. | ### Connect failures New, in development: a headless connect link sends the browser back to your `returnUrl` with `status=failed` and an `error` code, for example `oauth_denied`, `link_expired`, `account_in_another_workspace` or `connection_failed`. Mint a fresh link to retry. Do not poll: a failed connect does not recover on its own. ## Errors outside the envelope These answers come back as an HTTP status before any tool runs, so they carry no envelope. | Status | When | What to do | | --- | --- | --- | | `401` | The token is missing, expired or invalid. The response carries a `WWW-Authenticate` challenge. | Fetch a new token. See [Authentication](https://docs.native.no/authentication). | | `403` `insufficient_scope` | A tool that changes data was called with a token that lacks `mcp:write`. | Request the token with `mcp:read mcp:write`. | | `429` | Too many requests from one IP address in a minute. The edge refuses the request before it reaches a tool. | Slow down and back off. See [Limits](https://docs.native.no/native-api/limits#request-rate). | An argument that breaks a tool’s input schema is rejected by the MCP SDK before the tool runs. You get the SDK’s validation error, not the envelope. Check your call against the `inputSchema` that `tools/list` returns. ## Retrying - Respect `retryable`. Never send a call again after `retryable: false` without changing something first. - After a timeout or a dropped connection on a write, read the current state before you retry, with `get_post_status`, `get_post_batch_status` or `view_post`. A lost response does not prove that nothing changed. - `publish_post_now` is safe to call again. It retries only the destinations that failed and never dispatches a published one again. The skip reasons `stuck_at_provider` and `story_partially_published` need Native support or a new post, not a retry. - A partly refused `reschedule_posts` call is not untouched: the other entries ran. Do not resend the whole list. - Never call `regenerate_post_image` again to retry a render that is still running. Poll its job with `get_generation_job`. - Back off exponentially, with jitter, on errors that are retryable. Stop on errors that are not. ### Repeated calls are not deduplicated For each tool call, the gateway attaches an `Idempotency-Key` to every write it makes to Native, and Native replays the stored result for a repeated key. The transport keeps no state between calls, though, so each tool call gets a fresh random part in its key. **If you send the same tool call again, it is a new attempt, and Native does not deduplicate it.** Caller-supplied idempotency keys are planned, not available. Until then, check the current state before you retry any write that creates or publishes something: `create_posts`, `publish_post_now` and `schedule_post`. New, in development: `create_profile` is idempotent on its own terms. A repeat with the same `externalId` answers with the profile the first call made, with `created: false`. ### Codes you do not recognise New codes can appear at any time for new failure cases. Treat a code you do not recognise as its `retryable` flag says. ## Example A call with the official MCP SDK client, branching on the code: TypeScript: ```ts const result = await client.callTool( { name: "schedule_post", arguments: { id: postId, scheduledAt: "2026-11-02T09:00:00+01:00" }, }, undefined, { timeout: 150_000 }, ); const data = (result.structuredContent as { data: any } | undefined)?.data; if (result.isError) { const error = data.error as { code: string; message: string; retryable: boolean; blockedDestinations?: { platform: string; code: string }[]; }; switch (error.code) { case "schedule_preflight_failed": // Nothing was booked. Fix every destination it names, then call again. for (const blocked of error.blockedDestinations ?? []) { console.warn(blocked.platform, blocked.code); } break; case "approval_stale": // Read the post again with view_post and have it reviewed again. break; default: if (error.retryable) { // Read the current state first, then retry with backoff. } else { // Change something before calling again. Never parse error.message. } } } ```