Native API· Errors

Native APIReference

Preview

Errors

How a Native API tool call fails: the error envelope, when to retry, and every error code with the fields that come with it.

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.
  • Part of the call failed. Some tools succeed as a whole and report failures per destination or per post. See 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.
  • 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": <envelope> }. content[0].text holds the same envelope as a JSON string.

structuredContent.data

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

  • okbooleanRequired

    Always false on a failure.

  • errorobjectRequired

    What went wrong.

    • codestringRequired

      The failure, as a stable code. Branch on this.

    • messagestringRequired

      A sentence Native wrote for people. It can change at any time, so show it or log it, but never parse it.

    • retryablebooleanRequired

      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.

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

  • invalidFieldsstring[]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.

  • fieldErrorsobject[]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.

    • fieldstringRequired

      The field path.

    • messagestringRequired

      Why it was refused.

  • blockedDestinationsobject[]Optional

    On schedule_preflight_failed, and in a reschedule_posts entry: every destination that will not publish the post as it stands.

    • platformstringRequired

      The destination.

    • codestringRequired

      The rule it breaks. See pre-flight codes.

      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

    • characterCountintegerOptional

      With caption_too_long: the caption’s length. With reddit_title_too_long: the title’s length.

    • characterLimitintegerOptional

      With caption_too_long or reddit_title_too_long: the destination’s limit.

    • imageCountintegerOptional

      With too_many_images: how many pictures the post has.

    • imageLimitintegerOptional

      With too_many_images: how many the destination takes.

  • refusedDestinationsobject[]Optional

    On schedule_failed: every destination the provider refused.

  • scheduledDestinationsstring[]Optional

    On schedule_failed: the destinations that hold a booking even though another one refused.

  • lockedDestinationsobject[]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.

    • platformstringRequired

      The destination.

    • platformPostIdstringRequired

      The id unschedule_platform takes to withdraw it. One call per entry.

  • publishedPlatformsstring[]Optional

    On post_already_published and already_published: the destinations that are live.

  • unavailablePlatformsstring[]Optional

    On platform_not_connected: the destinations the brand has no live connection to.

  • connectedPlatformsstring[]Optional

    On platform_not_connected: everything the brand can publish to now. It may be empty.

  • unsupportedPlatformsstring[]Optional

    On platform_not_supported: the destinations Native does not publish to at all.

  • supportedPlatformsstring[]Optional

    On platform_not_supported: the destinations this brand could publish to.

  • supportedChannelsstring[]Optional

    New, in development: on platform_not_supported from create_connect_link, the channels Native can open a headless link for, in lowercase.

  • overLimitPlatformsstring[]Optional

    On caption_over_limit: the destinations whose limit the caption exceeds.

  • characterCountintegerOptional

    On caption_over_limit: the length of the caption you sent.

  • characterLimitintegerOptional

    On caption_over_limit: the limit the caption has to fit, the shortest among the destinations that refused it.

  • resetDateISO 8601 stringOptional

    On usage_limit_exceeded: when the workspace’s AI budget refills.

  • jobIdstringOptional

    On image_generation_in_progress: the render already running, for get_generation_job. Present when Native can say which render it is.

  • websiteRefusalstringOptional

    New, in development: on invalid_website_url, why the website was refused. See website refusals.

    One of: BLOCKED_DOMAIN, URL_NOT_REACHABLE, URL_UNSAFE, URL_NOT_ALLOWED

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

#
CoderetryableWhat it means
bad_requestfalseInvalid 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_foundfalseThe id is wrong, or it is not yours.
forbiddenfalseThis principal is not allowed to do this.
subscription_requiredfalseThe workspace has no active plan.
writes_disabledfalseWrites are switched off on this deployment. Only tools that change data return it.
internal_errortrueNative failed on its side. Retry shortly.
upstream_timeouttrueNative did not answer within 120 seconds. The work may still be running, so read the current state before you retry.
upstream_unavailabletrueNative could not be reached. Retry with backoff.
upstream_identity_failedtrueThe gateway could not authenticate itself to Native. Retry with backoff.
identity_provider_unavailabletrueThe 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_429trueNative is rate limiting. Back off, then retry.
upstream_<status> for another 4xxfalseNative refused the call with a code the gateway does not recognise, for example upstream_409. Change the call before you send it again.
upstream_<status> for a 5xxtrueNative failed with a code the gateway does not recognise. Retry with backoff.

Partner access

#
CoderetryableWhat it means
partner_onlyfalseNew, in development: a partner-only tool was called without a partner principal. Nothing changed.
partner_not_foundfalseNew, 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_failedtrueNew, 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

#
CoderetryableWhat it means
profile_not_readytrueNew, 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_urlfalseNew, in development: create_profile refused the website before it created anything. websiteRefusal says why.
stripe_billed_workspacefalseNew, 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

#
CoderetryableWhat it means
font_not_foundfalseNew, 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_unreadablefalseNew, 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_httpsfalseNew, in development: upload_brand_logo was given a sourceUrl that is not https. Nothing was fetched. Send the same address as https.
source_not_publicfalseNew, 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_unavailablefalseNew, 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_busytrueNew, 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

#
CoderetryableWhat it means
platform_not_connectedfalseThe 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_supportedfalseNative 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_expiredfalseNew, in development: the connectToken expired or its link died. Mint a fresh link with create_connect_link.
headless_connect_unavailablefalseNew, 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

#
CoderetryableWhat it means
usage_limit_exceededfalseThe workspace’s AI budget is used up. Nothing AI-backed succeeds until resetDate.
ai_generation_failedtrueAI generation failed. Retry.
media_requiredfalseEvery 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_limitfalseThe 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_requiredfalseA REDDIT destination needs reddit: { subreddit }, and none was sent. Send it, or drop REDDIT.
image_generation_in_progressfalseA 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_renderingfalseNew, 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_publishedfalseSome 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 explains the content digest behind these codes.

CoderetryableWhat it means
approval_requiredfalseNew, in development: the workspace requires approval and there is none on record. Partner workspaces require it by default. Record one with approve_post.
approval_stalefalseThe content changed after it was approved. Read the post again, have it reviewed again, and approve it again.
approval_rejectedfalseThe latest decision is a rejection. Editing the post does not clear it. Only a new approve_post does.
design_changed_after_approvalfalseNew, 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_changedfalseThe 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

#
CoderetryableWhat it means
schedule_preflight_failedfalseNative checked the post before it contacted any platform. Nothing was booked and the post is untouched. blockedDestinations names each problem. See pre-flight codes.
schedule_failedVariesAt least one destination was refused at the provider, and others may have booked. refusedDestinations and scheduledDestinations say which. See when a schedule partly fails.
already_scheduled, post_already_scheduledfalseA 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_errortrueThe 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.

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

{
  "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.

CoderetryableWhat to do
channel_not_connectedfalseSend the client a link from create_connect_link, then retry.
channel_needs_reauthfalseThe client has to reconnect the account. Send a link from create_connect_link, then retry.
permission_deniedfalseThe account lacks a permission, for example a Page role. Reconnect with the right account.
rate_limitedtrueThe platform is rate limiting. Retry later with backoff.
provider_timeouttrueCheck get_post_status first, because it may have gone through. Then retry.
provider_unavailabletrueRetry later.
media_requiredfalseAttach or generate media.
media_invalidfalseReplace the image or video. The platform refused its aspect ratio, size, format or duration.
content_rejectedfalseThe platform refused the content, for example on policy or length. Edit it, have it approved again, then retry.
duplicate_contentfalseThe platform sees the post as a duplicate. Change the content, or drop the destination.
validation_failedfalseFix what the message names. Do not retry unchanged.
unknown_errorfalseDo 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.

CodeWhat it means
caption_too_longThe destination’s caption is over its limit. characterCount and characterLimit say by how much.
media_requiredThe destination publishes nothing without a picture or a video.
image_aspect_invalidA picture is outside Instagram’s feed band of 0.75 to 1.91.
too_many_imagesThe post has more pictures than the destination takes. imageCount and imageLimit say by how many.
video_requiredThe destination takes exactly one video and no pictures, as YouTube does.
video_not_supportedThe destination publishes no video at all.
post_emptyThe post has neither a caption nor a picture.
reddit_subreddit_requiredA Reddit destination has no subreddit.
reddit_subreddit_not_foundReddit does not know the subreddit.
reddit_flair_requiredThe subreddit requires a flair and the post has none. The flair is chosen in the Native app.
reddit_title_too_longThe 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:

CodeStatusWhat it means
publishing_in_progresspendingThe platform accepted it and is finishing in the background. Instagram does this routinely. Do not retry. Poll get_post_status.
already_publishedskippedAlready live. Publishing again would duplicate it.
held_by_providerskippedBooked for a scheduled time. Withdraw it with unschedule_platform first if it should go out now.
stuck_at_providerskippedThe provider took it and never reported back. Retrying could duplicate it. Contact Native support.
story_partially_publishedskippedSome story frames are live. A retry would duplicate them.
not_requestedskippedNot named in platforms.
A code per destinationfailedThe 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.

CoderetryableWhat it means
content_rejectedfalseThe image provider refused the request, for example on content policy. Change the prompt.
quota_exhaustedfalseThe image provider refused the render on quota or billing.
provider_timeouttrueThe image provider timed out.
provider_unavailabletrueThe image provider was unavailable or rate limiting.
generation_failedtrueThe 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:

websiteRefusalWhat it means
BLOCKED_DOMAINA social media or platform address, not the client’s own website.
URL_NOT_REACHABLEThe address does not resolve, or refused the connection.
URL_UNSAFEThe address is flagged as harmful.
URL_NOT_ALLOWEDThe 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.

StatusWhenWhat to do
401The token is missing, expired or invalid. The response carries a WWW-Authenticate challenge.Fetch a new token. See Authentication.
403 insufficient_scopeA tool that changes data was called with a token that lacks mcp:write.Request the token with mcp:read mcp:write.
429Too 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.

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

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

The Native API is not open yet

Partner access is being built now. Tell us what you would build, and we will be in touch when it opens.

Contact sales