Guides
Errors & retries
Every error, at the request level or per target, uses one normalised shape, so you (or an agent) can handle failures the same way everywhere.
The error envelope
Errors return { "error": { … } } with a stable type, a human message, an optional platform, and a retryable boolean.
{
"error": {
"type": "entitlement_exceeded",
"message": "Not enough credits: this post costs 5, you have 2.",
"platform": null,
"retryable": false
}
}Two levels of error
- Request-level: the whole call is rejected, because nothing could proceed for any destination. An unknown media id, an unknown account id, no text, bad auth, out of credits. These return a
4xxstatus and the envelope above. Deferred Meta advertising endpoints return501instead, as described below. No post is created. - Per-target: the request was fine, but a network will not take it (over a limit, missing media, invalid option). The call returns
201, the post is created, and the failingtargets[].errorcarries the same envelope. You're not charged for failed targets.
A per-target failure never changes the status code. You get 201 whether one target failed or all of them, and whether the post was immediate or scheduled. So read state and targets[], not just the status:
post state | What happened |
|---|---|
published | Every target went out. |
partial | Some targets published, some failed. Check each one. |
scheduled | At least one target is queued to fire. |
failed | Nothing published, and nothing is queued. On a scheduled post this means every target was refused, so it will never fire. |
Meta advertising features are deferred
The retained ad-account and branded-partner endpoints, Facebook Partnership Ads permission reads and actions, and Facebook branded-content post reads return HTTP 501 with the normal error envelope:
{
"error": {
"type": "not_implemented",
"code": "meta_ads_deferred",
"message": "Meta advertising features are deferred in PostLake. Ad-account and branded-partner lookups, Facebook Partnership Ads permissions, and Facebook branded-content post reads are unavailable. Reconnecting will not enable them.",
"retryable": false
}
}What to do: do not retry or reconnect to enable these features. This is a PostLake feature deferral, not a missing-permission problem that a new connection can fix. Existing SDK methods remain available to call but return this error. Supported non-ad features, including organic publishing, analytics, ordinary tagged posts, and non-ad branded-content publishing, are unaffected.
When a network refuses
These come from the network rather than from us. We recognise the message, attach a code you can branch on, and say what actually fixes it. Anything we do not recognise is passed through unchanged, because a confident wrong instruction is worse than none.
| Code | Network | What to do |
|---|---|---|
account_required | Any | The request did not name the connected account to use. Choose a connected professional Instagram account that was connected through Facebook. |
account_unknown | Any | A connected-account id does not resolve, or is no longer connected. List them with GET /v1/social-accounts, or retrieve one with GET /v1/social-accounts/{id} to check it is still connected. |
analytics_date_invalid | Any | An analytics date is not a valid UTC calendar date. Use YYYY-MM-DD dates in from and to. |
analytics_filter_invalid | Any | An analytics origin filter is unknown. Use dashboard, api or agent for origin; agent identities use stable client IDs. |
analytics_mode_invalid | Any | An analytics mode is unknown. Use period for observed activity or content for lifetime content performance. |
analytics_network_invalid | Any | An analytics network is unknown. Use a platform identifier returned by GET /v1/platforms. |
analytics_range_invalid | Any | An analytics range is reversed or exceeds 365 days. Choose an inclusive range of 1 to 365 UTC days. |
analytics_refresh_cooldown | Any | Another analytics refresh was requested too soon. Poll GET /v1/analytics/refresh and wait five minutes before starting another refresh. |
analytics_refresh_unavailable | Any | Background analytics collection is temporarily unavailable. Read stored results with GET /v1/analytics and try refreshing later. |
aspect_ratio_unsupported | Instagram only accepts aspect ratios between 4:5 and 1.91:1. Crop the image and try again. | |
branded_content_not_enabled | Any | The Page has not turned on branded content partner allowlisting. In Meta Business Suite open the Page, go to branded content settings and turn on partner allowlisting. Reconnecting will not change it. |
branded_content_private | Any | A TikTok post is marked as a paid partnership and set to private at the same time. Branded content has to be visible to somebody. Pick PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS or FOLLOWER_OF_CREATOR, or drop the paid-partnership disclosure. TikTok's own composer greys the private option out for the same reason, and its API refuses the pair. |
cadence_invalid | Any | A cadence preview has an invalid date, size, timezone, daylight-saving time or slot collision. Correct the field named in param using the response guidance. Previewing never changes scheduled posts. |
channel_gone | Any | A target was accepted and the channel was disconnected before it published. This target failed and was not charged. Reconnect the channel and post again; the other targets on the post are unaffected. |
comment_delete_unsupported | Any | The connected network does not allow comments to be deleted through its API. Choose a connected network that supports deleting comments. Check GET /v1/platforms before calling this endpoint. |
comment_hide_unsupported | Any | The connected network does not allow comments to be hidden through its API. Choose a connected network that supports hiding comments. Check GET /v1/platforms before calling this endpoint. |
comment_id_required | Any | A comment moderation request was sent without a comment id. Pass the comment id returned by the comments endpoint. |
creator_marketplace_not_onboarded | Any | The brand has not accepted the creator marketplace terms. Complete the onboarding and accept the terms shown by the selected Meta creator product. The error response's fix names the exact Facebook or Instagram path. Nothing in this API can complete that one-off consent for you. |
creator_search_unsupported | Any | The network has no creator marketplace at all, so there is nothing to search. Check discovers.creators on GET /v1/social-accounts/{id} first. A false there is a fact about the network, not something a reconnect or a permission changes. |
credits_insufficient | Any | The funded credit balance cannot cover a paid operation. Check your balance and buy credits or replenish a paid plan before retrying. Free monthly credits cannot fund X. |
destination_ambiguous | Any | A scheduled post has a Pinterest board override and more than one Pinterest destination. Remove the shared board override or select just one Pinterest board before saving. |
destination_unavailable | Any | A selected scheduled destination is disconnected or needs to reconnect. Reconnect that exact account, or remove it from the final accounts list. |
duplicate_destination | Any | The same social account appears more than once in a scheduled post edit. Include each acc_ id only once in accounts. |
duplicate_post | Facebook, X | Facebook refuses two identical posts in a row. Change the wording and post again. |
field_conflict | Any | Two mutually exclusive ways of selecting destinations were sent together. Send either accounts for the final list or targetAccounts to repair a disconnected target. |
field_invalid | Any | A supplied field has an invalid type or value. Use the field type in the API schema. dryRun is a boolean and snapshot is the string returned by preview. |
field_required | Any | The request did not include any of the fields this endpoint can update. Send at least one of the fields named in the error message, then try the request again. |
field_unknown | Any | The request carried a field we do not recognise, usually a typo. Remove it or fix the spelling. A mistyped field would otherwise be ignored silently, which is how a setting you thought you sent goes missing. |
field_wrong_type | Any | A field was sent as the wrong shape, e.g. a string where an array was expected. `param` names it. Check the field named in `param` against the schema at GET /openapi.json. |
handle_not_a_username | Any | A numeric id was passed where a username was expected. Instagram looks people up by username only. If the id came from a hashtag result, stop: Meta strips the author from those and publishes no way back from a media id or permalink to the person. Creator search is the only route from a post-shaped thing to a contactable account. |
handle_not_found | Any | The network has no account with that handle. Check the spelling. Instagram only answers for professional (business or creator) accounts, so a personal handle comes back as not found. A media or post id is not a handle: Meta publishes no way to get from a post to its author, so a hashtag result cannot be turned into one. |
hashtag_only_search | Any | A multi-word query was sent to a network that only searches hashtags. Instagram has no keyword search. Search a single hashtag with no spaces. Networks with real keyword search (Threads, Bluesky) accept the phrase as-is. |
image_too_large | TikTok | TikTok rejected an image's dimensions after upload. Images must be between 540x960 and 1080x1920. |
instagram_facebook_door_required | Any | The feature exists only on an Instagram connected through Facebook, and this one was connected with Instagram Login. Reconnect through Facebook: GET /v1/connect-link with platform=instagram and variant=facebook. Hashtag discovery and Facebook-only comment controls need that route; direct Instagram Login supports Stories and messaging when granted. Check connectVariant on GET /v1/social-accounts/{id} before calling. |
instagram_messages_switched_off | This Instagram account has message access switched off, which is separate from the permission. In the Instagram app open Settings, then Messages and story replies, then Connected tools, and turn on Allow access to messages. Reconnecting will not fix it. | |
instagram_reply_window_closed | Instagram only allows a reply within 24 hours of the person's last message, and that window has closed. A reply a person writes can use humanAgent: true for 7 days; otherwise answer from the Instagram app. | |
instagram_story_business_required | Any | A Facebook-connected Story targets a known non-Business Instagram account. For Facebook Login Stories, switch the Instagram profile to Business and reconnect its linked Page. Direct Instagram Login also supports Story publishing, subject to Meta's account eligibility checks. |
instagram_story_facebook_connection_required | Any | Legacy error from versions that incorrectly required Facebook Login for Stories. Current PostLake supports both routes. Retry on the current API with placement=story and one image or video. Reconnecting through Facebook is not required for Instagram Login Stories. |
instagram_story_first_comment | Any | A Story includes a first comment, which Stories do not support. Remove the first comment. Put viewer-facing words in the image or video itself. |
instagram_story_media_type | Any | A Story uses an unsupported media type, or PostLake could not convert its PNG image to JPEG. Upload a JPEG or PNG image, or MP4/MOV video. If PNG conversion failed, try another PNG or JPEG. |
instagram_story_one_media | Any | A Story has zero or multiple media items. Attach exactly one JPEG or PNG image, or MP4/MOV video per Story. Make separate posts for multiple frames. |
instagram_story_option_unsupported | Any | A Story includes a feed or Reel-only option. Remove the option named in the error before scheduling. PostLake does not silently ignore Story-incompatible options. |
invalid_agent_name | Any | An agent display name is too long or contains control characters. Use a display name of at most 60 visible characters. |
invalid_agent_name_request | Any | An agent tried to update fields other than its own display name. Send only a nickname string, or null to clear it. This endpoint cannot rename another agent. |
invalid_return_url | Any | The connect-link returnUrl is missing, too long, not a URL, includes credentials or a fragment, or is not https (except localhost). Send an absolute https URL without credentials or a fragment, e.g. https://yourapp.com/social-connected. http is allowed only for localhost during development. |
invalid_webhook_delivery_status | Any | A webhook-delivery filter was set to a status PostLake does not recognise. Use pending or dead for status, then request the delivery list again. |
invalid_webhook_events | Any | Webhook events were not sent as an array. Send an array of supported event names. |
invalid_webhook_url | Any | The webhook URL cannot be parsed as a complete URL. Provide a complete HTTPS URL. |
link_unreachable | Facebook could not read the link in this post. Check the URL opens publicly, then try again. | |
linkedin_video_unsupported | LinkedIn does not accept video posts to a personal profile through the API. Post it as an image or text instead. | |
live_scheduling_unsupported | Any | A planned start time was sent for a Facebook live broadcast. Create the broadcast without plannedStartAt. Start the encoder, then use the live broadcast start endpoint when you are ready to go live. |
media_required | YouTube | YouTube needs the video file itself. Attach a video and try again. |
media_too_large | Bluesky | Bluesky caps images at roughly 1MB. Compress the image and try again. |
media_type_unsupported | Instagram, X | Instagram rejected this file. Use JPEG for images, or MP4 with H.264 video and AAC audio. |
media_unknown | Any | A media id in the request does not resolve. Upload with POST /v1/media and use the med_… id it returns. A media id is scoped to your account, so one from somewhere else will not resolve. |
media_unreadable | Pinterest could not read the image. Make sure it is a public JPEG or PNG under 20MB. | |
meta_identity_check | Facebook wants this Page's identity confirmed before it will publish. Open Meta Business Suite, finish the prompt on the Page, then post again. It is a one-time step. | |
missing_permission | Facebook, Instagram | Facebook has not granted a permission this post needs. Reconnect the Page and accept every permission on the consent screen. |
missing_scope | TikTok | The TikTok connection is missing a permission it needs. Reconnect TikTok and accept every permission on the consent screen. |
needs_reauth | TikTok, Facebook, Instagram, X, LinkedIn, Bluesky, Threads | The TikTok connection has expired. Reconnect TikTok, then post again. |
no_channels | Any | The account has no connected channels at all, so there is nowhere to publish. Connect one first. GET /v1/connect-link returns a signed link a person can open to authorise a network; the API cannot complete an OAuth consent on its own. |
no_matching_accounts | Any | The requested platforms are real, but nothing is connected on them. Connect that network first, or pick one you already have from GET /v1/social-accounts. |
no_target | Any | The request named no destination: no accounts, and either no profile or a profile with nothing connected. Pass `accounts` with acc_… ids from GET /v1/social-accounts, or `profile` with a username that has channels connected. |
option_invalid | YouTube | That YouTube category id does not exist. Pick one YouTube lists for your region. |
own_posts_unsupported | Any | The connected network does not expose its own-post list through its API. Choose a connected network that supports reading its own posts. |
pinterest_board_missing | That Pinterest board no longer exists or is not visible to this account. Pick another board. | |
pinterest_cover_generation_failed | Any | PostLake could not create the required JPEG cover from a Pinterest video. Retry shortly. If it continues, upload a JPEG or PNG cover and pass its media id as platformOptions.pinterest.thumbnail. |
pinterest_site_blocked | Pinterest could not fetch a media URL from our host. Republish: images and covers are uploaded as bytes now, not fetched by Pinterest. | |
place_search_unsupported | Any | The connected network does not provide place search through its API. Choose a connected network that supports place search. Check GET /v1/platforms before calling this endpoint. |
post_changed | Any | The scheduled post changed or began publishing during an edit. Read the post again and retry only if it is still scheduled. |
post_state_invalid | Any | The post is not in a state that allows this operation. Check `state` on GET /v1/posts/{id}. Drafts are edited and deleted; scheduled posts are moved and cancelled; published posts are retracted. |
posts_query_invalid | Any | A dashboard Posts query has an invalid page size, cursor or date range. Correct the field named in param using the response guidance, or clear the filters and start again. |
profile_lookup_unsupported | Any | The connected network does not support profile discovery through its API. Choose a connected network that supports profile discovery. |
profile_required | Any | The account has more than one profile, so which to post to is ambiguous. Pass `profile` with one of the usernames from GET /v1/profiles. |
profile_unknown | Any | The named profile does not exist on this account. List them with GET /v1/profiles and use a `username` from there. |
profile_update_unsupported | Any | The connected network does not allow profile updates through its API. Choose a connected network that supports profile updates. |
publishing_allowance_unsupported | Any | The connected network does not report its remaining publishing allowance through its API. Choose a connected network that reports its own publishing allowance. |
quota_exceeded | YouTube | The daily YouTube API quota is spent. It resets at midnight Pacific time. |
rate_limited | TikTok, Bluesky | TikTok allows 6 requests a minute per account. Wait a minute and try again. |
schedule_immutable | Any | The scheduled post is within 30 seconds of publishing. Do not change destinations this close to publication. Create a separate post if needed. |
schedule_in_past | Any | scheduledAt is not in the future. Send a future ISO timestamp, or omit `scheduledAt` to publish now. |
tagged_posts_unsupported | Any | The connected network does not expose posts that tagged this account through its API. Choose a connected network that supports tagged-post reads. |
target_not_scheduled | Any | A replacement channel was requested for a target that is not scheduled on this post. Read the post again and use a scheduled target account ID in targetAccounts. |
target_platform_mismatch | Any | A replacement channel belongs to a different platform from the scheduled target. Choose a connected channel on the same platform as the target being replaced. |
target_preflight_failed | Any | At least one destination failed validation while requireAllTargets was enabled. Fix the listed target, use a platform-specific media override, or omit requireAllTargets to allow compatible destinations to publish. |
temporarily_blocked | Facebook has temporarily blocked this action, usually after too much activity too quickly. Wait a few hours and try again. | |
text_invalid | YouTube | YouTube rejected the description. Remove any < or > characters and keep it under 5,000. |
text_required | Any | The post has no text. Send a non-empty `text` for feed posts and other destinations that require it. An Instagram Story-only post may use an empty string because Story captions are not displayed. |
text_too_long | X | The post is over X's character limit. Shorten it and try again. |
tiktok_account_restricted | TikTok | TikTok has restricted this account from creating posts. Only TikTok can lift that, so check the account's status in the TikTok app. |
tiktok_privacy_level | TikTok | Pick a privacy setting this account actually offers. Call the publish-info endpoint to see which ones are available to it. |
tiktok_spam_risk | TikTok | TikTok's anti-spam checks blocked this post. Slow the posting rate down and try again later. |
tiktok_too_many_pending | TikTok | TikTok caps you at 5 uploads awaiting action in any 24 hours. Finish or delete the pending ones in the TikTok app, then post again. |
tiktok_unaudited | TikTok | This TikTok app is still in limited mode: the account must be set to Private and the post visibility set to Only me. Change both in the TikTok app and try again. |
tiktok_url_unverified | TikTok | This is ours to fix, not yours. Our media domain needs re-verifying with TikTok. Contact support and we will sort it. |
timezone_invalid | Any | The timezone is not an IANA zone name. Use an IANA zone name, e.g. Europe/London or America/New_York. Abbreviations like GMT or PST are not accepted because several of them are ambiguous. |
timezone_without_schedule | Any | A timezone was sent with no scheduledAt to interpret. Drop `timezone`, or add `scheduledAt`. A timezone only means something when there is a time to interpret. |
title_invalid | YouTube | YouTube rejected the title. It cannot be empty, and cannot contain < or >. |
unknown_webhook_event | Any | At least one requested webhook event is unsupported. Use one of the documented webhook event names. |
webhook_https_required | Any | The webhook endpoint does not use HTTPS. Use an HTTPS endpoint. |
x_account_locked | X | X has locked this account. Sign in at x.com and complete their checks, then try again. |
x_daily_usage_limit | Any | This workspace reached its daily X usage safety budget. Wait until the next UTC day or contact support. Retrying immediately does not reset the budget. |
x_managed_app_required | Any | Customer-owned X app credentials were supplied. X always uses PostLake's app. Use the hosted connection flow once X access is enabled; buying credits does not unlock access. |
x_service_spending_limit | Any | PostLake's shared X app reached its daily safety budget. No paid operation was sent. Try again after the next UTC day or contact support. |
x_unavailable | X | X posting is briefly unavailable on our side. Nothing is wrong with your post or your account. Try again shortly, and contact support if it persists. |
youtube_no_channel | YouTube | This Google account has no YouTube channel. Create one at youtube.com, then reconnect. |
youtube_upload_limit | YouTube | This channel has hit YouTube's upload limit for now. Wait a few hours and try again. |
Checking before you publish costs nothing: POST /v1/posts/validate runs the same checks and returns the same codes and fixes per target, without creating anything.
Error types
| Type | HTTP | Meaning | Retry? |
|---|---|---|---|
invalid_request | 400 | Malformed body or invalid field/option. | Fix & resend |
unauthorized | 401 | Missing or invalid API key. | No |
entitlement_exceeded | 402 | Out of credits, or your plan doesn't allow this (e.g. X/TikTok on free). | After topping up / upgrading |
rate_limited | 429 | Too many requests. | Yes, after a short back-off |
| platform errors | — | A specific network rejected the target (see platform). | Depends on the message |
Error codes
Alongside type, a validation error carries a granular code, the request field at fault in param, a plain-language fix, and a docs link back to this table. Branch on code: it is stable, and it is the only field specific enough to act on. type stays coarse on purpose.
{
"type": "invalid_input",
"message": "TikTok requires at least one media item",
"code": "media_required",
"fix": "TikTok cannot publish text on its own. Attach an image or a video and post again.",
"docs": "https://docs.postlake.dev/errors#media_required",
"param": "media",
"platform": "tiktok",
"retryable": false
}Codes we return before publishing
These are caught before anything is charged or published, so a post that fails them never costs a credit. Call POST /v1/posts/validate to get them without creating a post.
| Code | param | What it means | How to fix it |
|---|---|---|---|
media_required | media | The network cannot publish text on its own. | Attach at least one image or video. TikTok, Instagram, YouTube and Pinterest all require media. |
video_required | media | The network publishes video only. | Attach a video file. YouTube will not take a text or image post. |
video_not_supported | media | The network will not take a video on this post type. | Remove the video, or send an image or text-only version to that network. |
image_not_supported | media | The network will not take images on this post type. | Remove the images, or send the post to a network that supports them. |
media_type_unsupported | media | The file format is rejected outright by that network. | Convert the file. TikTok takes JPEG and WebP, and rejects PNG. |
image_too_large | media | The image is above the network's pixel ceiling. | Resize it. TikTok's ceiling is 1080x1920. Oversized carousels are accepted at upload and fail minutes later, which is why this is checked up front. |
image_too_small | media | The image is below the network's pixel floor. | Use a larger source image. Scaling up to clear the floor will look soft. |
text_too_long | text | The caption is over that network's character limit. | Shorten the text, or set a shorter caption for just that network with textOverrides. |
title_too_long | platformOptions.title | The title is over that network's limit. | Shorten the title. TikTok photo posts allow 90 characters. |
option_invalid | platformOptions.* | A platformOptions value is not one this network accepts. | Set it to one of the listed values. GET /v1/platforms returns the valid set for every option. |
needs_reauth | accounts | The connection to that network has expired. | Reconnect the channel in the dashboard, then post again. No amount of retrying will fix it. |
Codes the networks return
These come back after we have tried to publish. They carry a fix only where we can identify the error with confidence: an unrecognised rejection returns the network's own message and no advice, rather than a guess.
| Code | param | What it means | How to fix it |
|---|---|---|---|
missing_scope | accounts | The connection lacks a permission the post needs. | Reconnect and accept every permission on the network's consent screen. |
duplicate_post | text | The network refuses a post identical to a recent one. | Change the wording and post again. |
rate_limited | — | The network is throttling this account. | Back off and retry. TikTok allows 6 requests a minute per account. |
temporarily_blocked | — | Meta has temporarily blocked the action after too much activity. | Wait a few hours, then try again. |
quota_exceeded | — | The daily API quota for that network is spent. | Wait for the reset. YouTube's is midnight Pacific. |
tiktok_too_many_pending | — | TikTok caps you at 5 uploads awaiting action per 24 hours. | Finish or delete the pending uploads in the TikTok app. |
tiktok_account_restricted | — | TikTok has restricted the account from posting. | Only TikTok can lift this. Check the account in their app. |
tiktok_spam_risk | — | TikTok's anti-spam checks blocked the post. | Slow the posting rate and try again later. |
tiktok_unaudited | — | The TikTok app is in limited mode. | Set the account to Private and the post to Only me. |
link_unreachable | text | The network could not read a link in the post. | Check the URL opens publicly, then try again. |
media_too_large | media | The file is over the network's byte limit. | Compress it. Bluesky caps images near 1MB. |
media_unreadable | media | The network could not read the file. | Check it is a public, valid image or video. |
Retrying safely
When retryable is true (e.g. rate_limited), back off and retry. Always send the same Idempotency-Key on retries so a post that actually went out isn't duplicated. On 429, honour any Retry-After header. Non-retryable errors (unauthorized, invalid_request) need a fix, not a retry.
A 403 that isn't from us
If you get a 403 whose body is a Cloudflare page mentioning error 1010, that is our edge rejecting the request before it reaches the API. It is not an authentication problem, so checking your key will not help.
The usual cause is Python's standard library: urllib.request sends User-Agent: Python-urllib/3.x, which the bot rules block. Set any other agent and it goes through:
req.add_header("User-Agent", "my-app/1.0")
requests, httpx, curl, fetch and the Go client all send their own agent and are unaffected.