<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chrononyte</title>
    <description>The latest articles on DEV Community by Chrononyte (@chrononyte).</description>
    <link>https://dev.to/chrononyte</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4051924%2F5a0c17ef-7f32-4937-aaf4-5cae91ba7e8f.png</url>
      <title>DEV Community: Chrononyte</title>
      <link>https://dev.to/chrononyte</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chrononyte"/>
    <language>en</language>
    <item>
      <title>What the Instagram API doesn't tell you about publishing</title>
      <dc:creator>Chrononyte</dc:creator>
      <pubDate>Tue, 28 Jul 2026 19:13:13 +0000</pubDate>
      <link>https://dev.to/chrononyte/what-the-instagram-api-doesnt-tell-you-about-publishing-3m5e</link>
      <guid>https://dev.to/chrononyte/what-the-instagram-api-doesnt-tell-you-about-publishing-3m5e</guid>
      <description>&lt;p&gt;I spent a while building a service that publishes to Instagram on a schedule. Most of that time did not go into the interesting parts. It went into errors whose message points nowhere near the actual cause.&lt;/p&gt;

&lt;p&gt;Here is the list I wish I had found on day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Publishing is two calls, and the first one publishes nothing
&lt;/h2&gt;

&lt;p&gt;You do not post a photo. You create a &lt;strong&gt;media container&lt;/strong&gt;, then you publish that container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /{ig-user-id}/media          -&amp;gt; returns a creation_id
POST /{ig-user-id}/media_publish  -&amp;gt; takes that creation_id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first call is the one that looks like it worked. It returns an id, your workflow goes green, and nothing appears on the profile. That is not a silent failure: a container is a temporary object, and until you publish it, it is not a post.&lt;/p&gt;

&lt;p&gt;If you are debugging "the API says OK but nothing is online", this is almost always it.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Instagram downloads the image itself, from a URL, with no cookies
&lt;/h2&gt;

&lt;p&gt;For images you never upload bytes at all. You give Instagram an address, and &lt;strong&gt;Meta's servers&lt;/strong&gt; go and fetch it. That single fact explains a whole family of errors, the most common being:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Only photo or video can be accepted as media type&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;which usually means: what came back from your URL was not an image. The classic case is a Google Drive share link. It looks like an image in your browser, but to a server with no session it answers with an HTML page, and Meta reads HTML, shrugs, and tells you it is not a photo.&lt;/p&gt;

&lt;p&gt;So the address has to return the raw bytes with an image content type, publicly, no login. A quick check before you wire anything up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sIL&lt;/span&gt; &lt;span class="s2"&gt;"https://your-host/photo.jpg"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at the final status and the content type. If it is not &lt;code&gt;200&lt;/code&gt; and &lt;code&gt;image/jpeg&lt;/code&gt; or &lt;code&gt;image/png&lt;/code&gt;, Instagram will not take it either. While you are there, avoid pointless redirects: an address that answers &lt;code&gt;301&lt;/code&gt; and bounces elsewhere is one more thing that can go wrong in a hop you do not control.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Video is processed asynchronously, and nobody waits for you
&lt;/h2&gt;

&lt;p&gt;Reels and video stories do not behave like images. After you create the container, Instagram has to process the file, and how long that takes depends on the video. Publish immediately and you get errors that read like something else entirely.&lt;/p&gt;

&lt;p&gt;The container carries a status you are supposed to poll until it is finished. Meta's own docs, on the Threads side of the same family, put a number on the wait: &lt;em&gt;"It is recommended to wait on average 30 seconds before publishing a media container to give our server enough time to fully process the upload."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you are scheduling for a specific minute, this changes the shape of your job: you cannot start at that minute. You have to start &lt;strong&gt;before&lt;/strong&gt; it, and publish when both the container is ready and the clock says go.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Carousels have edges that are easy to hit
&lt;/h2&gt;

&lt;p&gt;Between 2 and 10 images. Images only. Each one has to be reachable at its own public address, and they can live on different hosts, which is handy when your slides come from different places.&lt;/p&gt;

&lt;p&gt;The unpleasant part is when the check happens. If your own code accepts eleven images and passes them on, you find out at publish time, from Meta, hours after you queued the post. Every limit you know about is worth checking the moment the user asks for something, not when the job runs. I had this exact bug in my own service, and the fix was five lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The token that expires without telling anyone
&lt;/h2&gt;

&lt;p&gt;Short-lived user tokens, long-lived user tokens, Page tokens. The one you want for publishing is a &lt;strong&gt;Page token&lt;/strong&gt;, and the flow that gets you a permanent one is not the one you land on first. What happens otherwise is that everything works for a while and then quietly stops, usually on the day you are not looking.&lt;/p&gt;

&lt;p&gt;Worth building the "your connection is no longer valid" path early, and making it loud. An automation that fails silently is worse than one that never worked, because you find out from the silence on your own profile.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. What actually helped
&lt;/h2&gt;

&lt;p&gt;Three habits, all boring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Log what you sent, not just what came back.&lt;/strong&gt; Half the 500s I chased were an expression that resolved to nothing, so the request went out with an empty parameter and Meta answered with something generic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the media before sending it.&lt;/strong&gt; Size, format, dimensions, duration. Knowing your own numbers turns "Meta rejected it" into "Meta rejected a 12 MB png", which is an answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail early, with the reason.&lt;/strong&gt; Anything you can check when the post is queued should be checked there.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;None of this is hard once you know it. It is just undocumented in the places you look first, and each item costs an afternoon.&lt;/p&gt;

&lt;p&gt;I ended up turning all of it into a small service, &lt;a href="https://www.chrononyte.com/projects/rubinyun/" rel="noopener noreferrer"&gt;Rubinyun&lt;/a&gt;, because I got tired of rebuilding the same queue: it holds the post, waits for the container, and publishes at the minute you asked for. But the list above is worth having whether or not you use anything of mine, and if you are building it yourself, the two-step flow and the video wait are the parts to get right first.&lt;/p&gt;

</description>
      <category>api</category>
      <category>automation</category>
      <category>webdev</category>
      <category>instagram</category>
    </item>
  </channel>
</rss>
