<?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: Andrei · InTager</title>
    <description>The latest articles on DEV Community by Andrei · InTager (@intager).</description>
    <link>https://dev.to/intager</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%2F4156408%2F0f03b367-0083-4651-872c-94aed719dba2.png</url>
      <title>DEV Community: Andrei · InTager</title>
      <link>https://dev.to/intager</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/intager"/>
    <language>en</language>
    <item>
      <title>Rebuilding a 2020 Instagram app in 2026 — what broke and what replaced it</title>
      <dc:creator>Andrei · InTager</dc:creator>
      <pubDate>Fri, 02 Oct 2026 05:41:10 +0000</pubDate>
      <link>https://dev.to/intager/rebuilding-a-2020-instagram-app-in-2026-what-broke-and-what-replaced-it-3p7n</link>
      <guid>https://dev.to/intager/rebuilding-a-2020-instagram-app-in-2026-what-broke-and-what-replaced-it-3p7n</guid>
      <description>&lt;p&gt;&lt;em&gt;I build InTager, the app this post is about. This post was drafted by an AI assistant from the project's repo history and fact-checked against it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I built InTager in early 2020, stopped in November and came back to it six years later. Almost nothing still worked. Here's what had broken, what I replaced it with, and what I only found out by shipping it again.&lt;/p&gt;

&lt;h2&gt;
  
  
  2020: a neural net, then Rekognition, then Instagram login
&lt;/h2&gt;

&lt;p&gt;It started in February 2020 as a small CNN trained on Instagram posts to predict hashtags for a photo. It struggled with heavily imbalanced classes, so in March I switched to AWS Rekognition for labels and mapped them to hashtags through a synonym dictionary I wrote by hand. The app logged in with Instagram, read your posts and suggested tags. In November 2020 I stopped working on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I found in 2026
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Meta switched off the Instagram Basic Display API on December 4, 2024.&lt;/strong&gt; Login and reading posts both depended on it, and there's no replacement for personal accounts — what's left serves professional ones only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The domain had lapsed.&lt;/strong&gt; API host, OAuth redirect and privacy policy link were all hard-coded to it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Flutter code wouldn't build.&lt;/strong&gt; Pre-null-safety Dart, a 2020 beta channel, widgets that no longer exist, and an in-app purchase plugin from someone's fork.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The old App Store listing had to go too. It promised likes and followers without paying and described matching you with people who follow back. That's an engagement scheme; Apple would reject it today, and they'd be right. The new listing just says what the app does.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I replaced each part with
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The gallery instead of Instagram.&lt;/strong&gt; I wanted it to work for everyone, and the only API left works for professional accounts. So there's no Instagram integration at all now: pick a photo, copy the text, post it yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One model call instead of labels plus a dictionary.&lt;/strong&gt; The backend sends the photo to Claude with a structured-output schema and gets back several captions in different tones plus a list of hashtags in one response. Well under a cent per generation — and it can write captions, which no label API ever could.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One Flask process instead of Lambda.&lt;/strong&gt; The serverless glue, DynamoDB and dead-letter queues went. What's left is a single gunicorn process on Railway with SQLite on a mounted volume. A generation is one person choosing one photo; it writes a few rows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A fresh Flutter project in the same repo, same bundle id.&lt;/strong&gt; Six years of breaking changes were more work than starting over, and what keeps the App Store record (ratings, history) is the bundle id, not the code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The paywall on the server.&lt;/strong&gt; Quota is checked before the model call and charged only after a successful answer, so a provider timeout never costs anyone a generation. Ten free, then one-time packs verified server-side from signed StoreKit 2 transactions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No stored photos.&lt;/strong&gt; Cache key = SHA-256 of the image + language + mood. Only the hash and the text are kept.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I did most of the rebuild together with an AI coding assistant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things I only learned by shipping
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The App Store Connect API can refuse a submission without telling you why&lt;/strong&gt; (&lt;code&gt;This resource cannot be reviewed&lt;/code&gt;). I spent a while guessing. The web UI listed four separate reasons for the same version in one click: an unanswered advertising-identifier question, a stale copyright, a privacy policy URL on the dead domain, and missing iPad screenshots for a universal build.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An In-App Event needs a deep link that opens the app.&lt;/strong&gt; Mine had no URL scheme, so a seasonal event became a new build and another review instead of a metadata edit. Add a scheme in your first release even if you have no plans for events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seasonal content has to come from the server.&lt;/strong&gt; The Halloween mood is a server entry with a title in every interface language, and a test refuses a mood that's missing one. Without that test, a new mood shows up in English in the middle of a Spanish screen.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Then Instagram changed the rules
&lt;/h2&gt;

&lt;p&gt;In December 2025 Instagram announced a cap of five hashtags per post or Reel. A list of twenty or thirty suggestions is now a menu rather than something to paste, and the useful part is choosing the five that describe the photo. I'm reworking the hashtag side around that; for now the app suggests a list and you pick up to five. I wrote down how I choose them: &lt;a href="https://intager-be.up.railway.app/five-hashtags?ref=devto" rel="noopener noreferrer"&gt;How to choose five hashtags for a photo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;InTager is on the App Store for iPhone and iPad — ten generations free, no account (&lt;a href="https://intager-be.up.railway.app/?ref=devto" rel="noopener noreferrer"&gt;site&lt;/a&gt;). Happy to answer questions about the Flutter, StoreKit 2 or App Store Connect parts.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>ios</category>
      <category>ai</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
