Instagram Graph API publishing: limits and errors

    How publishing through the Instagram API works: containers, account types, the daily cap, media rules and the errors it returns, from a working scheduler.

    Updated · 11 min read · Prices and limits checked against the vendors' own pages and APIs on that date.

    You publish to Instagram through the API in two calls: POST /{ig-user-id}/media creates a container that holds your picture or video and caption, and POST /{ig-user-id}/media_publish with that container's creation_id makes it a post. For video the two calls are not enough on their own, because Instagram processes the file in between and the container must report status_code: FINISHED before the second call works. It only works for professional accounts (Business or Creator), never for personal ones, and the media has to sit at a public URL when Instagram fetches it.

    That is the whole shape. What costs people days is everything around it: a daily cap that Meta's own pages state two different ways, JPEG-only pictures, a status field a naive script never reads, and error codes that mean three different things depending on the subcode. This guide comes from the code Preflight runs every day to publish to Instagram, so it also says where publishing fails in practice. Facts were checked against Meta's documentation on 8 October 2026.

    What you need before the first call

    An account Instagram can publish to. The API publishes to Instagram professional accounts: Business or Creator. A personal account cannot be published to by any tool; switching it to Creator or Business is free and done in the Instagram app.

    One of two ways to connect it. Meta now offers two integrations, and they differ in host, token and permission names:

    Instagram API with Instagram LoginInstagram API with Facebook Login
    Hostgraph.instagram.comgraph.facebook.com
    TokenInstagram user access tokenFacebook Page access token
    Publishing permissioninstagram_business_content_publish (with instagram_business_basic)instagram_content_publish (with instagram_basic and pages_read_engagement)
    Needs a Facebook PageNoYes, linked to the Instagram account

    If the user holds a Business Manager role on the Page, Facebook Login also asks for ads_management or ads_read. With Facebook Login, the person connecting needs the MANAGE or CREATE_CONTENT task on the Page, and a Page that requires Page Publishing Authorization or two-factor authentication blocks publishing until that is done.

    Access level. Both integrations work at Standard access for accounts that belong to your own app's team. For anyone else's account you need Advanced access, which means Meta's app review with a screencast of the publishing flow.

    A token that lives. An Instagram Login token is long-lived for 60 days and can be refreshed (graph.instagram.com/refresh_access_token) only while it is still valid; once it lapses, the user has to authorise again. A Facebook Page token obtained from a long-lived user token does not expire. Preflight refreshes the first kind before it runs out and treats a failed refresh as a reason to ask the user to reconnect, not as a transient error.

    The two steps

    Step 1: create the container. The parameters depend on what you publish:

    • Feed picture: image_url, optional caption, optional alt_text (up to 1,000 characters, pictures only).
    • Reel: media_type=REELS, video_url, optional caption, share_to_feed, cover_url or thumb_offset, trial_params.
    • Story: media_type=STORIES with image_url or video_url. No caption.
    • Carousel: one container per item with is_carousel_item=true (no caption on the children), then a parent with media_type=CAROUSEL, children as a comma-separated list of up to 10 container IDs, and the caption.

    video_url takes a public URL. Large files can be sent with upload_type=resumable to rupload.facebook.com, which Meta documents for Facebook Login apps only.

    Step 2: wait. Creating a container returns an ID immediately, and that ID says nothing about whether the media was fetched, decoded and accepted. Read GET /{container-id}?fields=status_code,status until it says:

    status_codeMeaning
    IN_PROGRESSStill processing. Wait.
    FINISHEDReady. Publish now.
    ERRORProcessing failed. The status field carries the reason or a subcode.
    EXPIREDNot published within 24 hours. Create a new container.
    PUBLISHEDAlready published.

    Meta recommends polling once a minute for at most five minutes. Pictures are usually ready within seconds; a long reel can take longer than the five minutes, so a worker that insists on finishing in one run will either time out or hold a slot. Preflight waits about 30 seconds for a video inside the publishing job and then parks the container as pending; a separate job looks once a minute and publishes it the moment Instagram reports FINISHED.

    Step 3: publish. media_publish with the creation_id returns the media ID.

    Why a tool that does not wait reports success and nothing appears. A script that calls media_publish straight after creating a video container gets an error (code 9007, subcode 2207027, "media not ready") at best. A script that swallows the error, or counts the successful container creation as the result, tells the user "published" for a post that was never made. The container call succeeding means only that Instagram accepted the request. Success is a media ID from media_publish.

    What each post type accepts

    TypeMediaLimitsNotes
    Feed pictureJPEG only8 MB; ratio 4:5 to 1.91:1; width 320 to 1,440 px (scaled outside)sRGB; MPO and JPS not supported. Alt text allowed.
    CarouselPictures and video mixed2 to 10 items; every picture is cropped to the first item's ratio, 1:1 by defaultNo captions or locations on children. Reels cannot be items. Counts as one post.
    ReelMP4 or MOV; H.264 or HEVC; AAC3 s to 15 min; 300 MB; 23 to 60 fps; ratio 0.01:1 to 10:1, 9:16 recommended; width up to 1,920 pxmoov atom at the front, no edit lists, closed GOP. Cover: JPEG up to 8 MB.
    StoryJPEG, or video with the Reel codecsVideo 3 to 60 s and 100 MB; picture 8 MB; 9:16 recommendedExpires after 24 hours. No caption. No link, poll or location stickers.
    Trial reelAs a Reeltrial_params with graduation_strategy of MANUAL or SS_PERFORMANCEShown to non-followers first; graduated by hand or on performance.

    Three details trip people up. A phone photo is 3:4, which is 0.75, below the 0.8 floor of 4:5, so it is refused; crop it first. A PNG or WebP is refused outright: the API takes JPEG only. And the published object does not say what it was: a Reel comes back with media_type: VIDEO and a story with IMAGE or VIDEO; read media_product_type to tell them apart. For every network side by side, see the limits table.

    The daily cap

    Meta's documentation gives two numbers for how many posts an account may publish through the API per rolling 24 hours:

    • The content publishing guide says 100 in its rate-limit section, and 50 in its carousel section.
    • The media_publish reference and the content_publishing_limit reference both say 50.

    Treat 50 as the working number, since it is the one the endpoint itself reports (the 100 may reflect an older or planned figure; Meta does not reconcile them). The cap is counted when you call media_publish, not when you create containers, and a carousel counts as one. The window is a moving 24 hours, so a slot frees 24 hours after each individual publish, not at midnight. Creating containers has its own, separate ceiling of 400 per rolling 24 hours.

    You do not have to guess. GET /{ig-user-id}/content_publishing_limit?fields=config,quota_usage returns quota_usage (publishes in the last 24 hours, or since a since Unix timestamp no older than 24 hours) and config.quota_total and config.quota_duration (currently 50 and 86,400 seconds). Without fields, only the usage comes back. Meta's own advice is that apps enforce the limit themselves, especially for scheduled posts.

    When the limit is hit, media_publish fails with code 9, subcode 2207042. It is not permanent. Preflight treats it as a wait: it retries after three hours, up to 12 attempts, which covers a full day of window rolling, instead of failing the post after five quick retries inside the same window. If you build your own, queue by the quota_usage you read and keep a margin, because other tools publishing to the same account share the same count.

    Errors you will meet

    Meta's error-code reference lists these for content publishing (all returned as HTTP 400). The third column is what to do, and where Preflight acts on its own, it says so.

    ErrorMeaningWhat to do
    Code 9, subcode 2207042Daily publishing limit reachedWait for the window to roll. Preflight retries every 3 hours, up to 12 times.
    Code 9007, subcode 2207027Media not readyPoll status_code until FINISHED, then publish.
    Code 36000 / 2207004Image over 8 MiBCompress; Preflight converts and fits it first.
    Code 36001 / 2207005Unsupported image formatSend a JPEG.
    Code 36003 / 2207009Aspect ratio out of rangeCrop to between 4:5 and 1.91:1.
    Code 36004 / 2207010Caption too long, or too many hashtags or mentionsMax 2,200 characters, 30 hashtags, 20 @mentions (a separate code 100 / 2207040 covers mentions).
    Code 352 / 2207026Unsupported video formatMP4 or MOV, with the codecs above.
    Code 9004 / 2207052Media URL could not be fetchedURL must be public, reachable by Meta, and ASCII.
    Code 24 / 2207006Media not foundOften a missing permission or expired token; create a new container.
    Code 24 / 2207008Container does not exist or expiredRetry once or twice over 30 s to 2 min, then create a new container.
    Code -1 / 2207032, -1 / 2207053Media creation or upload failed (53 mostly on video)Create a new container and retry.
    Code 100 / 2207028Carousel with fewer than 2 or more than 10 itemsSend 2 to 10.
    Code 25 / 2207050Account inactive, checkpointed or restrictedThe owner must sign in to the Instagram app and resolve it.
    Code 190Token expired or invalidReconnect the account. Preflight flags it and stops retrying.
    Code 200 or 10Missing permissionAdd the scope and reconnect. Not retried.
    Code 100Bad argumentRead error_user_msg. Not retried.
    Codes 4, 17, 32, 613Rate limiting (app, user, Page, call)Back off; these are measured over an hour. Preflight waits 30 minutes, up to 8 attempts.

    When status_code is ERROR, the status field holds a subcode you can look up in Meta's error codes page. Preflight splits those into two groups: a reason Meta names is the file's fault, so it is permanent and shown to the user; an unnamed "unknown" failure is usually a dropped transcode on Meta's side, which is retried.

    What the API will not do

    • Schedule natively. None of the Instagram endpoints takes a publish time. scheduled_publish_time is a Facebook Page feature; Meta's publishing guide and media_publish reference mention no scheduling at all. Every Instagram scheduler, Preflight included, keeps the post itself and calls the API at the chosen minute. Store the file and the caption, not the container, which expires after 24 hours.
    • Tag people, places and products from every app. user_tags (public accounts, with coordinates on pictures), location_id (a Page ID with location data) and product_tags (Shop catalogue, extra permissions) exist in the reference. collaborators takes up to three usernames. Preflight sends collaborators, alt text, trial reels, the reel cover and share-to-feed, and does not send user tags, locations, product tags, the AI-generated label or audio names.
    • Music. There is no way to attach a track from Instagram's library; audio_name only renames the audio of your own Reel, once.
    • Edit a caption after publishing. The publishing endpoints list no update. Preflight marks Instagram as not editable.
    • Delete. The publishing references say deletion is unsupported. In practice Preflight's code issues DELETE /{media-id} for accounts connected through a Facebook Page, which needs instagram_manage_contents, and tells users that accounts connected directly with Instagram Login cannot delete through the API. We could not find that call in Meta's current reference pages, so treat it as observed behaviour, not documented behaviour, and test it before you promise it.
    • Personal accounts. Not at all, by any app.

    How Preflight handles it

    Everything above runs before and around the two calls. Before upload, Preflight checks the caption length, 30 hashtags and 20 mentions, the 4:5 to 1.91:1 ratio, Reel length and frame rate, story length and size, and the 10-item carousel. A picture that is not a JPEG or is over 8 MB is converted once: turned upright, fitted to 1,440 pixels wide, transparency put on white, and the JPEG handed to Instagram while the original stays in your library. Containers are polled, videos are parked and resumed, transient errors are retried with the delay Meta asks for, token failures are flagged for reconnecting, and the daily cap becomes a three-hour wait. Preflight does not call content_publishing_limit ahead of time; it reacts to code 9, subcode 2207042, which is the one answer the API cannot get wrong.

    These checks run on every plan, the free one included, and the same rules are available through the API for tools that post on your behalf. To connect an account and see what goes out, start at the Instagram integration page; the full rule list is on Instagram limits, and how scheduling works in the product is in scheduling Instagram posts.

    Checked against Meta's Instagram content publishing guide, the IG User media reference and the content_publishing_limit reference on 8 October 2026, and against Preflight's own publishing code.

    Questions people ask

    Can you post to Instagram through the API?
    Yes, to Business and Creator accounts, in two calls: create a media container with POST /{ig-user-id}/media, wait until its status_code is FINISHED, then call POST /{ig-user-id}/media_publish with the container's ID. Feed pictures, carousels, Reels and stories are supported; the media must be at a public URL and pictures must be JPEG.
    Is there a daily limit on Instagram API posts?
    Yes, counted per account over a rolling 24 hours and enforced on media_publish. Meta's publishing guide states 100 in one place and 50 in another, while the content_publishing_limit and media_publish references say 50. Read the live value from content_publishing_limit (config.quota_total) and treat 50 as the working number. A carousel counts as one post, and the failure is code 9, subcode 2207042.
    Do I need a Business account?
    You need a professional account: Business or Creator. A personal account cannot be published to by the API or by any tool. Depending on the integration, the account is connected through a linked Facebook Page (Facebook Login) or directly (Instagram Login), and your app needs the matching content publish permission and, for other people's accounts, Advanced access.
    Can the Instagram API schedule posts?
    Not natively. Meta's Instagram publishing endpoints have no scheduled publish time parameter, and containers expire after 24 hours, so a scheduler stores the post itself and runs the two calls at the chosen time. Preflight does exactly that, and respects the daily cap while doing it.
    Why did my container never publish?
    Usually one of four reasons: the video was still IN_PROGRESS when you called media_publish (error 2207027), so you must poll status_code until FINISHED; the status turned ERROR because the file or format was rejected, so read the status field; the container was older than 24 hours and EXPIRED; or the account was at its daily publishing limit. Creating a container only means Instagram accepted the request, not that a post exists.

    Write it once. Let the rules be our problem.

    Preflight checks every post against each network's rules before it leaves, then publishes it to twelve networks. Free plan, no card.