Breaking changes will be marked when they occur; minor versions stay backward-compatible when possible.
September 2026 (OpenAPI description)
The whole API as an OpenAPI 3.1 file
- +https://status200uploads.com/openapi.yaml describes every endpoint of both URLs in OpenAPI 3.1: the requests, every answer with its fields, the error codes of each status, the Idempotency-Key and the x-status200-retry table of which answers may be sent again. Import it into Postman or Insomnia, or generate a client from it. It is written from the code and checked against the live answers of both URLs
- *A method /api/v2/posts does not answer (for example PUT) is still 405 method_not_allowed, and now carries a message too: every error of /api/v2/posts and of the reads has error.code and error.message (the media endpoint keeps its {"error": "message"} shape)
- */api/v2/posts reads the word Bearer in any capital letters ("Authorization: bearer rl_..."), as the older URL, /api/v2/media and the reads always did. It answered 401 before
- *The docs name every code the API answers with, among them pinterest_board_required, media_wrong_for_post_type, options_outside_post, no_container, the networks' own refusals (pinterest_rejected and x_rejected with the network's own status), the 401s of Instagram and TikTok that ask for a reconnect (RECONNECT_REQUIRED, REFRESH_TOKEN_EXPIRED, NO_TOKEN, and TOKEN_REFRESH_FAILED when a sign-in could not be renewed just now) and the media endpoint's 405 and import_budget_exhausted. They said queue_failed for a post that could not be queued for the next day: that answer has always been 500 api_error. The queued_for_next_day example shows its upgrade_url and status200, and monthly_limit_reached lists all its fields
- ~The differences of the older URL are listed in full (its api_error refusals of the request, the order of its checks, the answers only /api/v2 gives), and a media source is checked with two requests of 8 seconds each, so up to about 16 seconds
- +A "Using Python" guide. A Python package and an n8n node are coming soon; until then any HTTP library and n8n's HTTP Request node do the same
September 2026 (safe retries)
Send a request again safely with an Idempotency-Key
- +POST /api/v2/posts, POST /api/v2/media and the older api-posts and api-media-upload URLs take an optional Idempotency-Key header: 1 to 255 printable ASCII characters naming this one request. The same key with the same JSON body within 24 hours of the first request gets the first answer back, status and body byte for byte, with Idempotent-Replayed: true, and nothing is posted or imported a second time. A request without the header works exactly as before
- +Remembered: every 2xx (published, processing, still_publishing, scheduled, queued; for media a 200 or 202 with its file_id) and an answer whose outcome could not be confirmed, such as a 5xx platform_error after the post was handed to the network. Not remembered: refusals and failures the network reported with their own code, so a fixed request can be sent again with the same key. A key belongs to the API key that sent it; posts and media are kept apart; the two posts URLs share keys, and so do the two media URLs
- +New answers: 400 idempotency_key_invalid (empty, over 255 characters or not printable ASCII), 409 idempotency_in_progress (the first request with the key is still working; Retry-After 5 and retry_after_seconds), 409 idempotency_outcome_unknown (the first request never finished: check GET /api/v2/posts, then use a new key), 422 idempotency_key_reused (the key was used for a different body; first_used_at) and 503 idempotency_unavailable (the key could not be checked; nothing was sent; Retry-After 5). Nothing is sent with any of them
- +An answer sent again takes no 20-second wait, is logged and counted as a request, and never uses the post allowance. GET, DELETE and dry runs ignore the header. CORS allows the header and exposes Idempotent-Replayed and Retry-After
- ~The n8n guide: with the header, Retry On Fail (Max Tries 5, Wait Between Tries 5000 ms) never posts twice a post whose answer was lost, with a key built from the item and the network such as {{ $json.id }}-tiktok (an id that is never empty), never $now or a random value, and the same body on every try
September 2026 (reading through the API)
Read your profiles, posting options and posts, and follow a post with the id every answer now names
- +GET https://status200uploads.com/api/v2/accounts lists your profiles (profile_id, profile_name, handle) and the networks connected to each, with health (ready, reconnect_required or unknown), connected_at and updated_at
- +GET /api/v2/accounts/{profile_id}/options answers what the profile may do on each network, asked of the networks themselves: the TikTok privacy levels post.tiktok.privacyLevel accepts, Pinterest boards for post.pinterest.boardId, Skool groups (and one group's labels, ?skool_group_slug=), the Facebook Page, the Instagram publishing limit and YouTube's choices. One row per network with status ok, not_connected, reconnect_required, unavailable or no_options; always 200 (?platforms= to ask only some)
- +GET /api/v2/posts lists your posts and your scheduled posts that have not produced a post yet (waiting, cancelled, or failed before anything was sent), newest first by at (when a post was sent, when a scheduled post is due). Once a scheduled post has gone out, each network's post is listed instead, with its scheduled_post_id, so nothing is listed twice. limit (1-100, default 20), cursor, status, platform, kind and profile_id filter it; next_cursor and next_url give the next page, and a cursor that is not ours is refused with 400
- +GET /api/v2/posts/{id} reads one post (status, done, link, error, and figures for a successful post: null never means zero; done is true once it will not change by itself, a timeout included once nothing is checking it any more, so a polling loop always ends) or one scheduled post, before and after it goes out (outcome and each network's results)
- +Every POST answer that made a post or a scheduled post, at both URLs and whatever its status, has a new top-level field status200: {post_id, scheduled_post_id, status_url}. status_url is GET /api/v2/posts/{id}. data.post_id and the top-level post_id / scheduled_post_id are unchanged. It is not on a dry run or on a refusal answered before any post was recorded; any answer that left a post in History has it, a failed one included
- +Reads are limited to 60 a minute per account (all its API keys together), and posting options to 10 a minute per account, because each asks the networks themselves: 429 rate_limited with Retry-After, error.retry_after_seconds and error.limit. Reads are not counted in usage_count. Every read answers with Cache-Control: private, no-store
- ~The reads are on status200uploads.com/api/v2 only: GET on the older URL is still 405. CORS on /api/v2/posts now allows GET
- +The AI connector's status200_list_posts also lists posts waiting to go out, cancelled ones and ones that failed before anything was sent, and status200_get_post of a scheduled post shows each network's result once it has gone out
- *The note on a post with no figures in status200_get_post_performance says what is true: figures are collected every 6 hours during the first 7 days after publishing, on the networks that report them. Before, it said an hourly sweep collected them within a few hours
September 2026 (scheduling through the API)Breaking
Schedule a post with post.scheduledFor, and cancel it with DELETE
- ~A post.scheduledFor more than 60 seconds ahead now schedules the post: the answer is 202 with "code": "scheduled", scheduled_post_id and scheduled_at (data.status is "scheduled", and there is no "queued" field), and the post is published at that time, usually within a few minutes of it. Before, it was refused with 400 scheduling_not_available_yet. A past time, "now" or a time within the next 60 seconds still publishes at once
- ~A time must say its time zone: ISO 8601 ending in Z or an offset such as +02:00, a Unix time in seconds or milliseconds, or a date with the month as a word as e-mail and HTTP write it ("Wed, 30 Sep 2026 10:00:00 GMT", "... +0200", "... EDT"). A time without a zone ("2026-09-30T10:00:00", or a date alone) is refused with 400 scheduled_for_invalid and error.reason no_time_zone, also when it is in the past (before, a past one published at once). error.reason is unreadable for a value that is not a date and time (a date written with numbers only, such as 01/10/2026, included: it is January in one country and October in another; before, a past one published at once), and too_far_ahead for a time more than 365 days ahead
- +At most 500 posts can wait per account: the next is refused with 409 scheduled_queue_full. When the waiting posts cannot be counted, the answer is 503 schedule_check_unavailable with Retry-After 5, and nothing was scheduled
- +Scheduling works on every plan, Free included, and a scheduled post takes no 20-second wait. The plan gate is checked at once (Skool needs Beginner or higher, X the X add-on: 403 plan_required). The allowance and the X add-on's meter are checked when the post goes out: over a daily allowance it moves to the same time on the next day; on Free, a post over the month's 60 fails and shows on the Scheduled page and in the failure email
- +mediaUrls of a scheduled post, and a Reel's instagram.coverUrl, are fetched when it goes out, so they must still work then: the answer warns with media_url_not_kept, and an address that already answers "not found" is refused with 400 media_not_available (a cover that is gone by the time the Reel goes out leaves the Reel without its cover). An imported mediaID is kept until the post goes out, however far ahead. Skool's group and title are checked at once for a scheduled post, and skool_one_post_per_hour warns when two scheduled Skool posts of your account are less than an hour apart
- +posts_close_together warns when another post of your account on the same network is scheduled within 20 seconds: posts due together go out one after another with no gap, and a network can refuse posts that arrive that close together. Leave at least a minute between them
- +DELETE https://status200uploads.com/api/v2/posts/{id} (and DELETE https://app.status200uploads.com/functions/v1/api-posts/{id}) cancels a post that has not gone out, whoever scheduled it, with the scheduled_post_id of the 202: 200 with data.status "cancelled"; sent again, 200 with already_cancelled: true; 404 post_not_found; 400 bad_request for an id that is not a UUID; 409 not_cancellable once it is being published, published or failed (error.status says which); on /api/v2, 504 cancel_unconfirmed when the answer did not arrive in time (send it again). Each DELETE is logged and counted once in usage_count
- +A dry run with a time answers the outcome "schedule" (reason code scheduled, status 202) and schedules nothing
- +On /api/v2/posts a request with a time whose answer did not arrive in time is answered 202 schedule_unconfirmed: it may have been scheduled, so check the Scheduled page before sending it again
- ~The AI connector reads a time the same way: status200_schedule_post and status200_update_scheduled_post refuse a scheduled_at without a time zone (invalid_request, and the next step asks for the zone) instead of reading it in the server's own time zone, and a date written with numbers only (01/10/2026) as not a date
Migration Guide
Branch on code: "scheduled" means the post was scheduled for your time, "queued_for_next_day" (with "queued": true) that the daily allowance moved it. Never send a request again after a 202 scheduled: it would schedule the post a second time (a DELETE can always be sent again). Send times with a zone (Z or +hh:mm). For a post more than a few hours ahead, import the file (POST /api/v2/media) and send its mediaID rather than mediaUrls.
September 2026 (options of queued posts)
A post queued for the next day keeps its platform options
- *A pin queued for the next day keeps pinterest.altText and pinterest.dominantColor, as a pin published at once. Before, they were dropped
- ~A pin queued for the next day gets a description only from pinterest.description, as a pin published at once: content.text is its title. Before, content.text was also its description
- *A stored X post keeps x.whoCanReply and x.community (an X post is never queued for the next day: the X add-on has its own allowance, so this matters for scheduled posts)
- *A post queued for the next day keeps instagram.coverUrl, thumbOffset and collaborators (Reels) and locationId (feed images), and tiktok.isAiGenerated on a video, as a post published at once. Before, they were dropped when it went out
September 2026 (checking posts with no answer)
Posts we heard nothing back about are checked with the network
- +When a post was sent but no answer came back (the connection broke, or the network was slower than we can wait), we now ask the network ourselves whether the post exists. Facebook: if we find it on the Page, History shows success with its link; if it is not on the Page after a fair wait, History shows it failed and Retry is safe. Instagram: a post we find shows success. The check runs every 10 minutes and gives its answer within about an hour
- ~A Facebook or TikTok photo post sent through the API whose answer was lost is answered 202 still_publishing with its post_id instead of an error, and History shows "Not confirmed" while it is checked (this replaces "timeout, check before retrying")
- ~A post that may already be live and that the network gives us no way to check (X, LinkedIn, Skool, a TikTok post without a TikTok post number, an Instagram post not found yet) cannot be retried: Retry answers 409 outcome_unknown and History says to check the account first, so the same post is never published twice. If it is not there, publish it again as a new post
September 2026 (one engine)Breaking
One engine behind both API URLs
- ~A Facebook post with no media field is a text post on the older URL too: facebook.postType can be left out. On the older URL a media field sent empty ([] or null) without facebook.postType "text" is still refused with 400, so a failed media step never publishes the text alone
- *A Facebook text post without content.text is refused with 400 bad_request "A Facebook text post needs content.text" before anything is sent (the older URL answered 500 and showed a failed post)
- *On the older URL, a profile UUID with spaces around it is found (was 404), a media field sent as text where a list belongs (for example mediaUrls: "https://…") is answered 400 "post.content.<field> must be a list" (was 404 or 500), and an accountId that is not text, or is blank, is answered 400 "post.accountId is required" (was 500, and 404 for a blank one)
- ~TikTok and YouTube connections marked expired are tried and refreshed on the older URL, as /api/v2 already did. When the network no longer accepts the sign-in, the answer is the network's reconnect error (TikTok 401 REFRESH_TOKEN_EXPIRED or TOKEN_REFRESH_FAILED; YouTube "YouTube token expired. Please reconnect your account.") and History shows a failed post; before, the older URL answered 400 "not connected" without trying
- ~Every Facebook and TikTok post appears in History as processing as soon as it is sent, on both URLs. A post whose connection broke off mid-send shows "timeout, check before retrying" instead of not appearing, and a post our gateway lost the answer for (a 504, not a refusal from Facebook) stays processing until the outcome from Facebook is recorded, instead of showing failed
- ~On /api/v2/posts a network slower than the request can wait is answered 202 still_publishing a little earlier. The answer includes post_id whenever the post is already in History, and the request is counted once in usage_count
- ~A connection that breaks after the request was sent is answered 202 still_publishing instead of 502. 502 upstream_unavailable now always means nothing was sent
- *The API page shows your own IP address and client again for /api/v2/posts calls
- ~/api/v2/posts sends YouTube, Facebook and TikTok posts through the same engine as the older URL. Status codes and fields stay the same
- ~The 20-second wait is checked before the connection, as on the older URL: a request that is both too soon and for a network that is not connected gets 429 (was 400)
- ~A refusal because the Free month is used up no longer starts the 20-second wait
- ~Facebook on /api/v2/posts: an empty media list without facebook.postType is refused with 400, as on the older URL (it used to publish a text post)
- *A single .m4v video, or a video URL with #, is sent as a video post
- ~TikTok on /api/v2/posts: when TikTok does not confirm the upload, the answer is 400 (no_publish_id, or TikTok's error), and when our upload of a video stops it is 502 upload_worker_failed (it did not go through: send it again), as on the older URL. When our upload of a photo post stops after TikTok accepted it, the answer is 202 still_publishing (it may be live: do not send it again, History shows the result). Before, /api/v2 answered 2xx and History showed a failed post
- ~TikTok over the daily allowance whose host does not report the file size in time is refused with 400 tiktok_size_unknown instead of being queued
- +dryRun works on /api/v2/posts: send dryRun: true to check a post without sending it. The answer is 200 with dry_run: true and is not counted in usage_count. dryRun: false publishes as normal, and any other value is refused with 400. A check that gets no report in time is answered 503 dry_run_unavailable with Retry-After: send it again
Migration Guide
If your TikTok step treats any 2xx from /api/v2/posts as success, a 400 no_publish_id or 502 upload_worker_failed now means the upload did not go through: send it again. If your Facebook step sends an empty media list for text posts, leave the media fields out instead.
September 2026 (titles and descriptions)
YouTube and TikTok photo posts take a title and a description
- +YouTube videos and TikTok photo posts take a separate title and description, with the same rule in the dashboard, the API (both URLs, and posts queued for the next day) and the AI connector. YouTube: youtube.title (up to 100 characters) and youtube.description (up to 5,000 bytes). TikTok photo posts: the new tiktok.title (up to 90 characters) and tiktok.description (up to 4,000 characters)
- +What you leave out is filled from content.text (the caption). No title: the caption is the title. No description: the caption is the description when you sent a title (on TikTok, a title that is not the same as the caption). With neither, the caption is also the description when the title cannot hold all of it (on YouTube: more than one line, or over 100 characters; on TikTok: over 90 characters). When you send both fields, content.text is not used, and warnings says so with field_not_used
- ~A title or a description that is too long is shortened at the end of a word, never inside an emoji (a text with no space near the limit is cut where it fits), and < and > are removed from YouTube texts. The API refuses nothing for it: the post is sent, and the warnings in the answer say what was changed, with the codes title_shortened, description_shortened, characters_removed, text_partly_used and title_missing. The dashboard asks you to shorten a description that is too long before it posts
- *A TikTok photo post with a caption over 90 characters was refused by TikTok ("The request post info is empty or incorrect"), because the whole caption was sent as the photo title. This happened to 9 customer posts between July and August 2026. Now the title is shortened and the whole caption is the description
- *A YouTube description is measured in bytes, as YouTube measures it (a letter with an accent counts 2, an emoji 4). A description over 5,000 bytes, or with < or >, used to fail at YouTube after the answer said "processing". It is now shortened or cleaned and sent
- ~A TikTok video has one text, its caption, up to 2,200 characters: tiktok.title when sent, otherwise content.text. tiktok.description is not used on a video, and warnings says so
- *A post queued for the next day keeps its YouTube embeddable, license, publicStatsViewable, notifySubscribers and defaultLanguage options, and a scheduled TikTok photo post keeps its description and automatic music. Before, they were dropped
- ~The check that validate_post runs in the AI connector gives the same warning codes as a real post: for a YouTube title, caption_truncated is now title_shortened (Pinterest keeps caption_truncated), youtube_title_missing is now title_missing, and a YouTube text over 100 characters no longer gives caption_over_limit (it was a false warning). A TikTok photo text that is too long now gives title_shortened and description_shortened
September 2026 (fixes for API callers)Breaking
Profile names in any capital letters, a clear wait on every 429, YouTube titles, no early posts
- ~accountId finds a profile whatever the capital letters are, on both URLs: "@MyShop" finds "myshop". If two of your profiles differ only in capital letters, send the exact name or the profile UUID
- ~On the older URL (app.status200uploads.com/functions/v1/api-posts) an unknown profile is still answered 404 api_error, now with error.profiles listing your profiles, and the refusal no longer starts the 20-second wait
- +Every 429 carries retry_after_seconds in its body (error.retry_after_seconds on posts, retry_after_seconds on media imports), for tools such as n8n that read only the body. A throttled media import now also has a Retry-After header and "code": "rate_limited", on both URLs. The Retry-After header is sent only when the wait is an hour or less
- ~A post.scheduledFor more than 60 seconds in the future is refused with 400 and nothing is sent. Before, the field was ignored and the post went out at once, earlier than planned. A time in the past or within the next 60 seconds is still ignored. The time can be ISO 8601 with a time zone or a Unix time in seconds or milliseconds
- +A successful answer can carry a warnings array naming each field of the request that was not used and why (for example tiktok.privacyStatus instead of privacyLevel, an option block next to post instead of inside it, X madeWithAi and paidPartnership, LinkedIn carousel)
- +youtube.title sets the YouTube title, also for a post queued for the next day. When a title is sent and youtube.description is empty, content.text becomes the description. Without a title, the title still comes from content.text
- ~TikTok photo posts: PNG and GIF photos are sent to TikTok as JPEG copies so that the post can be published, because TikTok does not accept those formats; JPEG and WebP are sent as they are. HEIC, AVIF, BMP and TIFF photos are refused with 422 photo_format_not_supported before anything is sent. The warnings in the answer say when a photo was converted, and your original file is not changed
- ~The request count of an API key (usage_count, shown on the API page of the dashboard) now counts every request to the older URL, refused ones included, as /api/v2 already does. Before, it counted only posts that were sent or queued
Migration Guide
If you send post.scheduledFor with a future time, remove it to publish at once, or schedule the post in the dashboard. Scheduling through the API is planned.
September 2026 (large videos)
Videos up to 5 GB on X, LinkedIn, Facebook and Skool, 4 GB on TikTok
- ~Video limits: X, LinkedIn, Facebook and Skool 5 GB (5,368,709,120 bytes), TikTok 4 GB (4,000,000,000 bytes). YouTube stays at 5 GB, Instagram at 300 MB, Threads at 1 GB
- +A single video over 100 MB (X, Skool), 200 MB (LinkedIn) or 1 GB (Facebook) is sent in the background: 202 with data.status "processing", a job_id and a post_id; the result appears in History. Smaller videos answer as before
- +A TikTok video over 1 GB from our storage is fetched by TikTok itself; the answer is 200 "processing" with a publish_id, as for every TikTok post
September 2026 (pricing)Breaking
X add-on, Free monthly allowance, Skool from Beginner
- ~X posts need the X add-on: 403 plan_required with error.required "x_addon"
- +429 x_limit_reached / x_link_limit_reached when the X add-on's 200 posts or 20 link posts of the billing month are used, with resets_at
- ~Free over 60 successful posts per calendar month: 429 monthly_limit_reached with resets_at, not queued
- ~Skool from Beginner
- ~Skilled, Expert and Agency have no allowance; publish attempts capped at 500 per profile per UTC day
September 2026 (update)Breaking
Clearer errors, answers within 25 seconds, throttle on every network
- +A mediaID that does not exist on your account (never imported, deleted, or removed after 7 days) is answered at once with 404 media_not_found instead of 409 media_processing
- +POST /api/v2/media refuses a URL that does not return an image or a video with 415, and GET /api/v2/media answers 400 for a file_id that is not a UUID
- ~Every /api/v2/posts answer arrives within about 25 seconds: media still importing is waited for about 12 seconds (then 409 media_processing), and a network slower than that is answered 202 still_publishing (do not resend; the outcome appears in the post history)
- ~The 20-second throttle per network now also applies to TikTok, Facebook and YouTube, and a throttled post carries a Retry-After header
- ~An unknown profile is answered 403 account_not_found on every network, and every error from /api/v2/posts carries error.code
- ~A Facebook post without media no longer needs facebook.postType "text"
- ~X, Pinterest and Skool refusals come back as 4xx or 503 with their own codes (x_rejected, x_unavailable, pinterest_auth_failed, pinterest_sandbox_board, pinterest_rejected, skool_unavailable) instead of 500
- ~YouTube uploads answer 202 with a post_id, and the post history shows the upload's real outcome
- *TikTok photo posts over the size limit are refused with 413 MEDIA_TOO_LARGE before anything is sent
- *Unknown paths under /api/v2 answer a JSON 404 not_found
- !TikTok answers no longer include TikTok's temporary upload_url and upload_token
- +A mediaID whose import failed is answered 422 media_failed with the reason (was 500)
- ~Throttled posts carry error.code rate_limited, and Skool's own limits (one post per hour, 10 per day) answer 429 rate_limited with Retry-After instead of 500
- ~Facebook and LinkedIn answer 401 reconnect_required when they no longer accept the saved sign-in (also on image and video uploads), instead of 500
- ~403 plan_required answers include upgrade_url
September 2026
Large media imports, size limits up front, Threads
- +Media import accepts videos up to 5 GB. Large imports run in the background, report their progress on GET /api/v2/media?file_id=... and continue automatically after an interruption when the source server supports HTTP Range requests
- +Threads posting (platform "threads"): text only, one image or video, or a carousel of 2 to 20 items
- +accountId on /api/v2/posts accepts "@handle" or the profile name as well as the profile UUID, and /api/v2/posts accepts every network
- ~File size limits per network are checked before anything is sent: a file that is too large for the chosen network is refused with 413 MEDIA_TOO_LARGE
- ~A post whose media is still being imported is answered with 409 media_processing and a Retry-After header instead of a timeout
- ~The daily allowance counts successful posts only: a failed post no longer uses it
- ~A Pinterest post without a board id is refused with 400 before Pinterest is called
- *A single video sent to Instagram without a postType is published as a Reel
August 2026
Plan limits enforced on the server
- ~Plan limits are enforced on the server for every caller (dashboard, scheduler and API): a daily allowance per profile across all platforms, and X and Skool from the Apprentice plan
- ~A post over the daily allowance is queued for the next day (202 queued_for_next_day) instead of failing
July 2026
Skool
- +Skool community posts (platform "skool") with text, images and video
April 2026
X, LinkedIn and Pinterest
- +X (Twitter) posts with text, images and video, reply settings and Communities
- +LinkedIn posts with text, image or video
- +Pinterest image pins with board selection
March 2026
Instagram, Facebook and YouTube
- +Instagram (feed image, Reel, Story, carousel), Facebook (photo, video, Reel, text) and YouTube video posts
August 2025
Initial release
- +Create a post endpoint, starting with TikTok
- +Media import from a public URL: the returned file_id can be reused across posts
- +API keys with Bearer token authentication