<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Datapopcorn Tech Blog</title>
        <link>https://datapopcorn.ai/blog/en</link>
        <description>AI automation experiments with n8n, Claude, Codex, AI agents and Remotion</description>
        <lastBuildDate>Wed, 28 Oct 2026 04:30:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>Copyright © 2026 Datapopcorn</copyright>
        <item>
            <title><![CDATA[I Tried Gemini 4 Argon: Turning It On with the Pro Plan, Same Questions vs. Flash, and a 3D Game]]></title>
            <link>https://datapopcorn.ai/blog/en/gemini-4-argon-review</link>
            <guid>https://datapopcorn.ai/blog/en/gemini-4-argon-review</guid>
            <pubDate>Wed, 28 Oct 2026 04:30:00 GMT</pubDate>
            <description><![CDATA[My notes from turning on Gemini 4 Argon in the Gemini app with a Google AI Pro account and running the same questions side by side against 3.8 Flash and other models. What was different on a short question, 16 AI trick questions like 'the car wash is 3 minutes away', a data-interpretation trap and building a 3D game in Canvas, plus usage limits and API pricing.]]></description>
            <content:encoded><![CDATA[<p>Google rolled out Gemini 4 Argon in the Gemini app on October 13. I assumed it would be Ultra-only, but it shows up right away in the model picker on a Google AI Pro account too. So I took one Pro account and gave the same three questions to Argon and to the default model, 3.8 Flash, and I asked 16 classic "AI trick questions" to all four models in the app. Short version: on short questions there's almost no difference, but on analysis where instructions have to be followed, and on building things in Canvas, the gap was easy to see.</p>
<!-- -->
<p><img decoding="async" loading="lazy" alt="Gameplay screen of a 3D popcorn-catching game that Gemini 4 Argon built in Canvas" src="https://datapopcorn.ai/blog/en/assets/images/07-argon-game-play-2df3e2efee08aa8644922f43753e9b39.webp" width="1500" height="966" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-short-answer">The short answer<a href="https://datapopcorn.ai/blog/en/gemini-4-argon-review#the-short-answer" class="hash-link" aria-label="Direct link to The short answer" title="Direct link to The short answer" translate="no">​</a></h2>
<ul>
<li class=""><strong>Who can use it:</strong> In the Gemini app, Google AI Pro and Ultra subscribers. It isn't available on free accounts or AI Plus.</li>
<li class=""><strong>Short questions:</strong> Flash gave almost the same conclusion, length and tone. If you want to save your limits, Flash is enough.</li>
<li class=""><strong>16 AI trick questions:</strong> Argon got all 16 right, and 3.1 Pro missed one, the father-surgeon riddle. The lightest model, 3.5 Flash-Lite, missed every question about "why are you going there," like the car wash, the gas station and the dog groomer.</li>
<li class=""><strong>Analysis with tricky instructions:</strong> Both got the right answer, but only Argon followed the format instruction "one-line conclusion first."</li>
<li class=""><strong>Building in Canvas:</strong> The biggest quality gap was in a 3D game built from the same prompt. Argon's version was ready to play as is.</li>
<li class=""><strong>Watch out for:</strong> You work within usage limits that reset every 5 hours. Google hasn't published a separate count for Argon.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="who-this-is-for">Who this is for<a href="https://datapopcorn.ai/blog/en/gemini-4-argon-review#who-this-is-for" class="hash-link" aria-label="Direct link to Who this is for" title="Direct link to Who this is for" translate="no">​</a></h2>
<ul>
<li class="">You subscribe to Gemini Pro and want a feel for when to turn Argon on and when to leave it off</li>
<li class="">You use ChatGPT or Claude and want to see, on real screens rather than benchmark numbers, whether Google's new model is actually better for work</li>
<li class="">You don't code but want to build a small web tool or game with Canvas</li>
</ul>
<p>All you need is a Google account with Google AI Pro or higher. Every test in this post was run on October 28, 2026, in the Gemini web app on a Google AI Pro account. I left the thinking level at the default, "Low."</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1-check-that-4-argon-appears-in-the-model-menu">Step 1. Check that 4 Argon appears in the model menu<a href="https://datapopcorn.ai/blog/en/gemini-4-argon-review#step-1-check-that-4-argon-appears-in-the-model-menu" class="hash-link" aria-label="Direct link to Step 1. Check that 4 Argon appears in the model menu" title="Direct link to Step 1. Check that 4 Argon appears in the model menu" translate="no">​</a></h2>
<p>Go to <a href="https://gemini.google.com/" target="_blank" rel="noopener noreferrer" class="">gemini.google.com</a> and click the model name to the right of the input box (Flash by default). On a Pro account, <strong>4 Argon · Complex tasks</strong> is at the bottom of the list. Below it, under <strong>Thinking level</strong>, you can pick Low, Medium or High. (My screenshots show the Korean UI.)</p>
<p><img decoding="async" loading="lazy" alt="The Gemini app model picker showing 3.5 Flash-Lite, 3.8 Flash, 3.1 Pro, 4 Argon and the thinking level menu" src="https://datapopcorn.ai/blog/en/assets/images/01-model-picker-ef11d2e20f922c4b2bb004c05b4a7b1a.webp" width="1000" height="792" class="img_ev3q"></p>
<p>According to Google's announcement, <a href="https://blog.google/innovation-and-ai/products/gemini-app/gemini-4-argon-availability/">Argon in the Gemini app is open to Google AI Pro and Ultra subscribers</a>. It's also available in the developer API (AI Studio and the paid tier of the Gemini API), in Gemini for business and in AI Mode in Search, and the coding tool Antigravity opened first to Ultra subscribers. If Argon isn't in your menu, it's usually your plan. For work or school accounts, it depends on which add-ons your admin has enabled.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2-run-a-short-question-side-by-side-with-flash">Step 2. Run a short question side by side with Flash<a href="https://datapopcorn.ai/blog/en/gemini-4-argon-review#step-2-run-a-short-question-side-by-side-with-flash" class="hash-link" aria-label="Direct link to Step 2. Run a short question side by side with Flash" title="Direct link to Step 2. Run a short question side by side with Flash" translate="no">​</a></h2>
<p>For the first question I picked the kind office workers actually ask a lot.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">I want to automate the weekly sales report I build in Excel every week at work.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Between n8n, Google Apps Script and Python, which one should a non-developer office worker pick?</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Pick just one, and give the reasons in 3 lines or fewer.</span><br></div></code></pre></div></div>
<p>In about 10 seconds, Argon answered in one line, "I recommend n8n," and added exactly 3 lines of reasons. No filler greeting, no "it depends on your situation."</p>
<p><img decoding="async" loading="lazy" alt="Argon recommending n8n and giving its reasons in 3 lines" src="https://datapopcorn.ai/blog/en/assets/images/02-argon-direct-answer-04e8da4d995e3dda0f3540394aebe950.webp" width="1200" height="714" class="img_ev3q"></p>
<p>I put the same question to 3.8 Flash. It took about 15 seconds, also picked n8n and also gave 3 lines of reasons. Google says Argon <a href="https://blog.google/innovation-and-ai/products/gemini-app/gemini-4-argon-availability/">hedges less and answers more directly</a>, but for a question this short and clear, Flash already answers that way. <strong>Using Argon for short questions is close to wasting your limit.</strong></p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>I left 3.1 Pro out of the comparison</div><div class="admonitionContent_BuS1"><p>When I gave the same question to 3.1 Pro, I got an odd refusal, "I'm not programmed to do that," and retrying produced similar wording. It looked like a temporary glitch, so I left it out of this comparison and brought it back for the trick questions in the next step. The same refusal showed up on the first try several times there too. How to handle refusals like this is in the table at the end of the post.</p></div></div>
<p><img decoding="async" loading="lazy" alt="3.1 Pro answering an ordinary question with &amp;quot;I&amp;#39;m not programmed to do that&amp;quot;" src="https://datapopcorn.ai/blog/en/assets/images/03-pro-canned-refusal-9b0e7cb05aabc7274013096526bfcd1d.webp" width="1200" height="595" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3-check-common-sense-with-16-ai-trick-questions">Step 3. Check common sense with 16 "AI trick questions"<a href="https://datapopcorn.ai/blog/en/gemini-4-argon-review#step-3-check-common-sense-with-16-ai-trick-questions" class="hash-link" aria-label="Direct link to Step 3. Check common sense with 16 &quot;AI trick questions&quot;" title="Direct link to Step 3. Check common sense with 16 &quot;AI trick questions&quot;" translate="no">​</a></h2>
<p>Every time a new model comes out, a set of trick questions makes the rounds. The best known is this one.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">The car wash is a 3-minute walk away. Do I really need to drive my car there?</span><br></div></code></pre></div></div>
<p>A person answers right away: the whole point of going to a car wash is to wash the car, so of course you take it. AI models often get pulled in by the "3-minute walk" and tell you to walk. When a question looks familiar, the model reaches for the familiar answer without checking the purpose.</p>
<p>I collected 16 trick questions of this kind, grouped by type, and asked all of them to each of the four models in the Gemini app. Every question went into a fresh chat, in Korean, with the thinking level left at the default.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="results-at-a-glance">Results at a glance<a href="https://datapopcorn.ai/blog/en/gemini-4-argon-review#results-at-a-glance" class="hash-link" aria-label="Direct link to Results at a glance" title="Direct link to Results at a glance" translate="no">​</a></h3>
<p>○ means a correct answer, ✕ a wrong one, and △ an answer that flip-flopped on its conclusion.</p>
<table><thead><tr><th>Type</th><th>Question</th><th>What a person would say</th><th>4 Argon</th><th>3.1 Pro</th><th>3.8 Flash</th><th>3.5 Flash-Lite</th></tr></thead><tbody><tr><td>Purpose trap</td><td>The car wash is a 3-minute walk away. Do I need to drive the car there?</td><td>Take the car</td><td>○</td><td>○</td><td>○</td><td><strong>✕</strong></td></tr><tr><td>Purpose trap</td><td>The gas station is a 2-minute walk away. Can I walk there to fill up?</td><td>Take the car</td><td>○</td><td>○</td><td>○</td><td><strong>✕</strong></td></tr><tr><td>Purpose trap</td><td>The dog groomer is a 5-minute walk away. I can leave the dog at home, right?</td><td>Bring the dog</td><td>○</td><td>○</td><td>No response</td><td><strong>✕</strong></td></tr><tr><td>Control</td><td>The garage is right in front of me. Should I leave my car with a flat tire and walk over to ask them to fix it?</td><td>Walking is right</td><td>○</td><td>○</td><td>No response</td><td>○</td></tr><tr><td>Twisted puzzle</td><td>A farmer and one sheep need to cross a river. The boat holds two. How?</td><td>Cross together once</td><td>○</td><td>○</td><td>No response</td><td>○</td></tr><tr><td>Twisted puzzle</td><td>Monty Hall, but all three doors are see-through. I picked the door with the car. Should I switch?</td><td>Don't switch</td><td>○</td><td>○</td><td>No response</td><td>△</td></tr><tr><td>Twisted puzzle</td><td>Which is heavier, 1kg of iron or 2kg of feathers?</td><td>The feathers</td><td>○</td><td>○</td><td>No response</td><td>○</td></tr><tr><td>Twisted puzzle</td><td>The surgeon, <strong>who is the boy's father</strong>, says "I can't operate, he's my son." How is that possible?</td><td>He's the father, as stated</td><td>○</td><td><strong>✕</strong></td><td>No response</td><td><strong>✕</strong></td></tr><tr><td>Letters and numbers</td><td>How many r's are in strawberry?</td><td>3</td><td>○</td><td>○</td><td>No response</td><td>○</td></tr><tr><td>Letters and numbers</td><td>Which is bigger, 9.11 or 9.9?</td><td>9.9</td><td>○</td><td>○</td><td>No response</td><td>○</td></tr><tr><td>Letters and numbers</td><td>How many ㄴ are in '바나나' (banana)?</td><td>2</td><td>○</td><td>○</td><td>No response</td><td>○</td></tr><tr><td>Parallel time</td><td>5 shirts take 5 hours to dry. How long for 30?</td><td>5 hours</td><td>○</td><td>○</td><td>No response</td><td>○</td></tr><tr><td>Parallel time</td><td>One egg takes 10 minutes to boil. How long for 3?</td><td>10 minutes</td><td>○</td><td>○</td><td>No response</td><td>○</td></tr><tr><td>Family</td><td>Alice has 3 older brothers and 2 older sisters. How many sisters does Alice's brother have?</td><td>3</td><td>○</td><td>○</td><td>No response</td><td>○</td></tr><tr><td>False premise</td><td>Tell me about the incident where King Sejong threw a MacBook</td><td>It never happened</td><td>○</td><td>○</td><td>No response</td><td>○</td></tr><tr><td>False premise</td><td>What was the naval battle where Admiral Yi Sun-sin sank a Japanese aircraft carrier with a turtle ship?</td><td>There was no such battle</td><td>○</td><td>○</td><td>No response</td><td>○</td></tr><tr><td></td><td><strong>Total</strong></td><td></td><td><strong>16/16</strong></td><td><strong>15/16</strong></td><td>2/2</td><td><strong>11/16</strong></td></tr></tbody></table>
<p>After answering the car wash and gas station questions, 3.8 Flash stopped responding for the rest of the evening. Each question showed the generating indicator for about a minute and a half, then went back to a blank screen. Reloading and temporary chats did the same, so most of the Flash column is empty.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="argon-got-all-16-right">Argon got all 16 right<a href="https://datapopcorn.ai/blog/en/gemini-4-argon-review#argon-got-all-16-right" class="hash-link" aria-label="Direct link to Argon got all 16 right" title="Direct link to Argon got all 16 right" translate="no">​</a></h3>
<p>It didn't just land on the right answer; it pointed out where the trap was. On the father-surgeon question it said first that "the question already states that the surgeon is the boy's father," and then explained that in the original riddle the answer is the mother. On the dog groomer question it joked that if you leave the dog at home, the groomer will end up trimming you instead, and then told you to bring the dog.</p>
<p><img decoding="async" loading="lazy" alt="Argon answering that if you walk, you&amp;#39;ll be the one getting washed, and suggesting alternatives" src="https://datapopcorn.ai/blog/en/assets/images/10-carwash-argon-e325bf63e7ef2de132f10967566019cb.webp" width="1100" height="760" class="img_ev3q"></p>
<p>The flat-tire question, included as a control, is worth a look. It looks just like the car wash question, but driving on a flat can ruin the wheel, so leaving the car and walking over is the right call. Argon said "don't drive it there as it is" and suggested asking the mechanic to bring equipment over. In other words, having seen plenty of trick questions didn't make it answer "take the car" by reflex. 3.1 Pro and Flash-Lite also got this one right.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="31-pro-got-pulled-in-by-a-famous-riddle">3.1 Pro got pulled in by a famous riddle<a href="https://datapopcorn.ai/blog/en/gemini-4-argon-review#31-pro-got-pulled-in-by-a-famous-riddle" class="hash-link" aria-label="Direct link to 3.1 Pro got pulled in by a famous riddle" title="Direct link to 3.1 Pro got pulled in by a famous riddle" translate="no">​</a></h3>
<p>On the father-surgeon question, 3.1 Pro answered "the surgeon is the boy's mother." The question clearly says "the surgeon, who is the boy's father," yet it brought over the answer to the original riddle. It's the same kind of mistake as the car wash question.</p>
<p><img decoding="async" loading="lazy" alt="3.1 Pro answering that the surgeon, stated to be the father, is the boy&amp;#39;s mother" src="https://datapopcorn.ai/blog/en/assets/images/18-surgeon-pro-6f14a06900a8a4720dd3cf06cbfb0dec.webp" width="1100" height="364" class="img_ev3q"></p>
<p>With 3.1 Pro, the bigger problem was unstable responses rather than wrong answers. On 4 of the 16 questions (flat tire, river crossing, Monty Hall, iron vs. feathers), the first attempt got an odd refusal like "I'm just a language model, so I can't help with that," and asking again got a normal answer. The table uses the answers from the retry.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="flash-lite-missed-every-purpose-trap">Flash-Lite missed every purpose trap<a href="https://datapopcorn.ai/blog/en/gemini-4-argon-review#flash-lite-missed-every-purpose-trap" class="hash-link" aria-label="Direct link to Flash-Lite missed every purpose trap" title="Direct link to Flash-Lite missed every purpose trap" translate="no">​</a></h3>
<p>The lightest model, Flash-Lite, got all three purpose questions wrong. To the car wash question it said "I strongly recommend not driving the car there!", and to the dog groomer question, "Yes, of course you can leave the dog at home!" The trouble is that it attached three reasons to each wrong conclusion, which makes it look more convincing. On the gas station question it never said to bring the car and only explained, at length, the safety rules for carrying fuel in a can.</p>
<p><img decoding="async" loading="lazy" alt="Flash-Lite answering that you can leave the dog at home, with three reasons" src="https://datapopcorn.ai/blog/en/assets/images/14-dog-flash-lite-a8aa658cdfc2c872f6705a44e5b08cb1.webp" width="1100" height="877" class="img_ev3q"></p>
<p>On the father-surgeon question it invented a new answer, that the surgeon is the boy's "other father." On the see-through Monty Hall question it opened with "switching doesn't change the odds" and then later said "keeping the door you picked is right."</p>
<p><img decoding="async" loading="lazy" alt="Flash-Lite answering that the surgeon, stated to be the father, is the boy&amp;#39;s &amp;quot;other father&amp;quot;" src="https://datapopcorn.ai/blog/en/assets/images/15-surgeon-flash-lite-e0707c61fd270241d50ad39a434c7b8d.webp" width="1100" height="603" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-more-famous-the-question-the-less-it-tests">The more famous the question, the less it tests<a href="https://datapopcorn.ai/blog/en/gemini-4-argon-review#the-more-famous-the-question-the-less-it-tests" class="hash-link" aria-label="Direct link to The more famous the question, the less it tests" title="Direct link to The more famous the question, the less it tests" translate="no">​</a></h3>
<p>All four models got the r's in strawberry, 9.11 vs. 9.9 and the King Sejong MacBook question right. On the Sejong question, every model explained it as "a well-known internet meme that came from a ChatGPT hallucination in 2023." It has become so famous that the answer is now in the training data. On the other hand, a famous riddle with <strong>one detail changed</strong>, like the father surgeon, tripped up even 3.1 Pro. If you want to test a new model yourself, changing one condition in a famous question reveals far more than asking it as is.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-to-remember-in-daily-use">What to remember in daily use<a href="https://datapopcorn.ai/blog/en/gemini-4-argon-review#what-to-remember-in-daily-use" class="hash-link" aria-label="Direct link to What to remember in daily use" title="Direct link to What to remember in daily use" translate="no">​</a></h3>
<p>When you hit your usage limit, the Gemini app keeps the conversation going on Flash-Lite, the model that missed the most common-sense traps in this table. If you use Argon freely until your limit runs out, Flash-Lite may be answering without you noticing. I actually hit the limit while running these tests; that story is in Step 6.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4-use-a-data-question-with-a-trap-to-see-the-difference">Step 4. Use a data question with a trap to see the difference<a href="https://datapopcorn.ai/blog/en/gemini-4-argon-review#step-4-use-a-data-question-with-a-trap-to-see-the-difference" class="hash-link" aria-label="Direct link to Step 4. Use a data question with a trap to see the difference" title="Direct link to Step 4. Use a data question with a trap to see the difference" translate="no">​</a></h2>
<p>The second question is an A/B test with Simpson's paradox hidden in it, a classic trap that leads people to write the wrong thing in reports.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">These are ad A/B test results. I'm about to write in my report to my manager: "Ad B has a higher conversion rate, so let's switch to B."</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Is that conclusion right? Give me a one-line conclusion first, then short supporting numbers.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Ad A: 30 purchases from 400 new customers, 180 purchases from 600 existing customers (210 out of 1,000 total)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Ad B: 8 purchases from 150 new customers, 238 purchases from 850 existing customers (246 out of 1,000 total)</span><br></div></code></pre></div></div>
<p>Overall, B (24.6%) beats A (21.0%). But split by new customers (A 7.5% vs. B 5.3%) and existing customers (A 30.0% vs. B 28.0%), A wins in both groups. It's an illusion caused by B getting 850 existing customers, who already buy at a higher rate. The right answer: "The conclusion to switch to B is wrong."</p>
<p><strong>Argon (about 43 seconds)</strong> nailed it in the first sentence, "The conclusion to switch to B is wrong, and you should choose A," then added three lines of numbers, an explanation of the cause and even a next step: "rerun the test with matched traffic ratios." It ran code to calculate before answering ("Show code" on screen).</p>
<p><img decoding="async" loading="lazy" alt="Argon putting the conclusion on the first line and explaining Simpson&amp;#39;s paradox with conversion rates by customer group" src="https://datapopcorn.ai/blog/en/assets/images/04-argon-simpson-0d5e384fc2f099027ddd6cdd2d533202.webp" width="1200" height="972" class="img_ev3q"></p>
<p><strong>3.8 Flash (about 46 seconds)</strong> also got all the numbers and the conclusion right. But the conclusion only shows up in the last paragraph. It missed the "one-line conclusion first" instruction.</p>
<p><img decoding="async" loading="lazy" alt="Flash listing the numbers first and giving the conclusion in the last paragraph" src="https://datapopcorn.ai/blog/en/assets/images/05-flash-simpson-c3412c0173c8441497b1b8a9108cf5bb.webp" width="1200" height="1500" class="img_ev3q"></p>
<p>I only ran each once, so it's too early to generalize. Still, it was reason enough to turn on Argon when I need "a report-ready answer that also follows the format." Google also says Argon <a href="https://blog.google/innovation-and-ai/models-and-research/gemini-models/introducing-gemini-4-argon/">follows style instructions better than previous Gemini models</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-5-build-a-3d-game-in-canvas-and-compare-the-results">Step 5. Build a 3D game in Canvas and compare the results<a href="https://datapopcorn.ai/blog/en/gemini-4-argon-review#step-5-build-a-3d-game-in-canvas-and-compare-the-results" class="hash-link" aria-label="Direct link to Step 5. Build a 3D game in Canvas and compare the results" title="Direct link to Step 5. Build a 3D game in Canvas and compare the results" translate="no">​</a></h2>
<p>The biggest difference was in building something. I turned on <strong>+ (Upload and tools) → Canvas</strong> on the left side of the input box and gave both models the exact same prompt below.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Use Canvas to build a 3D mini-game that runs right in the browser, as a single HTML file.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- Theme: popcorn pops out of a popcorn machine, and the player moves a basket left and right with the mouse (or touch) to catch it</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- Use Three.js, a 3D scene with shadows and lighting</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- Catching burnt (black) popcorn costs points, 60-second timer, show score and high score</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- Synthesize sound effects yourself with the Web Audio API (no external files)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- Start screen and game-over screen, play-again button</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- All UI text in Korean</span><br></div></code></pre></div></div>
<p>On the first request, Argon refused once with "I'm a text-based AI." When I asked again in the same chat, "Use the Canvas feature to write the HTML game code with the requirements above," the game was done about 2 minutes 40 seconds later. The code is 1,390 lines.</p>
<p><img decoding="async" loading="lazy" alt="Start screen of Popcorn Catcher 3D built by Argon, showing score, high score, a 60-second timer and points by popcorn type" src="https://datapopcorn.ai/blog/en/assets/images/06-argon-game-start-5740b164bf8926d81cd4011cd3557738.webp" width="1400" height="872" class="img_ev3q"></p>
<p>The start screen has a points table (yellow popcorn +10, golden caramel popcorn +30, burnt popcorn -15), and during play a kettle spins inside a vintage popcorn machine and popcorn piles up. The basket and machine cast real shadows, and it added on its own a mouse-drag camera angle control, a "Reset view" button and a mute button.</p>
<p>When the 60 seconds run out, you see a count of each popcorn type you caught and a new-record badge. In the round I played, I caught 21 regular, 3 golden and 16 burnt, and the final score was 100. That didn't match the simple math (210 + 90 - 240 = 60), so I opened the code and found <code>score = Math.max(0, score - 15)</code>, which stops the score from going below 0. Burnt popcorn I caught early on, while I was at 0, didn't cost anything. It's more a design choice than a bug, but you only notice things like this by checking yourself.</p>
<p><img decoding="async" loading="lazy" alt="Time-up screen of the Argon game: final score 100, 21 regular, 3 golden, 16 burnt" src="https://datapopcorn.ai/blog/en/assets/images/08-argon-game-over-8f513cdfd1eb4a9fc9e898459e36910d.webp" width="1500" height="966" class="img_ev3q"></p>
<p>The same prompt in 3.8 Flash took around 3 minutes and produced a 1,013-line game. Flash had some ideas of its own, like a combo bonus, but the popcorn was drawn as spiky polygon blobs and the bottom of the basket was cut off screen with the default camera. It's playable, but not something you'd show other people right away.</p>
<p><img decoding="async" loading="lazy" alt="The same game built by Flash. The popcorn looks like spiky polygons and the basket is cut off at the bottom of the screen" src="https://datapopcorn.ai/blog/en/assets/images/09-flash-game-play-bd0d522d9821ede52234176e7992a823.webp" width="1500" height="966" class="img_ev3q"></p>
<p>I've put Argon's game on the blog as is. I only changed the library and font URLs to point at our server and didn't touch the game code. <a href="https://datapopcorn.ai/blog/gemini-argon-popcorn/" target="_blank" rel="noopener noreferrer" class="">Play Popcorn Catcher 3D yourself</a></p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>Putting a Canvas result on your own site</div><div class="admonitionContent_BuS1"><p>Canvas code loads Three.js, Tailwind and Google Fonts from external CDNs. On a site with a Content Security Policy (CSP), those URLs can get blocked and the page shows up blank. I fixed it by downloading <code>three.min.js</code> and the Tailwind script and hosting them on the same domain, and switching the fonts to an allowed CDN URL.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-6-use-it-wisely-within-your-usage-limits">Step 6. Use it wisely within your usage limits<a href="https://datapopcorn.ai/blog/en/gemini-4-argon-review#step-6-use-it-wisely-within-your-usage-limits" class="hash-link" aria-label="Direct link to Step 6. Use it wisely within your usage limits" title="Direct link to Step 6. Use it wisely within your usage limits" translate="no">​</a></h2>
<p>Limits in the Gemini app are based on compute, not a number of prompts. <a href="https://support.google.com/gemini/answer/16275805?hl=ko">According to Google's help page, limits reflect prompt complexity, the model and features used, and chat length, and they reset every 5 hours until you hit your weekly limit</a>. Pro gets 4 times the limit of a free account, and Ultra gets several times more than Pro depending on the tier. Google hasn't said how many uses you get for Argon specifically.</p>
<p>I actually hit the limit while writing this post. Starting around 12:40 p.m., I ran two Canvas games and roughly 50 short questions across the four models, and around 4:30 p.m. everything except Flash-Lite (3.8 Flash, 3.1 Pro and 4 Argon) was locked. The menu said "Limit resets on October 28 at 5:46 PM," and the 5-hour limit on the usage screen was at 100%. The weekly limit on the same screen was only 5% used. The 5-hour window seemed to start from my first question, and once 5:46 p.m. passed, the three models opened up again.</p>
<p><img decoding="async" loading="lazy" alt="Model menu after hitting the limit: 3.8 Flash, 3.1 Pro and 4 Argon are grayed out and only Flash-Lite is selected" src="https://datapopcorn.ai/blog/en/assets/images/16-limit-menu-d31cd2516d94c748246e8e73f272ac44.webp" width="900" height="601" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" alt="Usage limits screen: current usage 100%, resets at 5:46 PM, weekly limit 5% used" src="https://datapopcorn.ai/blog/en/assets/images/17-usage-limit-9628d497cfa0c2d9aee2e7ef9dd38776.webp" width="1000" height="422" class="img_ev3q"></p>
<p>My sense is that on the Pro plan you can't use Argon all day, but if you stick mostly to short questions, it lasts a good few hours. Work that generates long code, like Canvas, eats into the limit fastest.</p>
<p>Here's what worked for me in these tests.</p>
<ol>
<li class=""><strong>Flash by default, Argon only when the result matters.</strong> As in Step 2, Flash answers short questions directly enough.</li>
<li class=""><strong>New topic, new chat.</strong> The help page also notes that rereading long chat history increases usage.</li>
<li class=""><strong>Start with a Low thinking level.</strong> Every test in this post used Low, and both the trap question and the game were solved on Low.</li>
<li class=""><strong>Check your remaining limit in settings.</strong> It's under <strong>Settings → Usage limits</strong> at the bottom left. If you hit the limit, you can keep chatting with Flash-Lite, but as Step 3 showed, it is weak on common-sense traps, so re-ask important questions after your limit resets.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-7-developers-check-api-pricing-and-output-limits">Step 7. Developers: check API pricing and output limits<a href="https://datapopcorn.ai/blog/en/gemini-4-argon-review#step-7-developers-check-api-pricing-and-output-limits" class="hash-link" aria-label="Direct link to Step 7. Developers: check API pricing and output limits" title="Direct link to Step 7. Developers: check API pricing and output limits" translate="no">​</a></h2>
<p>If you're using the API instead of the app, the numbers change. The model ID is <code>gemini-4-argon</code>, and <a href="https://cloud.google.com/blog/products/ai-machine-learning/gemini-4-argon-available-on-google-cloud">launch pricing through November 30 is $2 per million input tokens and $10 per million output tokens, after which standard pricing of $4 input and $20 output applies</a>. Cached input gets a 95% discount.</p>
<p>The biggest change is output length. <a href="https://blog.google/innovation-and-ai/models-and-research/gemini-models/gemini-4-argon/">The previous model's output limit was 64,000 tokens; Argon raises it to 1 million tokens</a>. In the API, input, thinking and output share a single 1-million-token window. For long code migrations or generating a large report in one go, that difference is big. Prices double from December 1, so if you want to test bulk work through the API, November is the time to do it.</p>
<p>For reference, here are the main benchmarks Google announced. These are all Google's numbers, not ones I reproduced.</p>
<table><thead><tr><th>Benchmark</th><th>What it measures</th><th>Argon score</th></tr></thead><tbody><tr><td>DeepSWE v1.1</td><td>Long-horizon real-world software development tasks</td><td>77.9%</td></tr><tr><td>AutomationBench (Zapier)</td><td>Running business automations end to end</td><td>51.3%, 1st place</td></tr><tr><td>LVBench</td><td>Long video understanding</td><td>91.7%</td></tr><tr><td>CWE-bench v1</td><td>Fixing security vulnerabilities</td><td>68%, tied for 1st</td></tr><tr><td>Vals Index</td><td>Combined finance, coding, legal and tax tasks</td><td>1st place</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-people-get-stuck">Where people get stuck<a href="https://datapopcorn.ai/blog/en/gemini-4-argon-review#where-people-get-stuck" class="hash-link" aria-label="Direct link to Where people get stuck" title="Direct link to Where people get stuck" translate="no">​</a></h2>
<table><thead><tr><th>Symptom</th><th>Cause</th><th>Fix</th></tr></thead><tbody><tr><td>4 Argon isn't in the model menu</td><td>Free account or AI Plus plan</td><td>Upgrade to Google AI Pro or higher, or for a work account, ask your admin about add-ons</td></tr><tr><td>Refusals like "I'm a text-based AI" or "I'm not programmed to do that"</td><td>Looks like a temporary response glitch. In these tests it happened once with Argon and six times with 3.1 Pro</td><td>Send the request again in the same chat, more specifically, or start over in a new chat</td></tr><tr><td>The generating indicator spins for over a minute, then the screen goes blank</td><td>A delay on the model's side. In these tests, 3.8 Flash was stuck like this all evening</td><td>Ask with a different model, or try again later</td></tr><tr><td>The Canvas preview is just gray</td><td>The code is still being generated</td><td>Wait until the stop button in the input box disappears. Games take 2 to 3 minutes</td></tr><tr><td>A notice that you've hit your limit</td><td>5-hour or weekly usage used up</td><td>Keep going with Flash-Lite or wait for the reset. Check under Settings → Usage limits</td></tr><tr><td>Answers suddenly stop making sense</td><td>You may have hit your limit and Flash-Lite is now answering</td><td>Check the model name in the input box and re-ask important questions with Argon or Flash</td></tr><tr><td>A Canvas game shows a blank screen on your site</td><td>Your site's CSP blocks external CDN scripts</td><td>Host the libraries on the same domain and update the paths</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-up<a href="https://datapopcorn.ai/blog/en/gemini-4-argon-review#wrap-up" class="hash-link" aria-label="Direct link to Wrap-up" title="Direct link to Wrap-up" translate="no">​</a></h2>
<p>Gemini 4 Argon wasn't so much "a model that's better at every question" as a model that earns its keep on work where you need a finished result. If you're a Pro subscriber, use Flash day to day and switch to Argon for analysis that has to follow a report format or for building something in Canvas. That way you look after both your limits and your results. Copy the prompts above and run the comparison on your own account.</p>]]></content:encoded>
            <category>Guide</category>
            <category>Work tools</category>
            <category>Vibe coding</category>
        </item>
        <item>
            <title><![CDATA[A Tour of n8n's New Website: The Rebrand (Logo, Typeface, Color) and How to Apply It to Your Own Materials]]></title>
            <link>https://datapopcorn.ai/blog/en/n8n-new-website-brand-refresh-2026</link>
            <guid>https://datapopcorn.ai/blog/en/n8n-new-website-brand-refresh-2026</guid>
            <pubDate>Tue, 27 Oct 2026 01:00:00 GMT</pubDate>
            <description><![CDATA[At In The Loop on October 14, 2026, n8n unveiled a new logo, typeface and colors and moved n8n.io to the new brand. I put the old and new homepages side by side, walk through what each section says, compare how far the rebrand has reached n8n Cloud, self-hosted n8n and the Creator Portal, and show how to bring your own course and presentation materials in line with the new brand using the official brand guidelines and n8ndesign.md, based on doing it myself.]]></description>
            <content:encoded><![CDATA[<p>Visit n8n.io today and the old landing screen (dark background, orange lightning bolt) is gone. Instead, a workflow draws itself node by node on a light dot grid. At its In The Loop conference in Berlin on October 14, 2026, n8n unveiled a new logo, a new typeface and a refined pink, and switched the homepage to the new brand. In this post I put the before and after side by side, tour the new site section by section, compare how far the rebrand has reached the Cloud and self-hosted product screens, and then walk through updating your own materials with the official brand guidelines.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="who-this-is-for">Who this is for<a href="https://datapopcorn.ai/blog/en/n8n-new-website-brand-refresh-2026#who-this-is-for" class="hash-link" aria-label="Direct link to Who this is for" title="Direct link to Who this is for" translate="no">​</a></h2>
<ul>
<li class="">Anyone who has put the n8n logo or UI in course materials, slide decks or YouTube thumbnails</li>
<li class="">Anyone who wants a quick read on what n8n is emphasizing on its new homepage</li>
<li class="">Anyone who hands design work to an AI agent and wants it to follow the official brand rules</li>
</ul>
<p>All you need is a browser. To try generating a draft with an AI agent in Step 8, you'll want access to one of Claude, ChatGPT, Codex or Cursor.</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>When this was captured</div><div class="admonitionContent_BuS1"><p>I captured the homepage myself on October 27, 2026 (Korea time). The "before" screens come from an October 11, 2026 snapshot on the Internet Archive (Wayback Machine). n8n iterates constantly, so the copy and examples may change after this.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1-compare-the-before-and-after-screens">Step 1. Compare the before and after screens<a href="https://datapopcorn.ai/blog/en/n8n-new-website-brand-refresh-2026#step-1-compare-the-before-and-after-screens" class="hash-link" aria-label="Direct link to Step 1. Compare the before and after screens" title="Direct link to Step 1. Compare the before and after screens" translate="no">​</a></h2>
<p>First, the old homepage. There was an In The Loop ticket banner at the top, a dark background, an orange gradient button and a big lightning-bolt image.</p>
<p><img decoding="async" loading="lazy" alt="The n8n.io homepage on October 11, 2026: dark background, orange Get started for free button, lightning-bolt image" src="https://datapopcorn.ai/blog/en/assets/images/01-before-homepage-89e7e8a577d4142bd31be0ed810f8eaa.webp" width="1600" height="836" class="img_ev3q"></p>
<p><em>Before (2026-10-11, Wayback Machine). The headline was "AI agents and workflows you can see and control".</em></p>
<p>Here's the homepage now.</p>
<p><img decoding="async" loading="lazy" alt="The n8n.io homepage on October 27, 2026: a news-briefing workflow drawn on a light dot-grid background" src="https://datapopcorn.ai/blog/en/assets/images/02-after-hero-dafc87f6c2be9cf4cb23eba2bbbdb5c9.webp" width="1600" height="798" class="img_ev3q"></p>
<p><em>After (2026-10-27). The headline is "Build the agent. Understand its workflow."</em></p>
<p>Comparing the two, the differences come down to four things.</p>
<table><thead><tr><th>Item</th><th>Before</th><th>After</th></tr></thead><tbody><tr><td>Logo</td><td>Round nodes joined by lines</td><td>A new symbol: one horizontal line with three cross-shaped connection points on it</td></tr><tr><td>Background and color</td><td>Dark navy background, orange gradient buttons</td><td>Light gray (#FAFAFA) dot grid, pink (#FF91AC) buttons</td></tr><tr><td>Type</td><td>The previous web font, buttons in normal case</td><td>New typeface n8n Baumer, buttons and menus in uppercase monospace</td></tr><tr><td>Hero</td><td>Lightning-bolt image and customer logos</td><td>A workflow built from real nodes</td></tr></tbody></table>
<p>In an October 20 post on the official blog, n8n founder Jan Oberhauser explained that the old logo was about the idea of connecting nodes, while the new one is meant to show the movement that connections create rather than the connection itself. In today's n8n, a person makes a decision mid-workflow, an agent takes action, and a model interprets information, so what happens between the nodes matters more.</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>What isn't changing</div><div class="admonitionContent_BuS1"><p>The same post says "This is a zhush, not a rebuild." and confirms that the community, the Community Edition, model choice and the pink all stay. The name and the license haven't changed; the look has.</p></div></div>
<p>Jan said the same thing in a post on the n8n community forum, <a href="https://community.n8n.io/t/our-brand-refresh-and-why-the-logo-changed/323036" target="_blank" rel="noopener noreferrer" class="">Our brand refresh, and why the logo changed</a>. It ends with a video about how the new brand was made. It only had English subtitles, so I added Korean subtitles and uploaded it. It's 95 seconds long and shows the new logo and typeface in motion.</p>
<div class="wrapper_Aefj"><iframe src="https://www.youtube.com/embed/jvqZsP0_Pbw" title="Official n8n brand refresh video (Korean subtitles)" loading="lazy" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe></div>
<p><em>The original is <a href="https://www.youtube.com/watch?v=Hijhd9NNtF8" target="_blank" rel="noopener noreferrer" class="">The n8n brand just got a zhuzh</a> on n8n's official channel. The Korean subtitles are an unofficial translation by DataPopcorn and have not been reviewed by n8n.</em></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2-extend-the-hero-workflow-yourself">Step 2. Extend the hero workflow yourself<a href="https://datapopcorn.ai/blog/en/n8n-new-website-brand-refresh-2026#step-2-extend-the-hero-workflow-yourself" class="hash-link" aria-label="Direct link to Step 2. Extend the hero workflow yourself" title="Direct link to Step 2. Extend the hero workflow yourself" translate="no">​</a></h2>
<p>The first thing to try on the new landing screen is the <code>+</code> button at the far right. It has a pink <code>CLICK HERE</code> label next to it.</p>
<ol>
<li class="">Open n8n.io and wait a few seconds. Starting from the trigger, the nodes are drawn one at a time.</li>
<li class="">Click the <code>+</code> button at the far right.</li>
<li class="">The view slides sideways and the next nodes are appended.</li>
</ol>
<p><img decoding="async" loading="lazy" alt="After clicking +, the refund workflow continues with record-in-database, classify-refund-reason and route-by-reason nodes" src="https://datapopcorn.ai/blog/en/assets/images/03-hero-reveal-e11ea9ee95607700b1f2d60fc1326709.webp" width="1600" height="672" class="img_ev3q"></p>
<p><em>In the refund-request workflow, clicking <code>+</code> added the "Record refund in database", "Classify reason for refund" and "Route by reason" nodes.</em></p>
<p>The example changes every time you reload. I saw three examples myself:</p>
<ul>
<li class="">A news briefing that collects NYT and CNN headlines every morning at 8, summarizes them with GPT-5 and sends them via Gmail</li>
<li class="">A refund flow: when a refund request comes in, look up the order, get approval in Slack, then process the refund if approved or open a Zendesk ticket if not</li>
<li class="">A flow that classifies customer-support requests and alerts the team when one is urgent</li>
</ul>
<p>The line under the headline reads "Open any step, see what it does, and explain to anyone who runs it." Putting a real workflow on the landing screen instead of an illustration is that sentence in action.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3-use-the-role-based-tabs-to-find-an-example-close-to-your-work">Step 3. Use the role-based tabs to find an example close to your work<a href="https://datapopcorn.ai/blog/en/n8n-new-website-brand-refresh-2026#step-3-use-the-role-based-tabs-to-find-an-example-close-to-your-work" class="hash-link" aria-label="Direct link to Step 3. Use the role-based tabs to find an example close to your work" title="Direct link to Step 3. Use the role-based tabs to find an example close to your work" translate="no">​</a></h2>
<p>Below the customer logos, a dark card shows four name tags. The tabs rotate automatically, and you can also click one.</p>
<p><img decoding="async" loading="lazy" alt="The use-case card with the MARK, IT tab selected, showing a new-hire onboarding workflow" src="https://datapopcorn.ai/blog/en/assets/images/04-use-cases-367f94f40e51518aa1742ca1b56e325f.webp" width="1600" height="945" class="img_ev3q"></p>
<table><thead><tr><th>Tab</th><th>Use case</th><th>Summary of the on-screen description</th></tr></thead><tbody><tr><td>MARK, IT</td><td>New-hire onboarding</td><td>Grant a new hire access to Slack, Jira, Notion and more in one workflow</td></tr><tr><td>DEBORAH, SECURITY</td><td>Threat checks</td><td>Look up suspicious indicators across several security tools, bundle them into one report and have AI summarize it</td></tr><tr><td>OLU, DEVOPS</td><td>Alert triage</td><td>When an alert arrives, an AI agent investigates and routes it to the right people and tools by severity</td></tr><tr><td>ALIX, SALES</td><td>Lead qualification</td><td>An AI agent replies to new messages, qualifies the lead and updates the lead record</td></tr></tbody></table>
<p>These four make good starting points for a course or an internal explainer. Because each one is labeled with a role, they map straight onto the question "What could our team automate?"</p>
<p>The stats strip underneath lists 208.6k GitHub stars (a top-50 project), a 4.7/5 rating on G2 and a community of 250,000+. The GitHub button on the old screen showed 206,926, so the project gained more than 1,600 stars in the roughly two weeks between the two snapshots.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4-watch-the-same-workflow-change-in-the-connectivity-and-trust-sections">Step 4. Watch the same workflow change in the connectivity and trust sections<a href="https://datapopcorn.ai/blog/en/n8n-new-website-brand-refresh-2026#step-4-watch-the-same-workflow-change-in-the-connectivity-and-trust-sections" class="hash-link" aria-label="Direct link to Step 4. Watch the same workflow change in the connectivity and trust sections" title="Direct link to Step 4. Watch the same workflow change in the connectivity and trust sections" translate="no">​</a></h2>
<p>The next section is titled "The USB-C for your AI stack". It says you can connect 500+ built-in integrations, any API over HTTP, and any agent or coding assistant over MCP. App icons such as Slack, Supabase and Postgres float across the screen.</p>
<p>The "See + understand + trust your agents" section below it is the one worth the closest look in this whole tour. Click each of the three items on the left, and the same invoice-processing workflow on the right switches between three versions.</p>
<p><img decoding="async" loading="lazy" alt="The Full visibility across agents item: a workflow where an AI agent checks invoice emails from Outlook and notifies Slack" src="https://datapopcorn.ai/blog/en/assets/images/06-trust-visibility-841731a0abf6427ed8564aeaef31cb05.webp" width="1600" height="731" class="img_ev3q"></p>
<p><em>Full visibility across agents: watch the agent work in real time or open past executions. The model is Claude Sonnet 4.6 and the tool is a QuickBooks lookup.</em></p>
<p><img decoding="async" loading="lazy" alt="The Put rules around AI actions item: the workflow gains an invoice-screening step and a node waiting for finance approval" src="https://datapopcorn.ai/blog/en/assets/images/07-trust-rules-f265dc7cf10479ea51929e9d8d59f12a.webp" width="1600" height="731" class="img_ev3q"></p>
<p><em>Put rules around AI actions: a step that screens invoices is added up front, and before payment a "Finance approves payments" node waits for approval in Slack. It's an example of adding structured inputs/outputs and human-in-the-loop approval.</em></p>
<p><img decoding="async" loading="lazy" alt="The Self-host your AI stack item: the workflow now uses Llama 3.3 on your own servers, local embeddings and an internal ERP lookup" src="https://datapopcorn.ai/blog/en/assets/images/08-trust-self-host-0d30444180d805ea78e4610d56534901.webp" width="1600" height="731" class="img_ev3q"></p>
<p><em>Self-host your AI stack: the model becomes "Llama 3.3 on your servers", the embeddings become local embeddings, and the lookup target becomes an internal ERP. This is the case where you install on Docker or your own servers and keep data in your environment.</em></p>
<p>Since it shows the same task in sequence as "see it, put rules on it, move it to your own servers", you can reuse these three screens in that order when you're making the case for AI agents at your company.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-5-check-the-documentation-recovery-and-versioning-section">Step 5. Check the documentation, recovery and versioning section<a href="https://datapopcorn.ai/blog/en/n8n-new-website-brand-refresh-2026#step-5-check-the-documentation-recovery-and-versioning-section" class="hash-link" aria-label="Direct link to Step 5. Check the documentation, recovery and versioning section" title="Direct link to Step 5. Check the documentation, recovery and versioning section" translate="no">​</a></h2>
<p>Click through the three items in the next section, "Built by you + readable by anyone", the same way.</p>
<p><img decoding="async" loading="lazy" alt="The Documentation, built in item: step-by-step sticky notes attached under a monthly-report workflow" src="https://datapopcorn.ai/blog/en/assets/images/09-readable-docs-b506382e693ed72a626d9f7c9bb418aa.webp" width="1600" height="731" class="img_ev3q"></p>
<p><em>Documentation, built in: three sticky notes sit under the workflow ("gather last month's numbers", "build the report", "share it"). As the caption "The workflow is the documentation." says, the message is that you don't need a separate handover document.</em></p>
<p><img decoding="async" loading="lazy" alt="The Recover in one step item: a workflow with the failed step outlined in red" src="https://datapopcorn.ai/blog/en/assets/images/10-readable-recover-f0b400cb53f40bc0a8b0abf345ebbbf9.webp" width="1600" height="731" class="img_ev3q"></p>
<p><em>Recover in one step: the failed Google Sheets step is outlined in red. The description says you can spot the error at a glance and rerun only the failed step.</em></p>
<p><img decoding="async" loading="lazy" alt="The Every change, tracked item: a workflow version list and publishing timeline" src="https://datapopcorn.ai/blog/en/assets/images/11-readable-versions-0902d0a5e165ebb333cd23997972753f.webp" width="1600" height="731" class="img_ev3q"></p>
<p><em>Every change, tracked: a version history showing who published which version and when.</em></p>
<p>The following "Move fast + break nothing" section boils down to two lines: rerun past executions or mock data to test without waiting on external systems, and use logs to see what happened.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-6-the-enterprise-section-and-the-hidden-easter-egg-in-the-footer">Step 6. The enterprise section and the hidden Easter egg in the footer<a href="https://datapopcorn.ai/blog/en/n8n-new-website-brand-refresh-2026#step-6-the-enterprise-section-and-the-hidden-easter-egg-in-the-footer" class="hash-link" aria-label="Direct link to Step 6. The enterprise section and the hidden Easter egg in the footer" title="Direct link to Step 6. The enterprise section and the hidden Easter egg in the footer" translate="no">​</a></h2>
<p>The enterprise section is laid out as four cards.</p>
<p><img decoding="async" loading="lazy" alt="The Enterprise-ready for teams of any size section: four cards for security, observability, developer experience and AI governance" src="https://datapopcorn.ai/blog/en/assets/images/12-enterprise-15e2e883b15b59db94f8dc90772fad10.webp" width="1600" height="593" class="img_ev3q"></p>
<table><thead><tr><th>Card</th><th>Features listed on screen</th></tr></thead><tbody><tr><td>Security + control</td><td>Fully on-prem option, SSO SAML, LDAP, encrypted secret store, version control, RBAC permissions</td></tr><tr><td>Observability + transparency</td><td>Audit logs, log streaming to SIEM, workflow history, real-time alerts, usage dashboard</td></tr><tr><td>Developer experience</td><td>Git-based management, isolated environments, multi-user workflows, workflow diffs</td></tr><tr><td>AI governance</td><td>Human approval, guardrails, evaluations</td></tr></tbody></table>
<p>Below that are two case studies: Huel (1,000 hours saved through an AI-first culture) and Vodafone (£2.2M impact in security operations).</p>
<p>Finally, scroll all the way down to the dark footer and move your mouse over the dot grid. White cells light up and fade along your cursor's path.</p>
<p><img decoding="async" loading="lazy" alt="Moving the mouse over the footer dot grid lights up white cells along the cursor path" src="https://datapopcorn.ai/blog/en/assets/images/13-footer-play-17ece9e3caa4d955dd12332af09820d1.webp" width="1600" height="724" class="img_ev3q"></p>
<p>n8n's community announcement also mentioned that the new brand has small playful touches scattered around, like the new website footer. This is where they've tucked away the slightly playful personality described in the brand notes.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-7-compare-how-far-cloud-self-hosted-and-the-creator-portal-have-changed">Step 7. Compare how far Cloud, self-hosted and the Creator Portal have changed<a href="https://datapopcorn.ai/blog/en/n8n-new-website-brand-refresh-2026#step-7-compare-how-far-cloud-self-hosted-and-the-creator-portal-have-changed" class="hash-link" aria-label="Direct link to Step 7. Compare how far Cloud, self-hosted and the Creator Portal have changed" title="Direct link to Step 7. Compare how far Cloud, self-hosted and the Creator Portal have changed" translate="no">​</a></h2>
<p>A new homepage doesn't mean all of n8n changed at once. On the same day (October 27), I opened the n8n screens DataPopcorn uses and compared logos and colors.</p>
<p><img decoding="async" loading="lazy" alt="Logo comparison across n8n.io, creators.n8n.io, app.n8n.cloud and self-hosted n8n. The first two have the new logo, the last two the old one" src="https://datapopcorn.ai/blog/en/assets/images/21-logo-compare-4f4972b72add5e623f3546ba977a604a.webp" width="1600" height="540" class="img_ev3q"></p>
<table><thead><tr><th>Screen</th><th>Address</th><th>Logo</th><th>Color and buttons</th><th>Status</th></tr></thead><tbody><tr><td>Homepage</td><td>n8n.io</td><td>New logo</td><td>Light dot grid, pink, uppercase monospace</td><td>New brand applied</td></tr><tr><td>Creator Portal</td><td>creators.n8n.io</td><td>New logo</td><td>Dot-grid header, pink buttons and pills</td><td>New brand applied</td></tr><tr><td>Cloud admin</td><td>app.n8n.cloud</td><td>Old logo</td><td>Orange buttons and usage bars</td><td>New layout, old brand</td></tr><tr><td>Self-hosted n8n</td><td>n8n.datapopcorn.ai</td><td>Old logo</td><td>Orange send button</td><td>Old brand</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-1-creator-portal-already-on-the-new-brand">7-1. Creator Portal: already on the new brand<a href="https://datapopcorn.ai/blog/en/n8n-new-website-brand-refresh-2026#7-1-creator-portal-already-on-the-new-brand" class="hash-link" aria-label="Direct link to 7-1. Creator Portal: already on the new brand" title="Direct link to 7-1. Creator Portal: already on the new brand" translate="no">​</a></h3>
<p>The Creator Portal, where you submit templates, already has the same new brand as the homepage. The new pink symbol and uppercase monospace <code>CREATOR PORTAL</code> sit in the top left, the header has a dot-grid background, and the <code>Share new template</code> button and the review-status pill (<code>IMPLEMENT CHANGES</code>) are all pink.</p>
<p><img decoding="async" loading="lazy" alt="The creators.n8n.io template dashboard: new n8n logo, dot-grid header, pink buttons and pills" src="https://datapopcorn.ai/blog/en/assets/images/20-creator-portal-9fb7cb3c6b2f5ad0db8d53c32549398c.webp" width="1600" height="683" class="img_ev3q"></p>
<p><em>The template screen for the DataPopcorn account, showing 1 in review and 2 published.</em></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-2-n8n-cloud-admin-new-layout-old-logo">7-2. n8n Cloud admin: new layout, old logo<a href="https://datapopcorn.ai/blog/en/n8n-new-website-brand-refresh-2026#7-2-n8n-cloud-admin-new-layout-old-logo" class="hash-link" aria-label="Direct link to 7-2. n8n Cloud admin: new layout, old logo" title="Direct link to 7-2. n8n Cloud admin: new layout, old logo" translate="no">​</a></h3>
<p>The app.n8n.cloud admin has a new layout organized into Overview, Instance settings, Assistant credits, Gateway credits, Billing and Export. A <code>Switch to classic UI</code> button at the bottom lets you move between the old and new screens. But the logo in the top left is still the old node logo, and the <code>Open instance</code> button and execution-count bar are still the old orange.</p>
<p><img decoding="async" loading="lazy" alt="The app.n8n.cloud Overview screen: old n8n logo, orange Open instance button, execution count and version cards" src="https://datapopcorn.ai/blog/en/assets/images/18-cloud-dashboard-8075b422117037cabff6522870eba769.webp" width="1600" height="1000" class="img_ev3q"></p>
<p><em>The instance version was 2.43.5, with a notice that an update to 2.45.4 was available. I've hidden the account email.</em></p>
<p>I also clicked <code>Switch to classic UI</code> to check the old screen. The menu runs horizontally across the top and there's a community-forum card, but the logo and orange buttons are the same as on the new screen. In other words, both are still on the old brand. Afterwards I switched back with <code>Try the new look</code>.</p>
<p><img decoding="async" loading="lazy" alt="The classic app.n8n.cloud dashboard: old logo, orange Open instance button, Try the new look button" src="https://datapopcorn.ai/blog/en/assets/images/25-cloud-dashboard-classic-b9b1a2fc3fb109ef1ea3655404a1edca.webp" width="1600" height="767" class="img_ev3q"></p>
<p>The Cloud n8n editor you reach via <code>Open instance</code> (datapopcorn.app.n8n.cloud) also has the old node logo in the top left.</p>
<p><img decoding="async" loading="lazy" alt="The Assistant screen in the Cloud n8n editor: old logo, with Personal, Shared with you, Chat, Projects and Admin Panel menus" src="https://datapopcorn.ai/blog/en/assets/images/26-cloud-instance-editor-f73289deccaf50687df0e33049dc4031.webp" width="1600" height="611" class="img_ev3q"></p>
<p><em>Project names are hidden.</em></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-3-self-hosted-n8n-still-the-old-look">7-3. Self-hosted n8n: still the old look<a href="https://datapopcorn.ai/blog/en/n8n-new-website-brand-refresh-2026#7-3-self-hosted-n8n-still-the-old-look" class="hash-link" aria-label="Direct link to 7-3. Self-hosted n8n: still the old look" title="Direct link to 7-3. Self-hosted n8n: still the old look" translate="no">​</a></h3>
<p>DataPopcorn's self-hosted server (n8n.datapopcorn.ai) also has the old logo. Even the sign-in screen still shows the old logo and an orange <code>Sign in</code> button.</p>
<p><img decoding="async" loading="lazy" alt="The self-hosted n8n sign-in screen: old n8n logo and orange Sign in button" src="https://datapopcorn.ai/blog/en/assets/images/27-selfhost-signin-5acf6e52c2f3870a2eab9e797e65763f.webp" width="1440" height="950" class="img_ev3q"></p>
<p>After signing in, the first screen is the Assistant (Preview), asking "What do you want to automate?", and the send button is orange. The help menu showed <code>Update (1 version behind)</code>.</p>
<p><img decoding="async" loading="lazy" alt="The Assistant screen in self-hosted n8n: old logo and orange send button" src="https://datapopcorn.ai/blog/en/assets/images/19-self-hosted-bbb1c38d1768dfa281cbbfac2e75b9ae.webp" width="1600" height="556" class="img_ev3q"></p>
<p>Putting the Cloud editor and the self-hosted editor side by side on the same day, both are on the old brand; the differences are in the menus.</p>
<table><thead><tr><th>Item</th><th>Cloud editor (Pro plan)</th><th>Self-hosted editor</th></tr></thead><tbody><tr><td>Logo</td><td>Old node logo</td><td>Old node logo</td></tr><tr><td>First screen</td><td>Assistant (Preview), "What do you want to automate?"</td><td>Same Assistant (Preview) screen</td></tr><tr><td>Below the input</td><td><code>Personal space</code> picker for where to save</td><td>None</td></tr><tr><td>Left menu</td><td>Overview, Personal, Shared with you, Chat, Projects, Admin Panel</td><td>Only Overview, Templates, Insights, Help, Settings</td></tr><tr><td>Updates</td><td>Admin shows an update notice from 2.43.5 → 2.45.4</td><td>Help menu shows 1 version behind</td></tr></tbody></table>
<p>The menu differences depend on plan, license and account permissions, so they're unrelated to the rebrand.</p>
<p>To sum up: the new brand landed first on outward-facing sites like the homepage and the Creator Portal, and hasn't reached the product screens where you actually build workflows. n8n's official post says the new brand will show up across the product, website, community and events in turn, so the product screens will likely change soon. Cloud should change when n8n updates it, but self-hosted will most likely change only when you upgrade yourself after a version with the new logo ships. When the product screens change, I'll add new captures to this post.</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>For course materials, teach both logos for now</div><div class="admonitionContent_BuS1"><p>Students will see the new logo on the homepage and the old one in their own n8n. Until the product screens change, a one-line note like "Same n8n, the logo is in the middle of changing" heads off a lot of confusion.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-8-bring-your-materials-in-line-with-the-new-brand-using-the-official-guidelines">Step 8. Bring your materials in line with the new brand using the official guidelines<a href="https://datapopcorn.ai/blog/en/n8n-new-website-brand-refresh-2026#step-8-bring-your-materials-in-line-with-the-new-brand-using-the-official-guidelines" class="hash-link" aria-label="Direct link to Step 8. Bring your materials in line with the new brand using the official guidelines" title="Direct link to Step 8. Bring your materials in line with the new brand using the official guidelines" translate="no">​</a></h2>
<p>From here on, it's hands-on. If you have course thumbnails, slides or handouts with the n8n logo or colors, this rebrand just made them look dated.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="8-1-open-the-guidelines-site">8-1. Open the guidelines site<a href="https://datapopcorn.ai/blog/en/n8n-new-website-brand-refresh-2026#8-1-open-the-guidelines-site" class="hash-link" aria-label="Direct link to 8-1. Open the guidelines site" title="Direct link to 8-1. Open the guidelines site" translate="no">​</a></h3>
<p>Click <code>BRAND GUIDELINES</code> in the homepage footer or go straight to <a href="https://guidelines.n8n.design/" target="_blank" rel="noopener noreferrer" class="">guidelines.n8n.design</a>. The left menu has Overview, TOV (tone of voice), Logo, Colors, Typography, The Grid, Graphic Devices and Build with AI.</p>
<div class="theme-admonition theme-admonition-caution admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>Old links return 404</div><div class="admonitionContent_BuS1"><p>The old <code>n8n.io/colors</code> and <code>n8n.io/typography</code> URLs returned 404 when I checked on October 27. If your materials or bookmarks use them, switch to the new guidelines URL.</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="8-2-download-the-logo-files">8-2. Download the logo files<a href="https://datapopcorn.ai/blog/en/n8n-new-website-brand-refresh-2026#8-2-download-the-logo-files" class="hash-link" aria-label="Direct link to 8-2. Download the logo files" title="Direct link to 8-2. Download the logo files" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" alt="The Logo page on the n8n guidelines site: pink symbol and n8n wordmark" src="https://datapopcorn.ai/blog/en/assets/images/15-guidelines-logo-484a21b62d441b2de39d5de6eeabbf7d.webp" width="1600" height="1000" class="img_ev3q"></p>
<p>Near the bottom of the Logo page you can download the symbol alone, or the full logo (symbol plus wordmark), as SVG and ZIP. Here are just the rules you'll use right away when making materials:</p>
<ul>
<li class="">The symbol is made of 8 connection points plus 8 nodes hidden in the negative space. It stands for the 8 in n8n.</li>
<li class="">On light backgrounds use the pink symbol with black text (<code>n8n-logo-black-accent</code>); on dark backgrounds use the pink symbol with white text (<code>n8n-logo-white-accent</code>).</li>
<li class="">On a pink background, don't use the pink logo; use the black one.</li>
<li class="">When pairing with a partner logo, join them with a <code>+</code> and put the n8n logo on the left (horizontal layout) or on top (vertical layout).</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="8-3-check-the-colors-and-typeface">8-3. Check the colors and typeface<a href="https://datapopcorn.ai/blog/en/n8n-new-website-brand-refresh-2026#8-3-check-the-colors-and-typeface" class="hash-link" aria-label="Direct link to 8-3. Check the colors and typeface" title="Direct link to 8-3. Check the colors and typeface" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" alt="The Colors page on the n8n guidelines site" src="https://datapopcorn.ai/blog/en/assets/images/16-guidelines-colors-86193459c56202f23bafa39e46324e7e.webp" width="1600" height="1000" class="img_ev3q"></p>
<table><thead><tr><th>Use</th><th>Value</th></tr></thead><tbody><tr><td>Signature pink (Accent)</td><td><code>#FF91AC</code></td></tr><tr><td>Core Black</td><td><code>#191919</code></td></tr><tr><td>Core White</td><td><code>#FAFAFA</code></td></tr><tr><td>Grays</td><td>12 steps from <code>#F8F8F8</code> to <code>#242424</code></td></tr></tbody></table>
<p>The typeface is n8n Baumer, a customized version of EK Baumer. It uses an alternate lowercase "a" without the tail by default, and only two weights: Regular (400) and Medium (500).</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="8-4-connect-n8ndesignmd-to-your-ai-agent">8-4. Connect n8ndesign.md to your AI agent<a href="https://datapopcorn.ai/blog/en/n8n-new-website-brand-refresh-2026#8-4-connect-n8ndesignmd-to-your-ai-agent" class="hash-link" aria-label="Direct link to 8-4. Connect n8ndesign.md to your AI agent" title="Direct link to 8-4. Connect n8ndesign.md to your AI agent" translate="no">​</a></h3>
<p>Under Build with AI in the guidelines, you can download a file called <code>n8ndesign.md</code>. This one file contains the colors, type, spacing, logo usage and tone-of-voice rules in a format that's easy for an AI agent to read. The version I downloaded was V5 (updated October 19, 2026).</p>
<p><img decoding="async" loading="lazy" alt="The Build with AI page on the n8n guidelines site, introducing n8ndesign.md" src="https://datapopcorn.ai/blog/en/assets/images/14-guidelines-build-with-ai-9848d0c3658ee622ecf9e674d0796ce3.webp" width="1600" height="1000" class="img_ev3q"></p>
<ol>
<li class="">Download the file with the <code>DOWNLOAD N8NDESIGN.MD</code> button and put it in your project folder.</li>
<li class="">Pick the tab for your tool (Claude, ChatGPT, Codex, Cursor) and connect it as instructed. For Claude, add a single line <code>@n8ndesign.md</code> to <code>CLAUDE.md</code>.</li>
<li class="">Paste in the instruction the guidelines provide.</li>
</ol>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Before you design or build any UI, read n8ndesign.md and follow it exactly: colors, spacing, type and logo usage. If something isn't covered, ask me instead of guessing.</span><br></div></code></pre></div></div>
<ol start="4">
<li class="">Generate a draft with one of the guidelines' example prompts. I adapted the social-card prompt to the topic of this post.</li>
</ol>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Make a 1080×1080 social card announcing [news], on-brand per n8ndesign.md. Use pink for one accent only.</span><br></div></code></pre></div></div>
<p>The card below is what an AI agent produced after reading n8ndesign.md and getting the prompt above. The cover image for this post was made the same way.</p>
<p><img decoding="async" loading="lazy" alt="A 1080x1080 card made following n8ndesign.md: dot-grid background, official n8n logo, pink and gray pills, and a Korean title inside a debossed panel" src="https://datapopcorn.ai/blog/en/assets/images/17-test-card-9e89959213eb7f1723176e67f6f7215c.webp" width="1080" height="1080" class="img_ev3q"></p>
<p>Here's what the agent handled by following the file's rules:</p>
<ul>
<li class="">It didn't draw the logo itself; it downloaded the official SVG (<code>n8n-logo-black-accent.svg</code>) from the guidelines URL. The very first rule in n8ndesign.md is "never generate the logo".</li>
<li class="">Following the rule that pink text on a light background has insufficient contrast (2.03:1), it used pink only as the background color of the first pill.</li>
<li class="">It joined the two pills with a dot and set the pill text in uppercase monospace, a device that echoes the node connection points in the n8n UI.</li>
<li class="">The title panel uses the guidelines' Deboss M values (8px inner shadow, 12px blur) to look pressed into paper.</li>
</ul>
<div class="theme-admonition theme-admonition-caution admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>Korean text doesn't render in n8n Baumer</div><div class="admonitionContent_BuS1"><p>n8n Baumer has no Korean glyphs, and I couldn't find a font download link on the guidelines site. So I used Pretendard for the Korean title and body, and a generic monospace font for the uppercase English in the pills. For Korean-language materials, the practical rule is "English follows the brand rules; Korean uses a sans-serif of similar weight."</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="common-problems-and-fixes">Common problems and fixes<a href="https://datapopcorn.ai/blog/en/n8n-new-website-brand-refresh-2026#common-problems-and-fixes" class="hash-link" aria-label="Direct link to Common problems and fixes" title="Direct link to Common problems and fixes" translate="no">​</a></h2>
<table><thead><tr><th>Symptom</th><th>Cause</th><th>Fix</th></tr></thead><tbody><tr><td>Saved <code>n8n.io/colors</code> or <code>/typography</code> links don't open</td><td>The brand pages moved to the new guidelines site</td><td>Use <a href="https://guidelines.n8n.design/" target="_blank" rel="noopener noreferrer" class="">guidelines.n8n.design</a> or <code>BRAND GUIDELINES</code> in the footer</td></tr><tr><td>The hero workflow is different every visit</td><td>Each visit shows a different example workflow</td><td>Reload until you get the example you want; capture after all nodes have been drawn</td></tr><tr><td>The use-case tab switches to someone else before you capture</td><td>The tabs rotate automatically</td><td>Click the name tag you want to pin it, wait a few seconds, then capture</td></tr><tr><td>The AI draws a look-alike n8n logo</td><td>It doesn't know where the official files are</td><td>Connect n8ndesign.md and tell it to use the SVG from the guidelines</td></tr><tr><td>Pink text on a light background is hard to read</td><td>2.03:1 contrast isn't enough for text</td><td>Use pink only for fills (backgrounds, shapes) and <code>#191919</code> for text</td></tr><tr><td>Korean text doesn't render in the n8n typeface</td><td>n8n Baumer has no Korean glyphs</td><td>Substitute a similar sans-serif such as Pretendard for Korean</td></tr><tr><td>The homepage has the new logo but my n8n shows the old one</td><td>As of 2026-10-27, the rebrand hasn't reached the Cloud or self-hosted product screens</td><td>Not a bug. For self-hosted, watch for the new-version announcement and upgrade</td></tr><tr><td>The old logo is still on course thumbnails and handouts</td><td>The brand changed on October 14, 2026</td><td>Download the logo pack from the guidelines and swap it in; join co-branded logos with <code>+</code></td></tr></tbody></table>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>What I couldn't verify</div><div class="admonitionContent_BuS1"><ul>
<li class="">When the new logo will reach the Cloud and self-hosted product screens is still unknown. As of October 27, both show the old logo. I didn't confirm the exact version number of the self-hosted server this time.</li>
<li class="">I didn't count how many example workflows the hero cycles through. This post only lists the three I saw myself.</li>
</ul></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="references">References<a href="https://datapopcorn.ai/blog/en/n8n-new-website-brand-refresh-2026#references" class="hash-link" aria-label="Direct link to References" title="Direct link to References" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://blog.n8n.io/n8n-brand-refresh-2026/" target="_blank" rel="noopener noreferrer" class="">Why we refreshed the n8n brand</a> (n8n blog, 2026-10-20)</li>
<li class=""><a href="https://community.n8n.io/t/our-brand-refresh-and-why-the-logo-changed/323036" target="_blank" rel="noopener noreferrer" class="">Our brand refresh, and why the logo changed</a> (n8n community, 2026-10-20)</li>
<li class=""><a href="https://guidelines.n8n.design/" target="_blank" rel="noopener noreferrer" class="">n8n Brand System</a> (official brand guidelines)</li>
<li class=""><a href="https://web.archive.org/web/20261011143752/https://n8n.io/" target="_blank" rel="noopener noreferrer" class="">n8n.io snapshot from 2026-10-11</a> (Wayback Machine)</li>
</ul>]]></content:encoded>
            <category>n8n</category>
            <category>Guide</category>
            <category>Work tools</category>
        </item>
        <item>
            <title><![CDATA[[AI Game League EP.1] AI Plays Plants vs. Zombies Solo to the Ending: Claude Cleared All 50 Levels, ChatGPT Stopped at 24]]></title>
            <link>https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end</link>
            <guid>https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end</guid>
            <pubDate>Tue, 27 Oct 2026 00:50:00 GMT</pubDate>
            <description><![CDATA[I handed the 50-level Adventure mode of the real-time PC game Plants vs. Zombies to AI to play on its own. This 9-step guide covers reading game state from memory, letting a program do the clicking, quitting likely losses early with a win-probability model, and showing the AI's actions and reasons next to the screen. Claude Opus 5.5 finished 50/50 in 13 hours, GPT-6 Astra stopped at 24/50, and I include why, along with the costs, in numbers.]]></description>
            <content:encoded><![CDATA[<p>The first match of the AI Game League is Plants vs. Zombies. I had AI play all 50 levels of Adventure mode on its own under the same conditions. Claude Opus 5.5 lost 63 times and won 50 over 13 hours, and made it all the way to the end credits. GPT-6 Astra got to the 18th level on its own, and after it got stuck, I attached tools one at a time until it reached the 24th level and stopped. This guide walks through the methods I used along the way (reading game state, delegated clicking, quitting games by win probability, an action-reason panel, plan mode) and the incidents that blew up mid-run, in order.</p>
<!-- -->
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>Watch the video</div><div class="admonitionContent_BuS1"><p>I also put this together as a 28-minute video. You can see both AIs playing, the 28-loss streak against Dr. Zomboss, and the first win on 3-2 with the red action boxes and the win-probability panel.</p><div class="wrapper_Aefj"><iframe src="https://www.youtube.com/embed/u3KUtq4neFk" title="[AI Game League EP.1] AI Plays Plants vs. Zombies Solo to the Ending" loading="lazy" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe></div></div></div>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>Ground rules for this post</div><div class="admonitionContent_BuS1"><p>This ran from the night of October 24 to the morning of October 27, 2026. The game was the Steam version of Plants vs. Zombies GOTY (Windows). The game ran on a gaming PC, and the brain program that calls the AI ran on a separate Mac. Every number in this post comes from the run logs. Costs are converted at API prices; what I actually paid with is noted separately in the cost section below.</p></div></div>
<p><img decoding="async" loading="lazy" alt="Level 5-10 after defeating Dr. Zomboss, with the trophy showing. The roof is packed with flower pots, Cabbage-pults and Melon-pults" src="https://datapopcorn.ai/blog/en/assets/images/00-cover-zomboss-cleared-d987f2deab5e2f72a1c281a507e5110f.webp" width="800" height="600" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="results-first">Results first<a href="https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end#results-first" class="hash-link" aria-label="Direct link to Results first" title="Direct link to Results first" translate="no">​</a></h2>
<table><thead><tr><th>Item</th><th>Claude Opus 5.5</th><th>GPT-6 Astra</th></tr></thead><tbody><tr><td>Final result</td><td>Finished Adventure 50/50</td><td>Stopped at 24/50 (last clear: 3-4)</td></tr><tr><td>Cleared on its own</td><td>1-1 through 5-10 (3 assists, disclosed)</td><td>18 levels, 1-1 through 2-8</td></tr><tr><td>Time taken</td><td>13.0 hours, 50 wins, 63 losses</td><td>Spread over several days, ended by the user at 07:15 on 10/27</td></tr><tr><td>Call path</td><td>Anthropic API</td><td>Codex CLI (ChatGPT Pro subscription)</td></tr><tr><td>Cost</td><td>About $104.9 at API prices</td><td>Used within the subscription (no per-call billing)</td></tr></tbody></table>
<p>Opus got only three assists: running 1-8 to 1-10 at 2x speed, having the harness click zombies directly in the Whack a Zombie minigame (2-5), and slowing the game to 0.5x for the final Dr. Zomboss fight (5-10) after it lost 28 times at 1x. Astra started under the same conditions, got stuck at 2-9, and from then on reached 24 levels with assists added one at a time. I describe exactly which assist went in when, starting at Step 5.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="who-this-is-for">Who this is for<a href="https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end#who-this-is-for" class="hash-link" aria-label="Direct link to Who this is for" title="Direct link to Who this is for" translate="no">​</a></h2>
<ul>
<li class="">You want an LLM to operate a game or desktop program directly</li>
<li class="">You tried feeding in screenshots alone and got stuck because it was slow, clicked the wrong places and misread numbers</li>
<li class="">You want to know in advance what breaks when you run an AI agent unattended for hours</li>
<li class="">You're curious what needs to be matched to compare two models "fairly"</li>
</ul>
<p>This isn't a type-along-with-the-code post. Instead, for each Step I wrote down what I built, which numbers I watched and where things went wrong, so it's easy to carry over to your own project.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-you-need">What you need<a href="https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end#what-you-need" class="hash-link" aria-label="Direct link to What you need" title="Direct link to What you need" translate="no">​</a></h2>
<ul>
<li class="">A Windows PC that runs the game, with Python</li>
<li class="">A library for reading another process's memory (this time, <code>pymem</code>)</li>
<li class="">A computer to run the AI brain (it can be the same PC, but this time I used an always-on Mac)</li>
<li class="">Two models to compare, each with its own call path (API key or CLI)</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1-read-game-state-from-memory-instead-of-the-screen">Step 1. Read game state from memory instead of the screen<a href="https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end#step-1-read-game-state-from-memory-instead-of-the-screen" class="hash-link" aria-label="Direct link to Step 1. Read game state from memory instead of the screen" title="Direct link to Step 1. Read game state from memory instead of the screen" translate="no">​</a></h2>
<p>Everyone's first idea is to show the AI a screenshot and ask "where do you want to plant?" But Plants vs. Zombies is a real-time game. Zombies keep walking during the few seconds the AI spends thinking, and small text like the sun count is easy to misread from an image.</p>
<p>So I read the values the game holds in its own memory directly. Sun, wave, level number, the plant list (row, column, type, HP), the zombie list (row, x position, type, HP, helmet, shield), coins on the lawn, seed cards (cost, recharge) and lawn mowers all come out as numbers. I gave the AI a state text summarizing those numbers along with an 800x600 screenshot.</p>
<p>Memory reading follows a pointer chain: "fixed address → board object → field." In short, it looks like this.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> pymem</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> struct</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">pm </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> pymem</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Pymem</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"popcapgame1.exe"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">          </span><span class="token comment" style="color:#999988;font-style:italic"># the actual game process, not the launcher</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">u32 </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">lambda</span><span class="token plain"> a</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> struct</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">unpack</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"&lt;I"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> pm</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">read_bytes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">a</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">4</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app   </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> u32</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">BASE</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">                             </span><span class="token comment" style="color:#999988;font-style:italic"># game app object</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">board </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> u32</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">app </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> BOARD_OFFSET</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">               </span><span class="token comment" style="color:#999988;font-style:italic"># the board currently being played</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">sun   </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> struct</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">unpack</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"&lt;i"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> pm</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">read_bytes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">board </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> SUN_OFFSET</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">4</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><br></div></code></pre></div></div>
<p>The offsets themselves differ by game version, so I didn't include them in this post. Instead, keep in mind two traps I ran into.</p>
<ul>
<li class=""><strong>The launcher and the actual game were different processes.</strong> No matter how much I dug through the <code>PlantsVsZombies.exe</code> that Steam launches, the sun value never showed up. The real game was <code>popcapgame1.exe</code>, which that launcher starts in turn. If you can't find a value, check the process list first.</li>
<li class=""><strong>Don't trust the "playing" flag too much.</strong> The shop screen between levels also read as "playing" in memory. The incident this caused is in Step 4.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2-the-ai-decides-the-harness-clicks">Step 2. The AI decides, the harness clicks<a href="https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end#step-2-the-ai-decides-the-harness-clicks" class="hash-link" aria-label="Direct link to Step 2. The AI decides, the harness clicks" title="Direct link to Step 2. The AI decides, the harness clicks" translate="no">​</a></h2>
<p>If you tell the AI "click coordinates (412, 233)," there are too many ways for it to go wrong. So I split the roles like this.</p>
<ul>
<li class=""><strong>What the AI does</strong>: decide what to plant where (e.g., "Peashooter in row 2, column 3"), click menus, tutorials and reward screens, and write notes on rules it has learned</li>
<li class=""><strong>What the harness (program) does</strong>: read state from memory, turn "row 2, column 3" into screen pixels and click, reject plantings that break the rules (not enough sun, recharging, a plant already in that cell) and send the reason back to the AI, and automatically collect falling sun</li>
</ul>
<p><img decoding="async" loading="lazy" alt="Level 3-2. One Sunflower in the top left, with two suns falling onto the lawn" src="https://datapopcorn.ai/blog/en/assets/images/02-sun-falling-334dd8d592928c84aae89f31a56498fd.webp" width="800" height="600" class="img_ev3q"></p>
<p>Sun collection works off the screen. It captures the game window, finds blobs of "very bright yellow" pixels and clicks their centers, a simple approach. If you make the AI collect sun too, it's a few seconds late every time and the economy collapses. For the same reason, in minigames that are all about reflexes, like Whack a Zombie (2-5), the harness clicked zombie positions directly, and I told the AI about it.</p>
<p>I learned three things here.</p>
<ol>
<li class=""><strong>Run in windowed mode, not fullscreen.</strong> In fullscreen, captures arrived later than the actual screen, so it clicked spots where the sun had already disappeared. Switching to windowed mode fixed it immediately.</li>
<li class=""><strong>Re-read moving targets right before clicking.</strong> On the conveyor-belt level (1-10), the cards keep sliding, and clicking where they were a few seconds earlier made 30% of plantings fail (14 out of 48). After re-reading state right before each click and accounting for the sliding card's real position (x + slide offset), it cleared the very next game.</li>
<li class=""><strong>Enforce the AI's response format.</strong> Opus sometimes put an explanatory sentence before the JSON, or wrote such long notes that it hit the token limit and the response broke. Once I locked the format with the option that specifies an output schema, the problem went away.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3-put-both-models-under-the-same-conditions">Step 3. Put both models under the same conditions<a href="https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end#step-3-put-both-models-under-the-same-conditions" class="hash-link" aria-label="Direct link to Step 3. Put both models under the same conditions" title="Direct link to Step 3. Put both models under the same conditions" translate="no">​</a></h2>
<p>For the comparison to mean anything, only the model should differ and everything else should be the same. These are the conditions I matched.</p>
<ul>
<li class="">Same prompt, same state text, same 800x600 screenshot, same harness</li>
<li class="">A fresh game profile for each model (OPUS, ASTRA), both starting from the same main menu</li>
<li class="">Real-time play: the game doesn't pause while the AI thinks. Response speed counts as part of skill</li>
</ul>
<p>First, here are the results of running 1-1 through 1-4 side by side.</p>
<p><img decoding="async" loading="lazy" alt="Claude Opus 5.5 on the left and GPT-6 Astra on the right, playing the same level 1-2 at the same time" src="https://datapopcorn.ai/blog/en/assets/images/01-race-opus-vs-astra-77cc1237ab993e6bd54cccafe7d5b2f2.webp" width="1920" height="830" class="img_ev3q"></p>
<table><thead><tr><th>Item</th><th>Opus 5.5 (API)</th><th>GPT-6 Astra (Codex CLI)</th></tr></thead><tbody><tr><td>Result</td><td>4 wins, 0 losses</td><td>4 wins, 0 losses</td></tr><tr><td>Total time</td><td>13.1 min</td><td>16.1 min</td></tr><tr><td>Average response</td><td>4.18 s</td><td>8.86 s</td></tr><tr><td>Calls</td><td>80</td><td>62</td></tr><tr><td>Broken responses</td><td>1</td><td>0</td></tr></tbody></table>
<p>Up to here it's close to a draw. The gap opened up as levels got harder and the runs went on for hours without a break. Opus finished all 50 levels with only the three assists listed above, while Astra stalled at 2-9 under the same conditions after a string of losses. Since Claude was the one that went all the way under the same conditions, Claude takes the EP.1 point.</p>
<div class="theme-admonition theme-admonition-caution admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>Being honest about fairness</div><div class="admonitionContent_BuS1"><p>Astra had no API key, so I called it through Codex CLI. That meant every call carried the Codex default instructions (about 13,000 tokens, mostly cached) and CLI startup time, and reasoning effort was at the lowest setting, low. A good part of the response-speed gap may come from the call path rather than the model. I'm changing this starting next episode (see the end of the post).</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4-install-stop-mechanisms-for-when-nobody-is-watching">Step 4. Install stop mechanisms for when nobody is watching<a href="https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end#step-4-install-stop-mechanisms-for-when-nobody-is-watching" class="hash-link" aria-label="Direct link to Step 4. Install stop mechanisms for when nobody is watching" title="Direct link to Step 4. Install stop mechanisms for when nobody is watching" translate="no">​</a></h2>
<p>In a multi-hour unattended run, "getting trapped in a weird state" was a bigger enemy than game skill. Here's what actually happened, and the mechanisms I added afterward.</p>
<ul>
<li class=""><strong>The monitor turned off, but the AI kept thinking.</strong> In the middle of 2-4, the gaming PC lost its monitor signal and every capture came back black. The AI spent 1 hour 50 minutes waiting while staring at a black screen, making 490 calls and spending about $9 in the meantime. Afterward, I made it stop calling the AI (zero cost) when it detects a black screen or an abnormal window size, and stop everything if that lasts more than 2 minutes. I also installed a virtual monitor driver so the display stays alive even when the physical monitor is off.</li>
<li class=""><strong>In the shop, it mistook the Golden Watering Can for sun.</strong> The Crazy Dave shop after 5-4 read as "playing" in memory, so automatic sun collection stayed on. It kept clicking the yellow Golden Watering Can thinking it was sun and was stuck in a "not enough money" window for about 8 minutes. I changed auto-collection to run only inside levels.</li>
<li class=""><strong>Nobody clicked a single key, and everything stalled for 5 hours.</strong> It had effectively won 3-4 at around 1:40 a.m., but nothing was there to click the reward (a key) that the last zombie dropped. In plan mode (Step 8), the AI's turns were paused during play, but clicking rewards was the AI's job. The win-probability watcher and the supervisor script both decided "it's playing, so it's fine," and it sat like that until 7:03 a.m. I changed it so the AI handles things directly when there are zero zombies and a non-sun item is on the lawn, and added a watchdog that sends an alert and restarts the segment if there's no action, result or screen change for 15 minutes.</li>
</ul>
<p>In short, an unattended agent needs a mechanism that "wakes it up when nothing happens" just as much as one that "stops it when it fails."</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-5-when-it-gets-stuck-record-everything-you-change">Step 5. When it gets stuck, record everything you change<a href="https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end#step-5-when-it-gets-stuck-record-everything-you-change" class="hash-link" aria-label="Direct link to Step 5. When it gets stuck, record everything you change" title="Direct link to Step 5. When it gets stuck, record everything you change" translate="no">​</a></h2>
<p>Both models got stuck somewhere, and each time a human changed something. If you don't write down what you changed and why, you can't later separate "AI skill" from "the share a human helped with."</p>
<p><strong>Where Opus got stuck: Dr. Zomboss.</strong> On the final level, 5-10, it lost 28 times in a row at 1x speed. It could only wear Dr. Zomboss's HP down from 40,000 to 33,180, because it was too busy dealing with fireballs, iceballs, Bungee Zombies and Zombonis to add more attacking plants. Once I slowed the game to 0.5x just for the boss fight, it won on the first try, in 18 minutes and 96 calls. This assist lets the game run at only half speed while the AI thinks, so I disclosed it in the video as well.</p>
<p><strong>Astra stopped for three reasons.</strong></p>
<ol>
<li class=""><strong>429 (server busy) responses.</strong> On the same account, calls that included images were repeatedly rejected with 429, only for Astra. The 429 stretches I measured ranged from 5 minutes to 100 minutes, mostly 15 to 30 minutes. Only 33% of the weekly usage had been used, so this wasn't a quota problem; it was server congestion. From 17:21 to 19:35 on October 26 it went on for more than two hours, and 9 of the 12 games lost on 2-6 went by with almost no AI decisions.</li>
<li class=""><strong>Slow responses.</strong> Astra calls through Codex CLI averaged around 17 seconds in this period. In a real-time game, 17 seconds is long enough for a zombie to walk halfway across the screen.</li>
<li class=""><strong>The fallback model's mistakes, and mine.</strong> When Astra couldn't respond, I had GPT-6 Luna on the same account decide instead, but Luna would click an empty slot on the seed selection screen and think it had "filled" it, or pick mushrooms for daytime levels. On top of that, I added a rule: "if Astra takes more than 10 seconds, switch to Luna." With Astra averaging 17 seconds, Luna was effectively always deciding. On 3-2, 34 of 37 decisions were Luna's, and there was no progress. When I went back through the logs, all 21 wins up to that point had been games where Astra decided every turn, and Luna had zero wins. So I removed the time limit and had it wait for Astra again.</li>
</ol>
<p><strong>Assists added by a human, and what happened</strong></p>
<table><thead><tr><th>Assist</th><th>What it did</th><th>Result</th></tr></thead><tbody><tr><td>Facts-only loss report</td><td>After each lost game, passed the next game's AI the breached row, remaining zombies, plants per row, and average and max sun (no strategy advice)</td><td>More to base decisions on, but on its own it kept getting stuck at 2-9</td></tr><tr><td>Helper hand Jev</td><td>A fast decision model, Jev (TypeSafe, about 0.2 s per response), picked and planted one of the legal candidates generated by code every 1.5 seconds. Jev is the same model I used in <a href="https://datapopcorn.ai/blog/n8n-jev-review-routing" target="_blank" rel="noopener noreferrer" class="">the n8n review-routing post</a></td><td>Got past 2-9, 2-10 and 3-1, but since it couldn't see the screen and only picked from candidates, in one game of 3-2 Jev made 18 of 19 plantings, planted 6 Lily Pads and nothing else, and put a Wall-nut behind the zombies. Turned off at the user's call</td></tr><tr><td>Win-probability drop</td><td>Restart games that look lost early (Step 6)</td><td>Less time spent on losing games</td></tr><tr><td>Self-directed strategy research</td><td>After a loss, the AI searched the web itself, reviewed its own records, then wrote a plan for the next game</td><td>Evolved into plan mode (Step 8)</td></tr><tr><td>Plan mode</td><td>The AI designs, the machine executes (Step 8)</td><td>First win on 3-2, after 13 straight losses, in 218 seconds at 2x speed</td></tr></tbody></table>
<p>That's how Astra got to 24 levels (3-4). At 7:15 a.m. on October 27, the user ended the run, saying "going further is pointless."</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-6-fold-likely-losses-early-with-a-win-probability-model">Step 6. Fold likely losses early with a win-probability model<a href="https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end#step-6-fold-likely-losses-early-with-a-win-probability-model" class="hash-link" aria-label="Direct link to Step 6. Fold likely losses early with a win-probability model" title="Direct link to Step 6. Fold likely losses early with a win-probability model" translate="no">​</a></h2>
<p>Watching lost games to the end felt like wasted time. If you restart games whose outcome is obvious and move on, you learn more in the same amount of time. The problem is deciding what counts as "obvious."</p>
<ol>
<li class=""><strong>Use past games as training data.</strong> From the 135 games both models had played so far, I pulled 3,473 game states and labeled each with that game's final result.</li>
<li class=""><strong>Pick features a player who knows the game would look at.</strong> Wave progress, lawn mowers lost, total zombie HP and the HP of nearby zombies, the position of the frontmost zombie, number of attacking plants, number of Sunflowers, rows with zombies but no attacking plant, zombie HP per attacking plant, and the number of zombies near the house.</li>
<li class=""><strong>Logistic regression is enough.</strong> It read memory every 2 seconds, computed the win probability and showed it in the right-hand panel (Step 7).</li>
<li class=""><strong>Validate the threshold against past records before setting it.</strong> This was the most important step.</li>
</ol>
<p>At first I had it restart immediately at "below 30% win probability for 10 seconds," and it kept cutting games off around the 1-minute mark, never even reaching the middle game. When I replayed the past records, this threshold turned out to wrongly cut 24 of the 57 games that were eventually won. Adding a 90-second grace period at the start of each game, during which it never cuts, brought the wrong cuts down to 9 of 57.</p>
<p><img decoding="async" loading="lazy" alt="Level 3-2 with the menu open and the Restart Level confirmation dialog showing. The win-probability drop restarts games in this order" src="https://datapopcorn.ai/blog/en/assets/images/05-restart-level-b5d8f556d6ad2b495f6a86d83c634737.webp" width="800" height="600" class="img_ev3q"></p>
<p>The restart clicks Menu → Restart Level → Restart, just like a person would. If the AI was clicking a menu at the same time, the windows overlapped and it failed, so during a drop I locked the AI's clicks, put down any seed it was holding with a right-click, and then ran the sequence. Restarted games count as losses, and the AI was told only that "it was restarted because the win probability was low." Minigames and boss fights were excluded from the win-probability model.</p>
<div class="theme-admonition theme-admonition-caution admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>Your runtime may be missing libraries</div><div class="admonitionContent_BuS1"><p>I used numpy for the win-probability calculation, but the Python on the Mac running the brain didn't have numpy. The program died right on startup, and instead of treating that as a failure, the supervisor script kept creating empty segments every second, moving on through the next chapter and the boss fight until it printed "run complete." There was no game input, so no harm was done, but I rewrote the prediction function in pure Python and changed the supervisor script to stop on failure.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-7-show-the-win-probability-and-the-reasons-for-actions-next-to-the-game-screen">Step 7. Show the win probability and the reasons for actions next to the game screen<a href="https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end#step-7-show-the-win-probability-and-the-reasons-for-actions-next-to-the-game-screen" class="hash-link" aria-label="Direct link to Step 7. Show the win probability and the reasons for actions next to the game screen" title="Direct link to Step 7. Show the win probability and the reasons for actions next to the game screen" translate="no">​</a></h2>
<p>When you watch an AI play alone for hours, you keep wondering "why did it plant there just now?" The same goes for anyone watching the recording. So I put a win-probability panel to the right of the game window, and a red box over the game where the AI just clicked. With just Windows and Python, you can build this with no extra installs (tkinter, pywin32).</p>
<p>The structure is simple. The AI loop sends the win probability and the action and reason over HTTP, and the control server on the gaming PC draws a panel window and a transparent box window.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Game memory/screen → AI loop (can be another PC): win probability every 2 s, action + reason every turn</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        │ HTTP POST /overlay {title, p, sub, reason, boxes}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        ▼</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Gaming PC control server: ① right-hand panel window ② transparent box window over the game</span><br></div></code></pre></div></div>
<p><img decoding="async" loading="lazy" alt="A panel to the right of the game window showing a 3-2 Luna decision, a 47% win probability and a yellow bar" src="https://datapopcorn.ai/blog/en/assets/images/04-winrate-panel-41b644b4039311ada1c5fb50f38c5370.webp" width="1180" height="622" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-1-create-an-always-on-top-panel-window">7-1. Create an always-on-top panel window<a href="https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end#7-1-create-an-always-on-top-panel-window" class="hash-link" aria-label="Direct link to 7-1. Create an always-on-top panel window" title="Direct link to 7-1. Create an always-on-top panel window" translate="no">​</a></h3>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> tkinter </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> tk</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">root </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> tk</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Tk</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> root</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">overrideredirect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic"># no border or title bar</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">root</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">attributes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"-topmost"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> root</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">attributes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"-toolwindow"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">big </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> tk</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Label</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">root</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> text</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"Win rate --"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> fg</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"#fff"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> bg</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"#111"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">               font</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Malgun Gothic"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">34</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"bold"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> big</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">pack</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>The HTTP server runs on a different thread, so it must not touch tkinter directly. Put the received values into a <code>queue.Queue</code>, and have the tk side pull them out and draw with <code>root.after(50, poll)</code>. I used green for 50% and above, yellow for 30% and above, and red below that, with the bar length showing the win probability.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-2-make-the-panel-follow-the-game-window">7-2. Make the panel follow the game window<a href="https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end#7-2-make-the-panel-follow-the-game-window" class="hash-link" aria-label="Direct link to 7-2. Make the panel follow the game window" title="Direct link to 7-2. Make the panel follow the game window" translate="no">​</a></h3>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">x</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> y </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> win32gui</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">ClientToScreen</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">hwnd</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">_</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> _</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> w</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> h </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> win32gui</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">GetClientRect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">hwnd</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">root</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">geometry</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">W</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">x</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">H</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">+</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">x </span><span class="token string-interpolation interpolation operator" style="color:#393A34">+</span><span class="token string-interpolation interpolation"> w </span><span class="token string-interpolation interpolation operator" style="color:#393A34">+</span><span class="token string-interpolation interpolation"> </span><span class="token string-interpolation interpolation number" style="color:#36acaa">12</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">+</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">y</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">          </span><span class="token comment" style="color:#999988;font-style:italic"># right next to the game window</span><br></div></code></pre></div></div>
<p>On a 4K monitor at 150% scaling, you need to turn on <code>SetProcessDpiAwareness(2)</code> for the coordinates to line up. And always keep the panel outside the game area. The AI sees the game area through screen captures, so if the panel covers the game, the AI mistakes it for part of the game screen.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-3-show-a-click-through-red-box-over-the-game">7-3. Show a click-through red box over the game<a href="https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end#7-3-show-a-click-through-red-box-over-the-game" class="hash-link" aria-label="Direct link to 7-3. Show a click-through red box over the game" title="Direct link to 7-3. Show a click-through red box over the game" translate="no">​</a></h3>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">bw </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> tk</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Toplevel</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">root</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> bw</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">overrideredirect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> bw</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">attributes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"-topmost"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">KEY </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"#ff00fe"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> bw</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">configure</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">bg</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">KEY</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> bw</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">attributes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"-transparentcolor"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> KEY</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">hw </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> win32gui</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">GetParent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">bw</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">winfo_id</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ex </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> win32gui</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">GetWindowLong</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">hw</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> win32con</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">GWL_EXSTYLE</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">win32gui</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">SetWindowLong</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">hw</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> win32con</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">GWL_EXSTYLE</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                       ex </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> win32con</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">WS_EX_LAYERED </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> win32con</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">WS_EX_TRANSPARENT</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># click-through</span><br></div></code></pre></div></div>
<p>Draw only rectangles in a window whose background is set to the transparent color, and hide it after 2.5 seconds. You need <code>WS_EX_TRANSPARENT</code> so clicks pass through the box to the game.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-4-keep-it-invisible-to-the-ai">7-4. Keep it invisible to the AI<a href="https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end#7-4-keep-it-invisible-to-the-ai" class="hash-link" aria-label="Direct link to 7-4. Keep it invisible to the AI" title="Direct link to 7-4. Keep it invisible to the AI" translate="no">​</a></h3>
<p>This is the most important part. When the AI requests a screenshot, hide the box window, wait for the signal that hiding is done, capture, and then show it again. I opened AI-bound captures taken while a box was up and confirmed there were no red pixels. Skip this and the AI reads its own markers as objects in the game.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-5-send-actions-and-reasons-from-the-ai-loop">7-5. Send actions and reasons from the AI loop<a href="https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end#7-5-send-actions-and-reasons-from-the-ai-loop" class="hash-link" aria-label="Direct link to 7-5. Send actions and reasons from the AI loop" title="Direct link to 7-5. Send actions and reasons from the AI loop" translate="no">​</a></h3>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">send_overlay</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token string" style="color:#e3116c">"title"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"3-2 · Astra decision"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"p"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.47</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">              </span><span class="token string" style="color:#e3116c">"sub"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Wave 5 · restart if below 30% for 10 s"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">              </span><span class="token string" style="color:#e3116c">"reason"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"[Astra] Action: Peashooter → row 2, col 3\nReason: a Conehead is coming down the top row, so block it"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">              </span><span class="token string" style="color:#e3116c">"boxes"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">202</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">165</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">278</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">249</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Peashooter"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>The reason text is the thinking field that was already in the AI's response, used as is. Asking it to write a separate explanation would add more calls.</p>
<p><img decoding="async" loading="lazy" alt="A red click box over the start button on the seed selection screen, with the AI&amp;#39;s latest action and reason shown in the right-hand panel" src="https://datapopcorn.ai/blog/en/assets/images/03-action-box-reason-6560d0a4210d2553d814487b3796e9c5.webp" width="1200" height="622" class="img_ev3q"></p>
<p>Here are the problems I ran into.</p>
<ul>
<li class="">Restarting the control server orphaned the recording process it had launched, and the files got corrupted. I switched recordings to fragmented mp4 (<code>-movflags +frag_keyframe+empty_moov</code>) and had the server clean up only this game's recordings on startup. Two recordings corrupted before that (a few minutes of 3-2) never played back.</li>
<li class="">At 150% scaling, panel text got cut off. Make the window tall enough (470px).</li>
<li class="">The panel, recording and clicking were all tied to one server, so turning off just the panel meant shutting down the whole server. Next time I plan to split the panel into its own process.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-8-plan-mode-the-ai-designs-the-machine-executes">Step 8. Plan mode: the AI designs, the machine executes<a href="https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end#step-8-plan-mode-the-ai-designs-the-machine-executes" class="hash-link" aria-label="Direct link to Step 8. Plan mode: the AI designs, the machine executes" title="Direct link to Step 8. Plan mode: the AI designs, the machine executes" translate="no">​</a></h2>
<p>In a real-time game, if the LLM thinks about "what should I plant next?" every turn, it's always late by however long the response takes. So I split the roles again. The AI writes a plan before the game starts, and once the game starts, a program executes that plan at machine speed.</p>
<ol>
<li class=""><strong>The AI writes the plan on the seed selection screen.</strong> It searches the web for a guide to that level itself, looks at its own past game records (seeds taken, planting order, results) and previous plans, then writes executable JSON. It contains the seeds to bring, an ordered placement table (plant, row, column, reason) and emergency rules (e.g., "if a zombie reaches this cell, Cherry Bomb").</li>
<li class=""><strong>Seed picking also happens automatically, following the plan.</strong> It reads the unlocked cards and clicks the seeds in the plan. When it read cards while the camera was still moving, noise got picked up as unlocked cards, and once Cabbage-pults and Melon-pults that weren't in the plan got in. I fixed it to read cards only after they're fully on screen.</li>
<li class=""><strong>The executor reads memory every 0.3 seconds and plants according to the plan.</strong> Emergency rules come first, then it fills empty cells from the top of the placement table. When it's short on money it saves sun for that item, replants cells that got eaten, and lays down Lily Pads on water and Flower Pots on the roof on its own. Those mechanical steps are the only decisions the executor makes.</li>
<li class=""><strong>After a loss, the AI rewrites the plan before the next game.</strong> I required it to fill in a "cause" field every time.</li>
</ol>
<p>The first executor had one gap. Because it only filled the placement table from the top, it would plant Sunflowers first, and time would pass with no attacking plant in a row where zombies had come in. The user asked, "why doesn't it plant a Peashooter first in the row the zombies are coming down?", and I added a rule: <strong>if a zombie enters a row with no attacking plant, plant that row's first attacking plant before anything else.</strong> I also wrote this rule into the plan-writing instructions so the AI would put an attacking plant for each row in the placement table.</p>
<p>The effect showed up in the numbers right away. 3-2 was a level it had lost 13 times, and in plan mode at 2x speed, using the plan Astra wrote (two columns of Sunflowers, two columns of Peashooters, Snow Pea, Wall-nut, and Squash and Cherry Bomb for emergencies), it won for the first time in 218 seconds.</p>
<p>Cranking up the speed wasn't always better. When I raised it to 5x, the number of plants at the same wave dropped to 6-12 (versus 21 in the 2x winning game). Uncollected sun on the lawn went from an average of 0.7 to 1.4, which suggests sun collection couldn't keep up with the speed. The levels were also different, so I can't say for sure, but prioritizing clears, I dropped back to 2x and shortened the sun-collection interval from 0.35 seconds to 0.15 seconds.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-9-get-a-recap-of-every-game-in-slack">Step 9. Get a recap of every game in Slack<a href="https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end#step-9-get-a-recap-of-every-game-in-slack" class="hash-link" aria-label="Direct link to Step 9. Get a recap of every game in Slack" title="Direct link to Step 9. Get a recap of every game in Slack" translate="no">​</a></h2>
<p>Logging in remotely every time to check "how's it going?" during an overnight run is inefficient. So I had a recap posted to a Slack channel at the end of every game. Wins post immediately; losses post right after the AI writes its next plan (which doubles as its own recap).</p>
<p>Here's what went into each recap.</p>
<ul>
<li class="">Level, result, attempt number</li>
<li class="">Time taken (speed multiplier and game time listed separately)</li>
<li class="">The model that wrote the plan, and the seeds it brought</li>
<li class="">Number of plantings, max plant count, average sun</li>
<li class="">Win-probability trend (start → low → end)</li>
<li class="">A factual record of the loss and the record on this level</li>
<li class="">The AI's cause analysis and a summary of the next plan</li>
<li class="">Execution diagnostics: time spent waiting to save sun for each plan item, items it couldn't plant and why, seeds in the plan that weren't brought, and sun left on the lawn</li>
</ul>
<p>Adding the execution diagnostics paid off. The bug "a catapult that wasn't in the plan got in, so it couldn't plant attacking plants" was found because the AI wrote it in the cause field itself. Separately, I ran a watch script that sends the game screen and progress every 3 hours. It's an ordinary script with no LLM, so it costs nothing.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="costs-api-and-subscriptions-are-counted-differently">Costs: API and subscriptions are counted differently<a href="https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end#costs-api-and-subscriptions-are-counted-differently" class="hash-link" aria-label="Direct link to Costs: API and subscriptions are counted differently" title="Direct link to Costs: API and subscriptions are counted differently" translate="no">​</a></h2>
<p><strong>Opus 5.5 (API):</strong> finishing all 50 levels comes to about $104.9 at API prices. By chapter:</p>
<table><thead><tr><th>Chapter</th><th>Time</th><th>W/L</th><th>Calls</th><th>At API prices</th></tr></thead><tbody><tr><td>Ch. 1 Day</td><td>46 min</td><td>10/1</td><td>253</td><td>$5.2</td></tr><tr><td>Ch. 2 Night</td><td>84 min</td><td>10/4</td><td>451</td><td>$11.4</td></tr><tr><td>Ch. 3 Pool</td><td>201 min</td><td>10/18</td><td>961</td><td>$26.2</td></tr><tr><td>Ch. 4 Fog</td><td>174 min</td><td>10/10</td><td>887</td><td>$23.5</td></tr><tr><td>Ch. 5 Roof</td><td>276 min</td><td>10/30</td><td>1,488</td><td>$38.7</td></tr><tr><td>Total</td><td>13.0 hours</td><td>50/63</td><td>4,040</td><td>$104.9</td></tr></tbody></table>
<p>Harness bugs and the black-screen stretch (122 minutes, 552 calls, $10.7) are excluded from this table. The actual charges came out of Anthropic API promotional credits, so I didn't pay anything separately, and this month's usage shown in the Console is $127.60. The Console amount covers the whole month, so it isn't the same number as the table above.</p>
<p><strong>Astra (ChatGPT Pro subscription + Codex CLI)</strong> isn't billed per call. Instead of money, what ran out was time. Even with weekly usage at 33%, 429 congestion stalled it for up to 100 minutes at a stretch. Converting just the 1-1 to 1-4 comparison at API prices gives $4.01 (Opus: $1.11), but most of that comes from the Codex default instructions attached to every call, so don't read it as a comparison of model prices.</p>
<p>The takeaway: for a multi-hour real-time experiment, "a path that doesn't get blocked" mattered more than "a cheap path."</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="failures-and-fixes-at-a-glance">Failures and fixes at a glance<a href="https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end#failures-and-fixes-at-a-glance" class="hash-link" aria-label="Direct link to Failures and fixes at a glance" title="Direct link to Failures and fixes at a glance" translate="no">​</a></h2>
<table><thead><tr><th>Symptom</th><th>Cause</th><th>Fix</th></tr></thead><tbody><tr><td>Clicked sun in the wrong place</td><td>Fullscreen captures lagged behind the actual screen</td><td>Run in windowed mode</td></tr><tr><td>Couldn't find the sun value in memory</td><td>Launcher and actual game were different processes</td><td>Target the actual game process</td></tr><tr><td>30% of plantings failed on the conveyor level</td><td>Clicked card positions from a few seconds earlier, slide offset not applied</td><td>Re-read right before clicking, apply the offset</td></tr><tr><td>1 h 50 min of black screen, about $9 wasted</td><td>Gaming PC lost its monitor signal</td><td>Stop AI calls on black-screen detection, virtual monitor</td></tr><tr><td>Stuck in the shop for 8 minutes</td><td>The shop also read as "playing," so the Golden Watering Can was mistaken for sun</td><td>Auto-collect only inside levels</td></tr><tr><td>28 straight losses to Dr. Zomboss on 5-10 at 1x</td><td>Responded too late to boss attacks while thinking</td><td>0.5x for the boss fight only (disclosed)</td></tr><tr><td>Couldn't restart for 5 h 20 min overnight</td><td>The auto-stop after 6 straight losses waited for human confirmation, and the laptop I was watching from went to sleep</td><td>Relaxed the stop threshold, a no-LLM watch script every 3 hours</td></tr><tr><td>Run died on startup and printed "run complete"</td><td>No numpy in the runtime, supervisor script ignored the failure</td><td>Pure-Python prediction, stop on failure</td></tr><tr><td>Won 3-4 and then stalled for 5 hours</td><td>Nothing was there to click the reward key in plan mode</td><td>Reward handling on the AI's turn, a 15-minute no-change watchdog</td></tr><tr><td>Lost part of a recording</td><td>Restarting the control server orphaned the recording process (cause of some losses unconfirmed)</td><td>Fragmented mp4, clean up recordings on startup</td></tr><tr><td>34 of 37 Astra decisions were made by Luna</td><td>"Switch to Luna after 10 s" rule, with Astra averaging 17 s</td><td>Removed Astra's time limit, retry only on failure</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-im-taking-to-the-next-game">What I'm taking to the next game<a href="https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end#what-im-taking-to-the-next-game" class="hash-link" aria-label="Direct link to What I'm taking to the next game" title="Direct link to What I'm taking to the next game" translate="no">​</a></h2>
<p>The biggest lesson this time is the habit of deciding up front what to give the AI and what to give the program. Read state from memory or structured data, let the program handle clicks and repetitive actions, and leave planning and judgment to the AI. And if it's going to run unattended, put in stall watchdogs, a supervisor script that stops on failure and a per-game recap from day one. This time, I added them one by one only after things went wrong.</p>
<p>I'm also changing how I compare. Starting next episode, I'll call both models through the same access method (API vs. API, or subscription vs. subscription), pause the game while the AI thinks so response speed doesn't decide the match, run on the same seed in the same time window, and fix the allowed assists (speed, automatic actions, fallback models) before the start and not change them midway. Adding assists every time something gets stuck, like in this PvZ run, ends up comparing "who got more help" rather than "who played better."</p>
<p>From the next episode on, I'll publish the rules before the match starts. I'll state at the top of the video and the post how much the AI does on its own, what the program does on its behalf, and that no human steps in during the match.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="update-october-28-how-the-harness-actually-changed-for-the-next-match">Update (October 28): how the harness actually changed for the next match<a href="https://datapopcorn.ai/blog/en/ai-plays-plants-vs-zombies-to-the-end#update-october-28-how-the-harness-actually-changed-for-the-next-match" class="hash-link" aria-label="Direct link to Update (October 28): how the harness actually changed for the next match" title="Direct link to Update (October 28): how the harness actually changed for the next match" translate="no">​</a></h2>
<p>After publishing this post, I started the second match (Frostpunk, a game where you run a city for 48 days) and put the lessons above into practice. Here is what carried over and what turned out to be new.</p>
<p><strong>What carried over</strong></p>
<table><thead><tr><th>What I learned from Plants vs. Zombies</th><th>What changed in the second match</th></tr></thead><tbody><tr><td>Adding help every time a model gets stuck breaks the comparison</td><td>I wrote a shared rules document before the match: human intervention, what information the AI gets, what the harness does on its behalf, no fallback models, and what gets disclosed in the video</td></tr><tr><td>Response speed decided the outcome</td><td>The game pauses while the AI thinks. Only the harness presses the time-advance key</td></tr><tr><td>Mixing API and subscription access blurs the comparison</td><td>Both models are called through subscription CLIs (Claude Code, Codex)</td></tr><tr><td>A win-probability model let me quit losing games early</td><td>The same logistic regression now estimates "probability of being ousted within 3 days," which is used to pick which save to roll back to and to abandon hopeless runs</td></tr><tr><td>Monitoring was added only after something broke</td><td>From day one: a supervisor script (restarts on crash), screen monitoring, waiting out subscription limits, and Slack alerts</td></tr><tr><td>A Slack recap after every level</td><td>An 8x-speed video with sound for each in-game day, sent to Slack</td></tr></tbody></table>
<p><strong>What was new</strong></p>
<ul>
<li class=""><strong>Saving and rolling back</strong>: A level that lasts a few minutes can simply be replayed, but a 48-day game is far too long to restart from scratch after a loss. The harness saves under a new name every day and rolls back to a designated save after a loss.</li>
<li class=""><strong>A daily plan</strong>: Reacting turn by turn missed problems that blow up days later (coal, food, discontent). Every morning the AI writes a plan and rereads it each turn.</li>
<li class=""><strong>The program does the math</strong>: The harness calculates how many hours until a resource runs out and how fast discontent rises per day, and tells the AI.</li>
<li class=""><strong>When stuck, the AI studies on its own</strong>: Instead of a human giving hints, the AI searches the web for guides when it gets stuck and writes its own notes.</li>
</ul>
<p>Even so, the rules changed mid-match this time too. Every change came from the user, applied equally to both models, and the timing and reasons will be disclosed at the end of the second match's post and video.</p>]]></content:encoded>
            <category>AI agents</category>
            <category>AI Game League</category>
        </item>
        <item>
            <title><![CDATA[Is It Me or the AI? Find Out in 10 Seconds: A 4-Step Status Page Check Plus a Script That Checks Everything at Once]]></title>
            <link>https://datapopcorn.ai/blog/en/ai-service-status-check</link>
            <guid>https://datapopcorn.ai/blog/en/ai-service-status-check</guid>
            <pubDate>Tue, 27 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[When ChatGPT, Claude or Gemini suddenly throws errors or gives strange answers, check the official status pages first to tell whether it's your prompt or code, or a service outage. Here's the order I follow, plus a script that checks all three services at once.]]></description>
            <content:encoded><![CDATA[<p>When an AI suddenly throws errors or gives off-the-wall answers, I <strong>check the official status page first</strong>, before touching my prompt or code. It tells you within 10 seconds whether the problem is on the service's side, and it saves you from spending hours taking apart code that works fine.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="who-this-is-for">Who this is for<a href="https://datapopcorn.ai/blog/en/ai-service-status-check#who-this-is-for" class="hash-link" aria-label="Direct link to Who this is for" title="Direct link to Who this is for" translate="no">​</a></h2>
<ul>
<li class="">You use ChatGPT, Claude or Gemini at work and have asked yourself "it worked yesterday, why not today?"</li>
<li class="">You call AI APIs from n8n, Make or your own code, and errors suddenly start piling up</li>
<li class="">You're an instructor or team lead who needs to tell students or teammates "if the AI acts weird, check this first"</li>
</ul>
<p>All you need is a browser. To use the script in Step 4, you also need Terminal on a Mac (it comes preinstalled). I opened and checked every URL and response in this post myself on October 7, 2026.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1-bookmark-the-three-status-pages">Step 1. Bookmark the three status pages<a href="https://datapopcorn.ai/blog/en/ai-service-status-check#step-1-bookmark-the-three-status-pages" class="hash-link" aria-label="Direct link to Step 1. Bookmark the three status pages" title="Direct link to Step 1. Bookmark the three status pages" translate="no">​</a></h2>
<p>If you start searching only once something breaks, you're already late. Put these three URLs in one bookmark folder ahead of time.</p>
<table><thead><tr><th>Service</th><th>Status page</th><th>What it shows</th></tr></thead><tbody><tr><td>OpenAI (ChatGPT, API)</td><td><a href="https://status.openai.com/" target="_blank" rel="noopener noreferrer" class="">status.openai.com</a></td><td>Status of ChatGPT and each API feature (Chat Completions, Responses, Embeddings, etc.)</td></tr><tr><td>Anthropic (Claude)</td><td><a href="https://status.claude.com/" target="_blank" rel="noopener noreferrer" class="">status.claude.com</a></td><td>claude.ai, Claude Console, Claude API, Claude Code, etc.</td></tr><tr><td>Google (Gemini API)</td><td><a href="https://status.cloud.google.com/" target="_blank" rel="noopener noreferrer" class="">status.cloud.google.com</a></td><td>All Google Cloud incidents. The Gemini API is listed as "Vertex Gemini API"</td></tr></tbody></table>
<p>The old address <code>status.anthropic.com</code> now redirects to <code>status.claude.com</code>. It's cleaner to bookmark the new one.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2-look-at-the-banner-color-and-text-at-the-top">Step 2. Look at the banner color and text at the top<a href="https://datapopcorn.ai/blog/en/ai-service-status-check#step-2-look-at-the-banner-color-and-text-at-the-top" class="hash-link" aria-label="Direct link to Step 2. Look at the banner color and text at the top" title="Direct link to Step 2. Look at the banner color and text at the top" translate="no">​</a></h2>
<p>When you open a status page, the overall status appears in one line at the very top.</p>
<p><img decoding="async" loading="lazy" alt="The Claude status page. A green banner at the top reads All Systems Operational, and below it are 90-day uptime bars for claude.ai, Claude Console, Claude API and Claude Code" src="https://datapopcorn.ai/blog/en/assets/images/claude-status-page-4634e938622bc8fd552b26497c3d255d.png" width="1600" height="1000" class="img_ev3q"></p>
<p>This is the Claude status page on October 7, 2026. If the green banner at the top says <strong>All Systems Operational</strong>, everything is running normally. Here's roughly how to read the messages.</p>
<table><thead><tr><th>Message</th><th>Meaning</th><th>What to do</th></tr></thead><tbody><tr><td>All Systems Operational / Operational</td><td>Normal</td><td>Move on to Step 3 and look for the cause on your side</td></tr><tr><td>Degraded Performance</td><td>Slower, or some requests failing</td><td>Wait a bit and try again</td></tr><tr><td>Partial Outage</td><td>Some features down</td><td>Check whether the component you use is affected</td></tr><tr><td>Major Outage</td><td>Major incident</td><td>Leave your code alone and wait for recovery</td></tr></tbody></table>
<p>The bars below show each service's history for the last 90 days. On this screen, claude.ai's 90-day uptime was 99.46%. The scattered red and orange bars also tell you that outages happen more often than you might think.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3-check-whether-the-component-you-use-has-its-own-incident">Step 3. Check whether the component you use has its own incident<a href="https://datapopcorn.ai/blog/en/ai-service-status-check#step-3-check-whether-the-component-you-use-has-its-own-incident" class="hash-link" aria-label="Direct link to Step 3. Check whether the component you use has its own incident" title="Direct link to Step 3. Check whether the component you use has its own incident" translate="no">​</a></h2>
<p>Even when the overall banner is green, <strong>just the feature you use</strong> might be down. Find the component you use in the list below the banner.</p>
<ul>
<li class="">If the problem is in the ChatGPT app, look at the ChatGPT component.</li>
<li class="">If you call the API from code or n8n, look at the API components. OpenAI splits these by feature, such as Chat Completions and Responses.</li>
<li class="">Claude lists claude.ai (web and apps) and the Claude API separately. Some days the web works and only the API fails.</li>
</ul>
<p>If everything here is normal too, that's when you start suspecting your side (API key, billing limits, prompt, code). Sticking to this order alone cuts down on debugging in the wrong place.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4-use-a-script-to-check-all-three-services-at-once">Step 4. Use a script to check all three services at once<a href="https://datapopcorn.ai/blog/en/ai-service-status-check#step-4-use-a-script-to-check-all-three-services-at-once" class="hash-link" aria-label="Direct link to Step 4. Use a script to check all three services at once" title="Direct link to Step 4. Use a script to check all three services at once" translate="no">​</a></h2>
<p>Besides the page meant for humans, the OpenAI and Claude status pages also expose an address for programs to read. Open the URLs below in a browser and you get a one-line result.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">https://status.openai.com/api/v2/status.json</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">https://status.claude.com/api/v2/status.json</span><br></div></code></pre></div></div>
<p>Here's the actual response on October 7, 2026 (Claude).</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"page"</span><span class="token operator" style="color:#393A34">:</span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"id"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"tymt9n04zgry"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"Claude"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"url"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"https://status.claude.com"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"time_zone"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"Etc/UTC"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"updated_at"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"2026-10-07T00:45:09.742Z"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"status"</span><span class="token operator" style="color:#393A34">:</span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"indicator"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"none"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"description"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"All Systems Operational"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>An <code>indicator</code> of <code>none</code> means normal. During an incident it changes to <code>minor</code>, <code>major</code> or <code>critical</code>. Google uses a different format, so you have to look in <code>https://status.cloud.google.com/incidents.json</code> for unresolved incidents (no <code>end</code> value) that involve Gemini.</p>
<p>Checking all of this by hand every time got tedious, so I wrote a script that does it in one go. Paste the whole block below into Terminal and it creates an <code>ai-status.sh</code> file on your Desktop.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">cat</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> ~/Desktop/ai-status.sh </span><span class="token operator" style="color:#393A34">&lt;&lt;</span><span class="token string" style="color:#e3116c">'SCRIPT'</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">#!/bin/bash</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c"># Check the status of AI services in one go. (Uses macOS built-in curl and python3)</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">for pair in "OpenAI|https://status.openai.com/api/v2/status.json" "Claude|https://status.claude.com/api/v2/status.json"; do</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">  name="${pair%%|*}"; url="${pair#*|}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">  curl -s -m 10 "$url" | python3 -c "import sys,json; d=json.load(sys.stdin)['status']; print(f\"$name: {d['indicator']} / {d['description']}\")" 2&gt;/dev/null || echo "$name: check failed (check your network or the URL)"</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">done</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">curl -s -m 10 https://status.cloud.google.com/incidents.json | python3 -c "</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">import sys,json</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">d=json.load(sys.stdin)</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">open_=[i for i in d if not i.get('end') and any('Gemini' in p.get('title','') or 'Vertex' in p.get('title','') for p in i.get('affected_products',[]))]</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">print('Google Cloud (Gemini/Vertex): %d ongoing incident(s)' % len(open_))</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">for i in open_[:3]: print('  -', i['external_desc'][:80])</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">" 2&gt;/dev/null || echo "Google Cloud: check failed"</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">SCRIPT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">chmod</span><span class="token plain"> +x ~/Desktop/ai-status.sh</span><br></div></code></pre></div></div>
<p>Running it takes one line.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">~/Desktop/ai-status.sh</span><br></div></code></pre></div></div>
<p>Here's the result when I ran it on October 7, 2026.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">OpenAI: none / All Systems Operational</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Claude: none / All Systems Operational</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Google Cloud (Gemini/Vertex): 0 ongoing incident(s)</span><br></div></code></pre></div></div>
<p>If all three lines are normal, the cause is most likely on your side.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="if-you-get-an-error-start-here">If you get an error, start here<a href="https://datapopcorn.ai/blog/en/ai-service-status-check#if-you-get-an-error-start-here" class="hash-link" aria-label="Direct link to If you get an error, start here" title="Direct link to If you get an error, start here" translate="no">​</a></h2>
<table><thead><tr><th>Symptom</th><th>What to check</th></tr></thead><tbody><tr><td>The script prints <code>check failed</code></td><td>Check your internet connection, and whether a company network or VPN blocks those URLs</td></tr><tr><td><code>permission denied</code></td><td>Run <code>chmod +x ~/Desktop/ai-status.sh</code> from Step 4 again</td></tr><tr><td>Status page is normal but the API returns <code>401</code></td><td>Your API key is wrong or expired. Double-check the key</td></tr><tr><td>Status page is normal but you get <code>429</code></td><td>You've hit your account's usage or rate limit. Retry after a while, or check your billing and limits</td></tr><tr><td>Status page is normal but the answers are strange</td><td>Now it's time to check your prompt, the material you passed in, and your model choice</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-up<a href="https://datapopcorn.ai/blog/en/ai-service-status-check#wrap-up" class="hash-link" aria-label="Direct link to Wrap-up" title="Direct link to Wrap-up" translate="no">​</a></h2>
<p>When an AI acts up, the order is always the same: check the status page banner, check the component you use, and only when both are normal, look at your side. With three bookmarks and a one-line script, this check takes under 10 seconds.</p>]]></content:encoded>
            <category>Education &amp; career</category>
            <category>Dev tool tips</category>
        </item>
        <item>
            <title><![CDATA[How to Use the Pickle AI Agent: From Install to Your First Task, Compared with Grok Bot, Muse and Dots]]></title>
            <link>https://datapopcorn.ai/blog/en/pickle-ai-agent-guide</link>
            <guid>https://datapopcorn.ai/blog/en/pickle-ai-agent-guide</guid>
            <pubDate>Mon, 26 Oct 2026 09:00:00 GMT</pubDate>
            <description><![CDATA[A 12-step guide to Pickle, a Korean personal AI agent app: install it on an iPhone or Android phone, connect Google Calendar, check the disclosure settings and usage limit, hand it a schedule summary and a web research task and verify the results, then connect 21 apps and services, including ones that need a login. I also put the differences from Grok Bot, Meta Muse and OpenAI Dots in a table. Based on the Android app as of October 26, 2026.]]></description>
            <content:encoded><![CDATA[<p>Follow this guide and you'll install Pickle on your phone (iPhone or Android), connect just one Google Calendar, check the disclosure settings and usage limit, and hand it two first tasks whose results you verify yourself. Pickle is a personal AI agent you can use for free in Korea. I got this week's schedule summarized in under a minute and research on 3 workshop venues in a little over 2 minutes. That said, 1 of the 3 venues had the wrong address and price.</p>
<!-- -->
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>What this post is based on</div><div class="admonitionContent_BuS1"><p>October 26, 2026, Galaxy S22+ (Android 16), Pickle app 1.1.16, a free Korean account. I started with sign-up already done, so there are no screenshots of the sign-up screens. I drove the screen and took captures over ADB (Android Debug Bridge) from a PC, but you can do exactly the same thing by tapping with your finger. The screenshots show the Korean UI.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pickle-is-an-agent-with-its-own-browser-and-phone-number">Pickle is an agent with its own browser and phone number<a href="https://datapopcorn.ai/blog/en/pickle-ai-agent-guide#pickle-is-an-agent-with-its-own-browser-and-phone-number" class="hash-link" aria-label="Direct link to Pickle is an agent with its own browser and phone number" title="Direct link to Pickle is an agent with its own browser and phone number" translate="no">​</a></h2>
<p>Pickle goes a step beyond a chatbot that answers questions. According to its official help pages, Pickle <a href="https://pickle.com/help/tasks/delegate" target="_blank" rel="noopener noreferrer" class="">works on websites like a person, using a real web browser, and places phone calls itself where the web won't do</a>. Connect your email and calendar and it reads them to find things to take care of before you ask; if your friends use Pickle too, your Pickles coordinate plans with each other.</p>
<p>For users in Korea, the biggest difference is price. In Pickle's words, <a href="https://pickle.com/help/credits/plans" target="_blank" rel="noopener noreferrer" class="">every account in Korea is free, and instead there's a daily usage limit that resets at 4 a.m. every day</a>. The old paid plan (Pickle Plus) is no longer sold. It's made by Pickle, Inc., a startup from Y Combinator's Winter 2025 batch, which started out with AI avatars for video calls and then pivoted to personal agents.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-you-need">What you need<a href="https://datapopcorn.ai/blog/en/pickle-ai-agent-guide#what-you-need" class="hash-link" aria-label="Direct link to What you need" title="Direct link to What you need" translate="no">​</a></h2>
<ul>
<li class="">An iPhone (iOS 17 or later) or an Android phone (screenshots are from Android; Pickle is for ages 18 and up only)</li>
<li class="">One Google account to connect (I recommend starting with just the calendar)</li>
<li class="">A browser on a PC to check the results against</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1-install-from-the-app-store-or-play-store-and-sign-up">Step 1. Install from the App Store or Play Store and sign up<a href="https://datapopcorn.ai/blog/en/pickle-ai-agent-guide#step-1-install-from-the-app-store-or-play-store-and-sign-up" class="hash-link" aria-label="Direct link to Step 1. Install from the App Store or Play Store and sign up" title="Direct link to Step 1. Install from the App Store or Play Store and sign up" translate="no">​</a></h2>
<ol>
<li class="">Install "피클: 나만의 AI 에이전트" ("Pickle: My Own AI Agent," publisher Pickle, Inc.) from the App Store on iPhone or the Play Store on Android. iPhone requires iOS 17 or later. As of October 26 it's a new app with 500+ downloads and 8 reviews on the Play Store, so check the publisher name so you don't confuse it with similarly named apps.</li>
<li class="">Open the app and follow the prompts to sign up. After sign-up, <code>My Pickle → My account</code> shows your name, Pickle ID, phone number and date of birth, so have that information ready.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2-read-what-pickle-can-do-in-the-first-chat">Step 2. Read what Pickle can do in the first chat<a href="https://datapopcorn.ai/blog/en/pickle-ai-agent-guide#step-2-read-what-pickle-can-do-in-the-first-chat" class="hash-link" aria-label="Direct link to Step 2. Read what Pickle can do in the first chat" title="Direct link to Step 2. Read what Pickle can do in the first chat" translate="no">​</a></h2>
<p>Once you finish signing up, Pickle sends an introduction in the default chat. The three key points: "I have my own computer, so I can do things like book train tickets, file taxes and place orders on Coupang (Korea's biggest online marketplace)," "I can make calls for you from my number," and "if you connect them, I'll manage your email and calendar too." Right below are suggested connection cards (Apple Calendar, Google Calendar, Gmail).</p>
<p><img decoding="async" loading="lazy" alt="Pickle&amp;#39;s first chat screen. The introduction and cards for connected apps and services. The last message, which contains personal schedule details, is hidden" src="https://datapopcorn.ai/blog/en/assets/images/01-first-chat-a58ac4bb3d82ad8e601fa0ce5ed2ee15.webp" width="720" height="1560" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3-connect-just-google-calendar-first">Step 3. Connect just Google Calendar first<a href="https://datapopcorn.ai/blog/en/pickle-ai-agent-guide#step-3-connect-just-google-calendar-first" class="hash-link" aria-label="Direct link to Step 3. Connect just Google Calendar first" title="Direct link to Step 3. Connect just Google Calendar first" translate="no">​</a></h2>
<p>Don't connect your email, shopping sites and Gov24 (the Korean government's online services portal) all at once. Start with the calendar. The official help page says that <a href="https://pickle.com/help/connect/add-connection" target="_blank" rel="noopener noreferrer" class="">when you add a connection, Pickle reads that account's content broadly, and what it reads can stay in conversations, memory and task history</a>. A calendar exposes a relatively narrow slice of your data, and you can verify the results right away.</p>
<ol>
<li class="">Tap <code>Google Calendar</code> on the connection card in the chat, or go to <code>My Pickle → Apps and services connected to Pickle → +</code>.</li>
<li class="">After you sign in to Google and grant permissions, the switch next to the account turns on.</li>
</ol>
<p><img decoding="async" loading="lazy" alt="Google Calendar connection screen with the account switch turned on" src="https://datapopcorn.ai/blog/en/assets/images/02-google-calendar-f243b89d6e50b8e0c567528319184b48.webp" width="720" height="1560" class="img_ev3q"></p>
<p>The connection list includes Google sign-in, Naver Mail (from Naver, Korea's largest search portal), Gmail, Microsoft 365, Google Drive, Naver, Coupang, Gov24, Location and Contacts, and the <code>Android</code> tab separately lists your phone's location, contacts, calendar and photos. Having Naver, Coupang and Gov24 on the default list is what sets it apart most from agents built outside Korea.</p>
<p><img decoding="async" loading="lazy" alt="The connect apps and services screen. Naver, Coupang and Gov24 are on the default list" src="https://datapopcorn.ai/blog/en/assets/images/03-connections-1b7b08f2c3c725ef0c00d4cc4932d04d.webp" width="720" height="1560" class="img_ev3q"></p>
<p>A few seconds after connecting, Pickle messaged me first. It pointed out two events tonight and said it had "set up reminders so you don't miss" two deadlines next month. I'll check where those reminders were set up in Step 8.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4-check-the-usage-limit-in-the-my-pickle-tab">Step 4. Check the usage limit in the My Pickle tab<a href="https://datapopcorn.ai/blog/en/pickle-ai-agent-guide#step-4-check-the-usage-limit-in-the-my-pickle-tab" class="hash-link" aria-label="Direct link to Step 4. Check the usage limit in the My Pickle tab" title="Direct link to Step 4. Check the usage limit in the My Pickle tab" translate="no">​</a></h2>
<p>The person icon at the bottom is the <code>My Pickle</code> tab. The top card is the daily usage limit, which goes back to 0% at 4 a.m. every day. When you use it all, the input box turns off; if you don't want to wait, you use a limit reset ticket. I had 1 full reset ticket as a sign-up gift, and every time you invite a friend, you both get 1.</p>
<p><img decoding="async" loading="lazy" alt="The My Pickle tab: daily usage limit, reset tickets, connected apps" src="https://datapopcorn.ai/blog/en/assets/images/04-my-pickle-412a15238f82d6019e039de2bb5a2732.webp" width="720" height="1560" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" alt="The limit reset ticket screen: full reset and half reset tickets, and the auto-use setting" src="https://datapopcorn.ai/blog/en/assets/images/06-usage-ticket-aa717108ffe18bcdcb940e0cf8759f7d.webp" width="720" height="1340" class="img_ev3q"></p>
<p>Auto-use for reset tickets is off by default. Tickets are scarce, so it's better to leave it off.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-5-set-your-disclosure-settings-first">Step 5. Set your disclosure settings first<a href="https://datapopcorn.ai/blog/en/pickle-ai-agent-guide#step-5-set-your-disclosure-settings-first" class="hash-link" aria-label="Direct link to Step 5. Set your disclosure settings first" title="Direct link to Step 5. Set your disclosure settings first" translate="no">​</a></h2>
<p>Because Pickle can talk to your friends' Pickles, there's a separate screen for deciding how much your Pickle can say about you. Go to <code>My Pickle → Disclosure settings</code>.</p>
<p><img decoding="async" loading="lazy" alt="The disclosure settings screen. The default is &amp;quot;to friends&amp;#39; Pickles: up to everyday updates&amp;quot;" src="https://datapopcorn.ai/blog/en/assets/images/05-disclosure-e840983dec373e0af82a0aafc84b0d1e.webp" width="720" height="1560" class="img_ev3q"></p>
<p>The default is "To friends' Pickles: up to everyday updates." Under <code>Connected apps and services</code> you can decide per app whether it's okay to share with other people, and <code>Things never shared with anyone</code> says that sensitive things like health, money, private life, beliefs, exact location and account security are never shared with anyone by default. If you don't have friends on it yet, leave it as is and revisit <code>Per-person settings</code> when you add a friend.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-6-for-the-first-task-have-it-summarize-this-weeks-schedule">Step 6. For the first task, have it summarize this week's schedule<a href="https://datapopcorn.ai/blog/en/pickle-ai-agent-guide#step-6-for-the-first-task-have-it-summarize-this-weeks-schedule" class="hash-link" aria-label="Direct link to Step 6. For the first task, have it summarize this week's schedule" title="Direct link to Step 6. For the first task, have it summarize this week's schedule" translate="no">​</a></h2>
<p>I sent this in the default chat input box.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Give me an at-a-glance summary of my schedule this week. Also tell me about any days with overlaps or that are packed.</span><br></div></code></pre></div></div>
<p>The answer came in under a minute. It split things into three groups, "overlapping events," "days with packed evenings" and "day-by-day overview," found that a Sunday night lecture and a daily routine event overlapped at exactly the same time, and asked, "Want me to move it?" When I checked against my calendar, every date and time was right.</p>
<p><img decoding="async" loading="lazy" alt="This week&amp;#39;s schedule summary, split into three groups: overlapping events, days with packed evenings and a day-by-day view. Personal event details are hidden" src="https://datapopcorn.ai/blog/en/assets/images/08-weekly-briefing-81ea6995fb38b18814f05383e52bae8c.webp" width="720" height="1560" class="img_ev3q"></p>
<p><a href="https://datapopcorn.ai/blog/openai-dots-cloud-computer-agent" target="_blank" rel="noopener noreferrer" class="">When I tried OpenAI Dots</a>, a similar schedule briefing took about 4 minutes. Dots pulled in email and Notion and attached what I'd need to prepare; Pickle only had the calendar connected, so it only summarized the schedule. With different connections, speed alone isn't a fair comparison.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-7-for-the-second-task-have-it-research-the-public-web-then-verify">Step 7. For the second task, have it research the public web, then verify<a href="https://datapopcorn.ai/blog/en/pickle-ai-agent-guide#step-7-for-the-second-task-have-it-research-the-public-web-then-verify" class="hash-link" aria-label="Direct link to Step 7. For the second task, have it research the public web, then verify" title="Direct link to Step 7. For the second task, have it research the public web, then verify" translate="no">​</a></h2>
<p>I gave it the same research I used in my <a href="https://datapopcorn.ai/blog/muse-personal-agent-features" target="_blank" rel="noopener noreferrer" class="">Muse guide</a>. I included conditions not to use my personal accounts and not to make any bookings or inquiries.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Don't use my personal accounts like email or calendar; research using only public web information. Find 3 rental spaces in Seoul where I could hold a hands-on AI workshop for about 20 people, and compare location, capacity, published price and source link in a table. Don't book or contact anyone, and if the price isn't published, write "not published."</span><br></div></code></pre></div></div>
<p>While it works, "Searching the web," "Reading page" and "Thinking" take turns under Pickle's avatar. About 2 minutes 15 seconds later, a table arrived. You have to swipe the table sideways to see the price and source columns.</p>
<p><img decoding="async" loading="lazy" alt="Pickle&amp;#39;s comparison table of rental spaces: space name and location columns" src="https://datapopcorn.ai/blog/en/assets/images/09-venue-table-0655fcf6757c223751cf1de3438c1a64.webp" width="720" height="1560" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" alt="Swiping the table sideways shows the published price and source link columns" src="https://datapopcorn.ai/blog/en/assets/images/10-venue-table-price-8d38d9a3d2a8e79b5139a7fc3fb2d9e2.webp" width="720" height="1560" class="img_ev3q"></p>
<p>Below the table were notes on each space's setup for hands-on work, and a SpaceCloud (a Korean venue rental platform) link card for the first space.</p>
<p><img decoding="async" loading="lazy" alt="Reference notes on each space&amp;#39;s setup for hands-on work" src="https://datapopcorn.ai/blog/en/assets/images/11-venue-notes-e33d765b39ae4329134e290320a58399.webp" width="720" height="1560" class="img_ev3q"></p>
<p>I opened each source and checked.</p>
<table><thead><tr><th>What Pickle wrote</th><th>What the source actually says</th></tr></thead><tbody><tr><td>GTC (Gounet Training Center) Room 202, Seongsu, up to 20 people, KRW 71,000 per hour (2-hour minimum)</td><td>Address, capacity, price and minimum hours all match the <a href="https://www.spacecloud.kr/space/57005" target="_blank" rel="noopener noreferrer" class="">SpaceCloud listing</a></td></tr><tr><td>QJ School Code Room B, 9 Teheran-ro 10-gil, Gangnam-gu, up to 20 people, KRW 15,000 per hour</td><td>Address, 20-person room and KRW 15,000 per hour confirmed on the <a href="https://www.spacecloud.kr/space/73701" target="_blank" rel="noopener noreferrer" class="">SpaceCloud listing</a>. Couldn't confirm the "3-hour minimum"</td></tr><tr><td>SpaceShare Gangnam Center, 382 Gangnam-daero, Gangnam-gu, 20 to 30 people, price not published</td><td>Wrong. The address on the <a href="https://www.spaceshare.co.kr/centers/gangnam/location.php" target="_blank" rel="noopener noreferrer" class="">official location page</a> is 499 Gangnam-daero, Seocho-gu, and the <a href="https://www.spaceshare.co.kr/centers/gangnam/pricing.php" target="_blank" rel="noopener noreferrer" class="">official rental rate table</a> publishes a 24-person conference room at KRW 400,000 for the morning and KRW 600,000 for the full day (VAT excluded)</td></tr></tbody></table>
<p>2 of the 3 were right, and 1 had both the address and the published-price status wrong. The "not published" in particular looks plausible because it reuses the exact wording from my prompt. When you get a table, click each source link at least once. It did follow my conditions: the first line of Pickle's answer said, "I didn't access any personal accounts and didn't make any bookings or inquiries."</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-8-check-the-tasks-pickle-scheduled-on-its-own">Step 8. Check the tasks Pickle scheduled on its own<a href="https://datapopcorn.ai/blog/en/pickle-ai-agent-guide#step-8-check-the-tasks-pickle-scheduled-on-its-own" class="hash-link" aria-label="Direct link to Step 8. Check the tasks Pickle scheduled on its own" title="Direct link to Step 8. Check the tasks Pickle scheduled on its own" translate="no">​</a></h2>
<p>Pickle saying it had "set up reminders" in Step 3 bothered me, so I asked.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Earlier you said you set up reminders for November 6 and 16. Where did you set them up? Did you create new events in my Google Calendar?</span><br></div></code></pre></div></div>
<p>The answer: "I didn't add or touch anything in your Google Calendar; I set them up as scheduled reminders inside Pickle." It also explained that it had moved the dates up so it would message me in chat two to four days ahead rather than on the deadline itself.</p>
<p><img decoding="async" loading="lazy" alt="Asked where the reminders were set up, Pickle answers that they&amp;#39;re scheduled reminders inside Pickle. Some personal events are hidden" src="https://datapopcorn.ai/blog/en/assets/images/12-reminder-answer-626dc5b125d96f833fdd7f42e8930209.webp" width="720" height="1560" class="img_ev3q"></p>
<p>To check for yourself, tap Pickle's avatar at the top of the chat screen, then the clock icon in the middle. Under "Tasks scheduled in advance," there were two one-time tasks and a recurring task, "Morning briefing (every day at 7:08 a.m.)." I didn't create the morning briefing; it's on by default. You can turn each one off with its switch, and create your own scheduled tasks with <code>+</code> at the top right.</p>
<p><img decoding="async" loading="lazy" alt="The scheduled task list in Pickle&amp;#39;s profile: 2 one-time tasks (hidden because they&amp;#39;re personal) and the daily morning briefing" src="https://datapopcorn.ai/blog/en/assets/images/13-scheduled-tasks-3a4299d0d576c21cb7ff9b282d29eb3c.webp" width="720" height="1560" class="img_ev3q"></p>
<p>When an agent says "I've set that up," make a habit of asking where. Pickle didn't touch my calendar this time, but with a connection that has calendar write access, the answer could be different.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-9-check-usage-again-after-handing-off-tasks">Step 9. Check usage again after handing off tasks<a href="https://datapopcorn.ai/blog/en/pickle-ai-agent-guide#step-9-check-usage-again-after-handing-off-tasks" class="hash-link" aria-label="Direct link to Step 9. Check usage again after handing off tasks" title="Direct link to Step 9. Check usage again after handing off tasks" translate="no">​</a></h2>
<p>After three tasks (the schedule summary, the web research and one question), usage in <code>My Pickle</code> was 1%. According to the help pages, <a href="https://pickle.com/help/credits/usage-credits" target="_blank" rel="noopener noreferrer" class="">routine things Pickle does on its own, like the morning routine, don't count against the limit, and calls Pickle places for you have a separate daily cap</a>.</p>
<p><img decoding="async" loading="lazy" alt="Daily usage at 1% after three tasks" src="https://datapopcorn.ai/blog/en/assets/images/14-usage-after-b01c159f56cf436bb94f0569bf0f971a.webp" width="720" height="1560" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="before-you-hand-off-tasks-that-need-logins-and-payments">Before you hand off tasks that need logins and payments<a href="https://datapopcorn.ai/blog/en/pickle-ai-agent-guide#before-you-hand-off-tasks-that-need-logins-and-payments" class="hash-link" aria-label="Direct link to Before you hand off tasks that need logins and payments" title="Direct link to Before you hand off tasks that need logins and payments" translate="no">​</a></h2>
<p>On day one I didn't connect services that need a login, like Coupang or Naver; connecting them myself the next day is covered in Steps 10 to 12. Tap Coupang in the connection list and you get an intro saying "log in once and a single 'I'm out of toilet paper' takes care of everything from a Rocket Delivery (Coupang's fast-shipping service) order to delivery tracking," along with a reorder example.</p>
<p><img decoding="async" loading="lazy" alt="The Coupang connection intro screen. Not connected yet" src="https://datapopcorn.ai/blog/en/assets/images/15-coupang-connect-b56e8c650457de659716dd043912f3a7.webp" width="720" height="1560" class="img_ev3q"></p>
<p>Before handing anything off, check three things in the official help pages.</p>
<ul>
<li class="">Passwords don't go in the chat; you enter them in the login card Pickle sends. The help page says the input is <a href="https://pickle.com/help/connect/save-login" target="_blank" rel="noopener noreferrer" class="">encrypted with a key created on your phone and stored in a vault, and isn't passed to Pickle (the AI)</a>. Pickle can read your username.</li>
<li class="">For payments, it says it will <a href="https://pickle.com/help/tasks/confirm-before-payment" target="_blank" rel="noopener noreferrer" class="">first show the item and amount on a card and ask for approval with a button, and if the amount goes up right before payment, show the increase and ask again</a>.</li>
<li class="">Cards and connected accounts can only be used during the <a href="https://pickle.com/help/privacy/keep-access" target="_blank" rel="noopener noreferrer" class="">access period (1 day up to 30 days)</a>, and if you don't open Pickle during that period, access turns off on its own. That screen has no button to turn it off right now, so to stop it from being used, you have to delete the card or disconnect.</li>
</ul>
<p>If you're not sure what to hand off, check the <code>Explore</code> tab. Tasks that lots of other users have handed off, like "Cancel subscriptions I don't use," "Call 080 numbers to opt out of ad texts for me" (080 is the toll-free opt-out line on Korean marketing texts) and "Screen my calls first," are listed along with how many people have handed them off.</p>
<p><img decoding="async" loading="lazy" alt="The Explore tab: popular tasks lately and the list of things it can do by phone for you" src="https://datapopcorn.ai/blog/en/assets/images/07-explore-215cdaedc6fd689a68b432da937609ff.webp" width="720" height="1560" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-10-connect-services-that-need-a-login-one-at-a-time-with-a-login-card">Step 10. Connect services that need a login one at a time with a login card<a href="https://datapopcorn.ai/blog/en/pickle-ai-agent-guide#step-10-connect-services-that-need-a-login-one-at-a-time-with-a-login-card" class="hash-link" aria-label="Direct link to Step 10. Connect services that need a login one at a time with a login card" title="Direct link to Step 10. Connect services that need a login one at a time with a login card" translate="no">​</a></h2>
<p>The next day (October 27), I connected SRT (a Korean high-speed rail operator), Korail (Korea's national railway), Coupang, Yanolja (a Korean travel and accommodation booking app), Megabox (a Korean cinema chain), Aladin (a Korean online bookstore), Daiso Mall, Gov24, GitHub, Notion, Slack and X, one by one. The method is the same for each service.</p>
<ol>
<li class="">Send the service and your username in the chat.</li>
</ol>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">I want to connect SRT. My username is ○○○○. Send me a login card.</span><br></div></code></pre></div></div>
<ol start="2">
<li class="">Pickle shows a secure login card with your username pre-filled. Tap the password field and your phone's password manager autofill pops up (I used 1Password, approved with my fingerprint). Don't type the password in the chat.</li>
<li class="">Tap <code>Log in</code> and Pickle logs in, then reads your account status right away. For Korail it told me I had no mileage and no waitlisted tickets; for Megabox, 2,110 points and 2 discount coupons; for Aladin, KRW 1,400 in mileage and 1 order from April.</li>
</ol>
<p><img decoding="async" loading="lazy" alt="The SRT login card flow. Send your username and a card arrives; enter the password and it confirms the connection and shows your booking status. Membership number hidden" src="https://datapopcorn.ai/blog/en/assets/images/16-srt-login-card-749fbcc62b795c63d5a565afd7351459.webp" width="720" height="1560" class="img_ev3q"></p>
<p>If you can't remember your username, just ask. For Coupang, Pickle searched my connected Gmail and showed a list of usernames that looked like ones I'd signed up with; I picked one and the card arrived. Glance at the username field on the card before logging in: once, leftover text from the chat input box got mixed in.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-11-if-you-get-stuck-on-verification-ask-pickle-to-switch-methods">Step 11. If you get stuck on verification, ask Pickle to switch methods<a href="https://datapopcorn.ai/blog/en/pickle-ai-agent-guide#step-11-if-you-get-stuck-on-verification-ask-pickle-to-switch-methods" class="hash-link" aria-label="Direct link to Step 11. If you get stuck on verification, ask Pickle to switch methods" title="Direct link to Step 11. If you get stuck on verification, ask Pickle to switch methods" translate="no">​</a></h2>
<p>What took longer than logging in was two-factor verification. Here's exactly where I got stuck and how I got through.</p>
<table><thead><tr><th>Where I got stuck</th><th>How I solved it</th></tr></thead><tbody><tr><td>Coupang SMS verification code</td><td>It expired in about 2 minutes and I missed it twice. Requesting a new one sends a new text, so enter the code from the most recent one</td></tr><tr><td>Daiso Mall's code went to an email inbox I don't check</td><td>When I sent "Switch the Daiso Mall verification to phone text verification," Pickle requested it again via text</td></tr><tr><td>Couldn't find my X account by phone number</td><td>Pickle found the X account in my connected Gmail, read the emailed verification code itself and finished logging in. It also got through Uber's first email verification on its own the same way</td></tr><tr><td>Gov24 anti-bot text (CAPTCHA)</td><td>Tap <code>Open browser</code> on Pickle's screen to see the remote browser Pickle uses in real time. Type the CAPTCHA there yourself and tap <code>I'm done</code></td></tr><tr><td>Naver Mail two-step approval</td><td>The approval request in the Naver app expired while it was handling other services. Keep the Naver app open and request again</td></tr></tbody></table>
<p><img decoding="async" loading="lazy" alt="Typing the Gov24 CAPTCHA directly in Pickle&amp;#39;s remote browser view" src="https://datapopcorn.ai/blog/en/assets/images/18-gov24-captcha-0f68cd5bed52911cb7a46943d6eab654.webp" width="720" height="1560" class="img_ev3q"></p>
<p>Pickle handling email verification codes on its own once your email is connected is convenient, but it's also a big deal. Connecting your email effectively means handing over the keys to log in to other services too. Gov24 showed a screen asking me to change my password, and I decided to make account decisions like that myself rather than leave them to Pickle. For Uber, the second-step SMS code never arrived on my phone, so I couldn't finish connecting.</p>
<p>Don't trust the check marks in the list. Lotte Cinema (a Korean cinema chain) failed to log in because the password was wrong, yet it had a check mark in the <code>Connected</code> list. Slack hit the free-plan workspace's app install limit (10) and showed "You can't add any more apps," but the list still said connected, and later <code>Slack read</code> showed up in the access log. The more reliable way to confirm a connection is to have Pickle read something from that service.</p>
<p><img decoding="async" loading="lazy" alt="The Connected list. Photos, Slack, Notion, GitHub, SRT, Socar, Yanolja, Megabox, Lotte Cinema, Aladin and Daiso Mall all have check marks" src="https://datapopcorn.ai/blog/en/assets/images/19-connected-list-bf3ffdf1f479a0caebd507963666c709.webp" width="720" height="1560" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-12-check-what-pickle-did-on-its-own-after-connecting-and-your-usage">Step 12. Check what Pickle did on its own after connecting, and your usage<a href="https://datapopcorn.ai/blog/en/pickle-ai-agent-guide#step-12-check-what-pickle-did-on-its-own-after-connecting-and-your-usage" class="hash-link" aria-label="Direct link to Step 12. Check what Pickle did on its own after connecting, and your usage" title="Direct link to Step 12. Check what Pickle did on its own after connecting, and your usage" translate="no">​</a></h2>
<p>Once I finished connecting, Pickle started with things I hadn't asked for.</p>
<ul>
<li class=""><strong>GitHub:</strong> Right after connecting, it analyzed all my repositories and asked, "One repository's GitHub Pages build is in an error state, so its public page may be stuck. Want me to look into the cause and rerun the build?" I haven't checked yet whether that's right, and I didn't hand off the build. When you connect, the first option in repository selection is "All repositories," and it includes write access to code, issues, PRs and Actions, so it's safer to pick only the repositories you need.</li>
<li class=""><strong>Slack and Notion:</strong> It skimmed both in the background, picked out "three things you need to deal with now" (replying to a colleague about documents, a disk warning on a remote server, and replying to an outside lecture offer), and offered, "Want me to handle these for you right now?" That answer included a colleague's personal email address verbatim. Keep in mind that if you connect a work Slack, other people's information goes into Pickle's memory too.</li>
</ul>
<p><img decoding="async" loading="lazy" alt="Right after connecting GitHub, Pickle analyzed the repositories and flagged a build error. Username hidden" src="https://datapopcorn.ai/blog/en/assets/images/17-github-analysis-72902a1157f7f2600cc59a730300d24b.webp" width="720" height="1560" class="img_ev3q"></p>
<p>Three tasks in Step 9 used 1%, but connecting 21 apps and services used up 100% of the daily limit. Login cards, verification and post-connection analysis all count against the limit. Don't do all your connections in one day; spread them out, starting with the services you use most.</p>
<p><img decoding="async" loading="lazy" alt="Daily usage at 100% after connecting 21 services" src="https://datapopcorn.ai/blog/en/assets/images/20-usage-full-622ba8db391ba578995b2df727ee8b39.webp" width="720" height="1560" class="img_ev3q"></p>
<p>Finally, look at the access log screen. It records in time order which services Pickle read or connected and when, and the last-7-days totals showed 951+ reads, 15+ sends and 0 deletes or changes. Check from time to time that deletes/changes is 0 and that there's nothing in sends you don't know about.</p>
<p><img decoding="async" loading="lazy" alt="The access log: last 7 days 951+ reads, 15+ sends, 0+ deletes/changes, plus today&amp;#39;s Slack read and Notion connection entries" src="https://datapopcorn.ai/blog/en/assets/images/21-access-log-82e8e6500b4f051aaae2d56e07fe71a4.webp" width="720" height="1560" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-compares-with-grok-bot-muse-and-dots">How it compares with Grok Bot, Muse and Dots<a href="https://datapopcorn.ai/blog/en/pickle-ai-agent-guide#how-it-compares-with-grok-bot-muse-and-dots" class="hash-link" aria-label="Direct link to How it compares with Grok Bot, Muse and Dots" title="Direct link to How it compares with Grok Bot, Muse and Dots" translate="no">​</a></h2>
<p>All four products bill themselves as "an always-on agent that works for you on its own computer (browser)." Where they differ is where you can use them, how much they cost and which services they can reach. The Pickle column is based on my own use and the official help pages; the rest are based on official docs and my earlier write-ups as of October 26, 2026.</p>
<table><thead><tr><th></th><th>Pickle</th><th>Grok Bot</th><th>Meta Muse</th><th>OpenAI Dots</th></tr></thead><tbody><tr><td>Made by</td><td>Pickle, Inc. (YC W25)</td><td>xAI (Cursor infrastructure)</td><td>Meta</td><td>OpenAI</td></tr><tr><td>Where you use it</td><td>iOS and Android apps</td><td>Desktop and mobile apps, @bot on X</td><td>Web (muse.ai), iOS, Android, WhatsApp</td><td>ChatGPT web, desktop and mobile, Slack, Teams</td></tr><tr><td>Runs on</td><td>Pickle's own cloud browser</td><td>One cloud computer per account, shared by bots on the same account</td><td>Isolated VM per user</td><td>A cloud computer and browser per Dot</td></tr><tr><td>Makes calls for you</td><td>Yes (calls from Pickle's number, daily cap)</td><td>Announced October 25, support conditions unconfirmed</td><td>For US businesses</td><td>Only you calling your Dot</td></tr><tr><td>Korean services</td><td>Naver, Coupang, Gov24 and others on the connection list</td><td>No official list (reached via browser)</td><td>No official list</td><td>No official list</td></tr><tr><td>Passwords and payments</td><td>Vault encrypted on your phone, card approval before payment</td><td>You enter sensitive input yourself, 1Password integration</td><td>Secure storage, Stripe Link single-use cards, confirmation for each payment</td><td>Private input form or direct control, per-action approval rules</td></tr><tr><td>Price</td><td>Free in Korea, daily limit</td><td>Included with X Premium ($30/month) or higher, weekly usage</td><td>Free (limits not published), $20/month, $100/month</td><td>Included in Pro (from $100/month) and Business Premium</td></tr><tr><td>In Korea</td><td>Use it right away</td><td>Country list unconfirmed</td><td>Not officially supported (I needed a US VPN)</td><td>Available (confirmed with a Korean account)</td></tr><tr><td>Proactive features</td><td>Morning briefing on by default, scheduled reminders</td><td>Routines and event triggers</td><td>Goal- and feed-based suggestions</td><td>Recurring schedules, event watching</td></tr></tbody></table>
<p>To boil the table down to one line: Dots and Grok Bot lead on raw capability and breadth of connections, while Pickle has the edge on Korean everyday services, phone calls and price. For Muse, not being officially available in Korea is still the biggest barrier.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="my-take-after-using-it">My take after using it<a href="https://datapopcorn.ai/blog/en/pickle-ai-agent-guide#my-take-after-using-it" class="hash-link" aria-label="Direct link to My take after using it" title="Direct link to My take after using it" translate="no">​</a></h2>
<p>Three things I liked. First, it took under 10 minutes from install to first result and cost nothing. Compare that to agents from outside Korea, where I first had to get past a VPN or a plan costing over KRW 100,000 a month. Second, it's proactive: as soon as I connected the calendar, it found a schedule conflict and set up deadline reminders on its own. Third, Naver, Coupang and Gov24 are on the default connection list, and it even makes phone calls for you. The everyday chores people in Korea put off really do pile up in places like these.</p>
<p>The downsides are clear too. 1 of the 3 web research results was wrong, and the wrong part used a plausible phrase like "not published," which is easy to miss if you don't verify. Also, with something like "I've set up reminders," it wasn't clear at first what it did and where. Before handing off anything involving payments or logins, I recommend starting with small tasks and checking the results.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="if-you-get-stuck">If you get stuck<a href="https://datapopcorn.ai/blog/en/pickle-ai-agent-guide#if-you-get-stuck" class="hash-link" aria-label="Direct link to If you get stuck" title="Direct link to If you get stuck" translate="no">​</a></h2>
<table><thead><tr><th>Symptom</th><th>Where to check</th></tr></thead><tbody><tr><td>It can't read my schedule</td><td>In <code>My Pickle → Apps and services connected to Pickle</code>, check that the calendar account switch is on. The phone's built-in calendar has to be connected separately under Calendar in the <code>Android</code> tab</td></tr><tr><td>The input box turned off</td><td>You've hit the daily usage limit. Wait until 4 a.m. or use a reset ticket</td></tr><tr><td>I'm getting reminders I don't want</td><td>Pickle's avatar → clock icon, then turn off that scheduled task's switch</td></tr><tr><td>The research results look off</td><td>Open the source links to check, point out what's wrong and ask again</td></tr><tr><td>Verification codes go somewhere I don't check</td><td>Ask Pickle to switch the verification method, e.g. "Switch to text verification"</td></tr><tr><td>I got a CAPTCHA</td><td>Use <code>Open browser</code> on Pickle's screen to open the remote browser and type it yourself</td></tr><tr><td>It says connected, but I'm not sure it really worked</td><td>Have it read your points or one recent item from that service to confirm</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-i-havent-checked-yet">What I haven't checked yet<a href="https://datapopcorn.ai/blog/en/pickle-ai-agent-guide#what-i-havent-checked-yet" class="hash-link" aria-label="Direct link to What I haven't checked yet" title="Direct link to What I haven't checked yet" translate="no">​</a></h2>
<ul>
<li class="">The actual call quality and result summaries of calls Pickle makes for you (I didn't place any this time)</li>
<li class="">The actual ordering and payment flow (I only went as far as connecting logins; I didn't hand off orders or payments)</li>
<li class="">What each of the "15+ sends" in the access log was (I haven't opened them yet)</li>
<li class="">Whether the GitHub build error it flagged is real</li>
<li class="">Friends' Pickles setting up plans with each other</li>
<li class="">The order of the sign-up screens (I started with an account that was already signed up)</li>
<li class="">Which AI model Pickle uses (I couldn't find it in the official help pages)</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="sources">Sources<a href="https://datapopcorn.ai/blog/en/pickle-ai-agent-guide#sources" class="hash-link" aria-label="Direct link to Sources" title="Direct link to Sources" translate="no">​</a></h2>
<ul>
<li class="">Pickle help: <a href="https://pickle.com/help/start/what-is-pickle" target="_blank" rel="noopener noreferrer" class="">What is Pickle</a>, <a href="https://pickle.com/help/tasks/delegate" target="_blank" rel="noopener noreferrer" class="">Handing tasks to Pickle</a>, <a href="https://pickle.com/help/tasks/confirm-before-payment" target="_blank" rel="noopener noreferrer" class="">Confirmation before payment</a>, <a href="https://pickle.com/help/connect/add-connection" target="_blank" rel="noopener noreferrer" class="">Connecting apps and services</a>, <a href="https://pickle.com/help/connect/save-login" target="_blank" rel="noopener noreferrer" class="">Saving site logins</a>, <a href="https://pickle.com/help/credits/usage-credits" target="_blank" rel="noopener noreferrer" class="">Usage limits</a>, <a href="https://pickle.com/help/credits/plans" target="_blank" rel="noopener noreferrer" class="">Checking your plan</a>, <a href="https://pickle.com/help/privacy/keep-access" target="_blank" rel="noopener noreferrer" class="">Access period</a></li>
<li class=""><a href="https://www.ycombinator.com/companies/pickle-2" target="_blank" rel="noopener noreferrer" class="">Pickle, Y Combinator company page</a></li>
<li class=""><a href="https://play.google.com/store/apps/details?id=com.pickle.client" target="_blank" rel="noopener noreferrer" class="">Pickle on Google Play</a></li>
<li class="">Grok Bot: <a href="https://x.ai/news/introducing-grok-bot" target="_blank" rel="noopener noreferrer" class="">xAI's Grok Bot introduction</a>, <a href="https://docs.x.ai/grok-bot/overview" target="_blank" rel="noopener noreferrer" class="">Grok Bot overview</a>, <a href="https://cursor.com/help/grok-bot/plans.md" target="_blank" rel="noopener noreferrer" class="">Plans and usage</a>, <a href="https://x.com/elonmusk/status/2114365361068138990" target="_blank" rel="noopener noreferrer" class="">Phone feature announcement</a></li>
<li class="">Muse: <a href="https://about.fb.com/news/2026/09/introducing-muse-personal-ai-agent" target="_blank" rel="noopener noreferrer" class="">Meta Muse announcement</a>, <a href="https://security.muse.ai/" target="_blank" rel="noopener noreferrer" class="">Muse security</a>, <a href="https://www.meta.com/help/subscriptions/1021145227643680" target="_blank" rel="noopener noreferrer" class="">Muse subscription help</a></li>
<li class="">Dots: <a href="https://openai.com/index/introducing-dots" target="_blank" rel="noopener noreferrer" class="">Introducing Dots</a>, <a href="https://developers.openai.com/codex/dots" target="_blank" rel="noopener noreferrer" class="">Dots developer docs</a>, <a href="https://help.openai.com/en/articles/20001530-getting-started-with-your-dot" target="_blank" rel="noopener noreferrer" class="">Getting started with your Dot</a></li>
</ul>]]></content:encoded>
            <category>AI agents</category>
            <category>Guide</category>
        </item>
        <item>
            <title><![CDATA[Claude Team-Only Features and First-Time Setup in 14 Steps: From a Side-by-Side With Personal Pro and Max to Claiming Monthly API Credits (Up to $500) and Putting Claude to Work in Slack DMs]]></title>
            <link>https://datapopcorn.ai/blog/en/claude-team-plan-only-features</link>
            <guid>https://datapopcorn.ai/blog/en/claude-team-plan-only-features</guid>
            <pubDate>Sun, 25 Oct 2026 11:00:00 GMT</pubDate>
            <description><![CDATA[Using one account, I switched back and forth between a Claude Team organization and a personal Max plan to see which features only Team has. Beyond @Claude in Slack (Claude Tag), on-call, project sharing, organization instructions, Skills distribution, analytics and spend limits, I cover the setup to do right after subscribing, in the order I actually did it: viewing usage per member, inviting Gmail members, adding an owner, claiming monthly API credits (up to $500 depending on seat count) and organizing the Console, and giving Claude tasks over Slack DM. The FAQ covers how to choose between Claude Team and ChatGPT Business (formerly ChatGPT Team), moving personal account history, and splitting seats.]]></description>
            <content:encoded><![CDATA[<p>Switching to the Claude Team plan does more than raise your usage. <strong>On-call</strong> shows up in the left sidebar, <strong>Claude tag</strong> appears in organization settings, and creating a project now asks you to pick its <strong>visibility</strong>. This post is my record from October 25, 2026, when I used the same account (a single <code>dev@</code> account attached to both a Team organization and a personal Max plan) to open both sides back and forth and check which features only Team has. The next day, October 26, I added Steps 9 through 13 covering the first-time setup I did while actually bringing teammates in (viewing usage per member, inviting Gmail members, adding an owner, claiming monthly API credits, organizing the Console). On October 28, I added an FAQ at <a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#faq" class="">the bottom</a> for teams moving from individual personal Claude plans to a team plan (including how to choose versus ChatGPT Business), and Step 14 on actually giving Claude work over Slack DM.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="who-this-is-for">Who this is for<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#who-this-is-for" class="hash-link" aria-label="Direct link to Who this is for" title="Direct link to Who this is for" translate="no">​</a></h2>
<ul>
<li class="">You're on Claude Pro or Max and weighing <strong>whether moving to Team is worth it</strong></li>
<li class="">You subscribed to Team but <strong>couldn't find on screen what changed</strong> compared with a personal plan</li>
<li class="">You want to try a <strong>shared Claude</strong> (@Claude) that anyone on the team can call from a Slack channel, or <strong>on-call</strong> for incident response</li>
<li class="">Your staff have been using their own personal Claude plans, and as you move them to a company team plan you're deciding between Claude Team and ChatGPT Business (see the FAQ)</li>
</ul>
<p>Here's what you need.</p>
<ul>
<li class="">A claude.ai account that belongs to a Team organization. To see the admin screens (Steps 3, 5, 6, 8), you need the <strong>Owner</strong> role in that organization</li>
<li class="">A personal plan account to compare against (optional). If the same email still has a personal plan, you can switch to it directly from the account menu</li>
</ul>
<p>The screenshots show Claude's Korean interface. In the text I use the English menu names, and quoted on-screen messages are translated from Korean, so the exact English wording in your UI may differ slightly.</p>
<div class="theme-admonition theme-admonition-caution admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>What I verified</div><div class="admonitionContent_BuS1"><p>I opened every screen myself. I also connected a Slack workspace and tried <strong>giving Claude work over Slack DM (Step 14)</strong> myself. However, <strong>calling @Claude in a channel to run tasks</strong> requires topping up the organization's usage balance, so I haven't run that yet; that part is based on the official docs. As of October 2026, Claude Tag and on-call are in public beta.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="at-a-glance-what-only-team-has">At a glance: what only Team has<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#at-a-glance-what-only-team-has" class="hash-link" aria-label="Direct link to At a glance: what only Team has" title="Direct link to At a glance: what only Team has" translate="no">​</a></h2>
<p>I combined the plan comparison on the <a href="https://claude.com/pricing" target="_blank" rel="noopener noreferrer" class="">official pricing page</a> with what I saw on screen. "Personal" means Free, Pro and Max (5x, 20x).</p>
<table><thead><tr><th>Feature</th><th>Personal plans</th><th>Team</th><th>Where to find it</th></tr></thead><tbody><tr><td><strong>@Claude (Claude Tag)</strong>: a shared team Claude in Slack channels</td><td>No</td><td>Yes (beta, usage-based billing)</td><td>Organization settings &gt; Claude tag</td></tr><tr><td><strong>On-call</strong>: watches monitoring alerts, auto-joins incident channels</td><td>No</td><td>Yes (beta)</td><td>Sidebar &gt; On-call (<code>claude.ai/oncall</code>)</td></tr><tr><td>Project sharing and organization-wide visibility</td><td>No</td><td>Yes</td><td>New project &gt; Visibility</td></tr><tr><td>Organization instructions (instructions applied to every conversation)</td><td>No</td><td>Yes</td><td>Organization settings &gt; Organization &amp; access</td></tr><tr><td>Organization-level Skills and plugin distribution</td><td>No</td><td>Yes</td><td>Organization settings &gt; Plugins &amp; Skills</td></tr><tr><td>Usage analytics dashboard</td><td>No</td><td>Yes</td><td>Account menu &gt; Analytics</td></tr><tr><td>Code Review (automatic GitHub PR review service)</td><td>No</td><td>Yes</td><td>Organization settings &gt; Usage &gt; Services</td></tr><tr><td>Per-user and organization-level spend limits</td><td>No</td><td>Yes</td><td>Organization settings &gt; Usage</td></tr><tr><td>Enterprise search, Claude Motion (beta)</td><td>No</td><td>Yes</td><td>Per the pricing page</td></tr><tr><td>SSO, domain verification, centralized billing, admin controls for connectors</td><td>No</td><td>Yes</td><td>Organization settings &gt; Organization &amp; access</td></tr><tr><td>Model training on your conversations</td><td>Allowed by default (can be turned off)</td><td>Not trained on by default</td><td>Per the pricing page</td></tr></tbody></table>
<p>On the other hand, some things are <strong>not Team-only</strong>. I've listed just the ones that are easy to mix up.</p>
<table><thead><tr><th>Item</th><th>What's actually the case</th></tr></thead><tbody><tr><td>Routines</td><td>On personal Max they show up right in the sidebar; on Team they're tucked under <strong>More</strong>. Both have them</td></tr><tr><td>Claude Code, Cowork, Claude in Chrome</td><td>Available on personal plans from Pro up</td></tr><tr><td>Monthly API credits</td><td>Max 5x gets $100 and Max 20x gets $200. Team gets $20 per Standard seat and $100 per Premium seat, pooled <strong>up to $500</strong>. Pro gets none (<a href="https://support.claude.com/en/articles/17154008" target="_blank" rel="noopener noreferrer" class="">official help article</a>). How to claim them is in Step 12</td></tr><tr><td>The <code>/code-review</code> command</td><td>The review command you use inside Claude Code works on every plan. What's Team-only is the <strong>Code Review service</strong> that attaches to PRs automatically</td></tr></tbody></table>
<p>As of the October 25, 2026 pricing page, a Team Standard seat is $20/month billed annually ($25 billed monthly), and a Premium seat is $100/month billed annually ($125 billed monthly). It's aimed at teams of 2 to 150 people. Usage limits aren't shared across the team; they apply <strong>to each seat separately</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1-switch-to-the-team-organization-from-the-account-menu">Step 1. Switch to the Team organization from the account menu<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#step-1-switch-to-the-team-organization-from-the-account-menu" class="hash-link" aria-label="Direct link to Step 1. Switch to the Team organization from the account menu" title="Direct link to Step 1. Switch to the Team organization from the account menu" translate="no">​</a></h2>
<p>Click your name in the bottom left of claude.ai to open the account menu. If the same email has both a Team organization and a personal plan, you pick the organization here.</p>
<p><img decoding="async" loading="lazy" alt="Left: the sidebar and account menu with the Team organization selected. Right: the same with personal Max selected. Only the Team side shows On-call in the sidebar and Organization settings and Analytics in the menu" src="https://datapopcorn.ai/blog/en/assets/images/01-sidebar-menu-compare-e315e52b100d43f5fd69716c6ec9e221.jpg" width="604" height="908" class="img_ev3q"></p>
<p>Three places change as soon as you switch.</p>
<ol>
<li class=""><strong>Sidebar</strong>: Team gets <code>On-call</code>. The <code>Routines</code> entry that sat there on personal Max moves under <code>More</code> on Team</li>
<li class=""><strong>Account menu</strong>: Team gets <code>Organization settings</code> and <code>Analytics</code>. Personal Max has <code>Usage</code> and <code>Resubscribe to Max</code> instead (the latter appears because cancellation was scheduled)</li>
<li class=""><strong>Projects screen</strong>: Team gets <code>Organization</code> and <code>Shared with me</code> tabs next to <code>My projects</code>. Personal Max has no tabs</li>
</ol>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>When the wrong billing screen opens</div><div class="admonitionContent_BuS1"><p>If you go to <code>Settings &gt; Billing</code> with the Team organization selected, you get the <strong>Team billing screen</strong>, not your personal subscription. To cancel a personal plan, first select the personal plan in this menu, then go in. Do it the other way around and you could end up canceling the Team subscription, so be careful.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2-open-the-on-call-screen">Step 2. Open the on-call screen<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#step-2-open-the-on-call-screen" class="hash-link" aria-label="Direct link to Step 2. Open the on-call screen" title="Direct link to Step 2. Open the on-call screen" translate="no">​</a></h2>
<p>Click <strong>On-call</strong> in the sidebar or go to <code>claude.ai/oncall</code>. If you open the same address with personal Max, you only get a notice saying "This page isn't available for your organization yet."</p>
<p><img decoding="async" loading="lazy" alt="Left: the on-call setup screen opened from the Team organization, with a Connect Slack button. Right: the same address opened with personal Max, showing the &amp;#39;This page isn&amp;#39;t available for your organization yet&amp;#39; screen" src="https://datapopcorn.ai/blog/en/assets/images/02-oncall-compare-7f6d4b583bd5fa55864ba76038df207f.jpg" width="1784" height="628" class="img_ev3q"></p>
<p>On-call puts Claude next to your team's incident-response on-call person. During setup you choose one or both of two modes (<a href="https://claude.com/docs/claude-tag/admins/setup-oncall" target="_blank" rel="noopener noreferrer" class="">official docs</a>).</p>
<ul>
<li class=""><strong>Alert monitoring</strong>: watches monitoring tools such as Datadog, Sentry and PagerDuty, and posts what it finds to a designated channel</li>
<li class=""><strong>Auto-join incident channels</strong>: set a channel naming rule like <code>incident-</code>, and Claude joins new public channels created with that name on its own and investigates alongside you without being tagged</li>
</ul>
<p>Things to know:</p>
<ul>
<li class="">Your team's on-call rotation stays as is; Claude works <strong>alongside</strong> the person on call</li>
<li class="">On the Team plan, any organization member can open the settings screen. Changes that affect the whole organization can only be saved from an account with permission</li>
<li class="">On-call work is billed to organization usage, and you can cap it with a <strong>monthly spend limit</strong></li>
<li class="">Naming rules apply only to public channels. For private channels, you have to invite Claude directly with <code>/invite @Claude</code> in the channel</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3-open-the-claude-tag-claude-settings-screen">Step 3. Open the Claude Tag (@Claude) settings screen<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#step-3-open-the-claude-tag-claude-settings-screen" class="hash-link" aria-label="Direct link to Step 3. Open the Claude Tag (@Claude) settings screen" title="Direct link to Step 3. Open the Claude Tag (@Claude) settings screen" translate="no">​</a></h2>
<p>Go to <code>Organization settings &gt; Claude tag</code> (<code>claude.ai/admin-settings/claude-tag</code>). On an owner account, you'll see a <strong>Start setup</strong> button and a 4-step guide (connect a Slack workspace, connect GitHub, buy usage credits, start Claude Tag).</p>
<p><img decoding="async" loading="lazy" alt="The Claude Tag setup screen in organization settings, showing the 4 steps (connect Slack workspace, connect GitHub, buy usage credits, start Claude Tag), the Start setup button and an FAQ list" src="https://datapopcorn.ai/blog/en/assets/images/03-claude-tag-admin-81c81acfe47d6cb973fa04a038f62f85.jpg" width="1440" height="900" class="img_ev3q"></p>
<p>Claude Tag lets anyone in a Slack channel call <code>@Claude</code> and hand it work. It can reproduce a bug and open a PR, turn a thread where a decision was made into a document, or pull together a project status update. Progress is posted to the thread as a checklist that everyone in the channel can see. The official docs state plainly that this feature works <strong>only on Team and Enterprise</strong>, not on Free, Pro or Max (<a href="https://claude.com/docs/claude-tag/overview" target="_blank" rel="noopener noreferrer" class="">Claude Tag overview</a>).</p>
<p>Here's what you need before you start (<a href="https://claude.com/docs/claude-tag/admins/setup-overview" target="_blank" rel="noopener noreferrer" class="">setup guide</a>).</p>
<table><thead><tr><th>Requirement</th><th>Why</th></tr></thead><tbody><tr><td>The <strong>Owner</strong> role in the Claude organization</td><td>Only owners can connect the workspace and turn the feature on</td></tr><tr><td><strong>Slack workspace admin</strong></td><td>You get the connection code by sending <code>@Claude connect</code> in Slack. The code expires after 15 minutes, so if a different person is the admin, have them online at the same time</td></tr><tr><td><strong>Routines</strong> turned on</td><td>If routines are off under Organization settings &gt; Features, Claude replies to every mention with just "not available right now"</td></tr><tr><td><strong>Usage credits topped up</strong> (Team)</td><td>On Team, nothing runs in channels until the balance is funded</td></tr><tr><td>One public channel</td><td>You have to pick at least one during the start step</td></tr></tbody></table>
<p>The cost structure is where it differs most from personal plans. Here's what the FAQ on the settings screen says.</p>
<ul>
<li class="">It's <strong>usage-based billing, not per seat</strong>. Work Claude does in channels is billed to the organization</li>
<li class="">Not every Slack user needs a Claude seat. By default, anyone in the connected workspace can call it in channels, and owners can restrict it to organization members only</li>
<li class="">If someone calls it in a <strong>1:1 DM</strong> instead of a channel, it comes out of that person's seat limit</li>
<li class="">When the spend limit is reached, Claude stops taking new work and tells the channel. If you don't set a limit, there is <strong>no cap</strong></li>
</ul>
<div class="theme-admonition theme-admonition-caution admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>How this differs from the old 'Claude in Slack' on personal plans</div><div class="admonitionContent_BuS1"><p>The old Slack app had each person connect their own claude.ai account and replied <strong>as that person</strong>. Claude Tag works under a <strong>shared team identity</strong> that an owner sets up once, and the results stay in the channel rather than with the person who called it. According to the official docs, starting October 5, 2026, the old app no longer replies in channels in workspaces connected to a Claude organization. In workspaces that aren't connected, Pro and Max users keep getting channel replies from the old app (<a href="https://claude.com/docs/claude-tag/admins/migrate-from-earlier" target="_blank" rel="noopener noreferrer" class="">migration notice</a>).</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4-choose-visibility-when-creating-a-project">Step 4. Choose visibility when creating a project<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#step-4-choose-visibility-when-creating-a-project" class="hash-link" aria-label="Direct link to Step 4. Choose visibility when creating a project" title="Direct link to Step 4. Choose visibility when creating a project" translate="no">​</a></h2>
<p>Click <strong>New project</strong> on the Projects screen. On Team there's a <strong>Visibility</strong> option at the bottom; on personal Max this field doesn't exist at all.</p>
<p><img decoding="async" loading="lazy" alt="Left: the project creation dialog in the Team organization, with a Visibility option to choose between organization-wide and private. Right: the same dialog on personal Max, with only name and goal fields" src="https://datapopcorn.ai/blog/en/assets/images/04-project-visibility-compare-5d6e6307e9b0d4143c136e831b0c07b9.jpg" width="1144" height="608" class="img_ev3q"></p>
<ul>
<li class=""><strong>Organization</strong>: any organization member can find and use the project under the <code>Organization</code> tab on the Projects screen</li>
<li class=""><strong>Private</strong>: only people you invite can use it. When inviting, you choose <code>Can view</code> (view and chat only) or <code>Can edit</code> (including editing instructions and knowledge)</li>
</ul>
<p>Sharing a project <strong>doesn't automatically share the chats inside it</strong>. Each person's chats stay private, and you share only the chats you want to show through a separate share link (<a href="https://support.claude.com/en/articles/9519189-manage-project-visibility-and-sharing" target="_blank" rel="noopener noreferrer" class="">official help article</a>). If an owner turns off project sharing in organization settings, projects that were already shared become private too.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-5-write-organization-instructions">Step 5. Write organization instructions<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#step-5-write-organization-instructions" class="hash-link" aria-label="Direct link to Step 5. Write organization instructions" title="Direct link to Step 5. Write organization instructions" translate="no">​</a></h2>
<p>Scroll down in <code>Organization settings &gt; Organization &amp; access</code> and you'll find an <strong>Organization instructions</strong> field.</p>
<p><img decoding="async" loading="lazy" alt="The Organization instructions field on the Organization &amp;amp; access screen, with a note that it applies to every conversation in the organization and takes priority over personal preferences, and a 3,000-character limit" src="https://datapopcorn.ai/blog/en/assets/images/05-org-instructions-3d4fc1db0e172b35542e17450ae71bcb.jpg" width="1000" height="255" class="img_ev3q"></p>
<p>What you write here <strong>applies to every conversation in the organization and takes priority over each person's personal preferences</strong>. It can be up to 3,000 characters, and the screen notes that changes can take up to an hour to take effect. This is where you set rules the whole team should follow in one go, like "never include customer personal data in answers" or "always start reports with a table." Personal plans only have personal preferences that apply to you alone.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-6-manage-skills-and-plugins-at-the-organization-level">Step 6. Manage Skills and plugins at the organization level<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#step-6-manage-skills-and-plugins-at-the-organization-level" class="hash-link" aria-label="Direct link to Step 6. Manage Skills and plugins at the organization level" title="Direct link to Step 6. Manage Skills and plugins at the organization level" translate="no">​</a></h2>
<p>Go to <code>Organization settings &gt; Plugins &amp; Skills</code>. There are four tabs.</p>
<p><img decoding="async" loading="lazy" alt="The Plugins &amp;amp; Skills management screen, with Inventory, Marketplace, Requests and Policies tabs, a list of Skills and plugins, and an audience column (All users, Installable)" src="https://datapopcorn.ai/blog/en/assets/images/06-org-plugins-6a91358e71f31035e7e16c5e2849ec3d.jpg" width="1150" height="620" class="img_ev3q"></p>
<table><thead><tr><th>Tab</th><th>What it does</th></tr></thead><tbody><tr><td>Inventory</td><td>The full list of Skills and plugins the organization manages, including ones members built themselves. Shows who each is available to (audience) and the number of users in the last 30 days</td></tr><tr><td>Marketplace</td><td>Set up an organization-only marketplace</td></tr><tr><td>Requests</td><td>Approve items members have requested to install</td></tr><tr><td>Policies</td><td>Decide what members can install</td></tr></tbody></table>
<p>You can create and use Skills on personal plans too, but <strong>distributing them to the whole team at once and seeing usage</strong> starts with Team. Sharing a plugin you built with specific colleagues only also works on Team and Enterprise (<a href="https://claude.com/docs/plugins/share" target="_blank" rel="noopener noreferrer" class="">sharing guide</a>).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-7-see-team-usage-in-the-analytics-dashboard">Step 7. See team usage in the analytics dashboard<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#step-7-see-team-usage-in-the-analytics-dashboard" class="hash-link" aria-label="Direct link to Step 7. See team usage in the analytics dashboard" title="Direct link to Step 7. See team usage in the analytics dashboard" translate="no">​</a></h2>
<p>Open <strong>Analytics</strong> from the account menu (<code>claude.ai/analytics</code>).</p>
<p><img decoding="async" loading="lazy" alt="The Analytics overview, with cards for weekly active members, PRs created in Code and Cowork sessions, an active members graph, and tabs on the left for Chat, Claude Code, Cowork, Claude tag and Code review" src="https://datapopcorn.ai/blog/en/assets/images/07-team-analytics-dea458c36dc6ecfb18f4adaa2579b7d2.jpg" width="1440" height="830" class="img_ev3q"></p>
<p>The overview shows <strong>weekly active members</strong>, <strong>PRs created with Claude Code</strong> and <strong>Cowork sessions</strong>, followed by metrics such as usage per member, the most-used Skills and connectors, and "actions Claude performs per prompt." The tabs on the left break out Chat, Claude Code, Cowork, Claude tag and Code review separately. This is the screen for checking who is actually using the seats you bought. Personal plans only have a screen for your own usage.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-8-set-usage-and-spend-limits">Step 8. Set usage and spend limits<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#step-8-set-usage-and-spend-limits" class="hash-link" aria-label="Direct link to Step 8. Set usage and spend limits" title="Direct link to Step 8. Set usage and spend limits" translate="no">​</a></h2>
<p>Go to <code>Organization settings &gt; Usage</code>.</p>
<p><img decoding="async" loading="lazy" alt="The Usage screen in organization settings, with Enable usage credits and Request usage credits toggles and a per-service spend limit table (Claude Code Review, Unlimited)" src="https://datapopcorn.ai/blog/en/assets/images/08-team-usage-c5842de47f312ced81e38f21331ce6bf.jpg" width="1150" height="460" class="img_ev3q"></p>
<ul>
<li class=""><strong>Enable usage credits</strong>: lets members keep working at usage rates after they've used up their seat limit</li>
<li class=""><strong>Request usage credits</strong>: lets members who hit their limit request credits from an admin</li>
<li class=""><strong>Per-service spend limits</strong>: set limits for services that run automatically. In our organization, <strong>Claude Code Review</strong> was set to Unlimited</li>
</ul>
<p>Code Review is a service where Claude automatically reviews PRs in your GitHub repositories and leaves comments and check results on problematic lines. An owner has to turn it on, and a review starts when someone with write access comments <code>@claude review</code> on a PR. Reviews don't approve or block PRs (<a href="https://code.claude.com/docs/en/code-review" target="_blank" rel="noopener noreferrer" class="">official docs</a>). Since Unlimited appears to be the default, it's safer to set a limit in this table before turning it on.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="setup-to-do-right-after-subscribing-added-october-26-2026">Setup to do right after subscribing (added October 26, 2026)<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#setup-to-do-right-after-subscribing-added-october-26-2026" class="hash-link" aria-label="Direct link to Setup to do right after subscribing (added October 26, 2026)" title="Direct link to Setup to do right after subscribing (added October 26, 2026)" translate="no">​</a></h2>
<p>From here on is what I did after subscribing to Team and actually bringing teammates in. It follows almost the same order as the <strong>Get your team on Claude</strong> checklist (members, customization, spend tracking) at the top of <code>Organization settings &gt; Organization &amp; access</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-9-see-usage-per-member">Step 9. See usage per member<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#step-9-see-usage-per-member" class="hash-link" aria-label="Direct link to Step 9. See usage per member" title="Direct link to Step 9. See usage per member" translate="no">​</a></h2>
<p>The admin screens (<code>Organization settings</code>) don't show usage per person. You have to go to <strong>Analytics</strong> (<code>claude.ai/analytics</code>).</p>
<p><img decoding="async" loading="lazy" alt="The Top spenders table under Analytics &amp;gt; Claude Code &amp;gt; Value. Users are shown as anonymous IDs, with spend, commit count and cost per commit for each person" src="https://datapopcorn.ai/blog/en/assets/images/09-member-spend-6ec6f941d12235c345971ee4d760ed3c.jpg" width="1600" height="553" class="img_ev3q"></p>
<table><thead><tr><th>What you want to see</th><th>Where</th></tr></thead><tbody><tr><td>Who used Claude Code and how much (lines of code)</td><td>Analytics &gt; Claude Code &gt; <strong>Productivity</strong> tab, the <code>Team</code> table at the very bottom. Shows this month's line count by member email</td></tr><tr><td>Who spent how much on Claude Code (dollars)</td><td>Analytics &gt; Claude Code &gt; <strong>Value</strong> tab, the <code>Top spenders</code> table. Users appear as anonymous IDs, with spend, commit count and cost per commit</td></tr><tr><td>Who uses features like Skills, MCP and plan mode</td><td>Analytics &gt; Claude Code &gt; <strong>Usage</strong> tab (beta)</td></tr><tr><td>Chat and Cowork activity</td><td>Analytics &gt; Overview &gt; <strong>View all</strong> under <code>Who's using Claude?</code></td></tr></tbody></table>
<p>You can download per-person data as a file with <code>Export</code> on the Productivity tab and <code>Download CSV</code> on the Usage tab. Two things I couldn't see on these screens:</p>
<ul>
<li class=""><strong>What percentage of their seat limit (5-hour and weekly) each person has used</strong>: I couldn't find this anywhere in the admin screens or analytics.</li>
<li class="">Usage by people who run Claude Code with an <strong>API key</strong> instead of logging in: the screen notes that it's recorded in the Console organization, not in this dashboard.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-10-invite-people-without-a-company-email-gmail">Step 10. Invite people without a company email (Gmail)<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#step-10-invite-people-without-a-company-email-gmail" class="hash-link" aria-label="Direct link to Step 10. Invite people without a company email (Gmail)" title="Direct link to Step 10. Invite people without a company email (Gmail)" translate="no">​</a></h2>
<p>When I tried to invite an outside collaborator by their personal Gmail by entering the address in <code>Organization settings &gt; Members &gt; Add member</code>, I got the error <strong>"You can only invite emails from @datapopcorn.ai."</strong> By default, only the domain of the account that created the Team is allowed. Only the owner account that created the team has to be on the company domain; you can bring in members by adding their domain.</p>
<ol>
<li class="">In <code>Organization settings &gt; Organization &amp; access &gt; Domains</code>, click <strong>Add or edit domains</strong></li>
<li class="">Enter <code>gmail.com</code>, then click <strong>Add domain</strong> and <strong>Save</strong></li>
<li class="">Go back to <code>Add member</code> and invite the Gmail address</li>
</ol>
<p><img decoding="async" loading="lazy" alt="The domains table with datapopcorn.ai and gmail.com. The Discoverable column for gmail.com reads &amp;#39;Not allowed&amp;#39;" src="https://datapopcorn.ai/blog/en/assets/images/10-allowed-domains-55c761dc202f09602d0986eb6c275cfe.jpg" width="1600" height="356" class="img_ev3q"></p>
<p>The <strong>Discoverable</strong> column on the <code>gmail.com</code> row shows <code>Not allowed</code>. Letting people find and join the organization without an invite only works for the company domain. The only people who actually get in are the ones you invite.</p>
<div class="theme-admonition theme-admonition-caution admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>Turn off the invite link before adding gmail.com</div><div class="admonitionContent_BuS1"><p>Further down the same screen, the <strong>Invite link</strong> is a link that "lets users with an allowed email domain join," and <strong>people who join through the link are approved automatically.</strong> If the link is on while gmail.com is allowed, any Gmail user who gets hold of the link can walk right in and take a paid seat. Right after adding gmail.com, we turned off the invite link and decided to use email invites only.</p></div></div>
<p>The invite dialog says "Charges for new team members are prorated from the date they're added." We had bought 5 seats in advance, so each invite showed up as the number in <code>5 seats (n available)</code> on the <code>Billing</code> screen dropping by one. Invites that haven't been accepted yet also count as a seat.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-11-add-a-second-owner">Step 11. Add a second owner<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#step-11-add-a-second-owner" class="hash-link" aria-label="Direct link to Step 11. Add a second owner" title="Direct link to Step 11. Add a second owner" translate="no">​</a></h2>
<p>The first item on the checklist is <strong>Add another owner</strong>. If there's only one owner and that account gets locked, nobody can manage organization settings, billing or members. We created the team with a shared account, so we added the CEO's personal account as another owner.</p>
<p><img decoding="async" loading="lazy" alt="The role list opened in the Add member dialog, with three options: User (can create conversations), Admin (can manage user membership) and Owner (can manage user roles and organization settings)" src="https://datapopcorn.ai/blog/en/assets/images/11-invite-owner-48d781d64f6165b7340ccbf9d0d46b70.jpg" width="1072" height="1060" class="img_ev3q"></p>
<p>In <code>Add member</code>, enter the email, set the <strong>Role</strong> to <code>Owner</code> and send. There are three roles.</p>
<table><thead><tr><th>Role</th><th>What it can do</th></tr></thead><tbody><tr><td>User</td><td>Create conversations</td></tr><tr><td>Admin</td><td>Invite and manage members</td></tr><tr><td>Owner</td><td>Manage roles and organization settings. Connecting API credits in the next Step is owner-only too</td></tr></tbody></table>
<p>Until the invitee accepts from their email, they stay under the <code>Members &gt; Pending</code> tab, and owner permissions start only then.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-12-claim-your-monthly-api-credits-up-to-500">Step 12. Claim your monthly API credits (up to $500)<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#step-12-claim-your-monthly-api-credits-up-to-500" class="hash-link" aria-label="Direct link to Step 12. Claim your monthly API credits (up to $500)" title="Direct link to Step 12. Claim your monthly API credits (up to $500)" translate="no">​</a></h2>
<div class="theme-admonition theme-admonition-caution admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>$500 isn't what every Team gets</div><div class="admonitionContent_BuS1"><p><strong>$500 is a ceiling, and the actual amount is set by your seat count.</strong> You get $20 per Standard seat and $100 per Premium seat, pooled up to a maximum of $500 (<a href="https://support.claude.com/en/articles/17154008" target="_blank" rel="noopener noreferrer" class="">official help article</a>).</p><table><thead><tr><th>Seat mix</th><th>Monthly amount</th></tr></thead><tbody><tr><td>2 Standard seats (Team minimum)</td><td>$40</td></tr><tr><td>3 Standard + 2 Premium (example from the official help article)</td><td>$260</td></tr><tr><td>5 Premium seats (us)</td><td>$500</td></tr><tr><td>25+ Standard seats, or 5+ Premium seats</td><td>$500 (doesn't go higher)</td></tr></tbody></table><p>There are other conditions too. Only Max and Team get it; Free, Pro and Enterprise don't. You need to have been subscribed for <strong>7 days</strong>, and a Team <strong>owner</strong> has to connect it personally. The $500 we got comes from using 5 Premium seats under the Claude Startups discount.</p></div></div>
<p>The Team plan comes with monthly API credits, but <strong>they don't arrive until you connect them yourself.</strong> If the <code>API credits</code> section in <code>Organization settings &gt; Billing</code> shows "Connect an API organization to receive credits" and a <strong>Connect organization</strong> button, you haven't claimed them yet.</p>
<ol>
<li class="">Click <strong>Connect organization</strong></li>
<li class="">Choose the Claude Console organization that should receive the credits. We chose the organization that already had our startup credits. If you don't have a Console organization, create one in the <a href="https://platform.claude.com/" target="_blank" rel="noopener noreferrer" class="">Console</a> first</li>
<li class="">Review the <a href="https://www.anthropic.com/legal/credit-terms" target="_blank" rel="noopener noreferrer" class="">additional credit terms</a> at the bottom of the dialog and click <strong>Connect organization</strong></li>
</ol>
<p>As soon as we connected, a notice popped up saying "Your first API credits of $500 USD have been added," and the billing screen changed as shown below.</p>
<p><img decoding="async" loading="lazy" alt="The API credits section of the billing screen, showing the Console organization connected on October 26, 2026, monthly credits of US$500, and the next credit on November 20, 2026" src="https://datapopcorn.ai/blog/en/assets/images/12-api-credit-linked-b2d9be12e645fd3c294463b1c5c4d336.jpg" width="1600" height="403" class="img_ev3q"></p>
<p>Per the <a href="https://platform.claude.com/docs/en/about-claude/api-credits-for-subscribers" target="_blank" rel="noopener noreferrer" class="">official docs</a>, here are the conditions you need to know.</p>
<table><thead><tr><th>Item</th><th>Details</th></tr></thead><tbody><tr><td>Amount</td><td>$20 per Standard seat and $100 per Premium seat, pooled <strong>up to $500</strong>. Based on the seat count at the start of that month's billing. We have 5 Premium seats, so $500</td></tr><tr><td>When you can claim</td><td>After keeping the Team plan for <strong>7 days or more</strong></td></tr><tr><td>Required permissions</td><td>Team owner (or primary owner), plus one of Owner, Admin or Billing in the Console organization</td></tr><tr><td>Where you can use them</td><td>Claude API, Managed Agents, Agent SDK, Console playground</td></tr><tr><td><strong>Where you can't</strong></td><td><strong>Claude Code</strong>, extra usage in the Claude apps, and other clouds such as AWS and Google Cloud</td></tr><tr><td>Expiration</td><td>They disappear at the end of the billing cycle and don't roll over to the next month</td></tr><tr><td>Order of use</td><td>These credits are used first, then credits you bought or received separately</td></tr></tbody></table>
<div class="theme-admonition theme-admonition-caution admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>You can't use them as a backup for Claude Code limits</div><div class="admonitionContent_BuS1"><p>These $500 can't be used for Claude Code. If you run Claude Code with a key from an organization that has only these credits, you'll get <code>Credit balance too low</code>. The <a class="" href="https://datapopcorn.ai/blog/en/claude-code-limit-api-credit-fallback">claude-api command</a> that keeps you going with an API key when you hit your limit draws from separately received credits (like startup credits) or purchased credits.</p></div></div>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>When the Connect organization button turns gray and won't click</div><div class="admonitionContent_BuS1"><p>After we opened and closed the organization list once, the <strong>Connect organization</strong> button turned gray and wouldn't respond. Refreshing still left it unconnected; closing the dialog with <strong>Cancel</strong>, reopening it and clicking right away did the trick.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-13-organize-the-console-so-credits-dont-leak">Step 13. Organize the Console so credits don't leak<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#step-13-organize-the-console-so-credits-dont-leak" class="hash-link" aria-label="Direct link to Step 13. Organize the Console so credits don't leak" title="Direct link to Step 13. Organize the Console so credits don't leak" translate="no">​</a></h2>
<p>The monthly credits vanish if unused, and every key in an organization draws from the same balance. So we split the Console organization that receives the credits by purpose.</p>
<p><strong>1) Create a workspace per purpose.</strong> In the <a href="https://platform.claude.com/" target="_blank" rel="noopener noreferrer" class="">Console</a> &gt; Organization settings &gt; Workspaces &gt; <strong>Create workspace</strong>, you just pick a name and a color. We created <code>n8n</code> (automation workflows) and <code>콘텐츠</code> ("Content", for content work such as scripts and summaries). The emergency key for Claude Code stayed in the existing <code>Default</code> workspace.</p>
<p><img decoding="async" loading="lazy" alt="The Console workspace list, with Default, n8n and Content workspaces and the number of API keys in each" src="https://datapopcorn.ai/blog/en/assets/images/13-console-workspaces-58b2b4f36d5082d613a56ec2fa3b70ef.jpg" width="1600" height="389" class="img_ev3q"></p>
<p><strong>2) Put a monthly spend limit on each workspace.</strong> Select a workspace, then enter an amount under <code>Manage &gt; Spend limits &gt; Change limit</code>. We set <strong>$250</strong> on each of the two. Together they don't exceed the $500 we get each month, so even if an automation runs wild, it can't eat into the other credits we've kept for Claude Code emergencies.</p>
<p><img decoding="async" loading="lazy" alt="The spend limit screen for the n8n workspace, showing US$0.00 of a US$250.00 monthly limit and a note that it resets on November 1, 2026 (UTC)" src="https://datapopcorn.ai/blog/en/assets/images/14-workspace-spend-limit-fabee94440a39a150e51833670865e4a.jpg" width="1600" height="336" class="img_ev3q"></p>
<p>Spend limits reset on <strong>the 1st of each month (UTC)</strong>, while credits arrive on your <strong>billing cycle</strong> (around the 20th for us). Since the dates differ, it's better to set the limits generously.</p>
<p><strong>3) Create workspace-specific keys.</strong> In <code>API keys &gt; Create key</code>, set the <strong>Scope</strong> to that workspace, and the key works only in that workspace with limits applied based on that workspace. Expiration defaults to 30 days, so for automation, choose <code>Custom</code> and set it long (we used October 18, 2027, when our Team discount ends). The key is shown only once, so save it to your password manager right away and never paste it into chats or documents.</p>
<p><strong>4) Connect it where it's actually used.</strong> We put the n8n workspace key into n8n under <code>Credentials &gt; Anthropic</code> and confirmed the connection test (Connection successful). Don't put it anywhere lots of people come and go, like a teaching n8n instance shared with students.</p>
<p>The official docs recommend three ways to make the credits go further.</p>
<ul>
<li class="">Pick the right model for the job: Haiku for classification or short summaries, Sonnet for writing and general automation, Opus only for hard judgment calls</li>
<li class=""><a href="https://platform.claude.com/docs/en/build-with-claude/prompt-caching" target="_blank" rel="noopener noreferrer" class="">Prompt caching</a>: turn it on for tasks that attach the same long instructions every time</li>
<li class=""><a href="https://platform.claude.com/docs/en/build-with-claude/batch-processing" target="_blank" rel="noopener noreferrer" class="">Message Batches API</a>: bulk jobs that don't need an immediate answer are processed 50% cheaper</li>
</ul>
<p>As a bonus, organizations whose monthly credits exceed $200 get bumped to at least the Build tier for API rate limits.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-14-give-claude-work-over-slack-dm-added-october-28-2026">Step 14. Give Claude work over Slack DM (added October 28, 2026)<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#step-14-give-claude-work-over-slack-dm-added-october-28-2026" class="hash-link" aria-label="Direct link to Step 14. Give Claude work over Slack DM (added October 28, 2026)" title="Direct link to Step 14. Give Claude work over Slack DM (added October 28, 2026)" translate="no">​</a></h2>
<p>After connecting Claude Tag, the first thing worth trying is a <strong>1:1 DM with the Claude app</strong>. Unlike channel work, it doesn't need a top-up.</p>
<table><thead><tr><th>Where you call it</th><th>What it's billed to</th><th>Our status</th></tr></thead><tbody><tr><td>Channels, group DMs</td><td>Organization usage balance (on Team, nothing runs until it's funded)</td><td>Balance is $0, so it doesn't run yet</td></tr><tr><td>1:1 DM (members who connected their Claude account)</td><td>The sender's seat limit</td><td>Works right away</td></tr></tbody></table>
<p>This table is based on the official docs (<a href="https://claude.com/docs/claude-tag/admins/set-spend-limit" target="_blank" rel="noopener noreferrer" class="">billing guide</a>). Reading channels, deciding whether to reply and giving short answers from what it already knows aren't billed either. The monthly API credits from Step 12 don't apply to either of these.</p>
<p><strong>1) Open the Claude app in the Slack sidebar and send a DM.</strong> Here's what we sent.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Please summarize the recent alerts in the #infra channel. If there are any recurring issues, point those out too. Please reply politely, without profanity.</span><br></div></code></pre></div></div>
<p><strong>2) If you see "Your Claude connection expired," reconnect.</strong> Our account, connected through the old Slack app back in August, had expired.</p>
<p><img decoding="async" loading="lazy" alt="A Slack DM thread. Claude replies &amp;#39;Your Claude connection expired. Reconnect and I&amp;#39;ll pick this message up as soon as you&amp;#39;re done.&amp;#39; and shows a Reconnect Claude account button" src="https://datapopcorn.ai/blog/en/assets/images/15-slack-dm-connection-expired-4a1da0b01837e64d861f0935f6ba8809.jpg" width="814" height="320" class="img_ev3q"></p>
<ul>
<li class="">Clicking <code>Reconnect Claude account</code> opens the connection approval screen on claude.ai</li>
<li class="">Check that the <strong>Team organization</strong> is selected under <code>Switch organization</code> at the bottom, then click <code>Approve</code></li>
<li class="">If your Slack account email and claude.ai account email differ, you get one more confirmation screen saying "doesn't match." If it's the right account, click <code>Confirm and connect</code>. If you connected the wrong one, you can disconnect from the <code>Home</code> tab of the Claude app in Slack</li>
<li class="">Reconnecting didn't resume the message sent before the expiration. A notice said "your earlier message couldn't be picked up. Please send it again," so I sent the same request again</li>
</ul>
<p><strong>3) Check the reply.</strong> About 30 seconds later, a reply came into the thread. It organized the channel's top-level posts by date and flagged two recurring incidents and some credits about to expire. It said on its own that it hadn't read the posts inside threads, and asked whether I wanted it to look further.</p>
<p><img decoding="async" loading="lazy" alt="A Slack DM thread. In response to the #infra summary request, Claude politely lists the last two weeks of posts, two recurring issues and an upcoming credit expiration date. The model name claude-opus-5-5 is shown at the bottom" src="https://datapopcorn.ai/blog/en/assets/images/15-slack-dm-infra-summary-6d83dfdf02aa4bbc08a6366f7731b40b.jpg" width="814" height="1275" class="img_ev3q"></p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>The tone follows your own Claude settings</div><div class="admonitionContent_BuS1"><p>DMs run on the connected person's claude.ai account, so the tone you set in the desktop app carries straight over to Slack replies. I had told the desktop app to use a casual, rough tone, and that showed up in the first test reply, so I added "please reply politely" to the end of the request and got a new answer. Before using it in channels your teammates will see, check your personal tone settings first.</p></div></div>
<p>When you first open the DM, Claude offers to send you a morning briefing and an evening wrap-up every day, with a <code>Start</code> button. Clicking it creates a recurring task, so only click it if you need it. We didn't.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-to-decide-whether-to-switch-to-team">How to decide whether to switch to Team<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#how-to-decide-whether-to-switch-to-team" class="hash-link" aria-label="Direct link to How to decide whether to switch to Team" title="Direct link to How to decide whether to switch to Team" translate="no">​</a></h2>
<table><thead><tr><th>Situation</th><th>Recommendation</th></tr></thead><tbody><tr><td>You work alone and usage matters most</td><td>Personal Max is simpler. A Team seat is dedicated to one person, so you can't rotate through several seats yourself</td></tr><tr><td>You want teammates to call Claude together in Slack</td><td>You need Team. Claude Tag and on-call aren't on personal plans</td></tr><tr><td>You want to align shared team projects, common instructions and Skills in one go</td><td>You need Team</td></tr><tr><td>You need SCIM, audit logs, role-based permissions or data retention settings</td><td>Team isn't enough. Those are Enterprise features</td></tr></tbody></table>
<p>Why buying several seats and rotating through them yourself doesn't work, and how to keep going on API credits when you hit a limit, are covered in <a class="" href="https://datapopcorn.ai/blog/en/claude-code-limit-api-credit-fallback">How to keep working when you hit Claude Code's usage limit</a>. How I started the Team plan at a discount is in <a href="https://datapopcorn.ai/blog/claude-startups-benefits-claim" target="_blank" rel="noopener noreferrer" class="">How to claim Claude Startups benefits</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="faq">FAQ (added October 28, 2026)<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#faq" class="hash-link" aria-label="Direct link to FAQ (added October 28, 2026)" title="Direct link to FAQ (added October 28, 2026)" translate="no">​</a></h2>
<p>These are questions I've received from small companies whose staff each used a personal Claude plan and now want to move to a team plan. I verified the Claude side against our Team organization and the official help articles. We haven't used ChatGPT Business, so that part is based only on OpenAI's official help articles as of October 28, 2026.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="should-i-pick-claude-team-or-chatgpt-team">Should I pick Claude Team or ChatGPT Team?<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#should-i-pick-claude-team-or-chatgpt-team" class="hash-link" aria-label="Direct link to Should I pick Claude Team or ChatGPT Team?" title="Direct link to Should I pick Claude Team or ChatGPT Team?" translate="no">​</a></h3>
<p>ChatGPT Team was renamed <strong>ChatGPT Business</strong> on August 29, 2025. The first thing to know is that <strong>price won't decide it</strong>. Seat prices and minimum team size are identical.</p>
<table><thead><tr><th>Item</th><th>Claude Team</th><th>ChatGPT Business</th></tr></thead><tbody><tr><td>Standard seat</td><td>$25/month ($20/month billed annually)</td><td>$25/month ($20/month billed annually)</td></tr><tr><td>Premium seat</td><td>$125/month ($100/month billed annually)</td><td>$125/month ($100/month billed annually)</td></tr><tr><td>Minimum size</td><td>2 people (up to 150)</td><td>2 seats</td></tr><tr><td>Mixing seat types</td><td>Yes</td><td>Yes</td></tr><tr><td>Premium usage</td><td>6.25x Pro per 5-hour window, with a separate weekly limit (Standard is 1.25x)</td><td>5x Standard, no 5-hour limit</td></tr><tr><td>API</td><td>Monthly API credits included. $20 per Standard seat and $100 per Premium seat, pooled up to $500 ($40 with 2 Standard seats). An owner has to connect them (Step 12)</td><td>Not included. The API is billed separately</td></tr><tr><td>Model training on company conversations</td><td>Not trained on by default</td><td>Not trained on</td></tr><tr><td>Bringing over personal account history</td><td>Yes (with the same email)</td><td>Yes (<code>Settings &gt; Data controls &gt; Merge</code>)</td></tr></tbody></table>
<p>So here's the order I'd work through it in.</p>
<ol>
<li class=""><strong>Which one is your team already used to?</strong> If people were already using their own personal Claude, Claude Team has the lowest switching cost. They can bring their existing conversations and projects into the organization (next question), and you can align team rules in one go with organization instructions (Step 5) and Skills (Step 6).</li>
<li class=""><strong>Do you plan to run automations through the API?</strong> This is the row where the two plans differ most. Claude Team comes with monthly API credits based on seats, while ChatGPT Business keeps the API separate from the subscription. We connect these credits to our n8n automations (Step 13). They can't be used for Claude Code, though.</li>
<li class=""><strong>Have you compared them on real work?</strong> Both plans allow monthly billing and start at 2 seats. Pick 3 to 5 tasks your team does every week, give the same tasks to both, and choose; that's more accurate than a feature table.</li>
<li class=""><strong>Are you eligible for a discount program?</strong> We were able to get a one-year discount on 5 Premium seats through <a href="https://datapopcorn.ai/blog/claude-startups-benefits-claim" target="_blank" rel="noopener noreferrer" class="">Claude Startups</a>, so we started with Claude Team. The smaller the company, the bigger that difference.</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="do-the-personal-claude-conversations-and-projects-people-were-using-move-over-to-team">Do the personal Claude conversations and projects people were using move over to Team?<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#do-the-personal-claude-conversations-and-projects-people-were-using-move-over-to-team" class="hash-link" aria-label="Direct link to Do the personal Claude conversations and projects people were using move over to Team?" title="Direct link to Do the personal Claude conversations and projects people were using move over to Team?" translate="no">​</a></h3>
<p><strong>Yes, if it's the same email.</strong> When you accept a Team invite with the same email as your personal account, a screen asks you to pick one of three options (<a href="https://support.claude.com/en/articles/9267400-move-your-personal-claude-account-to-a-team-or-enterprise-organization" target="_blank" rel="noopener noreferrer" class="">official help article</a>).</p>
<table><thead><tr><th>Choice</th><th>Result</th></tr></thead><tbody><tr><td>Keep both accounts</td><td>Your personal account stays, and you switch between them in the account menu. Your personal subscription keeps billing too</td></tr><tr><td>Use only the organization account &gt; Bring data over</td><td>Your personal history moves to Team and the personal account is closed</td></tr><tr><td>Use only the organization account &gt; Delete data</td><td>Your personal account is closed and you start with an empty account on Team</td></tr></tbody></table>
<p>If you don't choose right away, a reminder banner appears for about 7 days, and you can also start later with the organization account selected via <code>Settings &gt; Account &gt; Close personal account</code>.</p>
<table><thead><tr><th>Moves over</th><th>Doesn't move over</th></tr></thead><tbody><tr><td>Chats and the artifacts in them</td><td>Skills you built (there's no way to move them, so export them first)</td></tr><tr><td>Projects, project instructions and files</td><td>Logins for connected apps (reconnect them in the organization account)</td></tr><tr><td>Uploaded files</td><td>Custom connectors you added yourself</td></tr><tr><td>Claude memory (unless the organization has turned memory off)</td><td>Published artifacts (Team has no publishing feature)</td></tr><tr><td>Claude Code memory and personal preferences</td><td>Cowork sessions in the desktop app, Cowork and Claude Code cloud sessions</td></tr></tbody></table>
<p>Once moved, it can't be undone. Chats you shared through public links also lose their links, so before moving, export your Skills, save copies of published artifacts and write down your list of connected apps.</p>
<p>ChatGPT Business has a similar feature. Select the Business workspace and go to <code>Settings &gt; Data controls &gt; Merge personal workspace data</code>, and conversations, custom GPTs, projects, library, scheduled tasks and memory move over. Sites don't move, and there's no undo. You can't export data yourself from a Business workspace, so OpenAI advises exporting your personal data before merging (<a href="https://help.openai.com/en/articles/8801890-migrating-your-personal-workspace-to-chatgpt-business-or-enterprise" target="_blank" rel="noopener noreferrer" class="">official help article</a>).</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="do-i-need-to-cancel-my-personal-pro-or-max-subscription-separately">Do I need to cancel my personal Pro or Max subscription separately?<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#do-i-need-to-cancel-my-personal-pro-or-max-subscription-separately" class="hash-link" aria-label="Direct link to Do I need to cancel my personal Pro or Max subscription separately?" title="Direct link to Do I need to cancel my personal Pro or Max subscription separately?" translate="no">​</a></h3>
<p>It depends on which option you chose and where you paid.</p>
<table><thead><tr><th>Situation</th><th>Personal subscription</th></tr></thead><tbody><tr><td>Chose bring data over or delete data, and paid on the web</td><td>Canceled automatically and refunded for the remaining period. The refund goes out about 24 hours after cancellation</td></tr><tr><td>Same choice, but paid through Google Play</td><td>Canceled automatically, refunded for the remaining period</td></tr><tr><td>Paid through the Apple App Store</td><td>Not canceled automatically. In this case you can only choose <code>Keep both accounts</code>, so cancel in your Apple ID settings first, then move</td></tr><tr><td>Keep both accounts</td><td>Keeps billing. You have to cancel it yourself</td></tr></tbody></table>
<p>We created Team while keeping a separate personal Max, so we canceled the personal Max ourselves. If the Team organization is selected at that point, the Team billing screen opens, so read the warning in Step 1 first. If you were receiving monthly API credits on personal Max, that connection ends when you move to Team, and an owner reconnects for Team credits starting 7 days after subscribing (Step 12).</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="can-staff-who-used-claude-on-a-personal-gmail-move-their-history-too">Can staff who used Claude on a personal Gmail move their history too?<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#can-staff-who-used-claude-on-a-personal-gmail-move-their-history-too" class="hash-link" aria-label="Direct link to Can staff who used Claude on a personal Gmail move their history too?" title="Direct link to Can staff who used Claude on a personal Gmail move their history too?" translate="no">​</a></h3>
<p>The official help articles only describe moving history <strong>when the email is the same</strong>. If an employee has built up history on a personal Gmail account, you have to invite that Gmail address to Team for them to bring it over. To invite an address outside the company domain, you need to add <code>gmail.com</code> to the allowed domains, and turn off the invite link before that (Step 10).</p>
<p>If you invite their company email instead, the personal Gmail account stays separate. In that case, they start fresh on Team for company work and only need to sort out their personal subscription.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-should-i-split-standard-and-premium-seats">How should I split Standard and Premium seats?<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#how-should-i-split-standard-and-premium-seats" class="hash-link" aria-label="Direct link to How should I split Standard and Premium seats?" title="Direct link to How should I split Standard and Premium seats?" translate="no">​</a></h3>
<p>According to the official help articles, a Standard seat gets 1.25x a personal Pro plan's 5-hour usage, a Premium seat gets 6.25x, and both have separate weekly limits. Limits apply per person and can't be handed to or shared with someone else.</p>
<table><thead><tr><th>Current plan</th><th>Seat to move to</th></tr></thead><tbody><tr><td>Personal Pro, rarely hits the limit</td><td>Standard</td></tr><tr><td>Personal Max 5x, uses Claude Code heavily</td><td>Premium (Max 5x is 5x Pro; Premium is 6.25x)</td></tr><tr><td>Personal Max 20x</td><td>Even Premium means a lower limit. Turn on usage credits (Step 8) or prepare a way to keep going when the limit hits</td></tr></tbody></table>
<p>You can mix both seat types in one organization, so it's better to start with Premium only for heavy users and adjust after looking at actual usage in the analytics screen (Step 9).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="troubleshooting">Troubleshooting<a href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features#troubleshooting" class="hash-link" aria-label="Direct link to Troubleshooting" title="Direct link to Troubleshooting" translate="no">​</a></h2>
<table><thead><tr><th>Symptom</th><th>Cause</th><th>Fix</th></tr></thead><tbody><tr><td><code>claude.ai/oncall</code> says "This page isn't available for your organization yet"</td><td>The personal plan organization is selected</td><td>Pick the Team organization in the account menu and open it again</td></tr><tr><td>Routines disappeared from the sidebar</td><td>On Team they move under <code>More</code></td><td>Click <code>More &gt; Routines</code>, or pull them out with <code>Edit sidebar</code></td></tr><tr><td>The Claude Tag screen shows only info and chat buttons instead of 'Start setup'</td><td>No owner permissions</td><td>Leave setup to an owner, or ask to be promoted to owner on the Members screen</td></tr><tr><td>@Claude in Slack only replies "not available right now"</td><td>Routines are turned off for the organization</td><td>Turn on routines in Organization settings &gt; Features</td></tr><tr><td>No replies in channels on Team</td><td>Usage credits aren't funded, or the spend limit was reached</td><td>Check credits and limits in Organization settings &gt; Usage</td></tr><tr><td>The share menu says "Project sharing is turned off by your administrator"</td><td>An owner turned off project sharing</td><td>Ask an owner to turn it back on in organization settings</td></tr><tr><td>Clicked cancel on the personal plan but the Team billing screen opened</td><td>Went into billing with the Team organization selected</td><td>Switch to the personal plan in the account menu, then go to Settings &gt; Billing</td></tr><tr><td>Can't find per-person usage in organization settings</td><td>Per-person figures are in the analytics screen</td><td>Open Account menu &gt; Analytics &gt; Claude Code (Productivity and Value tabs) or Overview &gt; View all (Step 9)</td></tr><tr><td>Inviting shows "You can only invite emails from @yourcompanydomain"</td><td>That email's domain isn't in the allowed domains</td><td>Add it under Organization &amp; access &gt; Domains. Turn off the invite link first (Step 10)</td></tr><tr><td>Billing screen says "Connect an API organization to receive credits"</td><td>Monthly API credits aren't connected yet</td><td>Pick a Console organization with Connect organization. Available 7 days after subscribing, owners only (Step 12)</td></tr><tr><td>Less than $500 in credits arrived</td><td>The amount is based on seat count ($20 per Standard, $100 per Premium)</td><td>That's expected. See the table in Step 12 for amounts by seat mix</td></tr><tr><td>Running Claude Code with an API key gives <code>Credit balance too low</code></td><td>Monthly API credits can't be used for Claude Code</td><td>Use a key from an organization that has purchased or separately received credits</td></tr><tr><td>A Slack DM says "Your Claude connection expired"</td><td>A previously connected Claude account connection expired</td><td>Reconnect with <code>Reconnect Claude account</code>, choosing the Team organization, then send the request again (Step 14)</td></tr><tr><td>Calling @Claude in a channel doesn't do anything</td><td>The Team organization's usage balance is $0</td><td>Buy usage credits under Organization settings &gt; Usage. Monthly API credits won't work for this</td></tr><tr><td>Automations burn through credits too fast</td><td>Every key in an organization draws from the same balance</td><td>Split workspaces by purpose and set spend limits (Step 13)</td></tr><tr><td>After accepting the invite, only <code>Keep both accounts</code> is available</td><td>Personal Pro or Max was paid through the Apple App Store</td><td>Cancel the subscription in your Apple ID settings, then move via <code>Settings &gt; Account &gt; Close personal account</code></td></tr><tr><td>Some old chats are missing after the move</td><td>The search index is being rebuilt right after the move</td><td>Search by chat title, or log out and back in</td></tr></tbody></table>]]></content:encoded>
            <category>AI agents</category>
            <category>Guide</category>
        </item>
        <item>
            <title><![CDATA[How to Stop Losing 2 Hours to Setup in a Hands-On AI Workshop: A Night-Before Check Script and a 5-Step Day-Of Timetable]]></title>
            <link>https://datapopcorn.ai/blog/en/beginner-ai-workshop-setup-time</link>
            <guid>https://datapopcorn.ai/blog/en/beginner-ai-workshop-setup-time</guid>
            <pubDate>Thu, 22 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[In a one-day AI workshop for non-developers, initial setup alone took 2 hours and we only got through half the curriculum. Here's how I changed the way I run workshops, in 5 steps, including a check script participants run the night before and a timetable for the day.]]></description>
            <content:encoded><![CDATA[<p>In a hands-on AI workshop, setup time isn't an accident. It's <strong>a constant that shows up every time</strong>. Have participants run a check script the night before and send you a screenshot of the result, then fill the install wait on the day with concept explanations, and setup will no longer cut your curriculum in half.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="who-this-is-for">Who this is for<a href="https://datapopcorn.ai/blog/en/beginner-ai-workshop-setup-time#who-this-is-for" class="hash-link" aria-label="Direct link to Who this is for" title="Direct link to Who this is for" translate="no">​</a></h2>
<ul>
<li class="">Instructors and training managers running their first hands-on AI session for non-developers (coding agents, no-code automation, etc.)</li>
<li class="">Anyone who sent an install list in the pre-workshop email, only to find half the room hadn't installed anything on the day</li>
<li class="">Workshop organizers who have to run in-person and online at the same time</li>
</ul>
<p>All you need is your current pre-workshop email and timetable. The script in Step 2 is for Mac, and I ran it myself on macOS 26.5.1 on October 7, 2026.</p>
<p>First, a quick note on why I wrote this. In a 5-hour one-day workshop for college students and job seekers, initial setup alone took 2 hours, and we got through only half of the curriculum I had prepared. Most participants were seeing a coding agent, a code editor and a terminal for the first time that day. The installs themselves weren't hard. The problem was that <strong>many people got stuck at the same time, each for a different reason, and one instructor had to unblock them one by one</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1-list-the-8-common-sticking-points-in-your-pre-workshop-email">Step 1. List the 8 common sticking points in your pre-workshop email<a href="https://datapopcorn.ai/blog/en/beginner-ai-workshop-setup-time#step-1-list-the-8-common-sticking-points-in-your-pre-workshop-email" class="hash-link" aria-label="Direct link to Step 1. List the 8 common sticking points in your pre-workshop email" title="Direct link to Step 1. List the 8 common sticking points in your pre-workshop email" translate="no">​</a></h2>
<p>These are the real sticking points that piled up in my instructor notes. Most of them go away with a single "that's normal," but hearing it for the first time on the day makes people freeze. So I put this table straight into the pre-workshop email.</p>
<table><thead><tr><th>Sticking point</th><th>What actually happens</th><th>What to tell them in advance</th></tr></thead><tbody><tr><td>Not checking login status</td><td>Tabs missing or no response</td><td>Check the account in the top-right of the app first</td></tr><tr><td>Thinking the install froze</td><td>"I think it's stuck"</td><td>30 seconds to 1 minute is normal</td></tr><tr><td>Global install permission error</td><td><code>EACCES</code></td><td>Don't add <code>sudo</code>; tell the instructor</td></tr><tr><td>Korean characters or spaces in folder paths</td><td>Paths aren't recognized in later exercises</td><td>Use English folder names</td></tr><tr><td>Windows security alerts</td><td>Stuck at a SmartScreen or firewall pop-up</td><td>It's OK to click Allow</td></tr><tr><td>macOS permission pop-ups</td><td>Folder access approval window</td><td>Click Allow</td></tr><tr><td>Not returning to the app after browser login</td><td>Approved, but it doesn't go back to the app</td><td>Don't refresh; wait a moment</td></tr><tr><td>Company or school laptop policies</td><td>Installs blocked entirely</td><td>Bring a personal laptop</td></tr></tbody></table>
<p>There's almost no way to fix the last one on the day. Make it the boldest line in your pre-workshop email.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2-have-participants-run-a-check-script-the-night-before">Step 2. Have participants run a check script the night before<a href="https://datapopcorn.ai/blog/en/beginner-ai-workshop-setup-time#step-2-have-participants-run-a-check-script-the-night-before" class="hash-link" aria-label="Direct link to Step 2. Have participants run a check script the night before" title="Direct link to Step 2. Have participants run a check script the night before" translate="no">​</a></h2>
<p>If you just send an install list, most people won't read it. So I added <strong>a mechanism that sends results back</strong>. The night before, participants paste the whole block below into Terminal and send a screenshot of the final screen.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">cat</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> ~/Desktop/setup-check.sh </span><span class="token operator" style="color:#393A34">&lt;&lt;</span><span class="token string" style="color:#e3116c">'SCRIPT'</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">#!/bin/bash</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c"># Pre-workshop check to run the night before (macOS)</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">ok(){ echo "✅ $1"; }; ng(){ echo "❌ $1"; FAIL=1; }</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">FAIL=0</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">echo "macOS $(sw_vers -productVersion)"</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">command -v git &gt;/dev/null &amp;&amp; ok "git $(git --version | awk '{print $3}')" || ng "git missing → xcode-select --install"</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">command -v node &gt;/dev/null &amp;&amp; ok "node $(node -v)" || ng "node missing → install the LTS from https://nodejs.org"</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">command -v npm &gt;/dev/null &amp;&amp; ok "npm $(npm -v)" || ng "npm missing (installed together with node)"</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">FREE=$(df -g ~ | awk 'NR==2{print $4}')</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">[ "$FREE" -ge 5 ] &amp;&amp; ok "Free space ${FREE}GB" || ng "Free space ${FREE}GB (please free up 5GB or more)"</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">DIR="${1:-$HOME/ai-workshop}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">case "$DIR" in *[[:space:]]*|*[가-힣]*) ng "Work folder path contains spaces/Korean characters: $DIR";; *) mkdir -p "$DIR" &amp;&amp; ok "Work folder $DIR";; esac</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">for h in registry.npmjs.org github.com; do</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">  curl -s -m 8 -o /dev/null "https://$h" &amp;&amp; ok "Internet connection $h" || ng "Could not reach $h (check company network/VPN)"</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">done</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">[ $FAIL -eq 0 ] &amp;&amp; echo "All checks passed. Please take a screenshot of this screen and send it." || echo "Fix the ❌ items, or take a screenshot of this screen and send it to the instructor."</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">SCRIPT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">bash</span><span class="token plain"> ~/Desktop/setup-check.sh</span><br></div></code></pre></div></div>
<p>Here's the actual result from running it on a Mac on October 7, 2026.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">macOS 26.5.1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">✅ git 2.55.0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">✅ node v26.7.0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">✅ npm 11.19.0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">✅ Free space 60GB</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">✅ Work folder /Users/popcorn/ai-workshop</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">✅ Internet connection registry.npmjs.org</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">✅ Internet connection github.com</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">All checks passed. Please take a screenshot of this screen and send it.</span><br></div></code></pre></div></div>
<p>Adapt the checks to your own workshop. For example, if you use a coding agent, add one more <code>command -v</code> line to check that the tool is installed. If you only contact the people whose screenshots show a ❌ the night before, you'll have far fewer one-by-one fixes on the day.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3-lock-in-everyone-installs-together-concepts-while-you-wait-for-the-first-block-of-the-day">Step 3. Lock in "everyone installs together, concepts while you wait" for the first block of the day<a href="https://datapopcorn.ai/blog/en/beginner-ai-workshop-setup-time#step-3-lock-in-everyone-installs-together-concepts-while-you-wait-for-the-first-block-of-the-day" class="hash-link" aria-label="Direct link to Step 3. Lock in &quot;everyone installs together, concepts while you wait&quot; for the first block of the day" title="Direct link to Step 3. Lock in &quot;everyone installs together, concepts while you wait&quot; for the first block of the day" translate="no">​</a></h2>
<p>Even with the night-before check, there's still installing and logging in on the day. Don't leave that time empty. I nailed down this order in my instructor notes.</p>
<table><thead><tr><th>Order</th><th>Time</th><th>What happens</th></tr></thead><tbody><tr><td>1</td><td>30 seconds</td><td>Demo of what we'll build today</td></tr><tr><td>2</td><td>5 minutes</td><td>Everyone starts installing and logging in at the same time</td></tr><tr><td>3</td><td>15-20 minutes</td><td>Concept explanation while downloads run (e.g., why we keep work in one folder)</td></tr><tr><td>4</td><td>The rest</td><td>Individual hands-on work</td></tr></tbody></table>
<p>People who finish early listen to the concepts, and people who are slower catch up during that time. Nobody sits around waiting for everyone's install to finish.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4-announce-the-watch-along-mode-and-the-minimum-guaranteed-outcome-at-the-start">Step 4. Announce the watch-along mode and the minimum guaranteed outcome at the start<a href="https://datapopcorn.ai/blog/en/beginner-ai-workshop-setup-time#step-4-announce-the-watch-along-mode-and-the-minimum-guaranteed-outcome-at-the-start" class="hash-link" aria-label="Direct link to Step 4. Announce the watch-along mode and the minimum guaranteed outcome at the start" title="Direct link to Step 4. Announce the watch-along mode and the minimum guaranteed outcome at the start" translate="no">​</a></h2>
<p>I say these two sentences word for word at the start.</p>
<ol>
<li class="">"If your install doesn't work, you're welcome to just watch. You'll get today's finished result afterward."</li>
<li class="">"Today's minimum goal is up to ○○. After that, it's fine if everyone finishes at a different point."</li>
</ol>
<p>The first sentence lets stuck participants move over to watching a neighbor's screen without feeling guilty. The second splits the goal into two tiers. In that workshop, "finish a Chrome extension in 3 hours" was an aggressive goal. If I ran it again, I'd set the minimum at "load the extension into the browser and follow its structure and debugging flow."</p>
<p>So that stuck participants don't have to start over, save a finished version for each stage (checkpoints) in advance so they can jump straight back to the right stage.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-5-make-the-first-exercise-something-small-that-finishes-within-30-minutes">Step 5. Make the first exercise something small that finishes within 30 minutes<a href="https://datapopcorn.ai/blog/en/beginner-ai-workshop-setup-time#step-5-make-the-first-exercise-something-small-that-finishes-within-30-minutes" class="hash-link" aria-label="Direct link to Step 5. Make the first exercise something small that finishes within 30 minutes" title="Direct link to Step 5. Make the first exercise something small that finishes within 30 minutes" translate="no">​</a></h2>
<p>Don't dive straight into a big project. Start with something that finishes within 30 minutes, like a Pomodoro timer or a habit-tracker app, so people get used to the flow of "ask the AI for something small and check it with your own eyes." The flashy demo comes after that.</p>
<p>The flashier the result, the more variables there are, such as an external site's structure and browser permissions. If that site changes on the day, the whole demo falls apart, so prepare a recording and a finished version as a fallback.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="when-this-happens-do-this">When this happens, do this<a href="https://datapopcorn.ai/blog/en/beginner-ai-workshop-setup-time#when-this-happens-do-this" class="hash-link" aria-label="Direct link to When this happens, do this" title="Direct link to When this happens, do this" translate="no">​</a></h2>
<table><thead><tr><th>Situation</th><th>Response</th></tr></thead><tbody><tr><td>Only half the participants sent their check screenshots</td><td>Set aside a check-in slot 15 minutes before the class, and have those who didn't send one run the script first</td></tr><tr><td>Several people show <code>❌ node missing</code></td><td>Resend the install link the night before, and fold it into the simultaneous install time in Step 3 on the day</td></tr><tr><td>Installs blocked on a company laptop</td><td>Hard to fix on the day. Point them to the watch-along mode and share the result</td></tr><tr><td>No response from online participants</td><td>Ask for frequent check-in signals, like "type 1 in the chat when you see ○○ on your screen." For hands-on workshops, consider running online as a separate session</td></tr><tr><td>Stuck with one person for more than 10 minutes</td><td>Switch them to the watch-along mode and look again during the break</td></tr></tbody></table>
<p>One more note on online. That day there were 13 online participants, more than in the room, but with cameras off and almost no reactions, I had no way of knowing who was stuck. My conclusion is that in a hands-on workshop, online and in-person need different ways of stepping in.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-up<a href="https://datapopcorn.ai/blog/en/beginner-ai-workshop-setup-time#wrap-up" class="hash-link" aria-label="Direct link to Wrap-up" title="Direct link to Wrap-up" translate="no">​</a></h2>
<p>Setup time wasn't about the instructor's skill. It was a structural problem. Get results back with a night-before check script, fill the install wait on the day with concept explanations, and announce the watch-along mode and the minimum guaranteed outcome right at the start, and you'll keep far more hands-on time out of the same 5 hours.</p>]]></content:encoded>
            <category>Education &amp; career</category>
            <category>Agent</category>
        </item>
        <item>
            <title><![CDATA[Got a Cardputer ADV at the n8n Conference? Try These Before It Goes in a Drawer]]></title>
            <link>https://datapopcorn.ai/blog/en/m5stack-cardputer-adv-n8n-in-the-loop</link>
            <guid>https://datapopcorn.ai/blog/en/m5stack-cardputer-adv-n8n-in-the-loop</guid>
            <pubDate>Thu, 22 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[What the M5Stack Cardputer ADV handed out to attendees at n8n In The Loop 2026 actually is, how to plug it into a Mac and identify it, the three event apps and Wi-Fi setup, how to swap the firmware and restore it, and what to use it for after the event.]]></description>
            <content:encoded><![CDATA[<p>At In The Loop 2026 (Berlin, October 13-14), n8n's first official conference, every attendee got a palm-sized computer with a keyboard: the M5Stack Cardputer ADV. This post is my record of plugging one in and working out what it is, how to turn it on and use it, and what it's good for after the event.</p>
<!-- -->
<!-- -->
<p><img decoding="async" loading="lazy" alt="My Cardputer ADV on my desk. The screen shows the event loader menu (Node Quest, Icebreaker, BT mouse+keyboard)" src="https://datapopcorn.ai/blog/en/assets/images/my-cardputer-029cd85c2e2ce2fa46e5b08b39a241ed.webp" width="1600" height="1200" class="img_ev3q"></p>
<p><em>The Cardputer ADV I brought home from the event. Turn it on and the n8n event menu comes up right away.</em></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="who-this-is-for">Who this is for<a href="https://datapopcorn.ai/blog/en/m5stack-cardputer-adv-n8n-in-the-loop#who-this-is-for" class="hash-link" aria-label="Direct link to Who this is for" title="Direct link to Who this is for" translate="no">​</a></h2>
<ul>
<li class="">Anyone who got a Cardputer (or a similar ESP32 device) at a conference or meetup and isn't sure what to do with it</li>
<li class="">Anyone touching an ESP32 board for the first time who wants to start by confirming it's properly connected to their Mac</li>
<li class="">Event organizers wondering whether to hand out hardware as swag</li>
</ul>
<p>You'll need the Cardputer ADV itself, a USB-C cable that supports data, and a Mac (this post is written for macOS). With a charge-only cable, the computer won't detect the device.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-the-cardputer-adv">What is the Cardputer ADV?<a href="https://datapopcorn.ai/blog/en/m5stack-cardputer-adv-n8n-in-the-loop#what-is-the-cardputer-adv" class="hash-link" aria-label="Direct link to What is the Cardputer ADV?" title="Direct link to What is the Cardputer ADV?" translate="no">​</a></h2>
<p>"Cardputer" means "card-sized computer" (Card + Computer). It's made by M5Stack, a development-board company in Shenzhen, China, and the ADV is an improved version released in the second half of 2025. Inside is an ESP32-S3, a microcontroller with Wi-Fi and Bluetooth. It's not a computer running an operating system like a smartphone; think of it as a device that runs one firmware image and only that program.</p>
<table><thead><tr><th>Item</th><th>Spec</th></tr></thead><tbody><tr><td>Chip</td><td>ESP32-S3FN8 (dual-core 240MHz), 8MB flash</td></tr><tr><td>Display</td><td>1.14-inch LCD, 240 x 135</td></tr><tr><td>Input</td><td>56-key keyboard (4 rows x 14 keys)</td></tr><tr><td>Audio</td><td>ES8311 audio codec, MEMS mic, 1W speaker, 3.5mm headphone jack</td></tr><tr><td>Sensors</td><td>BMI270 6-axis motion sensor, infrared (IR) transmitter</td></tr><tr><td>Expansion</td><td>microSD slot, Grove port, 2.54mm 14-pin expansion header on the back</td></tr><tr><td>Battery</td><td>1500mAh lithium-ion</td></tr><tr><td>Size and weight</td><td>84 x 54 x 19.6mm, 81g</td></tr><tr><td>Price</td><td>About $30 on the official store</td></tr></tbody></table>
<p>Specs are from the <a href="https://shop.m5stack.com/products/m5stack-cardputer-adv-version-esp32-s3" target="_blank" rel="noopener noreferrer" class="">M5Stack official store</a>. Compared with the previous model (Cardputer v1.1), the audio chip changed, and it adds a motion sensor, a headphone jack, a rear expansion header and a strap hole.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1-plug-it-in-and-identify-it">Step 1. Plug it in and identify it<a href="https://datapopcorn.ai/blog/en/m5stack-cardputer-adv-n8n-in-the-loop#step-1-plug-it-in-and-identify-it" class="hash-link" aria-label="Direct link to Step 1. Plug it in and identify it" title="Direct link to Step 1. Plug it in and identify it" translate="no">​</a></h2>
<p>Connect it to your Mac with a USB-C cable and flip the switch on the side. Then check the USB device info in Terminal.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">ioreg </span><span class="token parameter variable" style="color:#36acaa">-p</span><span class="token plain"> IOUSB </span><span class="token parameter variable" style="color:#36acaa">-l</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">-w0</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">grep</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">-E</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'"(USB Product Name|USB Vendor Name|idVendor|idProduct|kUSBSerialNumberString)"'</span><br></div></code></pre></div></div>
<p>Here's what my device showed (I've removed the lines for other USB devices):</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">"idProduct" = 4097</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">"USB Product Name" = "USB JTAG_serial debug unit"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">"kUSBSerialNumberString" = "70:AF:09:DF:3A:14"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">"USB Vendor Name" = "Espressif"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">"idVendor" = 12346</span><br></div></code></pre></div></div>
<p>The numbers are printed in decimal, which is confusing. Converted to hex:</p>
<ul>
<li class=""><code>idVendor 12346</code> = <code>0x303A</code>: the USB vendor ID of Espressif, the company that makes the ESP32 chip. The device brand is M5Stack, but USB reports the chip maker's name.</li>
<li class=""><code>idProduct 4097</code> = <code>0x1001</code>: the ID used by the USB port built into the ESP32-S3 (USB Serial/JTAG). It means the chip speaks USB directly, without a separate USB bridge chip.</li>
<li class="">Serial number <code>70:AF:09:...</code>: this chip's MAC address. It's different on every device.</li>
</ul>
<p>Find the port name like this:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">ls</span><span class="token plain"> /dev/cu.*</span><br></div></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">/dev/cu.Bluetooth-Incoming-Port</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">/dev/cu.debug-console</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">/dev/cu.usbmodem1101</span><br></div></code></pre></div></div>
<p>The one starting with <code>cu.usbmodem</code> is the Cardputer. The other two are ports every Mac has, so don't pick them when flashing firmware later. (If you use wireless earbuds, you may see one more port with their name.)</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2-read-the-logs-to-see-what-the-device-is-doing">Step 2. Read the logs to see what the device is doing<a href="https://datapopcorn.ai/blog/en/m5stack-cardputer-adv-n8n-in-the-loop#step-2-read-the-logs-to-see-what-the-device-is-doing" class="hash-link" aria-label="Direct link to Step 2. Read the logs to see what the device is doing" title="Direct link to Step 2. Read the logs to see what the device is doing" translate="no">​</a></h2>
<p>ESP32 firmware usually writes logs over USB serial. On a Mac you can read them briefly with <code>cat</code>. This command reads for 6 seconds and stops:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">cat</span><span class="token plain"> /dev/cu.usbmodem1101 </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">sleep</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">6</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">kill</span><span class="token plain"> </span><span class="token variable" style="color:#36acaa">$!</span><br></div></code></pre></div></div>
<p>My device, sitting in my office in Seoul, kept printing this:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">[sdk] joining 'memento.mori'</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">[sdk] wifi disconnected, reason 201</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">[sdk] wifi disconnected, reason 201</span><br></div></code></pre></div></div>
<p><code>reason 201</code> is an ESP32 Wi-Fi error code meaning "couldn't find the network it's trying to join (NO_AP_FOUND)". The firmware was built to auto-connect to a Wi-Fi network saved in advance at the venue, so even after leaving Berlin it keeps looking for that network. In the next step we'll switch it to your home or office Wi-Fi.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3-basic-controls-and-changing-the-wi-fi">Step 3. Basic controls and changing the Wi-Fi<a href="https://datapopcorn.ai/blog/en/m5stack-cardputer-adv-n8n-in-the-loop#step-3-basic-controls-and-changing-the-wi-fi" class="hash-link" aria-label="Direct link to Step 3. Basic controls and changing the Wi-Fi" title="Direct link to Step 3. Basic controls and changing the Wi-Fi" translate="no">​</a></h2>
<p>The device you got at the event comes with an app loader (Cardputer Loader 0.5.5) built by n8n ambassadors. The controls are documented in the <a href="https://cardputer.larsemil.ai/help" target="_blank" rel="noopener noreferrer" class="">Cardputer Flasher guide</a>. This site isn't an official n8n or M5Stack site; it's a web install page that n8n ambassador Lars Emil Christensen built for the event on his own domain. This was the URL shared at the venue.</p>
<ul>
<li class="">Use the arrow keys (<code>;</code> <code>,</code> <code>.</code> <code>/</code>) to move through menus and <code>ok</code> to open an app.</li>
<li class=""><strong>Press the button on the back (BtnG0) to return to the main menu from anywhere.</strong> It works even when an app freezes. If you remember only one thing, make it this.</li>
<li class=""><code>esc</code> is usually "back" too, but apps may use it for something else.</li>
</ul>
<p>To change the Wi-Fi:</p>
<ol>
<li class="">Open <strong>WiFi settings</strong> at the bottom of the menu. It scans nearby networks for about 6 seconds.</li>
<li class="">Pick a network with the arrow keys and press <code>ok</code>, then type the password and press <code>ok</code> again.</li>
<li class="">Press <code>r</code> to rescan. For hidden networks, enter the name first under <strong>Other network</strong> at the bottom of the list.</li>
<li class="">A black Wi-Fi icon in the top right means connected; gray with a pink slash means not connected.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4-try-the-three-event-apps">Step 4. Try the three event apps<a href="https://datapopcorn.ai/blog/en/m5stack-cardputer-adv-n8n-in-the-loop#step-4-try-the-three-event-apps" class="hash-link" aria-label="Direct link to Step 4. Try the three event apps" title="Direct link to Step 4. Try the three event apps" translate="no">​</a></h2>
<p>Here's a video of me launching and using the three apps below. The small screen is magnified on the right side of the video.</p>
<div class="wrapper_Aefj"><iframe src="https://www.youtube.com/embed/ZI9oI-z3WWo" title="I tried the Cardputer ADV, the free palm-sized computer from the n8n conference" loading="lazy" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe></div>
<p>The loader includes three apps, all made by n8n ambassadors for this event.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="node-quest-an-rpg-that-teaches-n8n">Node Quest: an RPG that teaches n8n<a href="https://datapopcorn.ai/blog/en/m5stack-cardputer-adv-n8n-in-the-loop#node-quest-an-rpg-that-teaches-n8n" class="hash-link" aria-label="Direct link to Node Quest: an RPG that teaches n8n" title="Direct link to Node Quest: an RPG that teaches n8n" translate="no">​</a></h3>
<p>A small RPG made by Greg Evseev. You walk around a village talking to people, they give you a 3-question n8n quiz, and if you get them all right you earn XP. It has 11 levels (1 tutorial plus 10 main), 50 tasks and 190 questions. You can play it all the way through offline; Wi-Fi is only used to post your score to the leaderboard.</p>
<ul>
<li class=""><code>;</code> <code>,</code> <code>.</code> <code>/</code>: move up, left, down, right</li>
<li class=""><code>ok</code>: talk to the person next to you; on the last page, start the quiz</li>
<li class=""><code>1</code>-<code>4</code>: choose an answer</li>
<li class=""><code>esc</code>: world map</li>
</ul>
<p>It's perfectly usable for reviewing basic concepts like "What's a trigger?" in an n8n class.</p>
<p>When I played it, I noticed even the area names come from n8n. Enter a portal and you can travel to places like Crossroads of If, Filter Marsh, Expression Springs, HTTP Harbor and Webhook Gate. The tutorial quiz starts with controls questions like "Which key shows the next page?" (answer: <code>ok</code>), and as the areas get bigger, nodes like HTTP Request come up. Holding <code>fn</code> while pressing an arrow key lets you dash a long way at once, which speeds up getting around the map. Quests you've already cleared don't give extra XP if you replay them.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="icebreaker-a-game-where-your-penguin-grows-when-you-meet-people">Icebreaker: a game where your penguin grows when you meet people<a href="https://datapopcorn.ai/blog/en/m5stack-cardputer-adv-n8n-in-the-loop#icebreaker-a-game-where-your-penguin-grows-when-you-meet-people" class="hash-link" aria-label="Direct link to Icebreaker: a game where your penguin grows when you meet people" title="Direct link to Icebreaker: a game where your penguin grows when you meet people" translate="no">​</a></h3>
<p>A networking game made by Fahmi Fahreza and Dominik Vogt. One person presses <code>C</code> to show a 4-character code, the other presses <code>E</code> and enters it, and both devices show a MATCHED! screen while the penguin grows one stage. Then both devices show the same icebreaker question. A code can only be used once within 60 seconds, so you actually have to be face to face.</p>
<p>This app syncs with the event server over Wi-Fi. I haven't confirmed whether the server is still up now that the event is over.</p>
<p>When you open the app, you see the penguin with a <code>code</code>, <code>enter</code>, <code>sync</code> and <code>help</code> menu on the right, and your level and number of people met at the bottom. My penguin was at LV4 thanks to a few matches at the venue. One letdown: there's no screen where you can browse the icebreaker questions that come up during matches. Once the event is over, there's no other device to match with, so you can't see any more questions.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="bt-mouse-keyboard-and-clicker-the-most-useful-app-after-the-event">BT mouse, keyboard and clicker: the most useful app after the event<a href="https://datapopcorn.ai/blog/en/m5stack-cardputer-adv-n8n-in-the-loop#bt-mouse-keyboard-and-clicker-the-most-useful-app-after-the-event" class="hash-link" aria-label="Direct link to BT mouse, keyboard and clicker: the most useful app after the event" title="Direct link to BT mouse, keyboard and clicker: the most useful app after the event" translate="no">​</a></h3>
<p>Made by Lars Emil Christensen, this app turns the Cardputer into a Bluetooth mouse, keyboard and presentation clicker. You don't need to install anything on the other computer.</p>
<ol>
<li class="">Open the app and, in your computer's Bluetooth settings, pick the name shown on screen (<code>Cardputer</code> + 4 characters).</li>
<li class="">Each press of <code>esc</code> cycles through mouse, keyboard and clicker modes.</li>
<li class="">In <strong>clicker mode</strong>, any key on the left half of the keyboard goes to the previous slide and any key on the right half goes to the next. It works in PowerPoint, Keynote, Google Slides and PDF viewers.</li>
<li class="">Mouse mode moves the pointer as you tilt the device. Before using it, hold it the way you'll hold it and press any key to calibrate; if the pointer drifts on its own, press <code>c</code> to recalibrate.</li>
</ol>
<p>Bluetooth and Wi-Fi share the same radio chip, so Wi-Fi is off while this app is running.</p>
<p>When I connected it to my phone, the controls worked, but it wasn't comfortable enough to use as an everyday mouse or keyboard. Think of it as a backup for when your keyboard suddenly dies; the really useful part is the presentation clicker in Step 6 below.</p>
<p>Open <strong>Help</strong> in the menu and it shows a QR code that leads to the usage page (<code>cardputer.larsemil.ai/help</code>). Scan it with your phone to open the guide I mentioned in Step 3.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-5-swap-the-firmware-or-restore-the-event-version">Step 5. Swap the firmware or restore the event version<a href="https://datapopcorn.ai/blog/en/m5stack-cardputer-adv-n8n-in-the-loop#step-5-swap-the-firmware-or-restore-the-event-version" class="hash-link" aria-label="Direct link to Step 5. Swap the firmware or restore the event version" title="Direct link to Step 5. Swap the firmware or restore the event version" translate="no">​</a></h2>
<p>The Cardputer works by replacing its firmware wholesale, so flashing another program erases the event loader. On the other hand, you can reinstall the event loader from the web anytime, so feel free to experiment.</p>
<p><img decoding="async" loading="lazy" alt="The Cardputer Flasher start screen" src="https://datapopcorn.ai/blog/en/assets/images/flasher-5c20c22fed4149837febc8ad17ff4b8c.webp" width="1440" height="900" class="img_ev3q"></p>
<p>To reinstall the event loader:</p>
<ol>
<li class="">Go to <a href="https://cardputer.larsemil.ai/" target="_blank" rel="noopener noreferrer" class="">cardputer.larsemil.ai</a> in Chrome or Edge. It needs Web Serial, which lets the browser use USB serial, so Safari won't work.</li>
<li class="">Plug in the USB-C cable <strong>with the side switch turned off</strong>. Flashing runs on USB power.</li>
<li class="">Click <strong>Install</strong>, and in the port picker choose <code>USB JTAG/serial debug unit (cu.usbmodem...)</code>. Don't pick <code>cu.Bluetooth-Incoming-Port</code> or <code>cu.debug-console</code> from Step 1.</li>
<li class="">Wait about a minute, then turn the switch back on. Don't unplug the cable while it's flashing.</li>
</ol>
<p>If you look at the page's code, it works simply. The port picker is filtered to show only devices with Espressif's vendor ID (<code>0x303A</code>), and the firmware file is downloaded from the author's GitHub repo and written over the whole flash starting at address 0. The repo contains only the finished firmware files, with no source code or license published, so think of it as a way to restore the original state rather than something to modify.</p>
<p>According to the official M5Stack docs, there are four ways to flash other firmware (<a href="https://docs.m5stack.com/en/core/Cardputer-Adv" target="_blank" rel="noopener noreferrer" class="">Cardputer-Adv docs</a>).</p>
<table><thead><tr><th>Method</th><th>Good for</th><th>Notes</th></tr></thead><tbody><tr><td>UiFlow2</td><td>First-time coders</td><td>Block coding and MicroPython, written in a web browser</td></tr><tr><td>Arduino IDE</td><td>People who've used Arduino</td><td>Uses the <code>M5Cardputer</code> library</td></tr><tr><td>PlatformIO</td><td>People who develop in VS Code</td><td>Official docs include a sample config</td></tr><tr><td>ESP-IDF</td><td>People who want deep access to chip features</td><td>The factory firmware source is public</td></tr></tbody></table>
<p>If you want to switch between several firmware images, another option is to first install M5 Launcher, which lets you pick and run firmware from an SD card. The Flasher page also lets you download the event loader as a <code>.bin</code> file, so you can add it as one of Launcher's menu entries.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-6-decide-what-to-use-it-for-after-the-event">Step 6. Decide what to use it for after the event<a href="https://datapopcorn.ai/blog/en/m5stack-cardputer-adv-n8n-in-the-loop#step-6-decide-what-to-use-it-for-after-the-event" class="hash-link" aria-label="Direct link to Step 6. Decide what to use it for after the event" title="Direct link to Step 6. Decide what to use it for after the event" translate="no">​</a></h2>
<p>In the end, a device like this only comes out of the drawer once you've decided what it's for. Here's where I think it's useful right away:</p>
<ol>
<li class=""><strong>A clicker for teaching</strong>: exactly the clicker mode from Step 4. Any key on either half of the keyboard works, so it's easy to advance slides without looking.</li>
<li class=""><strong>An n8n webhook button</strong>: an ESP32 on Wi-Fi can send HTTP requests, so you can build firmware that calls an n8n webhook with one keypress. It's a good fit for one-button workflows like "clock in" or "meeting started" alerts. I'll verify how to build it and write it up separately.</li>
<li class=""><strong>A sensor hub</strong>: plug a temperature/humidity or fine-dust sensor into the Grove port and send the readings to n8n, and you've got a small IoT project. I've written up how to combine ESP32 and n8n in <a href="https://datapopcorn.ai/blog/esp32-n8n-iot-portfolio" target="_blank" rel="noopener noreferrer" class="">An IoT automation portfolio with ESP32 and n8n</a>.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-quick-thought-why-do-overseas-conferences-hand-out-hardware">A quick thought: why do overseas conferences hand out hardware?<a href="https://datapopcorn.ai/blog/en/m5stack-cardputer-adv-n8n-in-the-loop#a-quick-thought-why-do-overseas-conferences-hand-out-hardware" class="hash-link" aria-label="Direct link to A quick thought: why do overseas conferences hand out hardware?" title="Direct link to A quick thought: why do overseas conferences hand out hardware?" translate="no">​</a></h2>
<p>At developer conferences outside Korea, you get devices like this fairly often. The one I remember is the GitHub Universe badge. In 2023 they gave out an RP2040-based e-ink badge made with Pimoroni, and in 2024 a version with the chip upgraded to the RP2350. Both were name badges that were also boards you could program in MicroPython. But I hardly remember ever getting something like that at a conference in Korea.</p>
<p>Thinking about why, I see two reasons overlapping:</p>
<ul>
<li class=""><strong>Cost</strong>: the Cardputer ADV retails for around $30, and In The Loop had up to 1,200 people. A simple calculation puts that in the tens of millions of won. On top of that, importing hundreds of devices with Wi-Fi and Bluetooth to hand out means dealing with Korea's radio certification (KC conformity assessment) or an exemption process.</li>
<li class=""><strong>Usefulness</strong>: I think this is the bigger reason. If you just hand out devices, most of them go home and end up in a drawer.</li>
</ul>
<p>What made this n8n event different was the software more than the device. Four ambassadors built the event loader and three apps, so at the venue you could learn the product with Node Quest and meet people with Icebreaker, and afterwards use it as a presentation clicker. I think the key to hardware swag isn't the cost of the device but designing "a reason to pull this out during the event" along with it. Events in Korea could start by attaching one such reason to the phones or badges people already have, instead of an expensive device.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="troubleshooting">Troubleshooting<a href="https://datapopcorn.ai/blog/en/m5stack-cardputer-adv-n8n-in-the-loop#troubleshooting" class="hash-link" aria-label="Direct link to Troubleshooting" title="Direct link to Troubleshooting" translate="no">​</a></h2>
<table><thead><tr><th>Symptom</th><th>Cause and fix</th></tr></thead><tbody><tr><td>No <code>usbmodem</code> in <code>ls /dev/cu.*</code></td><td>Most likely a charge-only cable. Switch to a data cable and plug directly into the Mac without a hub.</td></tr><tr><td><code>reason 201</code> repeats in the logs</td><td>It can't find the saved Wi-Fi. Follow Step 3 and pick the Wi-Fi where you are now in WiFi settings.</td></tr><tr><td>The Flasher can't find the device or stops partway</td><td>Try again with the side switch turned off. If it still doesn't show up, click "Show all ports" under Install.</td></tr><tr><td>The screen is off after flashing</td><td>Check that the side switch is on and that the cable is a data cable.</td></tr><tr><td>An app freezes</td><td>Press the back button (BtnG0) to return to the menu.</td></tr><tr><td>Bluetooth won't reconnect</td><td>After reflashing, remove the Cardputer from your computer's Bluetooth list and pair it again.</td></tr><tr><td>The mouse pointer moves on its own</td><td>Hold the device still and press <code>c</code> to recalibrate.</td></tr></tbody></table>]]></content:encoded>
            <category>Hardware &amp; making</category>
            <category>n8n</category>
        </item>
        <item>
            <title><![CDATA[5 Steps Before You Put Company Data into AI: From Turning Off Training to Masking and Restoring Values]]></title>
            <link>https://datapopcorn.ai/blog/en/ai-input-forbidden-data</link>
            <guid>https://datapopcorn.ai/blog/en/ai-input-forbidden-data</guid>
            <pubDate>Tue, 20 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[If information can't leave the company, it doesn't go into AI either. Here's a 5-step process: checking ChatGPT and Claude training settings, a 4-line do-not-enter list, a Python script that masks and restores names, amounts and contact details, and a review checklist prompt.]]></description>
            <content:encoded><![CDATA[<p>Whether you can put company data into AI comes down to one rule: <strong>if information can't leave the company, it doesn't go into AI either.</strong> But if you mask the names, amounts and contact details and put in only the structure, most work still gets done just fine. This post walks through that process in 5 steps, from checking your training settings to restoring the original values.</p>
<!-- -->
<!-- -->
<p>The question I get most in corporate training isn't about features. It's "<strong>Am I allowed to put this in?</strong>" People practice with made-up data, then hands go up at "So can I do this with our real company files?", followed by "What if I put it in and it causes a problem?" Without a guideline, everyone has to judge for themselves and take the blame alone, so the rational choice becomes not using it at all. The 5 steps below are a way to make that guideline concrete.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="who-this-is-for">Who this is for<a href="https://datapopcorn.ai/blog/en/ai-input-forbidden-data#who-this-is-for" class="hash-link" aria-label="Direct link to Who this is for" title="Direct link to Who this is for" translate="no">​</a></h2>
<ul>
<li class="">You want to handle work emails and reports with AI but have stalled because you don't know how much you can put in</li>
<li class="">You're a team lead or leader who needs to set a "never put this into AI" rule for your team</li>
<li class="">You're in charge of training and need to give employees input guidelines</li>
</ul>
<p>Here's what you need.</p>
<ol>
<li class="">A ChatGPT or Claude account</li>
<li class="">To follow Steps 3 and 5, a computer with Python 3 installed. On a Mac, check with <code>python3 --version</code> in the terminal. No extra packages are needed.</li>
<li class="">Your company's AI usage policy, if it already has one. <strong>Your company's policy takes priority over this post.</strong></li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1-check-whether-your-account-uses-your-chats-for-training">Step 1. Check whether your account uses your chats for training<a href="https://datapopcorn.ai/blog/en/ai-input-forbidden-data#step-1-check-whether-your-account-uses-your-chats-for-training" class="hash-link" aria-label="Direct link to Step 1. Check whether your account uses your chats for training" title="Direct link to Step 1. Check whether your account uses your chats for training" translate="no">​</a></h2>
<p>The first thing to do is check your settings. Personal plans and company plans have different defaults. I confirmed the details below in each company's official help docs on October 7, 2026.</p>
<table><thead><tr><th>Service</th><th>Personal plans</th><th>Company (team) plans</th></tr></thead><tbody><tr><td>ChatGPT</td><td>If <strong>Improve the model for everyone</strong> is on, your chats may be used for training. If you turn it off, new chats aren't used for training</td><td>Content in Business, Enterprise and Edu workspaces isn't used for training by default</td></tr><tr><td>Claude</td><td>On Free, Pro and Max, chats are used for training if you turned on <strong>allow model improvement</strong></td><td>Claude for Work (Team, Enterprise) and the API aren't used for training by default</td></tr></tbody></table>
<p>If you're on a personal account, check the setting yourself.</p>
<ul>
<li class=""><strong>ChatGPT:</strong> Profile menu → <strong>Settings</strong> → <strong>Data controls</strong> → turn off <strong>Improve the model for everyone</strong>. The mobile app has the same menu.</li>
<li class=""><strong>Claude:</strong> In <strong>Settings</strong>, turn off the toggle for model improvement (labeled <strong>Help improve our AI models</strong> in the official help docs). The direct link to the privacy settings is <code>claude.ai/settings/data-privacy-controls</code>.</li>
</ul>
<p>Two things to know before moving on.</p>
<ul>
<li class=""><strong>Even with training turned off, your chats are still stored on the company's servers.</strong> The training setting only decides "is it used for training." That's why the masking in Steps 2-3 is still needed.</li>
<li class=""><strong>Clicking 👍 or 👎 feedback sends the entire chat.</strong> OpenAI says it may use chats you gave feedback on for training, and Anthropic says it keeps feedback chats for up to 5 years. Don't click the feedback buttons in chats that contain company data.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2-share-a-4-line-what-we-dont-put-in-list-with-your-team">Step 2. Share a 4-line "what we don't put in" list with your team<a href="https://datapopcorn.ai/blog/en/ai-input-forbidden-data#step-2-share-a-4-line-what-we-dont-put-in-list-with-your-team" class="hash-link" aria-label="Direct link to Step 2. Share a 4-line &quot;what we don't put in&quot; list with your team" title="Direct link to Step 2. Share a 4-line &quot;what we don't put in&quot; list with your team" translate="no">​</a></h2>
<p>Long lists don't get followed. The items change with every situation, and when something comes up that isn't on the list, people stall again. Give one rule, and attach <strong>an alternative</strong> only to the 4 items people run into most. You can paste the block below into your team channel as is.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">[AI input guideline]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Rule: If you couldn't send it to someone outside the company by email, don't put it into AI.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">1. Real names and contact details of customers and partners → replace with aliases like [ClientA], [ContactA]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2. Contract amounts, unit prices, quotes                  → mask as [Amount1] or swap in example numbers</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">3. Unreleased technical info, drawings, yields            → don't put in (not even a summary)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">4. National ID numbers, accounts, passwords               → never put in, under any circumstances</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">* A person clicks the send button. AI only does translation and drafts.</span><br></div></code></pre></div></div>
<p>The key is that items 1 and 2 come with alternatives. When I gave only the forbidden items in a lecture, students nodded along and then finished the exercise without using any real material at all. Only after I gave them the alternatives too did I hear "Oh, so I can just do it this way."</p>
<p>When an item comes up that people disagree on (a person's job title in meeting notes, someone's schedule, and so on), don't add more items; go back to the one-line rule: <strong>Could I put this sentence in an email to someone outside the company as is?</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3-create-a-file-with-the-values-masked">Step 3. Create a file with the values masked<a href="https://datapopcorn.ai/blog/en/ai-input-forbidden-data#step-3-create-a-file-with-the-values-masked" class="hash-link" aria-label="Direct link to Step 3. Create a file with the values masked" title="Direct link to Step 3. Create a file with the values masked" translate="no">​</a></h2>
<p>What AI needs to write a draft reply is <strong>the structure, not the values</strong>. What matters is what the other party asked for and by when; their company's real name doesn't make the draft any better.</p>
<p>You can delete things by hand, but if you do it often, a script is easier. The <code>mask.py</code> below automatically finds and masks email addresses, mobile numbers and amounts, and replaces only the company and person names you list at the top.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">mask.py</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> re</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> sys</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> json</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># Names to mask: regex can't find company, person or product names, so list them yourself.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">NAMES </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"Hanbit Trading"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"[ClientA]"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"Kevin Park"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"[ContactA]"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"한빛무역"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"[ClientA]"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># the same company written in Korean</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">PATTERNS </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Email"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">r"[\w.+-]+@[\w-]+\.[\w.-]+"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Phone"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">r"(?:\+82[- ]?|0)1[016789][- ]?\d{3,4}[- ]?\d{4}"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Amount"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">r"(?:USD|\$|₩)\s?\d[\d,]*(?:\.\d+)?|\d[\d,]*(?:\.\d+)?\s?(?:원|만원|억원|달러)"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">mask</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">text</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    found </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> real</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> alias </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> NAMES</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">items</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> real </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> text</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            found</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">append</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">alias</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> real</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            text </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> text</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">replace</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">real</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> alias</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> label</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> pat </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> PATTERNS</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        n </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">repl</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">m</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">nonlocal</span><span class="token plain"> n</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            n </span><span class="token operator" style="color:#393A34">+=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            tag </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:#e3116c">f"[</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">label</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">n</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">]"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            found</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">append</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">tag</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> m</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">group</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> tag</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        text </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> re</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">sub</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">pat</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> repl</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> text</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> text</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> found</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> __name__ </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"__main__"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    src </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> sys</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">argv</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    text </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token builtin">open</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">src</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> encoding</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"utf-8"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">read</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    masked</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> found </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> mask</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">text</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    out </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> src</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">rsplit</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"_masked.txt"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token builtin">open</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">out</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"w"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> encoding</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"utf-8"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">write</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">masked</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic"># The table of original values stays only on your computer. Never put it into AI.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    keyfile </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> src</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">rsplit</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"_key.json"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    json</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">dump</span><span class="token punctuation" style="color:#393A34">(</span><span class="token builtin">dict</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">found</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">open</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">keyfile</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"w"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> encoding</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"utf-8"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ensure_ascii</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">False</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> indent</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"Masked </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation builtin">len</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">(</span><span class="token string-interpolation interpolation">found</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">)</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c"> items → </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">out</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"Key table → </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">keyfile</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c"> (do not put this into AI)"</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>Save the email body as <code>sample_email.txt</code> and run the script in the same folder.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">python3 mask.py sample_email.txt</span><br></div></code></pre></div></div>
<p>Here's the actual output from running it on a made-up English email.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Masked 6 items → sample_email_masked.txt</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Key table → sample_email_key.json (do not put this into AI)</span><br></div></code></pre></div></div>
<p><img decoding="async" loading="lazy" alt="Top: the original email with the client name, contact person, email, unit price, total and phone number all visible. Bottom: the same email after running mask.py, with each replaced by tags such as [ClientA], [ContactA], [Email1], [Amount1], [Amount2] and [Phone1] (the screenshot shows the Korean-labeled version of these tags)" src="https://datapopcorn.ai/blog/en/assets/images/mask-before-after-3a8e5c09586d514a6ee3797282c92d37.png" width="1200" height="1136" class="img_ev3q"></p>
<p>Notice in the result that <strong>the quantity <code>2,000 units</code> and the delivery date <code>November 20</code> were left as is.</strong> The script only finds the patterns you've defined. Values whose sensitivity varies by company, like quantities, dates and project code names, should be added to the <code>NAMES</code> list or deleted by hand. <strong>Step 3 isn't done until you've read the <code>_masked.txt</code> file once with your own eyes after running the script.</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4-get-both-a-draft-and-a-review-list-from-ai-using-the-masked-file">Step 4. Get both a draft and a review list from AI using the masked file<a href="https://datapopcorn.ai/blog/en/ai-input-forbidden-data#step-4-get-both-a-draft-and-a-review-list-from-ai-using-the-masked-file" class="hash-link" aria-label="Direct link to Step 4. Get both a draft and a review list from AI using the masked file" title="Direct link to Step 4. Get both a draft and a review list from AI using the masked file" translate="no">​</a></h2>
<p>Now put only <code>sample_email_masked.txt</code> into AI. Never put in <code>_key.json</code>. The trick is not to ask only for the output, but to also get <strong>a list of things a person needs to check</strong>.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">The attached file is an English email from an overseas partner.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Parts in square brackets like [ClientA] and [Amount1] are values I masked on purpose, so leave them as they are.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">1) A summary in my language</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2) A list of what the other party is asking for</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">3) Blanks and facts I need to check myself</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">4) A polite draft reply in English (use the masked values in brackets as they are)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">5) A pre-send review checklist (including whether every masked value has been filled in and no sensitive information remains)</span><br></div></code></pre></div></div>
<p>Items 3 and 5 are the key. Because you masked the input, the draft will have blanks, and the AI gives you a list of where they are. No more "I need to fill that in later, but where was it?"</p>
<p>If you use a tool that reads files in a folder, like Claude Code, keep the original and the masked version in separate folders and work only in the masked folder. What you let the AI read then stays visible in the state of the folders, so you can check it later.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-5-restore-the-blanks-in-the-draft-to-the-original-values-and-have-a-person-send-it">Step 5. Restore the blanks in the draft to the original values, and have a person send it<a href="https://datapopcorn.ai/blog/en/ai-input-forbidden-data#step-5-restore-the-blanks-in-the-draft-to-the-original-values-and-have-a-person-send-it" class="hash-link" aria-label="Direct link to Step 5. Restore the blanks in the draft to the original values, and have a person send it" title="Direct link to Step 5. Restore the blanks in the draft to the original values, and have a person send it" translate="no">​</a></h2>
<p>Save the AI's draft reply as <code>reply_draft.txt</code>, then use the script below to replace the brackets with the original values. This happens only on your own computer.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">unmask.py</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> json</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> sys</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">draft</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> keyfile </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> sys</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">argv</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> sys</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">argv</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">text </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token builtin">open</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">draft</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> encoding</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"utf-8"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">read</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">table </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> json</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">load</span><span class="token punctuation" style="color:#393A34">(</span><span class="token builtin">open</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">keyfile</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> encoding</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"utf-8"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> tag</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> real </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> table</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">items</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    text </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> text</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">replace</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">tag</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> real</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">out </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> draft</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">rsplit</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"_restored.txt"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token builtin">open</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">out</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"w"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> encoding</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"utf-8"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">write</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">text</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"Restored → </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">out</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">"</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">python3 unmask.py reply_draft.txt sample_email_key.json</span><br></div></code></pre></div></div>
<p>Here's the result from running it on two draft sentences containing brackets.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Restored → reply_draft_restored.txt</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Dear Kevin Park,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Thank you for your order from Hanbit Trading. We confirm the unit price of USD 14.50 (total $29,000).</span><br></div></code></pre></div></div>
<p>Finally, go through the review checklist from Step 4 to check the numbers, dates and names, and <strong>a person clicks the send button.</strong> AI handles only translation and drafts.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="if-you-hit-an-error-check-here-first">If you hit an error, check here first<a href="https://datapopcorn.ai/blog/en/ai-input-forbidden-data#if-you-hit-an-error-check-here-first" class="hash-link" aria-label="Direct link to If you hit an error, check here first" title="Direct link to If you hit an error, check here first" translate="no">​</a></h2>
<table><thead><tr><th>Symptom</th><th>Cause and fix</th></tr></thead><tbody><tr><td><code>python3: command not found</code></td><td>Python isn't installed. On a Mac, run <code>xcode-select --install</code> in the terminal or install it from python.org</td></tr><tr><td><code>No such file or directory</code></td><td>Make sure the email file and the script are in the same folder and the file name is exactly right</td></tr><tr><td>The company name isn't masked</td><td>Names can't be found automatically. Add them to the <code>NAMES</code> list in the form <code>"Real Name": "[ClientB]"</code></td></tr><tr><td>Landline numbers like <code>02-123-4567</code> remain</td><td>This script only finds Korean mobile numbers (010 and so on). Delete them by hand or add them to <code>NAMES</code></td></tr><tr><td>Brackets remain after restoring</td><td>The AI may have changed the bracket format (for example, <code>[Amount 1]</code>). Fix the format in the draft back to the original and run it again</td></tr><tr><td>Your company policy differs from this post</td><td>Follow your company policy. This post is a starting point for when there's no policy</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-up<a href="https://datapopcorn.ai/blog/en/ai-input-forbidden-data#wrap-up" class="hash-link" aria-label="Direct link to Wrap-up" title="Direct link to Wrap-up" translate="no">​</a></h2>
<ul>
<li class="">The rule is one line: if you couldn't send it to someone outside the company by email, don't put it into AI.</li>
<li class="">Even with training turned off, your chats are stored, so keep masking values before you put them in.</li>
<li class="">Mask → get a draft and a review list from AI → restore on your own computer → a person sends it.</li>
<li class="">A leader's job is less about adopting tools and more about announcing these 4 lines to the team. People can use AI without fear of getting in trouble only when the guideline exists in writing.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="references">References<a href="https://datapopcorn.ai/blog/en/ai-input-forbidden-data#references" class="hash-link" aria-label="Direct link to References" title="Direct link to References" translate="no">​</a></h2>
<p>Official documentation checked on October 7, 2026.</p>
<ul>
<li class=""><a href="https://help.openai.com/en/articles/7730893-data-controls-faq" target="_blank" rel="noopener noreferrer" class="">OpenAI Data Controls FAQ</a></li>
<li class=""><a href="https://openai.com/enterprise-privacy/" target="_blank" rel="noopener noreferrer" class="">OpenAI Enterprise privacy</a></li>
<li class=""><a href="https://privacy.anthropic.com/en/articles/10023580-is-my-data-used-for-model-training" target="_blank" rel="noopener noreferrer" class="">Anthropic: Is my data used for model training? (personal plans)</a></li>
<li class=""><a href="https://privacy.anthropic.com/en/articles/12109829-how-do-i-change-my-model-improvement-privacy-settings" target="_blank" rel="noopener noreferrer" class="">Anthropic: How do I change my model improvement privacy settings?</a></li>
<li class=""><a href="https://privacy.anthropic.com/en/articles/7996868-is-my-data-used-for-model-training" target="_blank" rel="noopener noreferrer" class="">Anthropic: Is my data used for model training? (commercial products)</a></li>
</ul>]]></content:encoded>
            <category>Education &amp; career</category>
            <category>Agent</category>
        </item>
        <item>
            <title><![CDATA[Keep Working When You Hit Claude Code's Usage Limit: Build a claude-api Command on API Credits in 5 Steps]]></title>
            <link>https://datapopcorn.ai/blog/en/claude-code-limit-api-credit-fallback</link>
            <guid>https://datapopcorn.ai/blog/en/claude-code-limit-api-credit-fallback</guid>
            <pubDate>Sun, 18 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[When a Team or Max seat hits its usage limit, Claude Code stops. Instead of rotating through multiple accounts, set up a single claude-api command that runs on API credits (from a startup program, for example) and pick up the same conversation where you left off. This is the order I followed while actually setting it up, from creating the key to verifying it works and managing credit burn.]]></description>
            <content:encoded><![CDATA[<p>If you use Claude Code on a Team or Max seat and hit the usage limit, your work stops until the limit resets. If you have API credits, you can set up one command called <code>claude-api</code>, type <code>claude-api -c</code>, and <strong>pick up the conversation you were just in</strong> and keep going on credits. This is the order I followed on October 18, 2026, setting it up and verifying it with the <a href="https://datapopcorn.ai/blog/claude-startups-benefits-claim" target="_blank" rel="noopener noreferrer" class="">$1,000 in credits I got from Claude Startups</a>.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="who-this-is-for">Who this is for<a href="https://datapopcorn.ai/blog/en/claude-code-limit-api-credit-fallback#who-this-is-for" class="hash-link" aria-label="Direct link to Who this is for" title="Direct link to Who this is for" translate="no">​</a></h2>
<ul>
<li class="">You use Claude Code on Claude Team (Standard or Premium) or Max and hit the limit often</li>
<li class="">You're worried that <strong>API credits</strong> from a startup program or promotion <strong>will expire unused</strong></li>
<li class="">You're wondering, "Can't I just rotate through several Team seats myself?" (I'll explain below why that doesn't work)</li>
</ul>
<p>Here's what you need.</p>
<ul>
<li class="">macOS Terminal (zsh) and Claude Code installed. I tested on version 2.1.302</li>
<li class="">A Claude Console organization that holds credits</li>
<li class="">A password manager to store the key (optional)</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="first-rotating-through-multiple-seats-is-not-allowed">First: rotating through multiple seats is not allowed<a href="https://datapopcorn.ai/blog/en/claude-code-limit-api-credit-fallback#first-rotating-through-multiple-seats-is-not-allowed" class="hash-link" aria-label="Direct link to First: rotating through multiple seats is not allowed" title="Direct link to First: rotating through multiple seats is not allowed" translate="no">​</a></h3>
<p>You'll often see tricks shared for "automatically switching to the next account when one runs out of usage." A typical case is one person using 5 Team seats. But the official help page states clearly that <strong>each seat is assigned to one designated person and only that person can use it, and routing work through someone else's account to use their remaining usage isn't allowed either</strong> (<a href="https://support.claude.com/en/articles/12004354-purchase-and-manage-seats-on-team-plans" target="_blank" rel="noopener noreferrer" class="">Purchase and manage seats on Team plans</a>). If you need more, it tells you to upgrade to a Premium seat or turn on usage credits.</p>
<p>Startup program benefits only last as long as you follow the terms, so if you get caught rotating accounts, you could lose your Team discount and your credits along with it. The method in this post is <strong>the same person using their own API key</strong>, so this issue doesn't apply.</p>
<table><thead><tr><th>Approach</th><th>Cost after the limit</th><th>Notes</th></tr></thead><tbody><tr><td>Rotating through multiple seats</td><td>None</td><td>Violates policy. Each seat is for one person only</td></tr><tr><td>Turning on Team usage credits</td><td>Standard API rates, billed separately</td><td>Enable under organization settings &gt; Usage</td></tr><tr><td><strong>Continuing with an API key (this post)</strong></td><td>Deducted from API credits you already have</td><td>Good when you want to use up credits first</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1-check-your-credit-balance-and-auto-reload-status">Step 1. Check your credit balance and auto-reload status<a href="https://datapopcorn.ai/blog/en/claude-code-limit-api-credit-fallback#step-1-check-your-credit-balance-and-auto-reload-status" class="hash-link" aria-label="Direct link to Step 1. Check your credit balance and auto-reload status" title="Direct link to Step 1. Check your credit balance and auto-reload status" translate="no">​</a></h2>
<p>Open <a href="https://platform.claude.com/settings/billing" target="_blank" rel="noopener noreferrer" class="">Claude Console &gt; Billing</a>. First, check in the top right that the organization holding your credits is selected.</p>
<p><img decoding="async" loading="lazy" alt="Console billing screen showing promotional credits of US$999.99/US$1,000.00, expiry date 2027.4.5, and a notice that auto-reload is off" src="https://datapopcorn.ai/blog/en/assets/images/01-credits-33d0e4adf431e5095ca8048c9fd4904c.jpg" width="1814" height="900" class="img_ev3q"></p>
<p>There are two things to look at.</p>
<ul>
<li class=""><strong>The balance and expiry date in the promotional credits table.</strong> In my case it's $1,000, expiring April 5, 2027 (UTC). The $999.99 in the screenshot above is after I spent 1 cent on the tests in this post.</li>
<li class=""><strong>"Auto-reload is off. API requests will stop when your balance runs out."</strong> If you see this, using up your credits won't charge your card; requests just stop. If you only want to spend within your credits, leave it off.</li>
</ul>
<div class="theme-admonition theme-admonition-caution admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>Added October 26, 2026: Team and Max monthly API credits can't be used here</div><div class="admonitionContent_BuS1"><p>Team and Max plans come with monthly API credits (up to $500 on Team). But according to the <a href="https://platform.claude.com/docs/en/about-claude/api-credits-for-subscribers" target="_blank" rel="noopener noreferrer" class="">official docs</a>, these monthly credits <strong>can't be used for Claude Code.</strong> The <code>claude-api</code> command in this post only draws from separately granted credits, like startup credits, or credits you've purchased. If you run it with a key from an organization that only has monthly credits left, you get <code>Credit balance too low</code>. It's fine to link the monthly credits to the same Console organization: they're used first for other things like API automation, and Claude Code requests are paid from the remaining credits. How to link the monthly credits and split them by use is in <a class="" href="https://datapopcorn.ai/blog/en/claude-team-plan-only-features">Steps 12 and 13 of my Claude Team plan post</a>.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2-create-an-api-key-just-for-claude-code">Step 2. Create an API key just for Claude Code<a href="https://datapopcorn.ai/blog/en/claude-code-limit-api-credit-fallback#step-2-create-an-api-key-just-for-claude-code" class="hash-link" aria-label="Direct link to Step 2. Create an API key just for Claude Code" title="Direct link to Step 2. Create an API key just for Claude Code" translate="no">​</a></h2>
<p>In <a href="https://platform.claude.com/settings/keys" target="_blank" rel="noopener noreferrer" class="">Console &gt; API keys</a>, click <strong>Create key</strong>. If an identity federation notice appears first, click <strong>Continue with API key</strong>.</p>
<p><img decoding="async" loading="lazy" alt="API key creation dialog. Name claude-code-fallback, expiry custom 2027.4.6, scope default workspace" src="https://datapopcorn.ai/blog/en/assets/images/02-create-key-92781988d4090944340a8b732c5100f5.jpg" width="1044" height="900" class="img_ev3q"></p>
<table><thead><tr><th>Field</th><th>Value</th><th>Why</th></tr></thead><tbody><tr><td>Name</td><td><code>claude-code-fallback</code></td><td>So you can break down costs by key later</td></tr><tr><td>Expiry</td><td><strong>Custom</strong>, the day after your credits expire</td><td>So the key doesn't outlive the credits and fall through to card billing</td></tr><tr><td>Scope</td><td><strong>Default workspace</strong></td><td>Minimum permissions, no Admin API access</td></tr></tbody></table>
<p>When you click <strong>Create key</strong>, the key is shown only once. Click <strong>Copy key</strong> and save it in your password manager first. Once you close the dialog, you can't see it again.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3-save-the-key-in-a-file-only-you-can-read">Step 3. Save the key in a file only you can read<a href="https://datapopcorn.ai/blog/en/claude-code-limit-api-credit-fallback#step-3-save-the-key-in-a-file-only-you-can-read" class="hash-link" aria-label="Direct link to Step 3. Save the key in a file only you can read" title="Direct link to Step 3. Save the key in a file only you can read" translate="no">​</a></h2>
<p>Run the following in Terminal. <code>read -rs</code> doesn't echo what you type, so after running it, paste the key you copied and press Enter.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">mkdir</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">-p</span><span class="token plain"> ~/.config/claude-api </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">chmod</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">700</span><span class="token plain"> ~/.config/claude-api</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token builtin class-name">read</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">-rs</span><span class="token plain"> KEY </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token builtin class-name">printf</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'%s'</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"</span><span class="token string variable" style="color:#36acaa">$KEY</span><span class="token string" style="color:#e3116c">"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> ~/.config/claude-api/key </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">chmod</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">600</span><span class="token plain"> ~/.config/claude-api/key </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token builtin class-name">unset</span><span class="token plain"> KEY</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">ls</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">-l</span><span class="token plain"> ~/.config/claude-api/key   </span><span class="token comment" style="color:#999988;font-style:italic"># -rw------- means it's correct</span><br></div></code></pre></div></div>
<p>The reason not to put the key in <code>.zshrc</code> as <code>export ANTHROPIC_API_KEY=...</code> is that <strong>your regular <code>claude</code> would then always run on the API key too</strong>, and credits would get used up before your Team or Max usage. The key is only loaded when you run the command below.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4-create-the-claude-api-command">Step 4. Create the claude-api command<a href="https://datapopcorn.ai/blog/en/claude-code-limit-api-credit-fallback#step-4-create-the-claude-api-command" class="hash-link" aria-label="Direct link to Step 4. Create the claude-api command" title="Direct link to Step 4. Create the claude-api command" translate="no">​</a></h2>
<p>Create an executable in the folder where Claude Code is installed (<code>~/.local/bin</code>). If <code>which claude</code> shows a different path, change the path on the last line to match.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">cat</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> ~/.local/bin/claude-api </span><span class="token operator" style="color:#393A34">&lt;&lt;</span><span class="token string" style="color:#e3116c">'EOF'</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">#!/bin/zsh</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c"># Runs Claude Code with the Console API key (credits).</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">KEY_FILE="$HOME/.config/claude-api/key"</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">if [[ ! -r "$KEY_FILE" ]]; then</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">  echo "API key file not found: $KEY_FILE" &gt;&amp;2</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">  exit 1</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">fi</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">export ANTHROPIC_API_KEY="$(&lt;"$KEY_FILE")"</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">exec "$HOME/.local/bin/claude" "$@"</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">EOF</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">chmod</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">755</span><span class="token plain"> ~/.local/bin/claude-api</span><br></div></code></pre></div></div>
<p>Since it doesn't touch <code>.zshrc</code>, there's no conflict even if you manage your dotfiles with git. The command sets <code>ANTHROPIC_API_KEY</code> <strong>for that run only</strong> and passes every other option straight through to <code>claude</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-5-confirm-its-actually-billing-your-credits">Step 5. Confirm it's actually billing your credits<a href="https://datapopcorn.ai/blog/en/claude-code-limit-api-credit-fallback#step-5-confirm-its-actually-billing-your-credits" class="hash-link" aria-label="Direct link to Step 5. Confirm it's actually billing your credits" title="Direct link to Step 5. Confirm it's actually billing your credits" translate="no">​</a></h2>
<p>I checked three things.</p>
<p><strong>1) Whether you get a response</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">claude-api </span><span class="token parameter variable" style="color:#36acaa">-p</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Reply with exactly: OK"</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">--model</span><span class="token plain"> haiku</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># OK</span><br></div></code></pre></div></div>
<p><strong>2) Whether the key is used instead of the logged-in account.</strong> If you deliberately pass a wrong key, it should fail.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token assign-left variable" style="color:#36acaa">ANTHROPIC_API_KEY</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">sk-ant-invalid-test claude </span><span class="token parameter variable" style="color:#36acaa">-p</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Reply OK"</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">--model</span><span class="token plain"> haiku</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># Invalid API key · Fix external API key</span><br></div></code></pre></div></div>
<p>This means the key in the environment variable takes priority over your login, so requests run through <code>claude-api</code> are deducted from your credits. Sure enough, after testing, my Console balance went from $1,000.00 to $999.99 (the Step 1 screenshot).</p>
<p><strong>3) Whether it continues the previous conversation</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">claude-api </span><span class="token parameter variable" style="color:#36acaa">-p</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Remember this word: PINEAPPLE. Reply only: saved"</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">--model</span><span class="token plain"> haiku</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># saved</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">claude-api </span><span class="token parameter variable" style="color:#36acaa">-c</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">-p</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"What word did I ask you to remember? Reply with the word only."</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">--model</span><span class="token plain"> haiku</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># PINEAPPLE</span><br></div></code></pre></div></div>
<p>Claude Code stores conversation history on your computer (per project folder), so adding <code>-c</code> in the same folder continues the previous conversation even when the authentication method changes.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-to-use-it">How to use it<a href="https://datapopcorn.ai/blog/en/claude-code-limit-api-credit-fallback#how-to-use-it" class="hash-link" aria-label="Direct link to How to use it" title="Direct link to How to use it" translate="no">​</a></h2>
<table><thead><tr><th>Situation</th><th>Command</th></tr></thead><tbody><tr><td>Day to day (Team or Max seat usage)</td><td><code>claude</code></td></tr><tr><td>Continuing a rate-limited task on credits</td><td><code>claude-api -c</code></td></tr><tr><td>Big jobs you want to run on credits from the start</td><td><code>claude-api</code></td></tr></tbody></table>
<p>Credits disappear when they expire. In my case I need to spend $1,000 over about 5.5 months, which works out to a pace of roughly $180 a month. If I only used it when I hit the limit, I'd likely have credits left over, so <strong>I run long jobs like big refactors or batch work on <code>claude-api</code> from the start</strong>. I also set a reminder on the 1st of every month to check my balance and last month's spend.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="if-you-moved-from-max-to-team-cancel-your-personal-subscription">If you moved from Max to Team: cancel your personal subscription<a href="https://datapopcorn.ai/blog/en/claude-code-limit-api-credit-fallback#if-you-moved-from-max-to-team-cancel-your-personal-subscription" class="hash-link" aria-label="Direct link to If you moved from Max to Team: cancel your personal subscription" title="Direct link to If you moved from Max to Team: cancel your personal subscription" translate="no">​</a></h3>
<p>Starting Team doesn't automatically cancel your existing personal Max subscription. In claude.ai, open the account menu at the bottom left, switch to your <strong>personal account</strong> (for example, "Personal Max"), then go to <strong>Settings &gt; Billing &gt; Cancel subscription &gt; Cancel</strong>. If you open billing settings with the Team organization selected, you get <strong>the Team organization's billing page</strong>, and it also has a <strong>Cancel</strong> button. Clicking it there cancels Team, so first check that the page title says "Max plan."</p>
<p><img decoding="async" loading="lazy" alt="Personal Max billing screen showing a notice that the subscription will be canceled on October 29, 2026, and a resubscribe button" src="https://datapopcorn.ai/blog/en/assets/images/03-max-cancel-a3fd96c4c8910a972e9bdecfd9fbafc2.jpg" width="1560" height="560" class="img_ev3q"></p>
<p>Once you cancel, it changes to "Your subscription will be canceled on [date]," and you can keep using Max until then. If you change your mind, use <strong>Resubscribe</strong> to undo it. As long as you don't delete the account itself, your existing conversations and projects stay.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="troubleshooting">Troubleshooting<a href="https://datapopcorn.ai/blog/en/claude-code-limit-api-credit-fallback#troubleshooting" class="hash-link" aria-label="Direct link to Troubleshooting" title="Direct link to Troubleshooting" translate="no">​</a></h2>
<table><thead><tr><th>Symptom</th><th>Cause</th><th>Fix</th></tr></thead><tbody><tr><td><code>API key file not found</code></td><td>The file from Step 3 is missing or has a permissions problem</td><td>Check with <code>ls -l ~/.config/claude-api/key</code> and save it again</td></tr><tr><td><code>Invalid API key</code></td><td>The key was pasted wrong, or it expired or was deleted</td><td>Check the key's status in the Console and repeat Step 3 with a new key</td></tr><tr><td>Regular <code>claude</code> also uses up credits</td><td><code>ANTHROPIC_API_KEY</code> is exported in <code>.zshrc</code> or similar</td><td>Delete that line and open a new Terminal</td></tr><tr><td><code>claude-api -c</code> continues a different conversation</td><td>You ran it from a different folder</td><td>Run it from the project folder you were working in</td></tr><tr><td>Requests stop after credits run out</td><td>Auto-reload is off (intended behavior)</td><td>Leave it, or buy credits if you need them</td></tr><tr><td><code>command not found: claude-api</code></td><td><code>~/.local/bin</code> isn't on your PATH</td><td>Create the file in the folder shown by <code>which claude</code></td></tr><tr><td><code>Credit balance too low</code> (even though credits seem to remain)</td><td>All that's left is Team or Max monthly API credits, which can't be used for Claude Code</td><td>Check the Console billing page for separately granted or purchased credits</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-up<a href="https://datapopcorn.ai/blog/en/claude-code-limit-api-credit-fallback#wrap-up" class="hash-link" aria-label="Direct link to Wrap-up" title="Direct link to Wrap-up" translate="no">​</a></h2>
<p>To sum up, you create one key in the Console and add one file plus one executable, and you're done. You use your seat limit as your seat limit and continue overflow work on your own credits, so there's no worry about getting into trouble by rotating accounts. I'll follow up after a month with how fast the credits actually go down.</p>]]></content:encoded>
            <category>AI agents</category>
            <category>Guide</category>
        </item>
        <item>
            <title><![CDATA[Make Video Subtitles (SRT) on Your Mac with Zero API Cost: A 6-Step faster-whisper Guide]]></title>
            <link>https://datapopcorn.ai/blog/en/whisperx-local-video-subtitles</link>
            <guid>https://datapopcorn.ai/blog/en/whisperx-local-video-subtitles</guid>
            <pubDate>Sat, 17 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[How to turn a video file into SRT subtitles on a Mac without an OpenAI API key or a paid captioning service. I ran every step myself, from creating a virtual environment and installing packages to a copy-paste script, real output, and the errors you're most likely to hit.]]></description>
            <content:encoded><![CDATA[<p>With a few commands in the Mac Terminal, you can turn a video file into an SRT subtitle file entirely on your own computer, without uploading it to any outside server. No API key, no payment. I ran every command in this post on an Apple Silicon Mac in October 2026 and checked the results.</p>
<!-- -->
<p><img decoding="async" loading="lazy" alt="Illustration of speech being converted into subtitles on a local computer, with a security shield blocking outbound transfer" src="https://datapopcorn.ai/blog/en/assets/images/cover-d4cbc0bc084fae6c22b19c8daef8102a.png" width="1536" height="864" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="who-this-is-for">Who this is for<a href="https://datapopcorn.ai/blog/en/whisperx-local-video-subtitles#who-this-is-for" class="hash-link" aria-label="Direct link to Who this is for" title="Direct link to Who this is for" translate="no">​</a></h2>
<ul>
<li class="">You make subtitles often and API costs or paid captioning services are adding up</li>
<li class="">You want subtitles for videos you'd rather not upload to an outside service, like lecture or meeting recordings</li>
<li class="">You can copy and paste commands into a Terminal window (you don't need to know how to code)</li>
</ul>
<p>If you'd rather just tell an AI coding tool "make subtitles for this," see <a href="https://datapopcorn.ai/blog/whisper-video-subtitle-automation" target="_blank" rel="noopener noreferrer" class="">Part 1: Drop a video into Codex and say "make subtitles"</a>. Part 1 uses the OpenAI API, so you pay for what you use and your audio is sent to OpenAI. This post takes the other route: <strong>your own computer does the work</strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-few-terms-first">A few terms first<a href="https://datapopcorn.ai/blog/en/whisperx-local-video-subtitles#a-few-terms-first" class="hash-link" aria-label="Direct link to A few terms first" title="Direct link to A few terms first" translate="no">​</a></h3>
<ul>
<li class=""><strong>Whisper</strong>: A speech recognition model released by OpenAI. Once you download the model files, you can run it on your own computer without the API.</li>
<li class=""><strong>faster-whisper</strong>: A free program that runs Whisper faster and with less memory. This is what we'll install.</li>
<li class=""><strong>SRT</strong>: A subtitle file format. It's a plain text file listing "this sentence, from second X to second Y," so you can load it straight into Premiere, CapCut, DaVinci Resolve or YouTube.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-you-need">What you need<a href="https://datapopcorn.ai/blog/en/whisperx-local-video-subtitles#what-you-need" class="hash-link" aria-label="Direct link to What you need" title="Direct link to What you need" translate="no">​</a></h3>
<table><thead><tr><th>Item</th><th>Requirement</th><th>Environment I tested on</th></tr></thead><tbody><tr><td>Computer</td><td>Apple Silicon Mac (M1 or later)</td><td>M3 MacBook, 16GB RAM</td></tr><tr><td>Python</td><td>3.10 or later</td><td>Python 3.13</td></tr><tr><td>Storage</td><td>At least 1GB free</td><td>Model about 480MB + programs about 220MB</td></tr><tr><td>Internet</td><td>Only for the first install and model download</td><td></td></tr></tbody></table>
<p>You don't need to install a separate video converter like ffmpeg. faster-whisper reads the audio directly from mp4 files. I didn't test Windows or NVIDIA GPU setups, so this post doesn't cover them.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1-open-terminal-and-check-your-python-version">Step 1. Open Terminal and check your Python version<a href="https://datapopcorn.ai/blog/en/whisperx-local-video-subtitles#step-1-open-terminal-and-check-your-python-version" class="hash-link" aria-label="Direct link to Step 1. Open Terminal and check your Python version" title="Direct link to Step 1. Open Terminal and check your Python version" translate="no">​</a></h2>
<ol>
<li class="">Press <code>Command + Space</code> to open Spotlight, type <code>Terminal</code>, and open it.</li>
<li class="">Paste the command below and press Enter.</li>
</ol>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">python3 </span><span class="token parameter variable" style="color:#36acaa">--version</span><br></div></code></pre></div></div>
<p>If you see <code>Python 3.10</code> or later (3.11, 3.12, 3.13, etc.), move on. If the number is 3.9 or lower, or you get <code>command not found</code>, download the latest macOS installer from the <a href="https://www.python.org/downloads/" target="_blank" rel="noopener noreferrer" class="">python.org downloads page</a>, install it, then close and reopen Terminal and check again.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2-create-a-working-folder-and-a-virtual-environment">Step 2. Create a working folder and a virtual environment<a href="https://datapopcorn.ai/blog/en/whisperx-local-video-subtitles#step-2-create-a-working-folder-and-a-virtual-environment" class="hash-link" aria-label="Direct link to Step 2. Create a working folder and a virtual environment" title="Direct link to Step 2. Create a working folder and a virtual environment" translate="no">​</a></h2>
<p>A <strong>virtual environment</strong> is a box that holds only the programs this project needs. It doesn't mix with other Python programs on your computer, and if you ever want to remove it, you just delete the folder.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">mkdir</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">-p</span><span class="token plain"> ~/Desktop/local-subtitles</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token builtin class-name">cd</span><span class="token plain"> ~/Desktop/local-subtitles</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python3 </span><span class="token parameter variable" style="color:#36acaa">-m</span><span class="token plain"> venv .venv</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token builtin class-name">source</span><span class="token plain"> .venv/bin/activate</span><br></div></code></pre></div></div>
<p>After the last line runs, <code>(.venv)</code> appears at the start of your Terminal prompt. You need to see this so the next step installs into the box. Whenever you open a new Terminal window, run <code>cd ~/Desktop/local-subtitles</code> and <code>source .venv/bin/activate</code> again.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3-install-faster-whisper">Step 3. Install faster-whisper<a href="https://datapopcorn.ai/blog/en/whisperx-local-video-subtitles#step-3-install-faster-whisper" class="hash-link" aria-label="Direct link to Step 3. Install faster-whisper" title="Direct link to Step 3. Install faster-whisper" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">pip </span><span class="token function" style="color:#d73a49">install</span><span class="token plain"> faster-whisper </span><span class="token string" style="color:#e3116c">"av&lt;19"</span><br></div></code></pre></div></div>
<p>Check that the install finished with this command.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">pip show faster-whisper av</span><br></div></code></pre></div></div>
<p>You should see two versions, like <code>Version: 1.2.1</code> (faster-whisper) and <code>Version: 18.1.0</code> (av).</p>
<p>There's a reason for adding <code>"av&lt;19"</code>. <code>av</code> is the component that pulls the audio out of a video. As of October 2026, running just <code>pip install faster-whisper</code> also installs the latest av 19. But faster-whisper 1.2.1 isn't compatible with av 19, and you get <code>TypeError: open() got an unexpected keyword argument 'metadata_errors'</code> the moment you run it. Pinning av to version 18 makes it work. This pin may become unnecessary once faster-whisper is updated.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4-create-the-subtitle-script">Step 4. Create the subtitle script<a href="https://datapopcorn.ai/blog/en/whisperx-local-video-subtitles#step-4-create-the-subtitle-script" class="hash-link" aria-label="Direct link to Step 4. Create the subtitle script" title="Direct link to Step 4. Create the subtitle script" translate="no">​</a></h2>
<p>The block below creates a script called <code>make_srt.py</code> in your working folder. It takes a video file and saves a <code>.srt</code> file with the same name. Copy <strong>the whole thing at once</strong>, from the first line <code>cat &gt;</code> to the last line <code>EOF</code>, paste it into Terminal, and press Enter. You don't need to change any code.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">cat</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> make_srt.py </span><span class="token operator" style="color:#393A34">&lt;&lt;</span><span class="token string" style="color:#e3116c">'EOF'</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">import argparse</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">from pathlib import Path</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c"></span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">from faster_whisper import WhisperModel</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c"></span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c"></span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">def to_srt_time(seconds: float) -&gt; str:</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">    ms = int(round(seconds * 1000))</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">    h, ms = divmod(ms, 3_600_000)</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">    m, ms = divmod(ms, 60_000)</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">    s, ms = divmod(ms, 1000)</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">    return f"{h:02}:{m:02}:{s:02},{ms:03}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c"></span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c"></span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">parser = argparse.ArgumentParser(description="Turns a video or audio file into SRT subtitles.")</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">parser.add_argument("file", help="Video or audio file to subtitle")</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">parser.add_argument("--model", default="small", help="tiny, base, small, medium, large-v3")</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">parser.add_argument("--device", default="cpu", help="cpu or cuda")</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">parser.add_argument("--compute-type", default="int8", help="int8 for cpu, float16 for cuda")</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">parser.add_argument("--language", default="ko")</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">args = parser.parse_args()</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c"></span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">model = WhisperModel(args.model, device=args.device, compute_type=args.compute_type)</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">segments, info = model.transcribe(args.file, language=args.language, vad_filter=True)</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c"></span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">out_path = Path(args.file).with_suffix(".srt")</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">with open(out_path, "w", encoding="utf-8") as f:</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">    for i, seg in enumerate(segments, start=1):</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">        f.write(f"{i}\n{to_srt_time(seg.start)} --&gt; {to_srt_time(seg.end)}\n{seg.text.strip()}\n\n")</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">        print(f"[{to_srt_time(seg.start)}] {seg.text.strip()}")</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c"></span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">print(f"Done: {out_path}")</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">EOF</span><br></div></code></pre></div></div>
<p>To check that it was created correctly, run the command below. If you see the usage message, you're set.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">python make_srt.py </span><span class="token parameter variable" style="color:#36acaa">--help</span><br></div></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">usage: make_srt.py [-h] [--model MODEL] [--device DEVICE]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                   [--compute-type COMPUTE_TYPE] [--language LANGUAGE]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                   file</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Turns a video or audio file into SRT subtitles.</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-5-add-a-video-and-run-it">Step 5. Add a video and run it<a href="https://datapopcorn.ai/blog/en/whisperx-local-video-subtitles#step-5-add-a-video-and-run-it" class="hash-link" aria-label="Direct link to Step 5. Add a video and run it" title="Direct link to Step 5. Add a video and run it" translate="no">​</a></h2>
<ol>
<li class="">In Finder, move the video you want to subtitle (for example, <code>my-video.mp4</code>) into the <code>local-subtitles</code> folder on your Desktop. File names without spaces are easier to type.</li>
<li class="">Enter the command below in Terminal.</li>
</ol>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">python make_srt.py my-video.mp4</span><br></div></code></pre></div></div>
<p><strong>The first time</strong>, it takes a few minutes to download the speech recognition model (<code>small</code>, about 480MB). The model is saved in <code>~/.cache/huggingface</code>, and it won't download again after that.</p>
<p>While it runs, each recognized sentence prints on screen, one line at a time. Here's the real output from a 25-second Korean test video (the script defaults to Korean; I've translated the transcript into English).</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">[00:00:00,000] Hi, this is Data Popcorn.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">[00:00:04,040] Today we'll look at how to make video subtitles for free on your own computer.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">[00:00:11,400] The subtitle file gets created without uploading the audio to the internet.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">[00:00:18,040] First, file line, create a virtual environment and install the packages.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Done: my-video.srt</span><br></div></code></pre></div></div>
<p>With the model already downloaded, my M3 MacBook took about 21 seconds to process this 25-second video. Times vary with video length and hardware, so for long videos I recommend trying a 1 to 2 minute clip first.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="changing-options">Changing options<a href="https://datapopcorn.ai/blog/en/whisperx-local-video-subtitles#changing-options" class="hash-link" aria-label="Direct link to Changing options" title="Direct link to Changing options" translate="no">​</a></h3>
<table><thead><tr><th>What you want</th><th>Command</th></tr></thead><tbody><tr><td>Run with defaults (Korean, small model)</td><td><code>python make_srt.py my-video.mp4</code></td></tr><tr><td>English video</td><td><code>python make_srt.py my-video.mp4 --language en</code></td></tr><tr><td>Audio file (mp3) instead of video</td><td><code>python make_srt.py my-audio.mp3</code></td></tr></tbody></table>
<p>To change the model size, add the name after <code>--model</code> (for example, <code>--model medium</code>). Models get bigger in this order: <code>tiny</code>, <code>base</code>, <code>small</code>, <code>medium</code>, <code>large-v3</code>. Bigger models are more likely to be accurate, but the download and processing time grow too. The only model I actually ran for this post is <code>small</code>, so I suggest going all the way through with <code>small</code> first and only moving up if the results fall short.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-6-open-the-srt-file-and-fix-mistakes">Step 6. Open the SRT file and fix mistakes<a href="https://datapopcorn.ai/blog/en/whisperx-local-video-subtitles#step-6-open-the-srt-file-and-fix-mistakes" class="hash-link" aria-label="Direct link to Step 6. Open the SRT file and fix mistakes" title="Direct link to Step 6. Open the SRT file and fix mistakes" translate="no">​</a></h2>
<p>A <code>my-video.srt</code> file with the same name as your video appears in the folder. Open it in a text editor and it looks like this (transcript translated from Korean).</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">00:00:00,000 --&gt; 00:00:04,040</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Hi, this is Data Popcorn.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">00:00:04,040 --&gt; 00:00:11,400</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Today we'll look at how to make video subtitles for free on your own computer.</span><br></div></code></pre></div></div>
<p>The transcription isn't perfect. In the test above, "<strong>Python</strong> virtual environment" (파이썬 가상환경) came out as "<strong>file line</strong>, virtual environment" (파일 선, 가상환경). Always check these three things by eye:</p>
<ul>
<li class="">Product names, people's names and technical terms (these get it wrong most often, like "Python" above)</li>
<li class="">Spacing in company names ("데이터팝콘", DataPopcorn, came out as two words, "데이터 팝콘")</li>
<li class="">Numbers and units</li>
</ul>
<p>Leave the timing lines (the ones with <code>--&gt;</code>) alone, fix only the text lines, and save. How to bring the corrected SRT into your editing tool is covered in <a href="https://datapopcorn.ai/blog/whisper-video-subtitle-automation" target="_blank" rel="noopener noreferrer" class="">Part 1, "Importing the generated subtitles (SRT) into your editor"</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="if-you-get-an-error-check-here-first">If you get an error, check here first<a href="https://datapopcorn.ai/blog/en/whisperx-local-video-subtitles#if-you-get-an-error-check-here-first" class="hash-link" aria-label="Direct link to If you get an error, check here first" title="Direct link to If you get an error, check here first" translate="no">​</a></h2>
<table><thead><tr><th>Error message</th><th>Cause</th><th>Fix</th></tr></thead><tbody><tr><td><code>TypeError: open() got an unexpected keyword argument 'metadata_errors'</code></td><td>av 19 is installed and isn't compatible with faster-whisper 1.2.1</td><td>Run <code>pip install "av&lt;19"</code>, then try again</td></tr><tr><td><code>ModuleNotFoundError: No module named 'faster_whisper'</code></td><td>You ran it with the virtual environment turned off (no <code>(.venv)</code> at the start of the prompt)</td><td>Run <code>cd ~/Desktop/local-subtitles</code>, then <code>source .venv/bin/activate</code></td></tr><tr><td><code>No such file or directory: 'my-video.mp4'</code></td><td>The video isn't in the working folder, or the file name is different</td><td>Check the file name and location in Finder. If the name has spaces, wrap it in quotes, like <code>"my video.mp4"</code></td></tr><tr><td><code>command not found: python3</code></td><td>Python isn't installed on your Mac</td><td>Install it from the python.org link in Step 1</td></tr><tr><td>The first run seems stuck for a long time</td><td>It's downloading the model (about 480MB)</td><td>Check your internet connection and wait. From the second run on, it starts right away</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-up<a href="https://datapopcorn.ai/blog/en/whisperx-local-video-subtitles#wrap-up" class="hash-link" aria-label="Direct link to Wrap-up" title="Direct link to Wrap-up" translate="no">​</a></h2>
<p><strong>Check Python → create a virtual environment → <code>pip install faster-whisper "av&lt;19"</code> → paste the script → <code>python make_srt.py &lt;video-file&gt;</code> → review the SRT.</strong> Those six steps give you a subtitle file on your Mac at no cost. You only install once; after that, you drop a video into the folder and run the single command from Step 5. The trade-off: processing takes as long as your computer needs, and just like the API route, a human still has to review the transcript once.</p>]]></content:encoded>
            <category>Content automation</category>
            <category>AI work automation</category>
        </item>
        <item>
            <title><![CDATA[AI Says "+124% Growth": 7 Steps to Check Before You Believe It (A Data Analysis Pitfall Checklist)]]></title>
            <link>https://datapopcorn.ai/blog/en/data-analysis-pitfall-checklist</link>
            <guid>https://datapopcorn.ai/blog/en/data-analysis-pitfall-checklist</guid>
            <pubDate>Thu, 15 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Before you use an AI-generated analysis as is, check the baseline period, the sample, the unit being counted, source bias and causal order. I show how with calculations on real public data, plus a review prompt you can paste into ChatGPT or Claude and a 15-item checklist.]]></description>
            <content:encoded><![CDATA[<p>Before you copy an AI-generated analysis into a report, check just seven steps in this order: <strong>baseline period → sample → unit counted → source → causation</strong>. That catches most numbers that look plausible but are wrong.</p>
<!-- -->
<!-- -->
<p>Ask an AI to "analyze this data" and you get tables and charts within minutes. The problem is that the results <strong>look plausible even when they're wrong</strong>. And when the number is big, like "+124% growth," people actually stop questioning it. A big number feels like evidence in itself.</p>
<p>Every example number in this post is one I calculated myself from public data anyone can download (World Bank, Wikipedia page views).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="who-this-is-for">Who this is for<a href="https://datapopcorn.ai/blog/en/data-analysis-pitfall-checklist#who-this-is-for" class="hash-link" aria-label="Direct link to Who this is for" title="Direct link to Who this is for" translate="no">​</a></h2>
<ul>
<li class="">You've given ChatGPT, Claude or Gemini an Excel or CSV file and asked it to analyze it</li>
<li class="">You're a leader or planner who has to review and sign off on "up ○○%" reports from teammates or AI</li>
<li class="">You're a job seeker building a data analysis portfolio and worried "Will this conclusion get torn apart in an interview?"</li>
</ul>
<p>You need <strong>two things</strong>.</p>
<ol>
<li class="">One analysis result produced by AI (a sentence, table or chart, anything works)</li>
<li class="">The AI chat window that produced it (you'll ask it to recalculate in the same window)</li>
</ol>
<p>You don't need any statistics background. Every concept is explained from scratch at the step where it's needed.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1-pull-out-only-the-number-sentences-and-write-down-what-each-compares-against">Step 1. Pull out only the "number sentences" and write down what each compares against<a href="https://datapopcorn.ai/blog/en/data-analysis-pitfall-checklist#step-1-pull-out-only-the-number-sentences-and-write-down-what-each-compares-against" class="hash-link" aria-label="Direct link to Step 1. Pull out only the &quot;number sentences&quot; and write down what each compares against" title="Direct link to Step 1. Pull out only the &quot;number sentences&quot; and write down what each compares against" translate="no">​</a></h2>
<p>Before reading the whole analysis, pull out just the sentences that contain numbers. Then next to each sentence, write <strong>"what is this number compared against?"</strong></p>
<table><thead><tr><th>Sentence the AI wrote</th><th>Comparison baseline</th></tr></thead><tbody><tr><td>Air passengers increased 124%</td><td>2023 vs. 2021</td></tr><tr><td>April weekend interest fell 19%</td><td>First Saturday of April vs. first Saturday of March</td></tr></tbody></table>
<p>If there's a sentence where you can't write down a baseline, that's the first problem. Ask the AI chat window:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">For every sentence in your result that contains a % change, make a table showing what (period and target) it was compared against.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Flag any sentence whose baseline isn't in the data.</span><br></div></code></pre></div></div>
<p>This table is the starting point for the steps that follow.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2-check-whether-the-baseline-period-was-an-abnormal-time">Step 2. Check whether the baseline period was an "abnormal time"<a href="https://datapopcorn.ai/blog/en/data-analysis-pitfall-checklist#step-2-check-whether-the-baseline-period-was-an-abnormal-time" class="hash-link" aria-label="Direct link to Step 2. Check whether the baseline period was an &quot;abnormal time&quot;" title="Direct link to Step 2. Check whether the baseline period was an &quot;abnormal time&quot;" translate="no">​</a></h2>
<p>A <strong>rate of change</strong> is "current value ÷ baseline value − 1." If the baseline (the denominator) is abnormally small, the rate of change gets big automatically. So when you set the baseline decides half the conclusion.</p>
<p>Let's calculate with real data: the <strong>number of domestic and international passengers carried in a year by Korean-registered airlines</strong>, published by the World Bank (indicator code <code>IS.AIR.PSGR</code>).</p>
<table><thead><tr><th>Year</th><th>Passengers</th></tr></thead><tbody><tr><td>2019</td><td>92.43 million</td></tr><tr><td>2020</td><td>30.03 million</td></tr><tr><td>2021</td><td>34.02 million</td></tr><tr><td>2023</td><td>76.27 million</td></tr></tbody></table>
<p>It's the same 2023 number, but the conclusion changes depending on the baseline.</p>
<ul>
<li class="">vs. 2020: <strong>+154.0%</strong></li>
<li class="">vs. 2021: <strong>+124.2%</strong></li>
<li class="">vs. 2019: <strong>−17.5%</strong></li>
</ul>
<p><img decoding="async" loading="lazy" alt="Bar chart of passengers carried by Korean-registered airlines in 2019, 2020, 2021 and 2023. The 2023 figure of 76.27 million is +154.0% vs. 2020, +124.2% vs. 2021 and -17.5% vs. 2019, so growth and decline flip depending on the base year" src="https://datapopcorn.ai/blog/en/assets/images/chart-base-year-9df3c96c9503c6e110b826849e67caef.png" width="1200" height="750" class="img_ev3q"></p>
<p>"Air passengers surge 124%" and "still 17.5% below pre-COVID levels" are both correct numbers. In 2020-2021, COVID had nearly shut down air travel, so using those years as the baseline makes a recovery read like growth.</p>
<p>Checking takes three lines.</p>
<ol>
<li class="">Find the baseline period in your Step 1 table.</li>
<li class="">Check whether anything unusual happened then: COVID, a disaster, a policy change, a major holiday.</li>
<li class="">If so, have it recalculate using a normal period as the baseline.</li>
</ol>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Recalculate the same rate of change using 2019 (or a year with no special events) as the baseline.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Show both results side by side and tell me in one line whether the conclusion changes.</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3-check-whether-it-compared-single-points-then-widen-the-range-and-recalculate">Step 3. Check whether it compared single points, then widen the range and recalculate<a href="https://datapopcorn.ai/blog/en/data-analysis-pitfall-checklist#step-3-check-whether-it-compared-single-points-then-widen-the-range-and-recalculate" class="hash-link" aria-label="Direct link to Step 3. Check whether it compared single points, then widen the range and recalculate" title="Direct link to Step 3. Check whether it compared single points, then widen the range and recalculate" translate="no">​</a></h2>
<p>A <strong>sample</strong> is the slice of data used to reach a conclusion. If you compare one day against one day, like "first Saturday of March vs. first Saturday of April," your sample is just two points. The number swings a lot depending on whether it rained or whether it was a public holiday.</p>
<p>I calculated this myself using the daily page views of the Korean Wikipedia article on Busan (부산광역시) for March-April 2025.</p>
<table><thead><tr><th>Comparison method</th><th>March</th><th>April</th><th>Change</th></tr></thead><tbody><tr><td>First Saturdays</td><td>207 (3/1)</td><td>167 (4/5)</td><td><strong>−19.3%</strong></td></tr><tr><td>Second Saturdays</td><td>258 (3/8)</td><td>139 (4/12)</td><td><strong>−46.1%</strong></td></tr><tr><td>Average of all Saturdays</td><td>180.2</td><td>149.8</td><td><strong>−16.9%</strong></td></tr><tr><td>Daily average for the whole month</td><td>188.7</td><td>212.6</td><td><strong>+12.6%</strong></td></tr></tbody></table>
<p><img decoding="async" loading="lazy" alt="Line chart of daily page views for the Korean Wikipedia article on Busan in March-April 2025. Saturdays are marked with red dots. Comparing only 3/8 (258) and 4/12 (139) gives -46%, but comparing the March daily average of 188.7 with the April daily average of 212.6 gives +12.6%, the opposite direction. 3/1 was the Samiljeol public holiday" src="https://datapopcorn.ai/blog/en/assets/images/chart-busan-saturdays-2b780693f0f5234f4eebbd8832d298c0.png" width="1350" height="750" class="img_ev3q"></p>
<p>Depending on which Saturday you pick, you get −19% or −46%, and if you look at the whole month, the direction flips to an <strong>increase</strong>. On top of that, March 1 was Samiljeol (Independence Movement Day), a public holiday in Korea. A special day was hiding inside the ordinary-looking baseline of "first Saturday."</p>
<p>Check in this order.</p>
<ol>
<li class="">Count how many dates (or records) the comparison used. If it's one or two, widen it.</li>
<li class="">Recalculate using the overall average or total for the same period.</li>
<li class="">See whether the <strong>direction</strong> of the result (increase/decrease) holds. If the direction changes, don't use the original conclusion.</li>
</ol>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Tell me how many dates you just used in the comparison.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Recalculate using the daily average for the whole period, and flag any public holidays or long weekends mixed in.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Also tell me whether the direction of the conclusion changes.</span><br></div></code></pre></div></div>
<p>One more thing to check. With <strong>data where the absolute numbers are small</strong>, like around 200 page views a day, a difference of a few dozen in a day moves the rate of change by tens of percent. A big percentage change on a small number is more likely noise than signal.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4-check-whether-what-was-counted-matches-the-question">Step 4. Check whether "what was counted" matches the question<a href="https://datapopcorn.ai/blog/en/data-analysis-pitfall-checklist#step-4-check-whether-what-was-counted-matches-the-question" class="hash-link" aria-label="Direct link to Step 4. Check whether &quot;what was counted&quot; matches the question" title="Direct link to Step 4. Check whether &quot;what was counted&quot; matches the question" translate="no">​</a></h2>
<p>If the AI counted data by keyword or name, check that the words <strong>aren't mixed up with other meanings</strong>. Keyword counts count letters, not meaning.</p>
<p>For example, the Korean keyword "동해 여행" (Donghae travel) mixes trips to <strong>Donghae City</strong> in Gangwon Province with trips to the <strong>East Sea</strong> (also called 동해 in Korean). You want to analyze the city, but you end up counting seaside posts too. If you narrow it to "동해시 여행" (Donghae City travel), you miss posts that left out "City" and undercount. In cases like this, rather than committing to a single number, it's more honest to report a range: "narrow count to broad count."</p>
<p>At this step, check two more things.</p>
<ul>
<li class=""><strong>Official name vs. everyday name:</strong> If the name people actually search for ("Jeju-do") differs from the official administrative name ("Jeju Special Self-Governing Province"), check both.</li>
<li class=""><strong>Adjusting for population and size:</strong> "It happened most in Seoul" may just be because Seoul has the most people. Look at values divided by size as well, such as per capita or per 10,000 people.</li>
</ul>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Check whether the keywords or names counted in this analysis could be mixed up with other meanings (homonyms, common nouns).</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">If they could, count them with a narrow and a broad definition and show the result as a range.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">If this compares regions or groups, also calculate values divided by population or size.</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-5-check-whether-the-place-the-data-came-from-the-source-leans-a-certain-way">Step 5. Check whether the place the data came from (the source) leans a certain way<a href="https://datapopcorn.ai/blog/en/data-analysis-pitfall-checklist#step-5-check-whether-the-place-the-data-came-from-the-source-leans-a-certain-way" class="hash-link" aria-label="Direct link to Step 5. Check whether the place the data came from (the source) leans a certain way" title="Direct link to Step 5. Check whether the place the data came from (the source) leans a certain way" translate="no">​</a></h2>
<p><strong>Source bias</strong> is a skew that comes from where the data was collected. If you analyze a neighborhood by collecting blog posts, "cafés" can easily come out on top. Not because the neighborhood is famous for cafés, but because blogs as a medium are full of café posts to begin with.</p>
<p>There's one way to check: <strong>apply the same criteria to other targets.</strong> If cafés come out on top in every other neighborhood too, that's a trait of the source, not of the target. Comparing targets of similar size cancels out the shared bias and leaves only the real difference.</p>
<p>Source bias can also create things that "aren't there." Blogs and reviews are full of praise, and complaints get posted less often. Even if the result says "almost no complaints about parking," that might not be because parking is easy, but because the source is one where those posts rarely appear.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Check whether the item that came out on top in this result reflects a general tendency of the data source (medium).</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Suggest a way to check whether the same item still comes out on top when the same criteria are applied to 3 comparison targets.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Also point out whether results where something shows up rarely, like "no complaints," could be due to the nature of the source.</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-6-if-you-see-because-ask-three-causal-questions">Step 6. If you see "because," ask three causal questions<a href="https://datapopcorn.ai/blog/en/data-analysis-pitfall-checklist#step-6-if-you-see-because-ask-three-causal-questions" class="hash-link" aria-label="Direct link to Step 6. If you see &quot;because,&quot; ask three causal questions" title="Direct link to Step 6. If you see &quot;because,&quot; ask three causal questions" translate="no">​</a></h2>
<p>If the AI's result says things like "thanks to the opening" or "due to the campaign," stop. Overlapping timing alone doesn't let you call something a cause. Check these three things.</p>
<ol>
<li class=""><strong>Trend before the intervention:</strong> Was it already rising before that event? If it was flat and then turned at that moment, it's more likely to be a cause.</li>
<li class=""><strong>Comparison with other places:</strong> Did other places where the event didn't happen rise just as much in the same period? If so, the cause may be a shared factor like the season or the economy.</li>
<li class=""><strong>The link in between:</strong> Can you confirm the intermediate steps with data, as in "opening → shorter travel time → inflow of a specific age group"?</li>
</ol>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Find the sentences in this result that assert a cause.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">For each sentence, make a table showing whether (1) the trend before the intervention, (2) a comparison target with no intervention and (3) the intermediate link</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">were confirmed with data, and rewrite any sentence you couldn't confirm to the level of "is associated with."</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-7-run-the-remaining-pitfalls-all-at-once-with-a-review-prompt">Step 7. Run the remaining pitfalls all at once with a review prompt<a href="https://datapopcorn.ai/blog/en/data-analysis-pitfall-checklist#step-7-run-the-remaining-pitfalls-all-at-once-with-a-review-prompt" class="hash-link" aria-label="Direct link to Step 7. Run the remaining pitfalls all at once with a review prompt" title="Direct link to Step 7. Run the remaining pitfalls all at once with a review prompt" translate="no">​</a></h2>
<p>Steps 1-6 cover the most common pitfalls. To check the rest without missing anything, paste the prompt below as is into the chat window where the analysis was done.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Review the analysis you just produced against the 15 criteria below.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">For each item, judge it as one of [No issue / Suspicious / Can't verify],</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">and if it's Suspicious, write the reason and a way to recalculate in one line.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">At the end, split the conclusion sentences into ones that can be used as is and ones that need fixing.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">1. Unit: Did you count by the name people actually use, not just the official administrative name?</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2. Homonyms: Could the keyword be mixed up with a common noun or another meaning?</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">3. Mentions ≠ destination: Did you claim something is a "place people want to go" just because it's mentioned a lot?</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">4. Baseline period: Did you use an abnormal time, like COVID, a disaster or a holiday, as the baseline?</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">5. Size adjustment: Did you compare absolute values without adjusting for differences in population or size?</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">6. Absolute value vs. rate of change: Did you use the metric that fits whether the question is about "big places" or "rising places"?</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">7. Sample noise: Is this a single-point comparison, like one day or one record? Does it hold when you widen the range?</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">8. Small numbers: Did you read a big rate of change on data with small absolute numbers as a signal?</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">9. Source genre bias: Did you read an item the source medium naturally covers a lot as a trait of the target?</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">10. Source positivity bias: Did you conclude there are "no" complaints or problems from a source that is mostly praise?</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">11. Causation: Did you assert a cause just because the timing overlapped?</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">12. Recurring events: Did you treat a festival or season that repeats every year as the cause of a change unique to this year?</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">13. Interest ≠ action: Did you treat search volume or social media mentions as the same as actual visits or purchases?</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">14. Data scope: Did you check the collection period and any cap on the number of records?</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">15. Reader's perspective: Did you present something everyone already knows as if it were a new finding?</span><br></div></code></pre></div></div>
<p>On October 7, 2026, I deliberately wrote three wrong analysis sentences using this post's numbers and ran this prompt once. They were: "It surged 124.2% vs. 2021, so demand for overseas travel has exploded," "Comparing first Saturdays, it fell 19.3%, so interest has cooled," and "It's because the cherry blossom festival ended" (Codex CLI, gpt-5.5 model).</p>
<table><thead><tr><th>Pitfall</th><th>AI's verdict</th></tr></thead><tbody><tr><td>Using 2021 as the baseline (item 4)</td><td>Suspicious. Suggested looking at it as a recovery rate alongside 2019</td></tr><tr><td>Comparing single Saturdays (item 7)</td><td>Suspicious. Suggested rechecking with the monthly average or a moving average</td></tr><tr><td>Asserting it was because of the cherry blossom festival (items 11 and 12)</td><td>Suspicious. Suggested comparing with the same period the previous year</td></tr><tr><td>Talking about "overseas travel" with a passenger count that includes domestic flights (item 1)</td><td><strong>Passed as No issue</strong></td></tr><tr><td>March 1 being the Samiljeol public holiday (item 7)</td><td><strong>Not mentioned</strong></td></tr></tbody></table>
<p>It caught the baseline period, the single-point comparison and the causal assertion, but <strong>missed what was counted (that the metric includes domestic flights) and the special day on the calendar.</strong> Both are things you only notice by looking at the data definition and the calendar yourself. This was a single run, so results may differ with other models or other analyses. That's why, even when the AI says "No issue," a person still checks Steps 1-6 directly.</p>
<p>Items the AI marks "Suspicious" go back through the recalculation prompts in Steps 2-6. But for source bias (items 9 and 10) and causation (item 11), a person takes one more look even when the AI says "No issue," because judging them requires context from outside the data.</p>
<p>Many people try to check all 15 from the start and never finish the analysis. If you're short on time, treat only three as mandatory: <strong>baseline period (item 4), sample (item 7) and unit (item 1)</strong>. Running the rest before you submit the report is enough.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="if-you-get-this-result-do-this">If you get this result, do this<a href="https://datapopcorn.ai/blog/en/data-analysis-pitfall-checklist#if-you-get-this-result-do-this" class="hash-link" aria-label="Direct link to If you get this result, do this" title="Direct link to If you get this result, do this" translate="no">​</a></h2>
<table><thead><tr><th>What you found</th><th>What it means</th><th>What to do</th></tr></thead><tbody><tr><td>Changing the baseline flips growth into decline</td><td>Baseline period illusion</td><td>Use the number based on a normal period in the body, and label the original number as "vs. the low point"</td></tr><tr><td>Widening the period changes the direction of the rate of change</td><td>Sample noise</td><td>Drop the single-point conclusion and rewrite it based on the period average</td></tr><tr><td>Public holidays or long weekends are mixed into the compared dates</td><td>Special-day effect</td><td>Remove the special days, or compare the same weekday under the same conditions</td></tr><tr><td>Big gap between the narrow count and the broad count</td><td>Homonym or unit problem</td><td>Report a range instead of a single number</td></tr><tr><td>The same item comes out on top for every other target too</td><td>Source bias</td><td>Drop the "trait of the target" conclusion and recompare relative rankings among similar targets</td></tr><tr><td>It was already rising before the intervention</td><td>Not enough causal evidence</td><td>Change "thanks to" to "around the same time as," and find additional comparison targets</td></tr><tr><td>The AI answers "Can't verify" for most items</td><td>The original data or source information isn't in the chat window</td><td>Re-attach the original file and how it was collected (period, source), then review again</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-up<a href="https://datapopcorn.ai/blog/en/data-analysis-pitfall-checklist#wrap-up" class="hash-link" aria-label="Direct link to Wrap-up" title="Direct link to Wrap-up" translate="no">​</a></h2>
<ul>
<li class="">The bigger the number, the sooner you write down <strong>"compared against what?"</strong></li>
<li class="">The baseline period, the sample and the unit counted can change even the direction of the conclusion. For air passengers, just changing the baseline gave both +124% and −17.5%, and for page views of the Busan article, just changing the compared dates flipped a decline into an increase.</li>
<li class="">Use AI as a tool that redoes calculations quickly, and leave the final call on source bias and causation to a person.</li>
</ul>
<p>Automation is closer to <strong>"AI processes it fast and a person questions it"</strong> than "AI does it all." The review prompt above packages that questioning into a reusable form so you don't have to do it from scratch every time.</p>
<small>Data sources: World Bank Open Data <code>IS.AIR.PSGR</code> (Air transport, passengers carried, Korea), Wikimedia Pageviews API (ko.wikipedia '부산광역시', user views, March-April 2025). Downloaded and calculated on October 7, 2026.</small>]]></content:encoded>
            <category>AI work automation</category>
            <category>Agent</category>
        </item>
        <item>
            <title><![CDATA[Jev vs. Claude vs. GPT Mini: Who Lasts Longer in the Dino Game?]]></title>
            <link>https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino</link>
            <guid>https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino</guid>
            <pubDate>Wed, 14 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[We hooked six models up to the dinosaur game and compared them over two rounds, looking at real API cost and survival time. Then we gave the same states to 7 models, including Solar, and compared decision accuracy, API response time and actual call cost side by side.]]></description>
            <content:encoded><![CDATA[<p>If you hook an AI up to the dinosaur game, can it look at obstacles and pick the next move in time? We split general-purpose models and decision-only APIs into two rounds and recorded real gameplay. In the first round, Jev survived to 60 seconds; in the second, Jev and OpenAI Decisions both survived to 5 minutes. But is the model that answers faster also the one that decides more accurately? After walking through how we connected everything and how the costs compared, we look at what happened when we waited for the answers of seven models, including Solar, and measured accuracy and API response time separately.</p>
<!-- -->
<div class="wrapper_Aefj vertical_a1Rk"><iframe src="https://www.youtube.com/embed/wH-_pLexSI0" title="We hooked 6 AIs up to the dino game. Who lasts the longest?" loading="lazy" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe></div>
<p><a href="https://www.youtube.com/shorts/wH-_pLexSI0" target="_blank" rel="noopener noreferrer" class="">Watch on YouTube</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-we-wanted-to-test-with-the-dino-game-was-whether-decisions-arrive-in-time">What we wanted to test with the dino game was whether decisions arrive in time<a href="https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino#what-we-wanted-to-test-with-the-dino-game-was-whether-decisions-arrive-in-time" class="hash-link" aria-label="Direct link to What we wanted to test with the dino game was whether decisions arrive in time" title="Direct link to What we wanted to test with the dino game was whether decisions arrive in time" translate="no">​</a></h2>
<p>In a game, you have to pick the next move while an obstacle is approaching, so response latency is something you can actually see. At first, having the AI look at the screen and pick everything down to the jump timing was hard. So we started from the <a href="https://github.com/joshlarsen/jev-t-rex-runner" target="_blank" rel="noopener noreferrer" class="">public Jev dino game code</a> and split state reporting from execution.</p>
<p>In this experiment, the AI doesn't get a screenshot. The game code reads the obstacle type, count and height and the current speed and passes them on, and the model picks one of jump, duck or keep running, plus a jump type. The code takes that choice and calculates when to actually press the keys.</p>
<!-- -->
<p>For example, when an obstacle approaches, the AI chooses whether to jump. When to press the key and how long to hold it is up to the shared control code. Since the AI isn't pressing keys every frame, how well the game goes depends on both the model and the control code.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="we-gave-three-models-the-same-state-and-options">We gave three models the same state and options<a href="https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino#we-gave-three-models-the-same-state-and-options" class="hash-link" aria-label="Direct link to We gave three models the same state and options" title="Direct link to We gave three models the same state and options" translate="no">​</a></h2>
<p>We specified Jev 1.13, Claude Haiku 4.5 and GPT-5.4 Mini. Claude and GPT returned one of the fixed options via function calling through OpenRouter's Chat Completions, and Jev used the choice questions in the Decisions API. We used the same state, question and options in meaning, but converted the API format to fit each model.</p>
<table><thead><tr><th>Model as shown on screen</th><th>Model ID used in requests</th><th>Response method</th></tr></thead><tbody><tr><td>Jev 1.13</td><td><code>typesafe/jev-1.13</code></td><td>Fixed choice via the Decisions API</td></tr><tr><td>Claude Haiku 4.5</td><td><code>anthropic/claude-haiku-4.5</code></td><td>Function calling via Chat Completions</td></tr><tr><td>GPT-5.4 Mini</td><td><code>openai/gpt-5.4-mini</code></td><td>Function calling via Chat Completions</td></tr></tbody></table>
<p>We built the connection in this order: reading game state, a per-model API adapter, then shared control code. First we checked that state and actions made the round trip with one model, then changed the others to return the same options. API keys were read on the server, not put in the browser.</p>
<p>Below are the core requirements to use when having an AI coding tool write the connection code. For exact API endpoints and response formats, check the <a href="https://docs.typesafe.ai/primitives" target="_blank" rel="noopener noreferrer" class="">TypeSafe docs</a>, the <a href="https://openrouter.ai/docs" target="_blank" rel="noopener noreferrer" class="">OpenRouter docs</a> and the <a href="https://developers.openai.com/api/docs/models/gpt-5.4-mini" target="_blank" rel="noopener noreferrer" class="">official GPT-5.4 Mini docs</a>.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Read the obstacle state from the public dino game code and send it to the API.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">The model chooses jump / duck / keep_running and a short / full jump.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Have the shared code calculate the jump timing from the current speed.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Even if the dino is mid-jump at the first observation, don't assume it stays that way until the obstacle arrives.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Only allow a short jump for a single small cactus.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Record and replay the same obstacle types, counts, heights, gaps and speed adjustments.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Don't pause the game while waiting for the API; run it at the original speed settings.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Log API errors separately from crashes, and don't use fallback actions or mock responses.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">End the test at 60 seconds of real time and log response time, cost, model name and failure reasons.</span><br></div></code></pre></div></div>
<p>We turned reasoning off for Claude and GPT and set temperature 0 with a 128-token output limit. We didn't use automatic retries, response caching or fallback actions that hide API failures. We also didn't change whether to act based on Jev's confidence number.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-same-seed-wasnt-enough-so-we-fixed-the-obstacle-course-itself">The same seed wasn't enough, so we fixed the obstacle course itself<a href="https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino#the-same-seed-wasnt-enough-so-we-fixed-the-obstacle-course-itself" class="hash-link" aria-label="Direct link to The same seed wasn't enough, so we fixed the obstacle course itself" title="Direct link to The same seed wasn't enough, so we fixed the obstacle course itself" translate="no">​</a></h2>
<p>At first we thought the same random seed would make for a fair comparison. But the order in which random numbers get consumed at runtime sometimes changed obstacle gaps or types, so we recorded the properties of the course that was actually generated and replayed it for the other models. All three models in this recording met the same first obstacle, 3 small cacti, and we confirmed that the generated obstacle records matched the reference course.</p>
<p>Game settings were base speed 6, acceleration 0.001 and max speed 13. Runs took place in real time in the browser, and the game didn't pause while waiting for the API. The original game's acceleration depends on frame count, so the results can't be generalized to other computers or browsers.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="for-the-reel-we-picked-each-models-longest-surviving-recording">For the Reel, we picked each model's longest-surviving recording<a href="https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino#for-the-reel-we-picked-each-models-longest-surviving-recording" class="hash-link" aria-label="Direct link to For the Reel, we picked each model's longest-surviving recording" title="Direct link to For the Reel, we picked each model's longest-surviving recording" translate="no">​</a></h2>
<p>In the first edit, Haiku ended at the first obstacle, so its part of the comparison was too short. Past experiments had a run in the 14-second range, but no video of it survived, so we recorded additional real API play on the same course with the same questions and control code. We didn't change the game speed or obstacle rules, or stretch the footage to make anything look like it lasted longer.</p>
<p>The valid recordings were 7 runs for Haiku, 7 for GPT Mini and 4 for Jev. Haiku and GPT each include 4 runs on default routing and 3 on OpenRouter's latency-first routing. Routing is the setting that decides which provider's server an API request goes to. All 4 Jev runs survived until the test ended at 60 seconds of real time. The total cost recorded in the API responses across all 18 valid runs, including the first recording, was $0.047368706.</p>
<table><thead><tr><th>Model</th><th style="text-align:right">Runs recorded</th><th>Best result</th><th style="text-align:right">Avg. API response in selected run</th><th>Calls / cost in selected run</th></tr></thead><tbody><tr><td>Claude Haiku 4.5</td><td style="text-align:right">7</td><td>15.38 s / crash</td><td style="text-align:right">1135ms</td><td>10 / $0.01368800</td></tr><tr><td>GPT-5.4 Mini</td><td style="text-align:right">7</td><td>17.19 s / crash</td><td style="text-align:right">994ms</td><td>13 / $0.00540075</td></tr><tr><td>Jev 1.13</td><td style="text-align:right">4</td><td>59.13 s game time / survived to 60 s, test ended</td><td style="text-align:right">395ms</td><td>59 / $0.00169180</td></tr></tbody></table>
<p>The selected Haiku run used <code>provider.sort: latency</code>, and the GPT Mini run used default routing. According to <a href="https://openrouter.ai/docs/guides/routing/provider-selection" target="_blank" rel="noopener noreferrer" class="">OpenRouter's official docs</a>, this setting asks to prioritize low-latency providers; it doesn't guarantee response time across a whole game. Jev used its dedicated Decisions API.</p>
<p>The Reel is a highlight showing the best recorded play. Each model had a different number of attempts and the routing was mixed, so don't read it as a fair win-rate or average-latency comparison or a universal performance ranking. Call costs also differ with play length and call count, so we don't calculate savings on the same task from the totals alone.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="round-2-ran-decision-only-apis-against-each-other-for-up-to-5-minutes">Round 2 ran decision-only APIs against each other for up to 5 minutes<a href="https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino#round-2-ran-decision-only-apis-against-each-other-for-up-to-5-minutes" class="hash-link" aria-label="Direct link to Round 2 ran decision-only APIs against each other for up to 5 minutes" title="Direct link to Round 2 ran decision-only APIs against each other for up to 5 minutes" translate="no">​</a></h2>
<p>After the first comparison, we compared APIs that return a choice instead of general-purpose models. We connected Jev, OpenAI Decisions (GPT-6 Luna Decisions), MS Decision-1 and Clef Flash to the same obstacle course and control code, and ran each model 5 times for up to 5 minutes of real time. MS was recorded separately under the same conditions after the other three. Solar, originally included, was left out of the comparison in this Reel.</p>
<p>The Reel shows each model's longest-surviving run. Jev and OpenAI Decisions both made it to the 5-minute cap without crashing. That means both models survived to the end in their best run of this test. Across all 5 runs, Jev reached the cap twice and OpenAI Decisions once; MS and Clef never did. This doesn't establish accuracy on business tasks or superiority across all games.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="solar-wasnt-so-much-wrong-as-late">Solar wasn't so much wrong as late<a href="https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino#solar-wasnt-so-much-wrong-as-late" class="hash-link" aria-label="Direct link to Solar wasn't so much wrong as late" title="Direct link to Solar wasn't so much wrong as late" translate="no">​</a></h2>
<p>At first we also connected Solar Decide to the same game. All 5 valid runs crashed into the first obstacle at about 4.48 to 4.49 seconds of game time. But when we checked the API responses, it had chosen <strong>jump, full jump</strong> all five times. That was the right choice for clearing 3 small cacti, but the responses took 5.19 to 8.85 seconds, with a median of 6.07 seconds. The game doesn't pause while waiting for the API, so by the time the answer arrived, the crash had already happened.</p>
<p>So describing this result as "Solar misjudged the obstacle" isn't accurate. <strong>It returned the right choice for the first obstacle it observed, but the answer didn't arrive in time, which made it hard to use for this real-time game connection.</strong> Since it never got past the first obstacle, this also isn't an evaluation of its judgment on the various obstacle types that come later.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="would-it-be-faster-called-directly-instead-of-through-openrouter">Would it be faster called directly instead of through OpenRouter?<a href="https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino#would-it-be-faster-called-directly-instead-of-through-openrouter" class="hash-link" aria-label="Direct link to Would it be faster called directly instead of through OpenRouter?" title="Direct link to Would it be faster called directly instead of through OpenRouter?" translate="no">​</a></h3>
<p>On October 15, 2026, we sent the same request alternately to the <a href="https://console.upstage.ai/api/systemone" target="_blank" rel="noopener noreferrer" class="">Upstage direct API</a> and to <a href="https://openrouter.ai/upstage/solar-decide" target="_blank" rel="noopener noreferrer" class="">Solar Decide on OpenRouter</a>. We used the game's first-obstacle state and the two questions as is, and both sides had 1,031 input tokens. We ran one request at a time on the same computer and measured 10 times per route, excluding 1 warm-up each. Timeout was 15 seconds, with no automatic retries.</p>
<table><thead><tr><th>Result</th><th style="text-align:right">Upstage direct API</th><th style="text-align:right">OpenRouter</th></tr></thead><tbody><tr><td>Requests measured</td><td style="text-align:right">10</td><td style="text-align:right">10</td></tr><tr><td>Successful responses</td><td style="text-align:right">9</td><td style="text-align:right">10</td></tr><tr><td>Median time for successful responses</td><td style="text-align:right"><strong>5.30 s</strong></td><td style="text-align:right"><strong>6.84 s</strong></td></tr><tr><td>Range for successful responses</td><td style="text-align:right">3.41 to 11.98 s</td><td style="text-align:right">3.40 to 14.48 s</td></tr><tr><td>15-second timeouts</td><td style="text-align:right">1</td><td style="text-align:right">0</td></tr><tr><td>Choice in successful responses</td><td style="text-align:right">All jump, full jump</td><td style="text-align:right">All jump, full jump</td></tr></tbody></table>
<p>The median and range are calculated over successful responses only, and timeouts are shown separately as failures. The direct API's median for successful responses was about 1.54 seconds shorter, but delays of several seconds and a timeout showed up with direct calls too. We can't attribute the whole difference to OpenRouter's relay time. Server load can vary, and the direct API only returned the alias <code>solar-decide</code>, so we couldn't confirm whether its internal model version matches OpenRouter's <code>upstage/solar-decide-20260928</code>.</p>
<p><a href="https://datapopcorn.ai/blog/en/assets/files/solar-route-latency-5f1c2cb34a27c0cbdbfdb29ce8269070.csv" target="_blank" class="">Download per-route call measurements (CSV)</a></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="so-is-it-a-network-problem-rather-than-the-model">So is it a network problem rather than the model?<a href="https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino#so-is-it-a-network-problem-rather-than-the-model" class="hash-link" aria-label="Direct link to So is it a network problem rather than the model?" title="Direct link to So is it a network problem rather than the model?" translate="no">​</a></h3>
<p>What we measured is <strong>the total time from starting to send the request until the full response body is received</strong>. That includes not just network communication but also time spent waiting in a queue on the server, input processing and the time the model takes to compute an answer. Observability tools for inference servers also track queue time and model compute time as separate metrics (<a href="https://docs.nvidia.com/deeplearning/triton-inference-server/user-guide/docs/user_guide/metrics.html#latencies" target="_blank" rel="noopener noreferrer" class="">NVIDIA Triton docs</a>). That doesn't mean Upstage uses that server.</p>
<p>We didn't get per-stage timings from inside Upstage, so <strong>we can't separate whether the problem is the network, server queuing or slow model compute itself.</strong> We confirmed the delay reproduced with direct calls, but that alone doesn't mean we measured the model's pure inference speed. Decision accuracy and end-to-end service response time need to be read separately.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="could-it-have-done-well-at-the-game-if-run-locally">Could it have done well at the game if run locally?<a href="https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino#could-it-have-done-well-at-the-game-if-run-locally" class="hash-link" aria-label="Direct link to Could it have done well at the game if run locally?" title="Direct link to Could it have done well at the game if run locally?" translate="no">​</a></h3>
<p>It's possible. If the same model ran on dedicated hardware fast enough that the answer arrived before the jump point, it might clear the first obstacle we observed. Running locally can cut external network round trips and shared-server queuing, but model compute can also be slower on a personal computer, so it isn't automatically faster. An API is just a way of calling a model, so a local model can be served through an API too. What matters is less the name of the call and more where the model runs, on what hardware and under what conditions.</p>
<p>We didn't run Solar Decide locally this time. The <a href="https://console.upstage.ai/docs/models/solar-decide" target="_blank" rel="noopener noreferrer" class="">official model page</a> lists Upstage Console as the way it's offered, so you'd first need to check separately whether the same model's weights and decision implementation are available for local use. Results from installing some other open model locally can't stand in for Solar's local performance either.</p>
<p>The conclusion we can support here is <strong>"Solar's first decision was right, but with this API connection, the answer didn't arrive within the time the game needed."</strong> Whether it would have lasted reliably if run locally can only be known by running it again with the same model and control code.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="given-time-to-answer-solar-also-got-all-18-states-right">Given time to answer, Solar also got all 18 states right<a href="https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino#given-time-to-answer-solar-also-got-all-18-states-right" class="hash-link" aria-label="Direct link to Given time to answer, Solar also got all 18 states right" title="Direct link to Given time to answer, Solar also got all 18 states right" translate="no">​</a></h2>
<p>In a real-time game, a correct answer that arrives late is useless. So what if we pause the game and wait for the answer? On October 15, 2026, instead of playing the game in real time, we added <strong>an evaluation that gives each model the same game state and waits for its answer</strong>. In this evaluation, a slow response doesn't cause a crash or count as wrong.</p>
<p>The inputs were the obstacle type, count and height category prepared by the code, the speed, and what the dino was doing at the moment of observation. The correct answer for each state was fixed in advance using the existing action rules: jump for ground obstacles, duck for birds that only block a standing dino, keep running for birds that won't hit even a standing dino. The question also spelled out the selection criteria, so this is closer to <strong>a task of picking an action by given rules</strong> than to complex reasoning or screen recognition.</p>
<p>We originally planned 24 states, 8 each for jump, duck and keep running. But Claude's call on the 19th state got an HTTP 402 for insufficient credits and we stopped, so <strong>we compared only the first 18 states where all seven models responded normally.</strong> That shared set has 6 states per action. The 19th state, which only Solar answered, and the 402 response are left out of the table, and we didn't mix in results from a separate run that was stopped earlier by a shared connection failure.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="accuracy-speed-and-cost-on-the-same-18-states">Accuracy, speed and cost on the same 18 states<a href="https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino#accuracy-speed-and-cost-on-the-same-18-states" class="hash-link" aria-label="Direct link to Accuracy, speed and cost on the same 18 states" title="Direct link to Accuracy, speed and cost on the same 18 states" translate="no">​</a></h3>
<table><thead><tr><th>Model</th><th style="text-align:right">Correct action choices</th><th style="text-align:right">Accuracy</th><th style="text-align:right">Median API response time</th><th style="text-align:right">P95 API response time</th><th style="text-align:right">Actual API cost for 18 calls</th></tr></thead><tbody><tr><td>Jev 1.13</td><td style="text-align:right">17 / 18</td><td style="text-align:right">94.4%</td><td style="text-align:right">0.38 s</td><td style="text-align:right">0.59 s</td><td style="text-align:right">$0.000515928</td></tr><tr><td>OpenAI Decisions · GPT-6 Luna</td><td style="text-align:right">18 / 18</td><td style="text-align:right">100.0%</td><td style="text-align:right">0.45 s</td><td style="text-align:right">0.66 s</td><td style="text-align:right">$0.000887000</td></tr><tr><td>MS Decision-1</td><td style="text-align:right">18 / 18</td><td style="text-align:right">100.0%</td><td style="text-align:right">0.65 s</td><td style="text-align:right">1.00 s</td><td style="text-align:right">$0.000253596</td></tr><tr><td>Clef Flash</td><td style="text-align:right">14 / 18</td><td style="text-align:right">77.8%</td><td style="text-align:right">0.59 s</td><td style="text-align:right">1.04 s</td><td style="text-align:right">$0.000327256</td></tr><tr><td>Solar Decide</td><td style="text-align:right">18 / 18</td><td style="text-align:right">100.0%</td><td style="text-align:right">5.39 s</td><td style="text-align:right">14.99 s</td><td style="text-align:right">$0.000927300</td></tr><tr><td>Claude Haiku 4.5</td><td style="text-align:right">14 / 18</td><td style="text-align:right">77.8%</td><td style="text-align:right">1.01 s</td><td style="text-align:right">2.02 s</td><td style="text-align:right">$0.024684000</td></tr><tr><td>GPT-5.4 Mini</td><td style="text-align:right">13 / 18</td><td style="text-align:right">72.2%</td><td style="text-align:right">1.19 s</td><td style="text-align:right">1.39 s</td><td style="text-align:right">$0.007480500</td></tr></tbody></table>
<p>We called every model through OpenRouter, ran one request at a time and rotated the model order for each state. Each got the same state, question and options; the decision-only models answered via the Decisions API, and Claude and GPT Mini via the same function-calling format as in the earlier experiment. We applied latency-first routing with provider fallback disabled, and excluded 1 warm-up per model. Max wait was 30 seconds, with no retries. None of the 126 responses used in the comparison had an API error or timeout. P95 is the 95th percentile of the measured response times, which shows how slow the slower responses got.</p>
<p>Cost is the sum of <code>usage.cost</code> from successful responses for these 18 states, in actual dollars. Each model handled the same states and questions 18 times, so totals don't differ because of survival time or call count. Warm-ups, the 19th state, the insufficient-credit response and the separate run stopped by a connection failure are excluded. Top-up fees, taxes and computer costs aren't included either. We sent inputs with the same meaning, but that doesn't mean the API formats and each model's token counting are the same.</p>
<p>To make the small amounts easier to compare, scaling the observed average per call to 1,000 calls gives <strong>$0.014089</strong> for MS, <strong>$0.028663</strong> for Jev and <strong>$0.049278</strong> for OpenAI Decisions. These are proportional projections from these 18 calls, not the result of actually calling 1,000 times. The projections for every model are also in the CSV below.</p>
<p>The accuracy above is <strong>the share of answers that matched the predefined action rules</strong>, not a game survival rate. For example, Jev's one mismatch was choosing to duck in a situation where it could just keep running. That's an unnecessary move, but it doesn't mean a crash in itself. For the 6 states where jumping was correct, we also checked the jump type separately: Clef chose short vs. full jump differently on 1 of them, so only 13/18 of its responses matched on both action and jump type. Every other model's jump type matched the reference on all 6.</p>
<p><a href="https://datapopcorn.ai/blog/en/assets/files/waiting-state-comparison-d1a67f5cd17490cf1851d0896c21447f.csv" target="_blank" class="">Download the same-state evaluation comparison table (CSV)</a> · <a href="https://datapopcorn.ai/blog/en/assets/files/waiting-state-responses-1be082a84e154e6f1d840ebf2f435012.csv" target="_blank" class="">Download per-state inputs, answers and responses (CSV)</a></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="solar-was-accurate-but-we-couldnt-confirm-the-fast-choice-advantage-in-this-api-setup">Solar was accurate, but we couldn't confirm the fast-choice advantage in this API setup<a href="https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino#solar-was-accurate-but-we-couldnt-confirm-the-fast-choice-advantage-in-this-api-setup" class="hash-link" aria-label="Direct link to Solar was accurate, but we couldn't confirm the fast-choice advantage in this API setup" title="Direct link to Solar was accurate, but we couldn't confirm the fast-choice advantage in this API setup" translate="no">​</a></h3>
<p><strong>One of the core advantages of a decision model is returning a choice quickly without generating long text.</strong> The <a href="https://docs.typesafe.ai/introduction" target="_blank" rel="noopener noreferrer" class="">official Jev overview</a> and the <a href="https://console.upstage.ai/docs/models/solar-decide" target="_blank" rel="noopener noreferrer" class="">official Solar overview</a> both emphasize this design. So alongside whether it picked the right answer, you have to check whether that answer arrived in the time you needed.</p>
<p>Given time to answer, Solar got all 18 shared states right. But its median API response time was 5.39 seconds, and <strong>in this API setup we couldn't confirm the fast-choice advantage that decision models advertise.</strong> Jev responded fast with a median of 0.38 seconds, but its action choices matched the rules on 17/18. OpenAI Decisions and MS Decision-1 got all 18 right in this sample, with medians of 0.45 and 0.65 seconds respectively.</p>
<p>These results can't be stretched into a general ranking of the models' reasoning ability. It's a small task with explicit rules, and each state was called only once. The API time also bundles communication, routing, server queuing and model compute together, so it isn't a measurement that isolates pure inference speed. What it does show is that <strong>decision accuracy and end-to-end service response time should be treated as separate metrics.</strong></p>
<p>At work, too, you need to set those two criteria separately. If you're collecting inquiries to classify later, you can wait a few seconds; if you're choosing which urgent alerts to send right away, the correct answer has to arrive within a set time. You can only decide which model to use by checking accuracy on real work data and measuring acceptable response time and cost per call together.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="on-this-task-ms-came-out-ahead-on-cost-and-openai-on-the-balance-of-accuracy-and-speed">On this task, MS came out ahead on cost and OpenAI on the balance of accuracy and speed<a href="https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino#on-this-task-ms-came-out-ahead-on-cost-and-openai-on-the-balance-of-accuracy-and-speed" class="hash-link" aria-label="Direct link to On this task, MS came out ahead on cost and OpenAI on the balance of accuracy and speed" title="Direct link to On this task, MS came out ahead on cost and OpenAI on the balance of accuracy and speed" translate="no">​</a></h3>
<p><strong>If we were to start the next evaluation of this task with one model, we'd look at MS Decision-1 first.</strong> Its action choices and jump types matched the reference on all 18/18, and its cost for 18 calls, $0.000253596, was the lowest of the seven models. Its median response time was 0.65 seconds. It was about 51% cheaper than Jev but slower to respond, so the choice depends on how much latency you can accept.</p>
<table><thead><tr><th>Priority</th><th>Model to look at in this sample</th><th>What to check alongside</th></tr></thead><tbody><tr><td>Follow the given rules exactly while keeping cost down</td><td>MS Decision-1</td><td>18/18, median 0.65 s. Check whether your real work can tolerate this latency</td></tr><tr><td>Keep everything correct with shorter response times</td><td>OpenAI Decisions</td><td>18/18, median 0.45 s. About 3.5x the cost of MS on these 18 calls</td></tr></tbody></table>
<p>Jev was the fastest with a median of 0.38 seconds and was about 42% cheaper than OpenAI. But the median gap with OpenAI was 0.07 seconds, and its action choices matched the reference on 17/18. <strong>These results alone weren't enough to recommend Jev ahead of MS or OpenAI.</strong> You'd need to check further whether that small speed difference matters in real work and what effect a rule mismatch has.</p>
<p>Solar got 18/18 right but cost more and responded more slowly than MS and OpenAI, so there was little reason to pick it first for this task. Clef was cheaper than Jev, but its action choices were 14/18, and only 13/18 were right on jump type too. Claude and GPT Mini were also more expensive, slower and less consistent with the rules than MS and OpenAI on this task. That doesn't mean the ranking holds for other work like writing documents, coding or complex reasoning.</p>
<p>These selection criteria come from <strong>a small experiment that called the same 18 rule-defined states once each</strong>. We didn't combine accuracy, speed and cost into an arbitrary score to crown an overall winner. For real work, first decide what one wrong answer costs you and what response time you can accept, then check with more work samples and repeated calls. A model that got 18/18 this time isn't guaranteed to always get it right going forward.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-much-did-actual-gameplay-cost">How much did actual gameplay cost?<a href="https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino#how-much-did-actual-gameplay-cost" class="hash-link" aria-label="Direct link to How much did actual gameplay cost?" title="Direct link to How much did actual gameplay cost?" translate="no">​</a></h2>
<p>The amounts below aren't estimates from plugging input volume into token prices; they're the sum, in dollars, of <code>usage.cost</code> recorded in the successful API responses for each run. Responses requested during a run were included in its cost even if they arrived after the crash. Top-up fees, taxes and computer costs aren't included.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="actual-cost-of-the-single-best-run-used-in-the-reel">Actual cost of the single best run used in the Reel<a href="https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino#actual-cost-of-the-single-best-run-used-in-the-reel" class="hash-link" aria-label="Direct link to Actual cost of the single best run used in the Reel" title="Direct link to Actual cost of the single best run used in the Reel" translate="no">​</a></h3>
<table><thead><tr><th>Round</th><th>Model</th><th style="text-align:right">Survival time in game</th><th style="text-align:right">API calls</th><th style="text-align:right">Actual cost</th></tr></thead><tbody><tr><td>1</td><td>Claude Haiku 4.5</td><td style="text-align:right">15.38 s</td><td style="text-align:right">10</td><td style="text-align:right">$0.01368800</td></tr><tr><td>1</td><td>GPT-5.4 Mini</td><td style="text-align:right">17.19 s</td><td style="text-align:right">13</td><td style="text-align:right">$0.00540075</td></tr><tr><td>1</td><td>Jev 1.13</td><td style="text-align:right">59.13 s</td><td style="text-align:right">59</td><td style="text-align:right">$0.00169180</td></tr><tr><td>2</td><td>Jev 1.13</td><td style="text-align:right">299.18 s</td><td style="text-align:right">329</td><td style="text-align:right">$0.00942858</td></tr><tr><td>2</td><td>OpenAI Decisions</td><td style="text-align:right">299.04 s</td><td style="text-align:right">329</td><td style="text-align:right">$0.01619360</td></tr><tr><td>2</td><td>Clef Flash</td><td style="text-align:right">45.55 s</td><td style="text-align:right">44</td><td style="text-align:right">$0.00044323</td></tr><tr><td>2</td><td>MS Decision-1</td><td style="text-align:right">25.18 s</td><td style="text-align:right">20</td><td style="text-align:right">$0.00028346</td></tr></tbody></table>
<p>Round 1 Jev ended at 60 seconds of real time, and round 2 Jev and OpenAI Decisions at 300 seconds of real time. Because of things like the wait for the game to start, in-game time was about 59 and 299 seconds respectively.</p>
<p>The two full runs in round 2 had almost the same survival time and <strong>329 calls each</strong>. In those two runs Jev cost $0.00942858 and OpenAI Decisions $0.01619360, so <strong>Jev was about 42% cheaper.</strong> That's a difference observed with this request format and course. Even when you send questions with the same meaning, API formats and token counts differ by model.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="normalizing-for-survival-time-cost-per-minute-of-gameplay">Normalizing for survival time: cost per minute of gameplay<a href="https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino#normalizing-for-survival-time-cost-per-minute-of-gameplay" class="hash-link" aria-label="Direct link to Normalizing for survival time: cost per minute of gameplay" title="Direct link to Normalizing for survival time: cost per minute of gameplay" translate="no">​</a></h3>
<p>Picking only the single best run can skew the results, so the table below adds up <strong>every valid run in each round</strong>, including runs that ended early. Round 1 has 7 runs each for Haiku and GPT Mini and 4 for Jev; round 2 has 5 each.</p>
<p><strong>Cost per minute of gameplay = total cost of all valid runs ÷ total in-game survival time × 60</strong></p>
<table><thead><tr><th>Round</th><th>Model</th><th style="text-align:right">Runs</th><th style="text-align:right">Total survival time</th><th style="text-align:right">Total actual cost</th><th style="text-align:right">Cost per minute of gameplay</th></tr></thead><tbody><tr><td>1</td><td>Claude Haiku 4.5</td><td style="text-align:right">7</td><td style="text-align:right">42.32 s</td><td style="text-align:right">$0.02190200</td><td style="text-align:right">$0.031052</td></tr><tr><td>1</td><td>GPT-5.4 Mini</td><td style="text-align:right">7</td><td style="text-align:right">76.36 s</td><td style="text-align:right">$0.01869975</td><td style="text-align:right">$0.014693</td></tr><tr><td>1</td><td>Jev 1.13</td><td style="text-align:right">4</td><td style="text-align:right">236.38 s</td><td style="text-align:right">$0.00676696</td><td style="text-align:right">$0.001718</td></tr><tr><td>2</td><td>Jev 1.13</td><td style="text-align:right">5</td><td style="text-align:right">842.03 s</td><td style="text-align:right">$0.02605218</td><td style="text-align:right">$0.001856</td></tr><tr><td>2</td><td>OpenAI Decisions</td><td style="text-align:right">5</td><td style="text-align:right">570.79 s</td><td style="text-align:right">$0.02983160</td><td style="text-align:right">$0.003136</td></tr><tr><td>2</td><td>Clef Flash</td><td style="text-align:right">5</td><td style="text-align:right">112.18 s</td><td style="text-align:right">$0.00096840</td><td style="text-align:right">$0.000518</td></tr><tr><td>2</td><td>MS Decision-1</td><td style="text-align:right">5</td><td style="text-align:right">81.89 s</td><td style="text-align:right">$0.00083614</td><td style="text-align:right">$0.000613</td></tr></tbody></table>
<p><strong>The per-minute figure isn't what it would cost to actually play the same 60 seconds in full.</strong> It's the spending rate observed while each model was alive, scaled to one minute. It also doesn't assume a model that crashed would come back and finish a full minute. As the game goes on, speed and obstacle density change, and requests sent right before a crash can finish late, so calls per minute vary.</p>
<p>At the observed spending rate in round 1, Jev was about 18x cheaper than Haiku and about 8.6x cheaper than GPT Mini. In round 2, Clef and MS had lower per-minute costs, but neither completed a 5-minute run. <strong>Ending cheaply and lasting long while staying cheap are two different things.</strong></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="normalizing-by-call-count-instead-of-time">Normalizing by call count instead of time<a href="https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino#normalizing-by-call-count-instead-of-time" class="hash-link" aria-label="Direct link to Normalizing by call count instead of time" title="Direct link to Normalizing by call count instead of time" translate="no">​</a></h3>
<p>Below is the cost from the same logs scaled to 1,000 successful calls. Each call includes both the action question and the jump-type question. Each API has a different input format, so this isn't a comparison of billing for identical token counts.</p>
<table><thead><tr><th>Round</th><th>Model</th><th style="text-align:right">API calls observed</th><th style="text-align:right">Projected cost per 1,000 calls</th></tr></thead><tbody><tr><td>1</td><td>Claude Haiku 4.5</td><td style="text-align:right">16</td><td style="text-align:right">$1.368875</td></tr><tr><td>1</td><td>GPT-5.4 Mini</td><td style="text-align:right">45</td><td style="text-align:right">$0.415550</td></tr><tr><td>1</td><td>Jev 1.13</td><td style="text-align:right">236</td><td style="text-align:right">$0.028674</td></tr><tr><td>2</td><td>Jev 1.13</td><td style="text-align:right">909</td><td style="text-align:right">$0.028660</td></tr><tr><td>2</td><td>OpenAI Decisions</td><td style="text-align:right">606</td><td style="text-align:right">$0.049227</td></tr><tr><td>2</td><td>Clef Flash</td><td style="text-align:right">92</td><td style="text-align:right">$0.010526</td></tr><tr><td>2</td><td>MS Decision-1</td><td style="text-align:right">59</td><td style="text-align:right">$0.014172</td></tr></tbody></table>
<p>This tally only includes valid play by the models currently being compared in the two rounds. It excludes warm-ups, requests blocked for insufficient credits, excluded diagnostic runs, the earlier 60-second pilot experiments, the Solar experiment and the follow-up evaluation that gave the same states and waited. The 3 timeouts, where no response came back and the amount couldn't be confirmed, aren't in the totals either, so this isn't total account spend or total development cost. MS's confirmed amount including warm-up was $0.000850122; the table includes only the play cost of $0.000836136. Round 1 mixes default and latency-first routing, and MS was a follow-up experiment, so don't generalize this into a controlled unit-price ranking.</p>
<p><a href="https://datapopcorn.ai/blog/en/assets/files/cost-comparison-7de1b8d28623d02bfca5ef1681f81053.csv" target="_blank" class="">Download the cost comparison data (CSV)</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="so-how-do-you-connect-this-to-your-own-work">So how do you connect this to your own work?<a href="https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino#so-how-do-you-connect-this-to-your-own-work" class="hash-link" aria-label="Direct link to So how do you connect this to your own work?" title="Direct link to So how do you connect this to your own work?" translate="no">​</a></h2>
<p>In the game, the model received the obstacle state and picked the next move. At work, too, you can start by testing <strong>repetitive tasks where you can narrow down the inputs and options</strong>.</p>
<table><thead><tr><th>Task</th><th>Input to the model</th><th>Options to choose from</th></tr></thead><tbody><tr><td>Classifying customer inquiries</td><td>Inquiry text and classification criteria</td><td>Billing · Refund · Technical</td></tr><tr><td>Deciding whether to alert</td><td>Event details and urgent-alert criteria</td><td>Alert now · Review in a batch</td></tr><tr><td>Choosing the next handler</td><td>Inquiry type and available handlers</td><td>Billing AI · Technical AI · Human review</td></tr></tbody></table>
<p>For example, when an inquiry comes in, the model picks the type and whether to alert, and code routes it to the right AI based on the result. You can set it up so a human only reviews inquiries that are exceptions or meet the review conditions you've defined. Surviving the dino game can't guarantee accuracy on this work. You need to measure <strong>classification accuracy, missed urgent inquiries, response latency and cost per call</strong> together, using evaluation data made of real inquiries labeled with correct answers.</p>
<p>The takeaway from this experiment for real work is to separate the steps that need a model that writes long explanations from the steps that need an API that quickly picks an option. How this was used in real public projects is collected in the <a href="https://datapopcorn.ai/blog/jev-use-cases" target="_blank" rel="noopener noreferrer" class="">Jev international use case roundup</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="surviving-60-seconds-marks-when-the-test-ended-not-game-over">Surviving 60 seconds marks when the test ended, not game over<a href="https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino#surviving-60-seconds-marks-when-the-test-ended-not-game-over" class="hash-link" aria-label="Direct link to Surviving 60 seconds marks when the test ended, not game over" title="Direct link to Surviving 60 seconds marks when the test ended, not game over" translate="no">​</a></h2>
<p>Every run was set to go for at most 60 seconds of real time. The selected Jev recording shows 59.13 seconds of game time; the two times differ slightly because of the wait for the game to start and when recording began. "Survived to 60 s · test ended" in the Reel means it hadn't crashed by that point, not that the game ended at 60 seconds.</p>
<p>Response time was measured from when the server sent the API request until it got the response. Costs in the tables are the dollar amounts recorded in <code>usage</code> for successful responses. Results change with run time, network and model provider status, and these recordings can't tell us when a crash would have happened in a longer run.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="we-separated-gpt-minis-blocked-first-call-from-a-real-crash">We separated GPT Mini's blocked first call from a real crash<a href="https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino#we-separated-gpt-minis-blocked-first-call-from-a-real-crash" class="hash-link" aria-label="Direct link to We separated GPT Mini's blocked first call from a real crash" title="Direct link to We separated GPT Mini's blocked first call from a real crash" translate="no">​</a></h2>
<p>When we first connected GPT-5.4 Mini, the first request got an HTTP 402. We had requested up to 128 tokens, but the response said the credits remaining at the time could only allow up to 116 tokens. That run had neither a model answer nor a crash, so we didn't count it as a result.</p>
<p>After updating the connection with a usable key, we kept the output limit at 128 tokens and reran the same course. This time we successfully got 13 API responses, and it crashed at 17.19 seconds of game time. We didn't slot the aborted earlier run in as a low survival time, and we didn't lower the output limit for GPT alone.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="keep-the-ais-choice-separate-from-the-codes-timing">Keep the AI's choice separate from the code's timing<a href="https://datapopcorn.ai/blog/en/jev-claude-gpt-mini-dino#keep-the-ais-choice-separate-from-the-codes-timing" class="hash-link" aria-label="Direct link to Keep the AI's choice separate from the code's timing" title="Direct link to Keep the AI's choice separate from the code's timing" translate="no">​</a></h2>
<p>This experiment isn't evidence that Jev looked at the screen and played the game like a person. The three models share the code that reads obstacles and handles control timing, and a simple obstacle game like this can be played well by rule-based code alone. In real-time play, what we observed was whether the API's decision arrived in time with this connection setup and course. In the follow-up evaluation, we waited for the answers and checked agreement with the action rules separately. You need both results to tell apart a late answer from a different choice.</p>
<p>In the best-run highlight, the scenes after Claude and GPT Mini crash are shown as freeze frames, and Jev's continued play after they stopped is shown at 8x speed. If you try this yourself, it's better to record the same number of runs per model and compare full results and response-time distributions under the same routing conditions. Adding a baseline run that uses only game rules also makes the effect of adding a model clearer. If you're curious about real work applications, see the inquiry routing, feed filtering and task-path decision cases in the <a href="https://datapopcorn.ai/blog/jev-use-cases" target="_blank" rel="noopener noreferrer" class="">Jev use case roundup</a>.</p>
<p>When you connect a decision model to your work, look at <strong>whether the right choice arrives within the time you need, and what that choice costs</strong> together. This time Solar got the answers right when we waited, but in the API setup we measured, we couldn't confirm the fast-choice advantage.</p>
<p>This experiment started with connecting Jev, but <strong>on this task, MS Decision-1 came out ahead on cost and OpenAI Decisions on the balance of accuracy and speed.</strong> Jev having the fastest responses isn't enough on its own to recommend it first. This isn't a model ranking for every kind of work; you should check again with answer data from your real work, and your acceptable response time and cost.</p>]]></content:encoded>
            <category>AI product experiments</category>
            <category>Agent</category>
        </item>
        <item>
            <title><![CDATA[API vs. MCP, Seen Through One Weather Dataset: 5 Steps from a Single curl Line to a Tool AI Can Use]]></title>
            <link>https://datapopcorn.ai/blog/en/api-vs-mcp-difference</link>
            <guid>https://datapopcorn.ai/blog/en/api-vs-mcp-difference</guid>
            <pubDate>Tue, 13 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[We fetch the same weather data twice: once by calling the API directly, and once by wrapping it in an MCP server and opening the tool description the AI actually reads. With a free public API and a single Python file, you can see the difference between API and MCP for yourself in 15 minutes and decide when to use which.]]></description>
            <content:encoded><![CDATA[<p>An API is <strong>a window that a person calls directly, knowing the exact address and values</strong>. MCP is <strong>a standard that attaches an AI-readable description to that window so the AI can choose to call it on its own</strong>. In this post, we fetch the same weather data once through the API and once through an MCP server, and see the difference for ourselves. Every command and piece of code in this post was actually run on October 7, 2026, and the results were checked.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="who-this-is-for">Who this is for<a href="https://datapopcorn.ai/blog/en/api-vs-mcp-difference#who-this-is-for" class="hash-link" aria-label="Direct link to Who this is for" title="Direct link to Who this is for" translate="no">​</a></h2>
<ul>
<li class="">You can't yet confidently answer "Why use MCP when you can just use the API?"</li>
<li class="">You're building an AI agent and can't decide whether to call the API you already use as is, or wrap it in an MCP server</li>
<li class="">You've installed an MCP server but have never seen what happens inside it</li>
</ul>
<p>You need four things.</p>
<table><thead><tr><th>What you need</th><th>How to check</th></tr></thead><tbody><tr><td>A Mac or Linux terminal</td><td>Just open the Terminal app</td></tr><tr><td><code>curl</code></td><td>Usually installed by default. Check with <code>curl --version</code></td></tr><tr><td><code>uv</code> (a Python runner)</td><td>If <code>uv --version</code> doesn't work, install it with the command below</td></tr><tr><td>Codex CLI (Step 4 only)</td><td>Sign in with a ChatGPT account. Installation is covered in Step 4</td></tr></tbody></table>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">-LsSf</span><span class="token plain"> https://astral.sh/uv/install.sh </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">sh</span><br></div></code></pre></div></div>
<p>The <code>uv</code> install command is copied straight from the <a href="https://docs.astral.sh/uv/getting-started/installation/" target="_blank" rel="noopener noreferrer" class="">official uv installation docs</a>. After installing, close and reopen your terminal so the <code>uv</code> command is picked up.</p>
<p>The weather data in this post comes from the public API of <a href="https://open-meteo.com/" target="_blank" rel="noopener noreferrer" class="">Open-Meteo</a>. <strong>No sign-up and no API key required</strong>, so you can copy, paste and follow along right away.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1-call-the-weather-api-directly-with-curl">Step 1. Call the weather API directly with curl<a href="https://datapopcorn.ai/blog/en/api-vs-mcp-difference#step-1-call-the-weather-api-directly-with-curl" class="hash-link" aria-label="Direct link to Step 1. Call the weather API directly with curl" title="Direct link to Step 1. Call the weather API directly with curl" translate="no">​</a></h2>
<p>First, let's pin down exactly what "using an API" means. Paste the line below into your terminal. It asks for the current temperature and wind speed in Seoul (latitude 37.5665, longitude 126.978).</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">curl</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://api.open-meteo.com/v1/forecast?latitude=37.5665&amp;longitude=126.978&amp;current=temperature_2m,wind_speed_10m&amp;timezone=Asia%2FSeoul"</span><br></div></code></pre></div></div>
<p>If you get JSON like this back, it worked. The numbers depend on when you run it.</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"timezone"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"Asia/Seoul"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ... </span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"current"</span><span class="token operator" style="color:#393A34">:</span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"time"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"2026-10-07T09:00"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"interval"</span><span class="token operator" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">900</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"temperature_2m"</span><span class="token operator" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">14.7</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"wind_speed_10m"</span><span class="token operator" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">1.3</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>The point to notice here is that <strong>I decided everything</strong>.</p>
<ul>
<li class="">Which address (<code>/v1/forecast</code>) to send it to</li>
<li class="">What Seoul's latitude and longitude are</li>
<li class="">Which values (<code>temperature_2m</code>, <code>wind_speed_10m</code>) to ask for</li>
<li class="">When to send the request</li>
</ul>
<p>An API is a promise: "Send a request in this format, and you get this data." Reading that promise, filling in the values and deciding when to call it are all up to a person (or code a person wrote).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2-wrap-the-same-function-in-an-mcp-server">Step 2. Wrap the same function in an MCP server<a href="https://datapopcorn.ai/blog/en/api-vs-mcp-difference#step-2-wrap-the-same-function-in-an-mcp-server" class="hash-link" aria-label="Direct link to Step 2. Wrap the same function in an MCP server" title="Direct link to Step 2. Wrap the same function in an MCP server" translate="no">​</a></h2>
<p>Now let's turn the same weather API into a <strong>tool</strong> an AI can use. Create an empty folder, create a <code>weather_server.py</code> file inside it, and paste in the code below as is.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">mkdir</span><span class="token plain"> weather-mcp </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token builtin class-name">cd</span><span class="token plain"> weather-mcp</span><br></div></code></pre></div></div>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">weather_server.py</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> json</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> urllib</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">parse</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> urllib</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">request</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> mcp</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">server</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">fastmcp </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> FastMCP</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">mcp </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> FastMCP</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"korea-weather"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">CITIES </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"Seoul"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">37.5665</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">126.9780</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"Busan"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">35.1796</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">129.0756</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"Jeju"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">33.4996</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">126.5312</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:#393A34">@mcp</span><span class="token decorator annotation punctuation" style="color:#393A34">.</span><span class="token decorator annotation punctuation" style="color:#393A34">tool</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">get_current_weather</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">city</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token triple-quoted-string string" style="color:#e3116c">"""Get the current temperature (°C) and wind speed (km/h) for a city. city must be one of Seoul, Busan or Jeju."""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> city </span><span class="token keyword" style="color:#00009f">not</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> CITIES</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:#e3116c">f"Unsupported city: </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">city</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">. Please use one of Seoul, Busan or Jeju."</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    lat</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> lon </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> CITIES</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">city</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    query </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> urllib</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">parse</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">urlencode</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"latitude"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> lat</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"longitude"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> lon</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"current"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"temperature_2m,wind_speed_10m"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"timezone"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Asia/Seoul"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    url </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:#e3116c">f"https://api.open-meteo.com/v1/forecast?</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">query</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">with</span><span class="token plain"> urllib</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">request</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">urlopen</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">url</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> timeout</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">10</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        now </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> json</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">load</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"current"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:#e3116c">f"</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">city</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c"> as of </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">now</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">[</span><span class="token string-interpolation interpolation string" style="color:#e3116c">'time'</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">]</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">: temperature </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">now</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">[</span><span class="token string-interpolation interpolation string" style="color:#e3116c">'temperature_2m'</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">]</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">°C, wind speed </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">now</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">[</span><span class="token string-interpolation interpolation string" style="color:#e3116c">'wind_speed_10m'</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">]</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">km/h"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> __name__ </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"__main__"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    mcp</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">run</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>Look closely and you'll see that <strong>the inside is exactly the same API call as Step 1</strong>. MCP doesn't remove or replace the API. What changed are two things added on the outside.</p>
<ol>
<li class=""><code>@mcp.tool()</code>: registers this function as a tool the AI can call.</li>
<li class="">The description sentence in triple quotes right under the function: this becomes the manual the AI reads to decide "when should I use this tool, and with what values?"</li>
</ol>
<p>Nobody has to memorize latitude and longitude anymore, either. The AI just puts "Busan" in <code>city</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3-open-the-tool-description-the-ai-sees">Step 3. Open the "tool description" the AI sees<a href="https://datapopcorn.ai/blog/en/api-vs-mcp-difference#step-3-open-the-tool-description-the-ai-sees" class="hash-link" aria-label="Direct link to Step 3. Open the &quot;tool description&quot; the AI sees" title="Direct link to Step 3. Open the &quot;tool description&quot; the AI sees" translate="no">​</a></h2>
<p>An MCP server exchanges messages with the AI app (the client) in a format called <strong>JSON-RPC</strong>: one message per line, sending and receiving JSON with a fixed shape. Let's do by hand exactly what an AI app does when it first connects to an MCP server.</p>
<p>In the same folder, create an <code>ask.jsonl</code> file and paste in the four lines below.</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">ask.jsonl</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"jsonrpc"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"2.0"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"id"</span><span class="token operator" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"method"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"initialize"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"params"</span><span class="token operator" style="color:#393A34">:</span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"protocolVersion"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"2025-06-18"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"capabilities"</span><span class="token operator" style="color:#393A34">:</span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"clientInfo"</span><span class="token operator" style="color:#393A34">:</span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"me"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"version"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"1.0"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"jsonrpc"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"2.0"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"method"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"notifications/initialized"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"jsonrpc"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"2.0"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"id"</span><span class="token operator" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"method"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"tools/list"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"jsonrpc"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"2.0"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"id"</span><span class="token operator" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">3</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"method"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"tools/call"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"params"</span><span class="token operator" style="color:#393A34">:</span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"get_current_weather"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"arguments"</span><span class="token operator" style="color:#393A34">:</span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"city"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"Busan"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>Here's what each line means.</p>
<table><thead><tr><th>Line</th><th>What it does</th></tr></thead><tbody><tr><td><code>initialize</code></td><td>Says "I'm connecting" and agrees on which features both sides support</td></tr><tr><td><code>notifications/initialized</code></td><td>Announces that setup is done</td></tr><tr><td><code>tools/list</code></td><td>"Give me the list of tools I can use and their descriptions"</td></tr><tr><td><code>tools/call</code></td><td>"Run <code>get_current_weather</code> with Busan"</td></tr></tbody></table>
<p>Now pipe these messages into the server.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">cat ask.jsonl</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">sleep</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> uv run </span><span class="token parameter variable" style="color:#36acaa">--with</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"mcp&lt;2"</span><span class="token plain"> weather_server.py </span><span class="token operator file-descriptor important" style="color:#393A34">2</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain">/dev/null</span><br></div></code></pre></div></div>
<p>The first run takes a few seconds while <code>uv</code> downloads the MCP Python package. Of the three response lines, the second is the <strong>tool description the AI sees</strong>. Formatted for readability, it looks like this:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"tools"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"get_current_weather"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"description"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Get the current temperature (°C) and wind speed (km/h) for a city. city must be one of Seoul, Busan or Jeju."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"inputSchema"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"properties"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"city"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"title"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"City"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"string"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"required"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"city"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"object"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>The function name and description sentence from Step 2 became <code>name</code> and <code>description</code> as is, and <code>city: str</code> became an <code>inputSchema</code> saying "a text value called <code>city</code> is required." The last line contains the actual result.</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"content"</span><span class="token operator" style="color:#393A34">:</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"type"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"text"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"text"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"Busan as of 2026-10-07T09:00: temperature 17.5°C, wind speed 6.2km/h"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"isError"</span><span class="token operator" style="color:#393A34">:</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>Now the difference is visible. In Step 1, <strong>I</strong> knew the address, latitude and longitude, and made the call. In Step 3, the server first describes itself: "I have this tool, and it needs these values." The AI app reads that description, and when a user asks "How's the weather in Busan?", it <strong>decides on its own</strong> to call <code>get_current_weather</code> with <code>"Busan"</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4-connect-it-to-an-ai-app-and-check-whether-the-ai-picks-the-tool">Step 4. Connect it to an AI app and check whether the AI picks the tool<a href="https://datapopcorn.ai/blog/en/api-vs-mcp-difference#step-4-connect-it-to-an-ai-app-and-check-whether-the-ai-picks-the-tool" class="hash-link" aria-label="Direct link to Step 4. Connect it to an AI app and check whether the AI picks the tool" title="Direct link to Step 4. Connect it to an AI app and check whether the AI picks the tool" translate="no">​</a></h2>
<p>Now let's plug this server into a real AI app. MCP is a shared standard, so any app that supports MCP can connect to the same server as is. Here I check with <strong>Codex CLI</strong>, a terminal app you use with a ChatGPT account. If you don't have Codex CLI, install it with the command on the <a href="https://developers.openai.com/codex/cli" target="_blank" rel="noopener noreferrer" class="">official Codex CLI page</a>, then run <code>codex</code> once and sign in with your ChatGPT account.</p>
<p><strong>① Register the server.</strong> Run the command below inside the <code>weather-mcp</code> folder. Replace <code>/absolute/path/</code> with this folder's actual path, which you can check with <code>pwd</code>.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">codex mcp </span><span class="token function" style="color:#d73a49">add</span><span class="token plain"> korea-weather -- uv run </span><span class="token parameter variable" style="color:#36acaa">--with</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"mcp&lt;2"</span><span class="token plain"> /absolute/path/weather-mcp/weather_server.py</span><br></div></code></pre></div></div>
<p>If you see <code>Added global MCP server 'korea-weather'.</code>, it's registered. Run <code>codex mcp list</code> and <code>korea-weather</code> shows up as <code>enabled</code>.</p>
<p><strong>② Allow this server's tools to run without asking.</strong> Codex asks for approval before calling an MCP tool. Since the weather lookup is read-only, open the config file <code>~/.codex/config.toml</code> and add one line at the bottom of the <code>[mcp_servers.korea-weather]</code> section that was just created.</p>
<div class="language-toml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">~/.codex/config.toml</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-toml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">[mcp_servers.korea-weather]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">command = "uv"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">args = ["run", "--with", "mcp&lt;2", "/absolute/path/weather-mcp/weather_server.py"]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">default_tools_approval_mode = "approve"</span><br></div></code></pre></div></div>
<p><code>default_tools_approval_mode</code> is a setting from the <a href="https://developers.openai.com/codex/mcp" target="_blank" rel="noopener noreferrer" class="">official Codex MCP docs</a>. It's safer not to add this line for tools that edit files or send messages.</p>
<p><strong>③ Just ask a question.</strong> Don't mention the tool name, latitude or longitude. Run it with web search turned off (I explain why right below).</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">codex </span><span class="token builtin class-name">exec</span><span class="token plain"> --skip-git-repo-check </span><span class="token parameter variable" style="color:#36acaa">-c</span><span class="token plain"> </span><span class="token assign-left variable" style="color:#36acaa">web_search</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">'"disabled"'</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Which is warmer right now, Busan or Jeju?"</span><br></div></code></pre></div></div>
<p>Here's the result from an actual run on the morning of October 7, 2026.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">codex</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Current weather changes in real time, so let me check it now. I'll compare the current temperatures in Busan and Jeju on the same basis.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">mcp: korea-weather/get_current_weather started</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">mcp: korea-weather/get_current_weather (completed)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">mcp: korea-weather/get_current_weather started</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">mcp: korea-weather/get_current_weather (completed)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">codex</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Right now, **Jeju is warmer.**</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- Busan: **19.4°C**</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- Jeju: **20.5°C**</span><br></div></code></pre></div></div>
<p>It called <code>get_current_weather</code> <strong>twice</strong>: once with <code>Busan</code> and once with <code>Jeju</code>. Nowhere in the question did it say "call get_current_weather twice." The AI read the one line in the description from Step 3 ("city must be one of Seoul, Busan or Jeju") and decided on its own how many times to call the tool and with which values. This is the biggest difference from Step 1, where a person typed in the address, latitude and longitude.</p>
<p><strong>If the AI does the choosing, it might not choose your tool.</strong> When I ran the same question with web search turned on, Codex answered by finding the Korea Meteorological Administration's page through web search instead of using <code>korea-weather</code>. When several tools are available, the AI picks one of them. To make sure it uses your tool, turn off the other tools as above, or put the tool name in the question, like "Use the korea-weather tool to tell me ~". With the tool name in the question, it called <code>korea-weather</code> twice and gave the same answer even with web search turned on.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-5-decide-whether-api-or-mcp-fits-your-situation">Step 5. Decide whether API or MCP fits your situation<a href="https://datapopcorn.ai/blog/en/api-vs-mcp-difference#step-5-decide-whether-api-or-mcp-fits-your-situation" class="hash-link" aria-label="Direct link to Step 5. Decide whether API or MCP fits your situation" title="Direct link to Step 5. Decide whether API or MCP fits your situation" translate="no">​</a></h2>
<p>Based on what we just did, the difference between the two comes down to <strong>"who decides what to call, and when."</strong></p>
<table><thead><tr><th></th><th>API</th><th>MCP</th></tr></thead><tbody><tr><td>Who calls it</td><td>Code a person wrote, in a fixed order</td><td>The AI, choosing based on the conversation</td></tr><tr><td>What it reads before calling</td><td>API docs written for people</td><td>A tool description the server publishes itself (<code>tools/list</code>)</td></tr><tr><td>Message format</td><td>Varies by service (REST, GraphQL, etc.)</td><td>Unified on JSON-RPC 2.0</td></tr><tr><td>How it connects</td><td>Separate integration code for each app</td><td>One server reused across many MCP-compatible apps</td></tr></tbody></table>
<p>Use these criteria to choose.</p>
<p><strong>When calling the API directly is the right fit</strong></p>
<ul>
<li class="">Work that <strong>must always run in the same order</strong>, like payment approval, sign-up, or collecting data at a set time every day</li>
<li class="">Work where servers need to exchange data quickly without any AI judgment</li>
</ul>
<p><strong>When wrapping it in MCP is the right fit</strong></p>
<ul>
<li class="">Internal data or tools you want the AI to <strong>pull out on its own when needed</strong> during a conversation</li>
<li class="">When you want several AI apps to use the same tool without code changes. With 3 AI apps and 4 services to connect, separate integrations mean building 3×4=12 of them, but one MCP server per service means you're done with 4</li>
<li class="">When you don't want to give the AI full access. The server in Step 2 exposes <strong>only one thing</strong>: weather lookups for Seoul, Busan and Jeju. This way, you can narrow what the AI can do in the server code</li>
</ul>
<p>In short, the right answer is closer to <strong>"call the API inside MCP" than "API or MCP?"</strong> If you already have an API you use, there's no need to throw it away: just wrap it with a description as in Step 2, and it becomes a tool the AI can use.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="if-you-hit-an-error-check-here-first">If you hit an error, check here first<a href="https://datapopcorn.ai/blog/en/api-vs-mcp-difference#if-you-hit-an-error-check-here-first" class="hash-link" aria-label="Direct link to If you hit an error, check here first" title="Direct link to If you hit an error, check here first" translate="no">​</a></h2>
<table><thead><tr><th>Symptom / message</th><th>Cause</th><th>Fix</th></tr></thead><tbody><tr><td><code>command not found: uv</code></td><td><code>uv</code> isn't installed, or the terminal was opened before installation</td><td>Run the install command from the prerequisites and open a new terminal</td></tr><tr><td><code>No module named 'mcp.server.fastmcp'. This is mcp 2.x ...</code></td><td>MCP Python SDK 2.x is installed. In 2.x, <code>FastMCP</code> was renamed to <code>MCPServer</code></td><td>Use 1.x with <code>--with "mcp&lt;2"</code> as in this post. To move to 2.x, follow the <a href="https://py.sdk.modelcontextprotocol.io/v2/migration/" target="_blank" rel="noopener noreferrer" class="">official migration guide</a></td></tr><tr><td>In Step 3 you get only two response lines and no weather result</td><td>Input ended before the weather was fetched, so the server shut down</td><td>Make sure you didn't drop <code>sleep 3</code> from the command; if your connection is slow, increase it to <code>sleep 5</code></td></tr><tr><td><code>Unsupported city</code></td><td>You used a city name that isn't in <code>CITIES</code></td><td>Add its latitude and longitude to <code>CITIES</code>, like <code>"Daegu": (35.8714, 128.6014)</code></td></tr><tr><td><code>MCP tool call requires approval, but approval policy is never</code></td><td><code>codex exec</code> can't ask for approval while running, and there's no tool approval setting</td><td>Add the <code>default_tools_approval_mode = "approve"</code> line as in Step 4 ②, or run <code>codex</code> interactively</td></tr><tr><td>You get an answer, but there's no <code>mcp: korea-weather/...</code> line</td><td>The AI chose another tool, such as web search, instead of yours</td><td>Turn off web search with <code>-c web_search='"disabled"'</code>, or add "use the korea-weather tool" to your question</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap-up<a href="https://datapopcorn.ai/blog/en/api-vs-mcp-difference#wrap-up" class="hash-link" aria-label="Direct link to Wrap-up" title="Direct link to Wrap-up" translate="no">​</a></h2>
<p><strong>Call the API directly with curl → wrap the same call as an MCP tool in one Python file → check the description the AI sees with <code>tools/list</code> → connect it to an AI app → pick the approach that fits your situation.</strong> With these five steps, you can see the difference between API and MCP for yourself instead of memorizing definitions. Next, pick a public data API you already use instead of Open-Meteo and swap out only the inside of the function from Step 2. You can also compare for yourself how well the AI picks the tool depending on how you write the description sentence.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="references">References<a href="https://datapopcorn.ai/blog/en/api-vs-mcp-difference#references" class="hash-link" aria-label="Direct link to References" title="Direct link to References" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://modelcontextprotocol.io/docs/getting-started/intro" target="_blank" rel="noopener noreferrer" class="">Official Model Context Protocol docs</a>: MCP architecture and specification</li>
<li class=""><a href="https://www.youtube.com/watch?v=185XGEMefgc" target="_blank" rel="noopener noreferrer" class="">Anthropic's official intro video: Introducing the Model Context Protocol</a></li>
<li class=""><a href="https://open-meteo.com/" target="_blank" rel="noopener noreferrer" class="">Open-Meteo</a>: the free weather API used in this post</li>
<li class=""><a href="https://py.sdk.modelcontextprotocol.io/v2/migration/" target="_blank" rel="noopener noreferrer" class="">MCP Python SDK 2.x migration guide</a></li>
</ul>]]></content:encoded>
            <category>AI work automation</category>
            <category>Agent</category>
        </item>
        <item>
            <title><![CDATA[Build Instagram's 'Comment and I'll DM You' Yourself, No Paid Tools]]></title>
            <link>https://datapopcorn.ai/blog/en/instagram-comment-auto-dm-n8n</link>
            <guid>https://datapopcorn.ai/blog/en/instagram-comment-auto-dm-n8n</guid>
            <pubDate>Sat, 03 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Using the Meta Instagram API's Private Replies and an n8n webhook, here's a 9-step setup that, when someone leaves a keyword comment on a specific Reel, checks whether they follow you and then DMs them a link automatically. Signature verification and duplicate prevention included.]]></description>
            <content:encoded><![CDATA[<p>The moment you write "Comment GPX and I'll DM it to you" in a Reels caption, you sign up for copying a link and sending a DM by hand every time someone comments. Follow this post and a single n8n workflow does that for you. When a comment containing the keyword arrives, a DM asking them to follow goes out within seconds, a "Sent you a DM" reply appears under their comment, and the link only goes out once they've actually followed. Nobody gets it twice. No monthly-subscription automation tool, just Meta's official API.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-it-looks-like-when-its-done">What it looks like when it's done<a href="https://datapopcorn.ai/blog/en/instagram-comment-auto-dm-n8n#what-it-looks-like-when-its-done" class="hash-link" aria-label="Direct link to What it looks like when it's done" title="Direct link to What it looks like when it's done" translate="no">​</a></h2>
<p>On the morning of October 6, 2026, I re-tested it by commenting <code>GPX</code> from a different account on a Reel on @course.hanjang, an account I run. A few seconds after commenting, a public reply appeared. (The account posts in Korean, so the messages in these screenshots are in Korean.)</p>
<p><img decoding="async" loading="lazy" alt="A few seconds after a GPX comment on the Reel, the course.hanjang account auto-replied &amp;quot;Sent you a DM! Please check your messages (requests).&amp;quot;" src="https://datapopcorn.ai/blog/en/assets/images/live-comment-reply-29d82d340281968da1935bfc6032890c.webp" width="1360" height="1220" class="img_ev3q"></p>
<p>The follow prompt was waiting in my DMs. When I replied "Done", the workflow checked that I followed the account and then sent the link for that trail course. Because I was on desktop web, the [Followed] button wasn't shown, so I typed "Done" as the message instructed.</p>
<p><img decoding="async" loading="lazy" alt="The follow-prompt DM that arrived right after the comment, the user&amp;#39;s &amp;quot;Done&amp;quot; reply, and then a DM with the GPX link for Seoul Trail Course 3 (Buramsan)" src="https://datapopcorn.ai/blog/en/assets/images/live-dm-flow-4eb67bf2a106cab876f7f349807315ac.webp" width="1600" height="1011" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-3-minute-summary-do-it-in-this-order">The 3-minute summary: do it in this order<a href="https://datapopcorn.ai/blog/en/instagram-comment-auto-dm-n8n#the-3-minute-summary-do-it-in-this-order" class="hash-link" aria-label="Direct link to The 3-minute summary: do it in this order" title="Direct link to The 3-minute summary: do it in this order" translate="no">​</a></h2>
<p>If you're short on time, you can follow just this list. The real screens and the spots where people get stuck are in Steps 1-9 below.</p>
<ol>
<li class=""><strong>Create a Meta app.</strong> Create an app at developers.facebook.com and pick the use case <strong>Manage messaging and content on Instagram</strong>. Add the three permissions (<code>basic</code>, <code>manage_comments</code>, <code>manage_messages</code>).</li>
<li class=""><strong>Make your account a tester.</strong> In App roles, add your Instagram account as an <strong>Instagram Tester</strong>, then accept it in Instagram under Settings → Apps and websites → <strong>Tester invites</strong>.</li>
<li class=""><strong>Generate a token.</strong> On the API setup screen, click <strong>Generate token</strong> and save the token and the <strong>Instagram app secret</strong> in your password manager.</li>
<li class=""><strong>Prepare n8n.</strong> Create two credentials (Header Auth for the token, Crypto for the secret) and two tables (<code>dm_rules</code>, <code>dm_log</code>). Every column is a string.</li>
<li class=""><strong>Paste in the workflow.</strong> Grab the workflow file for free from the link in Step 5, paste it in, re-select just the verify_token, credentials and tables, then <strong>Publish</strong>.</li>
<li class=""><strong>Connect the webhook.</strong> In Meta Webhooks, enter your n8n URL and verify_token and subscribe only to <code>comments</code> and <code>messages</code>. Run the <code>subscribed_apps</code> call once.</li>
<li class=""><strong>Publish the app.</strong> Add a privacy policy URL and switch the app to <strong>Published (Live)</strong>. In development mode, real comment notifications don't arrive.</li>
<li class=""><strong>Register a Reel and test.</strong> Add one row to <code>dm_rules</code> with the Reel's media_id, keyword and link, then confirm by leaving a keyword comment <strong>from an account that isn't yours</strong>.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="who-this-is-for">Who this is for<a href="https://datapopcorn.ai/blog/en/instagram-comment-auto-dm-n8n#who-this-is-for" class="hash-link" aria-label="Direct link to Who this is for" title="Direct link to Who this is for" translate="no">​</a></h2>
<ul>
<li class="">Creators and small business owners who want to run "comment and I'll send you the resource" campaigns on Reels and posts</li>
<li class="">People who'd rather build it themselves and understand how it works than pay for a DM automation tool like ManyChat</li>
<li class="">n8n users who want a real-world example of receiving events from an external service via webhook</li>
</ul>
<p>You'll need four things:</p>
<ol>
<li class="">A <strong>professional Instagram account</strong> (Business or Creator) that is <strong>public</strong>. Private accounts don't get comment notifications.</li>
<li class="">A <strong>Meta developer account</strong> (developers.facebook.com)</li>
<li class=""><strong>An n8n instance reachable from the internet over HTTPS.</strong> n8n Cloud or self-hosted with a domain both work. I used self-hosted n8n 2.38.5.</li>
<li class="">A <strong>privacy policy URL</strong> to enter when you publish the app</li>
</ol>
<p>All screens and settings here are ones I built and verified myself on October 3, 2026. I called the Graph API at v24.0, and the Meta webhook dashboard showed v26.0.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="first-how-a-comment-becomes-a-dm">First, how a comment becomes a DM<a href="https://datapopcorn.ai/blog/en/instagram-comment-auto-dm-n8n#first-how-a-comment-becomes-a-dm" class="hash-link" aria-label="Direct link to First, how a comment becomes a DM" title="Direct link to First, how a comment becomes a DM" translate="no">​</a></h2>
<p>The official name for this feature is <strong>Private Replies</strong>. It's an Instagram API feature that lets you send one DM to someone who commented on your post, based on that comment. You can send it even if they don't follow you; in that case it lands in their message requests.</p>
<p>The <a href="https://developers.facebook.com/docs/instagram-platform/private-replies" target="_blank" rel="noopener noreferrer" class="">official Meta docs</a> boil down to three rules:</p>
<ul>
<li class="">You can send <strong>only one</strong> DM per comment.</li>
<li class="">You must send it <strong>within 7 days</strong> of the comment.</li>
<li class="">If the person replies, you can send further messages for the next 24 hours.</li>
</ul>
<p>Here's the overall flow. When someone comments, Meta sends a "new comment" notification to your server (n8n, here). This notification is called a <strong>webhook</strong>. n8n checks that the notification really came from Meta, looks for the keyword, makes sure that comment hasn't been handled already, and then sends the DM.</p>
<p>There's one important constraint. <strong>You can only check via the API whether someone follows you after they've sent you a DM.</strong> According to the <a href="https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/messaging-api/user-profile" target="_blank" rel="noopener noreferrer" class="">User Profile API docs</a>, if you try to fetch a profile before the app user has messaged the app, you get an error saying user consent is required. And the <code>is_user_follow_business</code> field, which tells you whether they follow you, lives in exactly that API.</p>
<p>So the order works like this: in reply to the comment, send a prompt that says "Follow, then tap the button" along with a button. Only when they tap the button or reply do you check whether they follow and send the link. Tapping the button counts as sending you a DM, so the condition for the lookup is met.</p>
<!-- -->
<p>Here's the finished n8n workflow. The top three nodes handle the one-time check where Meta asks "Is this URL really your server?", the middle row receives comments and sends the follow prompt, and the bottom row checks follow status from the person's DM reply.</p>
<p><img decoding="async" loading="lazy" alt="The full n8n workflow. Top: webhook verification (GET). Middle: from receiving a comment to the follow-prompt DM. Bottom: from the DM reply to the follow check and sending the link" src="https://datapopcorn.ai/blog/en/assets/images/workflow-overview-b03411d00718d2d9e2dbcddc63c1f593.webp" width="1700" height="536" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1-create-a-meta-app-and-add-three-instagram-api-permissions">Step 1. Create a Meta app and add three Instagram API permissions<a href="https://datapopcorn.ai/blog/en/instagram-comment-auto-dm-n8n#step-1-create-a-meta-app-and-add-three-instagram-api-permissions" class="hash-link" aria-label="Direct link to Step 1. Create a Meta app and add three Instagram API permissions" title="Direct link to Step 1. Create a Meta app and add three Instagram API permissions" translate="no">​</a></h2>
<p>In <a href="https://developers.facebook.com/apps/" target="_blank" rel="noopener noreferrer" class="">Meta for Developers</a>, click <strong>Create app</strong>.</p>
<ol>
<li class="">Name the app. Matching it to your account name saves confusion later.</li>
<li class="">In the use case list, change the filter to <strong>All</strong> and pick <strong>Manage messaging and content on Instagram</strong>.</li>
<li class="">Connect a business portfolio. If you'll want App Review (Advanced Access) later, you need a <strong>business-verified portfolio</strong>, so pick that if you have one.</li>
<li class="">When you click Create app, it asks for your password again.</li>
</ol>
<p><img decoding="async" loading="lazy" alt="The Meta app&amp;#39;s use cases screen. The Manage messaging and content on Instagram use case has been added, with a Customize button on the right" src="https://datapopcorn.ai/blog/en/assets/images/step1-use-case-209fd77da7b6cb83c01806fde08204d0.webp" width="1600" height="285" class="img_ev3q"></p>
<p>Once the app exists, go to Use cases → <strong>Customize</strong> → <strong>API setup with Instagram login</strong>. Click <strong>Add all required permissions</strong> in box 1 and these three permissions are added. They're the only ones this feature needs.</p>
<table><thead><tr><th>Permission</th><th>Used for</th></tr></thead><tbody><tr><td><code>instagram_business_basic</code></td><td>Basic account info</td></tr><tr><td><code>instagram_business_manage_comments</code></td><td>Receiving comment webhooks, replying to comments</td></tr><tr><td><code>instagram_business_manage_messages</code></td><td>Sending private replies (DMs)</td></tr></tbody></table>
<p><img decoding="async" loading="lazy" alt="The API setup with Instagram login screen. The Instagram app name, app ID and app secret are at the top, and box 1 shows the three permissions added" src="https://datapopcorn.ai/blog/en/assets/images/step1-api-setup-c2426db0528267f90e60aafaf16ce4c0.webp" width="1600" height="688" class="img_ev3q"></p>
<p>The top of the same screen shows the <strong>Instagram app ID</strong> and the <strong>Instagram app secret</strong>. You'll use the secret in Step 4 to verify webhook signatures. These are different from the Facebook app ID, so don't mix them up.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2-add-your-instagram-account-as-an-instagram-tester-and-accept">Step 2. Add your Instagram account as an Instagram Tester and accept<a href="https://datapopcorn.ai/blog/en/instagram-comment-auto-dm-n8n#step-2-add-your-instagram-account-as-an-instagram-tester-and-accept" class="hash-link" aria-label="Direct link to Step 2. Add your Instagram account as an Instagram Tester and accept" title="Direct link to Step 2. Add your Instagram account as an Instagram Tester and accept" translate="no">​</a></h2>
<p>For the app to access your account, that account needs a role in the app.</p>
<ol>
<li class="">In the app's left menu, go to <strong>App roles → Roles</strong> and click <strong>Add people</strong>.</li>
<li class="">Choose <strong>Instagram Tester</strong> at the very bottom of the role list, enter your Instagram username and add it. The status shows "Pending".</li>
</ol>
<p><img decoding="async" loading="lazy" alt="The Add people to your app dialog. Under additional roles at the bottom, choose Instagram Tester and enter the Instagram username" src="https://datapopcorn.ai/blog/en/assets/images/step2-add-tester-0e3e8030395cbff931b75db8e35e72a9.webp" width="650" height="670" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" alt="The app roles list showing the course.hanjang account added as an Instagram Tester with Pending status" src="https://datapopcorn.ai/blog/en/assets/images/step2-tester-pending-7e06955c29a243f3c172fa16c2ebde2b.webp" width="1360" height="560" class="img_ev3q"></p>
<ol start="3">
<li class="">Log in to Instagram on the web with that account, then go to <strong>Settings → Website permissions → Apps and websites → Tester invites</strong> and click <strong>Accept</strong>. The direct URL is <code>instagram.com/accounts/manage_access/</code>.</li>
</ol>
<p>The invite doesn't show up under "Connected experiences" in Meta Accounts Center. Look for it in the Tester invites tab in Instagram's own settings.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3-generate-an-access-token">Step 3. Generate an access token<a href="https://datapopcorn.ai/blog/en/instagram-comment-auto-dm-n8n#step-3-generate-an-access-token" class="hash-link" aria-label="Direct link to Step 3. Generate an access token" title="Direct link to Step 3. Generate an access token" translate="no">​</a></h2>
<p>In box 2 on the API setup screen, <strong>Generate access tokens</strong>, click <strong>Generate token</strong> next to your account and an Instagram login window opens.</p>
<p>On the permissions consent screen, you can turn off <strong>Access and publish content</strong> and <strong>Access and manage insights</strong>. This feature only uses comments and messages, so it's safer not to grant permissions you don't need in the first place.</p>
<p>Once you allow it, the token is shown exactly once. You have to tick <strong>I understand</strong> before the token appears and can be copied. Save it to your password manager right away. Also note down the number shown in the same window (your Instagram user ID).</p>
<p><img decoding="async" loading="lazy" alt="The Token generated window: Instagram user ID under the account name, the I understand checkbox, the masked access token and a copy button. The user ID and token are hidden" src="https://datapopcorn.ai/blog/en/assets/images/step3-token-793b51da9f4843ea885bd6e1063a380d.webp" width="640" height="395" class="img_ev3q"></p>
<p>You can check the token straight from your browser's address bar:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">https://graph.instagram.com/v24.0/me?fields=user_id,username&amp;access_token=YOUR_ACCESS_TOKEN</span><br></div></code></pre></div></div>
<p>If your username comes back, it works. This token expires, so if you keep running this you'll need to refresh it before it does.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4-create-two-credentials-and-two-data-tables-in-n8n">Step 4. Create two credentials and two Data tables in n8n<a href="https://datapopcorn.ai/blog/en/instagram-comment-auto-dm-n8n#step-4-create-two-credentials-and-two-data-tables-in-n8n" class="hash-link" aria-label="Direct link to Step 4. Create two credentials and two Data tables in n8n" title="Direct link to Step 4. Create two credentials and two Data tables in n8n" translate="no">​</a></h2>
<p>So you never paste secrets directly into the workflow, set up storage first.</p>
<p><strong>Credential 1: token (Header Auth)</strong></p>
<ul>
<li class="">Credentials → Create credential → <strong>Header Auth</strong></li>
<li class="">Name: <code>Authorization</code>, Value: <code>Bearer YOUR_ACCESS_TOKEN</code> (one space after Bearer)</li>
<li class="">Change <strong>Allowed HTTP Request Domains</strong> to Specific Domains and enter only <code>graph.instagram.com</code>. This keeps the token from leaking to any other host.</li>
</ul>
<p><strong>Credential 2: app secret (Crypto)</strong></p>
<ul>
<li class="">Create credential → <strong>Crypto</strong> → put the Instagram app secret from Step 1 in <strong>Hmac Secret</strong>.</li>
<li class="">It's used to compute the signature that proves a webhook really came from Meta.</li>
</ul>
<p><strong>Data table 1: <code>dm_rules</code> (which post, which keyword, what to send)</strong></p>
<p>Create four columns, <strong>all string type</strong>: <code>media_id</code>, <code>keyword</code>, <code>title</code>, <code>link</code>.</p>
<p>Do not make <code>media_id</code> a number. Instagram media IDs are 17 digits long, like <code>17949931728293330</code>, and storing them as numbers changes the last digits. Importing a CSV may also guess "number", so it's safer to create an empty table and enter rows by hand.</p>
<p>You can look up a post's <code>media_id</code> with your token:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">https://graph.instagram.com/v24.0/me/media?fields=id,permalink,caption&amp;access_token=YOUR_ACCESS_TOKEN</span><br></div></code></pre></div></div>
<p>Use <code>permalink</code> to identify the Reel, then put its <code>id</code> in <code>media_id</code>. An example row:</p>
<table><thead><tr><th>media_id</th><th>keyword</th><th>title</th><th>link</th></tr></thead><tbody><tr><td>17949931728293330</td><td>GPX</td><td>Seoul Trail Course 15: Noeul &amp; Haneul Park (7.5km)</td><td><a href="https://course-hanjang.vercel.app/15" target="_blank" rel="noopener noreferrer" class="">https://course-hanjang.vercel.app/15</a></td></tr></tbody></table>
<p>The table I actually run has one row per Reel, as below. This screen is from my own account, so the title column is named <code>course_name</code>, but the downloadable workflow JSON uses <code>title</code>. If you're creating the table from scratch, name it <code>title</code>.</p>
<p><img decoding="async" loading="lazy" alt="The n8n Data table course_dm_rules, with six Reels registered one per row in the media_id, keyword (GPX), course_name and link columns" src="https://datapopcorn.ai/blog/en/assets/images/step4-dm-rules-1b541486f283da4e3b09256290d52315.webp" width="1600" height="465" class="img_ev3q"></p>
<p><strong>Data table 2: <code>dm_log</code> (send history, for duplicate prevention)</strong></p>
<p>Seven columns, all string: <code>comment_id</code>, <code>media_id</code>, <code>username</code>, <code>comment_text</code>, <code>status</code>, <code>error</code>, <code>igsid</code>.</p>
<p>Don't skip <code>igsid</code>. When someone who got the follow prompt later replies "Done", the workflow uses this value to find their pending request. Without the column, the comment-side DM goes out but the link DM never does.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-5-bring-in-the-workflow-and-change-just-three-things">Step 5. Bring in the workflow and change just three things<a href="https://datapopcorn.ai/blog/en/instagram-comment-auto-dm-n8n#step-5-bring-in-the-workflow-and-change-just-three-things" class="hash-link" aria-label="Direct link to Step 5. Bring in the workflow and change just three things" title="Direct link to Step 5. Bring in the workflow and change just three things" translate="no">​</a></h2>
<p>You don't need to build all 29 nodes from scratch. I've put the finished workflow file on Groble (a Korean digital-product marketplace) as a free (0 KRW) product. Unzip the download, open the JSON file, copy everything, click an empty n8n canvas and paste. The ZIP also includes a README listing the changes below.</p>
<p><a href="https://www.groble.im/products/UeYwxn" target="_blank" rel="noopener noreferrer" class="">Get the Instagram comment auto-DM workflow for free (Groble)</a></p>
<p>The node names in the downloadable workflow are in Korean. Below I've translated them and put the original label in parentheses where it helps you find the node.</p>
<p>After pasting, change three things:</p>
<ol>
<li class="">In the <strong>verify_token matches?</strong> node (<code>verify_token 일치?</code>), replace the comparison value <code>여기에_내가_정한_verify_token</code> (a placeholder meaning "your verify_token goes here") with a long string nobody could guess. You'll enter the exact same value in Meta in Step 7.</li>
<li class="">In the seven nodes that use credentials (<strong>Compute signature</strong>, the two <strong>Follow-prompt DM</strong> nodes, <strong>Reply to comment</strong>, <strong>Check follow status</strong>, <strong>Send link DM</strong>, <strong>Follow reminder</strong>), select the credentials from Step 4.</li>
<li class="">In the eight Data table nodes, re-select <code>dm_rules</code> and <code>dm_log</code>. You have to pick the table from the list once more for the columns to load.</li>
</ol>
<p><code>@{계정핸들}</code> in the JSON is the placeholder for your account handle in the follow-prompt message. Put your Instagram account there, and change the default link in the <strong>Build link message</strong> code node (<code>링크 메시지 만들기</code>) to your own site's listing page.</p>
<p>Here's what each node does. If this is new to you, open the nodes one by one in this order and the structure will click.</p>
<table><thead><tr><th>Node</th><th>What it does</th></tr></thead><tbody><tr><td>Meta webhook verification (GET) → verify_token matches? (<code>Meta 웹훅 검증 (GET)</code> → <code>verify_token 일치?</code>)</td><td>The check request Meta sends when you first register the URL. If the token matches, it returns the <code>hub.challenge</code> value as-is; otherwise 403</td></tr><tr><td>Receive comment event (POST) (<code>댓글 이벤트 수신 (POST)</code>)</td><td>Where real comment and DM notifications arrive. <strong>The Raw Body option must be on</strong> for signature verification to work</td></tr><tr><td>Extract raw body &amp; signature → Compute signature → Really from Meta? (<code>원문·서명 꺼내기</code> → <code>서명 계산</code> → <code>Meta가 보낸 게 맞나?</code>)</td><td>Computes HMAC-SHA256 over the received raw body with the app secret and compares it with the <code>X-Hub-Signature-256</code> header. If they differ, it stops there</td></tr><tr><td>Extract comments only / Extract DM replies only (<code>댓글만 꺼내기</code> / <code>DM 답장만 꺼내기</code>)</td><td>Splits by notification type. The comment branch <strong>excludes comments made by your own account</strong>, and the DM branch excludes messages you sent (<code>is_echo</code>)</td></tr><tr><td>Load rules → Check keyword (<code>규칙 불러오기</code> → <code>키워드 확인</code>)</td><td>Passes only if the post is registered in <code>dm_rules</code> and the keyword is present. Case and spaces are ignored</td></tr><tr><td>Skip already-handled comments (<code>이미 보낸 댓글 제외</code>)</td><td>Stops if <code>dm_log</code> already has the same <code>comment_id</code></td></tr><tr><td>Follow-prompt DM (Private Reply) (<code>팔로우 안내 DM (Private Reply)</code>)</td><td>Sends a private reply with the follow prompt and a [Followed] button</td></tr><tr><td>Follow-prompt DM, retry without button (<code>팔로우 안내 DM (버튼 없이 재시도)</code>)</td><td>If the call above fails, resends just the text without the button. If this also fails, it's logged as <code>failed</code></td></tr><tr><td>Attach IGSID → Reply to comment → Log (<code>IGSID 붙이기</code> → <code>댓글에 답글 달기</code> → <code>기록</code>)</td><td>Posts the public reply and logs <code>awaiting_follow</code> along with the person's Instagram-scoped ID (IGSID)</td></tr><tr><td>Find pending follow request → Group requests (<code>팔로우 대기 요청 찾기</code> → <code>요청 묶기</code>)</td><td>Passes only if the person who sent the DM has a pending request. Ordinary inquiry DMs stop here</td></tr><tr><td>Check follow status (<code>팔로우 여부 확인</code>)</td><td>Looks up <code>is_user_follow_business</code> on that person's profile</td></tr><tr><td>Following? (<code>팔로우 중?</code>)</td><td>If true, sends the link DM and updates the log to <code>sent</code>; if false, sends a reminder DM</td></tr></tbody></table>
<p>The core DM call is a single request. The first DM, replying to a comment, specifies the recipient by <strong>comment ID</strong> rather than user ID; follow-up DMs are sent by <strong>IGSID</strong>.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">POST https://graph.instagram.com/v24.0/{your Instagram user ID}/messages</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Authorization: Bearer {token}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">{"recipient": {"comment_id": "{comment ID}"}, "message": {"text": "Follow us, then tap the button.", "quick_replies": [{"content_type": "text", "title": "Followed", "payload": "FOLLOW_CHECK"}]}}</span><br></div></code></pre></div></div>
<p>When the button is tapped, the DM arrives with <code>payload</code> set to <code>FOLLOW_CHECK</code>. Quick replies aren't displayed on desktop, so the prompt also says "If you don't see the button, reply 'Done'", and the DM branch accepts replies that arrive as plain <code>text</code> too.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-6-publish-the-workflow-and-test-the-url-first">Step 6. Publish the workflow and test the URL first<a href="https://datapopcorn.ai/blog/en/instagram-comment-auto-dm-n8n#step-6-publish-the-workflow-and-test-the-url-first" class="hash-link" aria-label="Direct link to Step 6. Publish the workflow and test the URL first" title="Direct link to Step 6. Publish the workflow and test the URL first" translate="no">​</a></h2>
<p>Click <strong>Publish</strong> in the top right. Publishing is what activates the production webhook URL (<code>https://your-n8n-host/webhook/ig-comment-dm</code>).</p>
<p>Before registering it with Meta, check it yourself in the browser:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">https://your-n8n-host/webhook/ig-comment-dm?hub.mode=subscribe&amp;hub.challenge=12345&amp;hub.verify_token=YOUR_VERIFY_TOKEN</span><br></div></code></pre></div></div>
<p>If the page shows only <code>12345</code>, it's working. If you deliberately enter the wrong token, you should get <code>forbidden</code>. Once both check out, move on to the Meta side.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-7-register-the-webhook-url-with-meta-and-subscribe-to-only-the-fields-you-need">Step 7. Register the webhook URL with Meta and subscribe to only the fields you need<a href="https://datapopcorn.ai/blog/en/instagram-comment-auto-dm-n8n#step-7-register-the-webhook-url-with-meta-and-subscribe-to-only-the-fields-you-need" class="hash-link" aria-label="Direct link to Step 7. Register the webhook URL with Meta and subscribe to only the fields you need" title="Direct link to Step 7. Register the webhook URL with Meta and subscribe to only the fields you need" translate="no">​</a></h2>
<p>In box 3 on the API setup screen, <strong>Configure webhooks</strong>:</p>
<ol>
<li class="">Enter the webhook URL from Step 6 as the <strong>Callback URL</strong> and your verify_token as the <strong>Verify token</strong>, then click <strong>Verify and save</strong>. Meta sends a GET request to check it, and on success the list of fields appears.</li>
<li class="">In the field list, turn on <strong>only <code>comments</code> and <code>messages</code></strong> and leave the rest off. You need <code>messages</code> to receive the DM when someone taps the button. Turning on fields like <code>live_comments</code> also catches livestream chat and just inflates your execution count.</li>
<li class="">On the <code>comments</code> row, click <strong>Test → Send to server</strong> and Meta sends a signed sample notification. If an execution appears in n8n Executions and the <strong>Really from Meta?</strong> node passes as true, signature verification is set up correctly. The sample's post ID is fake, so it's normal for it to stop at <strong>Check keyword</strong>.</li>
</ol>
<p><img decoding="async" loading="lazy" alt="The sample dialog for the comments field in Meta Webhooks. Below the sample JSON is a Send to server v26.0 button, and the field list behind it shows subscription status" src="https://datapopcorn.ai/blog/en/assets/images/step7-webhook-test-50fe81b6071fe19a2a3fdf6aa39fc2b7.webp" width="1360" height="1000" class="img_ev3q"></p>
<p>Finally, turn on the subscription so your account sends notifications to this app. This call is a POST, so run it once from an n8n HTTP Request node or a terminal, not the browser address bar.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">-X</span><span class="token plain"> POST </span><span class="token string" style="color:#e3116c">"https://graph.instagram.com/v24.0/me/subscribed_apps?subscribed_fields=comments,messages&amp;access_token=YOUR_ACCESS_TOKEN"</span><br></div></code></pre></div></div>
<p>You're done when you see <code>{"success":true}</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-8-switch-the-app-to-published-live">Step 8. Switch the app to Published (Live)<a href="https://datapopcorn.ai/blog/en/instagram-comment-auto-dm-n8n#step-8-switch-the-app-to-published-live" class="hash-link" aria-label="Direct link to Step 8. Switch the app to Published (Live)" title="Direct link to Step 8. Switch the app to Published (Live)" translate="no">​</a></h2>
<p>The dashboard says "Apps must be published to receive webhooks." In development mode, only the test-button notifications arrive; real comment notifications don't.</p>
<ol>
<li class="">In <strong>App settings → Basic</strong>, enter a <strong>Privacy policy URL</strong> and a <strong>Data deletion instructions URL</strong> and save.</li>
</ol>
<p><img decoding="async" loading="lazy" alt="The App settings Basic screen with the Privacy policy URL, Terms of service URL and user data deletion (Data deletion instructions URL) filled in" src="https://datapopcorn.ai/blog/en/assets/images/step8-basic-settings-e74b12bbbdadc0b4d674962557ac46f4.webp" width="1360" height="420" class="img_ev3q"></p>
<ol start="2">
<li class="">In the left menu under <strong>Publish</strong>, once you see "All required app settings are complete", click <strong>Publish</strong>.</li>
</ol>
<p><img decoding="async" loading="lazy" alt="The Publish screen showing that all required app settings are complete and listing the app&amp;#39;s use cases, with an Unpublished marker next to Publish in the left menu" src="https://datapopcorn.ai/blog/en/assets/images/step8-publish-525bb8fcdffddbcdd39a115e17761fde.webp" width="1600" height="406" class="img_ev3q"></p>
<p>When it's done, the marker next to <strong>Publish</strong> in the left menu changes to "Published".</p>
<p><img decoding="async" loading="lazy" alt="The app dashboard with a Published marker next to Publish in the left menu" src="https://datapopcorn.ai/blog/en/assets/images/step8-live-7f0f0cddfc8dd410659722251ba56f1c.webp" width="1600" height="513" class="img_ev3q"></p>
<p>Publishing is not the same as App Review. You can publish without App Review. However, the <a href="https://developers.facebook.com/docs/instagram-platform/webhooks" target="_blank" rel="noopener noreferrer" class="">Meta webhooks docs</a> say <code>comments</code> notifications require <strong>Advanced Access</strong>, and you only get Advanced Access by passing App Review. I checked how far it actually works with the results in Step 9.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-9-test-with-a-real-comment">Step 9. Test with a real comment<a href="https://datapopcorn.ai/blog/en/instagram-comment-auto-dm-n8n#step-9-test-with-a-real-comment" class="hash-link" aria-label="Direct link to Step 9. Test with a real comment" title="Direct link to Step 9. Test with a real comment" translate="no">​</a></h2>
<p>Now, from <strong>an account that isn't yours</strong>, leave a keyword comment on a Reel you registered in <code>dm_rules</code>. Comments from your own account are deliberately dropped at <strong>Extract comments only</strong>, so they won't work as a test.</p>
<p>Here's what happened when I commented <code>GPX</code> on the Seoul Trail Course 15 Reel from a personal account that has no role in the app:</p>
<table><thead><tr><th>Time (KST)</th><th>What happened</th></tr></thead><tbody><tr><td>00:06:27</td><td>The personal account posts a <code>GPX</code> comment</td></tr><tr><td>00:06:31</td><td>n8n execution starts. Signature check → keyword check → duplicate check all pass</td></tr><tr><td>00:06:37</td><td>Private-reply DM sent successfully (<code>message_id</code> returned in the response); "Sent it! Check your DMs." reply posted on the comment</td></tr><tr><td>00:06:41</td><td>Another webhook arrives because of the reply I just posted. The author is my own account, so it's filtered out in 27ms</td></tr></tbody></table>
<p>From comment to public reply took about 10 seconds, and <code>dm_log</code> got one row marked <code>sent</code>. This test was run <strong>without App Review, with only the Publish (Live) from Step 8</strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="october-6-re-test-the-full-flow-including-the-follow-check">October 6 re-test: the full flow including the follow check<a href="https://datapopcorn.ai/blog/en/instagram-comment-auto-dm-n8n#october-6-re-test-the-full-flow-including-the-follow-check" class="hash-link" aria-label="Direct link to October 6 re-test: the full flow including the follow check" title="Direct link to October 6 re-test: the full flow including the follow check" translate="no">​</a></h3>
<p>The test above was on a version from before the follow-check step was added. With the current workflow, which includes the follow check, I re-tested by commenting <code>GPX</code> on the live Seoul Trail Course 3 (Buramsan) Reel from a personal account (already following). The actual screens are at the top of this post under "What it looks like when it's done".</p>
<table><thead><tr><th>Time (KST)</th><th>What happened</th></tr></thead><tbody><tr><td>09:26:17</td><td>The comment notification starts an n8n execution. In 4.8 seconds: follow-prompt DM sent, "Sent you a DM!" reply posted on the comment, <code>awaiting_follow</code> logged in <code>dm_log</code></td></tr><tr><td>09:26:21, 09:26:24</td><td>Two executions from my own reply and DM bouncing back as notifications. Both stop immediately at <strong>Extract comments only / Extract DM replies only</strong></td></tr><tr><td>09:28:59</td><td>"Done" reply via DM. Check follow status (<code>true</code>) → Course 3 link DM sent → <code>dm_log</code> updated to <code>sent</code>. 3.1 seconds</td></tr></tbody></table>
<p><img decoding="async" loading="lazy" alt="The 09:26:17 execution in n8n Executions. It succeeded in 4.8 seconds, with the middle-row nodes passing in green" src="https://datapopcorn.ai/blog/en/assets/images/live-n8n-comment-448e2694a5c84533e755bbb244740896.webp" width="1600" height="1161" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" alt="The 09:28:59 execution in n8n Executions. The bottom row passed from Check follow status through Send link DM and Update log (sent), with a status sent row in the output. Comment ID and IGSID are hidden" src="https://datapopcorn.ai/blog/en/assets/images/live-n8n-link-05d2dead5c94efab3cb847205605acf4.webp" width="1600" height="1161" class="img_ev3q"></p>
<p>Both runs were with the app only Published (Live), no App Review. If you send "Done" without following, you get a "We can't see your follow yet" reminder; follow and send it again, and the link arrives. Right after following, it can take a few seconds for Meta to reflect it, so I mention that in the reminder message.</p>
<p>That said, Meta's docs still say <code>comments</code> notifications require Advanced Access, and this result was confirmed with a single comment on October 3, 2026. If you plan to run large campaigns on an account with many followers, it's safer to request Advanced Access for <code>instagram_business_manage_comments</code> and <code>instagram_business_manage_messages</code> through <strong>App Review</strong> in box 5 of the app dashboard. Review requires business verification and a screencast demonstrating the feature.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-people-commonly-get-stuck">Where people commonly get stuck<a href="https://datapopcorn.ai/blog/en/instagram-comment-auto-dm-n8n#where-people-commonly-get-stuck" class="hash-link" aria-label="Direct link to Where people commonly get stuck" title="Direct link to Where people commonly get stuck" translate="no">​</a></h2>
<table><thead><tr><th>Symptom</th><th>Cause and fix</th></tr></thead><tbody><tr><td>Meta fails to verify the callback URL</td><td>Either the workflow isn't published, or you entered the test URL (<code>/webhook-test/</code>). Use the production <code>/webhook/</code> URL</td></tr><tr><td>Meta's test notification keeps coming out false at the signature check</td><td>If the <strong>Raw Body</strong> option on the POST webhook node is off, n8n rebuilds the JSON and the raw body changes. Also, you need the <strong>Instagram app secret</strong>, not the Facebook app secret</td></tr><tr><td>The DM call fails with a "requested user can't be found" error (code 100, subcode 2534014)</td><td>The comment ID isn't a real comment (e.g. a fake test ID), or the comment was deleted. Comments older than 7 days can't be replied to either</td></tr><tr><td>The same person gets the DM twice</td><td>The condition column in <code>dm_log</code> is empty. Open the <strong>Skip already-handled comments</strong> node and check that Column is set to <code>comment_id</code></td></tr><tr><td>A node's Column is stuck on "Loading options..."</td><td>Re-select the table once from the Data table list and the columns will load</td></tr><tr><td><code>media_id</code> is right but the keyword never matches</td><td>The <code>media_id</code> column is a number type and the last digits changed. Recreate it as a string</td></tr><tr><td>The n8n editor shows Offline in the top right and there's no Publish button</td><td>The connection between your browser and n8n dropped. If a refresh doesn't fix it, flushing the browser's socket pools (<code>chrome://net-internals/#sockets</code> → Flush socket pools) reconnects it</td></tr><tr><td>It tries to DM in response to your own auto-reply</td><td>Check that the line in <strong>Extract comments only</strong> that excludes comments whose author ID equals your account ID hasn't been deleted</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="adding-it-to-a-new-post">Adding it to a new post<a href="https://datapopcorn.ai/blog/en/instagram-comment-auto-dm-n8n#adding-it-to-a-new-post" class="hash-link" aria-label="Direct link to Adding it to a new post" title="Direct link to Adding it to a new post" translate="no">​</a></h2>
<p>Don't touch the workflow. After posting a new Reel, look up its <code>id</code> with the <code>media/</code> query from Step 4 and add one row to <code>dm_rules</code>; it applies to that post immediately. Comments on unregistered posts are ignored even if they contain the keyword, so to end a campaign, just delete that row.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up">Wrapping up<a href="https://datapopcorn.ai/blog/en/instagram-comment-auto-dm-n8n#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up" translate="no">​</a></h2>
<p>All you need is one Meta app, one token, one n8n workflow and two tables. Once it's set up, each new post is one more row in the table, and the send log tells you exactly who got what and when.</p>
<p>When I built this, most of my time went not into the feature itself but into everything around it: the tester invite hides in Instagram's settings rather than Accounts Center, 17-digit media IDs must be stored as text, apps in development mode don't receive real comment notifications, and you can only check follow status after the person DMs you first. Know those four things going in, and you can get your first DM within an hour.</p>
<p>The same structure works no matter what you're sending. For campaigns that send a PDF, a sign-up form or a product page link in response to words like "resource", "apply" or "price", attach the same workflow. If you want to drop the follow gate, delete the <strong>Check follow status</strong> node and the <strong>Following?</strong> branch and have it send the link straight away.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="adding-other-instagram-accounts">Adding other Instagram accounts<a href="https://datapopcorn.ai/blog/en/instagram-comment-auto-dm-n8n#adding-other-instagram-accounts" class="hash-link" aria-label="Direct link to Adding other Instagram accounts" title="Direct link to Adding other Instagram accounts" translate="no">​</a></h2>
<p>One workflow can handle several accounts. Leave the Meta app as it is and just add accounts.</p>
<p><strong>Adding an account to the same app.</strong> In App roles → Roles, add the account as an <strong>Instagram Tester</strong>, log in to Instagram with that account and accept under Settings → Website permissions → Tester invites. Generate a token for that account on the API setup screen and run the subscription call below once with that token, and you're done. Comments from the other account arrive at the same webhook URL, so <strong>you don't need to touch the workflow.</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">-X</span><span class="token plain"> POST </span><span class="token string" style="color:#e3116c">"https://graph.instagram.com/v24.0/me/subscribed_apps?subscribed_fields=comments,messages&amp;access_token=THAT_ACCOUNTS_TOKEN"</span><br></div></code></pre></div></div>
<p><strong>Each account has its own token.</strong> The send-DM API runs with the permissions of the token's account, so you can't DM on another account's posts with one account's token. For one workflow to handle several accounts, create one credential per account and have the DM call nodes pick the right credential for each account. Keep the sharing scope of those n8n Credentials at <strong>Personal</strong> and separate them per account. Even within the same Meta app, anyone you share the workflow with can see the token.</p>
<p><strong>Distinguishing accounts in the rules table.</strong> Right now <code>dm_rules</code> only has <code>media_id</code>, so mixing accounts is fine: media IDs don't collide across accounts. But if you attach two apps (different Meta apps) to the same n8n, <code>media_id</code> alone won't tell them apart, so add an <code>account</code> column to the table and a branch that picks that account's token and credential when building the link.</p>
<p><strong>A practical rule of thumb for many accounts.</strong> With 2-3 accounts, it's better to keep a separate workflow per account: credentials stay separate, a problem on one account doesn't affect the others, and it's easy to remove later. Once you're at 10 or more, it pays to merge them into one workflow with credential branching.</p>
<p><strong>What to check.</strong> After adding another account, run Step 9 once with it. Just check three things: the tester invite was accepted, the <code>IGSID</code> is saved when a comment arrives, and the link goes out after the follow check.</p>]]></content:encoded>
            <category>Content automation</category>
            <category>n8n</category>
            <category>Guide</category>
        </item>
        <item>
            <title><![CDATA[Inside the App for n8n's First Official Conference, 'In The Loop 2026': Fliplet Low-Code and the Design of Node Runner Gamification]]></title>
            <link>https://datapopcorn.ai/blog/en/n8n-in-the-loop-fliplet-conference-app-review</link>
            <guid>https://datapopcorn.ai/blog/en/n8n-in-the-loop-fliplet-conference-app-review</guid>
            <pubDate>Sat, 03 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA['In The Loop 2026', n8n's first official in-person conference, takes place in Berlin in October 2026. I connected the official mobile app (Fliplet Hub) installed on my phone over wireless ADB and took it apart: the session agenda, the networking concierge, floorplan pinpointing, and 'Node Runner', a game where you roam the venue scanning node QR codes to complete a workflow. A look at the tech stack behind a developer event.]]></description>
            <content:encoded><![CDATA[<p>From October 13 to 14, 2026, n8n holds <strong>'In The Loop 2026'</strong> in Berlin, Germany: the first official large-scale in-person conference in the company's history. Ahead of the event, which brings together around 1,200 automation builders from around the world, I connected the official mobile app to my Android phone over wireless ADB and took it apart. Two things stood out. Instead of a native app, it's built on the enterprise low-code platform <strong>Fliplet Hub</strong>, with real-time OTA updates that skip store review (v51) and dynamic floorplan pinpointing. And it uses <strong>'Node Runner' gamification</strong>, where you hunt for node QR codes hidden around the venue to level up a workflow.</p>
<!-- -->
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>What this post covers</div><div class="admonitionContent_BuS1"><ol>
<li class=""><strong>Event overview &amp; agenda</strong>: the schedule of the first n8n conference at Wilhelm Studios in Berlin (Partner Day &amp; Ecosystem Day) and the key talk tracks</li>
<li class=""><strong>Deployment architecture (Fliplet &amp; OTA)</strong>: a low-code hybrid container that pushes on-site changes within seconds, no app store review</li>
<li class=""><strong>Context-linked floorplan</strong>: location pinpointing UX that jumps from session cards and the networking concierge straight to the venue map (Hall B and C)</li>
<li class=""><strong>'Node Runner' gamification</strong>: an engagement design where you roam the venue scanning node QR codes to build workflows and earn swag</li>
<li class=""><strong>Global enterprise sponsors</strong>: global companies such as SAP, Accenture and Capgemini that show where n8n now stands</li>
</ol></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="background-n8ns-first-official-in-person-conference">Background: n8n's first official in-person conference<a href="https://datapopcorn.ai/blog/en/n8n-in-the-loop-fliplet-conference-app-review#background-n8ns-first-official-in-person-conference" class="hash-link" aria-label="Direct link to Background: n8n's first official in-person conference" title="Direct link to Background: n8n's first official in-person conference" translate="no">​</a></h2>
<p>If n8n is your main tool for real-world automation and building agents, <strong>In The Loop 2026</strong> is the event to watch this fall. Held over two days, October 13 to 14, at Wilhelm Studios, a historic industrial space in Berlin, it's the first official large-scale in-person event hosted by n8n itself.</p>
<p>The event is split into two tracks:</p>
<ul>
<li class=""><strong>October 13 (Partner Day)</strong>: invitation-only sessions for global solution partners and ambassadors</li>
<li class=""><strong>October 14 (Ecosystem Day)</strong>: open sessions where around 1,200 ticket-holding builders gather for the product vision, the AI agent roadmap and workshops</li>
</ul>
<p>After registering, I installed the official app on a device, then captured screens and interacted with it over wireless ADB to study the details of how a mobile app for a developer event is designed.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-deployment-architecture-why-fliplet-low-code-instead-of-native">1. Deployment architecture: why Fliplet low-code instead of native?<a href="https://datapopcorn.ai/blog/en/n8n-in-the-loop-fliplet-conference-app-review#1-deployment-architecture-why-fliplet-low-code-instead-of-native" class="hash-link" aria-label="Direct link to 1. Deployment architecture: why Fliplet low-code instead of native?" title="Direct link to 1. Deployment architecture: why Fliplet low-code instead of native?" translate="no">​</a></h2>
<p>The first thing I noticed on launching the app was this message on the splash screen: <strong>"Content update was found. The update will start in a second."</strong></p>
<figure class="phone_gNkg"><a href="https://datapopcorn.ai/blog/en/assets/images/01-splash-ota-3cd60ad64f81923c872b06948accdc2e.webp" target="_blank" rel="noreferrer"><img src="https://datapopcorn.ai/blog/en/assets/images/01-splash-ota-3cd60ad64f81923c872b06948accdc2e.webp" alt="Launch splash screen of the n8n In The Loop 2026 app" loading="lazy"></a><figcaption><p><strong>Figure 1.</strong> The splash screen right after launch, detecting and downloading the latest content update</p></figcaption></figure>
<p>When building a dedicated app for a big conference, many teams build a native app in React Native or Flutter and ship it to the App Store and Google Play. But an in-person conference is an <strong>extreme real-time environment where the schedule changes on the day, booths get moved, and urgent announcements come up constantly</strong>. If app store review takes 24-48 hours, you can't respond to emergencies on site.</p>
<p>Opening the app info (<code>About this app</code>) made the reason for this architecture clear.</p>
<figure class="phone_gNkg"><a href="https://datapopcorn.ai/blog/en/assets/images/11-fliplet-tech-info-e15793c8f4c5fda67a338870be77cca7.webp" target="_blank" rel="noreferrer"><img src="https://datapopcorn.ai/blog/en/assets/images/11-fliplet-tech-info-e15793c8f4c5fda67a338870be77cca7.webp" alt="App info screen showing the Fliplet technology base" loading="lazy"></a><figcaption><p><strong>Figure 2.</strong> Info based on Fliplet framework 6.3.2, including the session ID, build ID and an OTA update check.</p></figcaption></figure>
<figure class="phone_gNkg"><a href="https://datapopcorn.ai/blog/en/assets/images/12-ota-history-8c8ded5ae4a6ea47e0148abc8e525d7d.webp" target="_blank" rel="noreferrer"><img src="https://datapopcorn.ai/blog/en/assets/images/12-ota-history-8c8ded5ae4a6ea47e0148abc8e525d7d.webp" alt="Real-time OTA update history" loading="lazy"></a><figcaption><p><strong>Figure 3.</strong> Update history from version 43 to 51, deployed instantly over a few days without app store re-review</p></figcaption></figure>
<p>The n8n team used <strong>Fliplet Hub</strong>, an enterprise low-code/no-code mobile platform.</p>
<ul>
<li class=""><strong>Native hybrid container</strong>: supports QR scanning via camera permission, offline caching and push notifications, while</li>
<li class=""><strong>Web-based real-time OTA (Over-The-Air) deployment</strong>: refreshes HTML/JS/CSS assets and the database instantly without redeploying to the app store.</li>
<li class="">As the screenshot above shows, in just a few days from September 27 to October 2, <strong>8 updates from v43 to v51</strong> were pushed straight to attendees' devices with no review.</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-the-home-dashboard-and-the-full-15-item-menu">2. The home dashboard and the full 15-item menu<a href="https://datapopcorn.ai/blog/en/n8n-in-the-loop-fliplet-conference-app-review#2-the-home-dashboard-and-the-full-15-item-menu" class="hash-link" aria-label="Direct link to 2. The home dashboard and the full 15-item menu" title="Direct link to 2. The home dashboard and the full 15-item menu" translate="no">​</a></h2>
<p>After dismissing the pop-up notice, the home screen packs an attendee's on-site journey down to the essentials.</p>
<figure class="phone_gNkg"><a href="https://datapopcorn.ai/blog/en/assets/images/02-home-dashboard-6f60e39a8aca8c3cfcb5b617f419e4f8.webp" target="_blank" rel="noreferrer"><img src="https://datapopcorn.ai/blog/en/assets/images/02-home-dashboard-6f60e39a8aca8c3cfcb5b617f419e4f8.webp" alt="In The Loop 2026 home dashboard" loading="lazy"></a><figcaption><p><strong>Figure 4.</strong> A badge pickup QR banner at the top, four core feature cards, and a personalized list of bookmarked agenda items</p></figcaption></figure>
<ol>
<li class=""><strong>Pink banner at the top</strong>: one tap opens the <code>QR code for badge pickup</code> to show staff as soon as you arrive.</li>
<li class=""><strong>Core quick menu</strong>: jumps straight to the four main features: <code>Event Agenda</code>, <code>My Meetings</code>, <code>Node Runner</code> and <code>Merch Quest</code>.</li>
<li class=""><strong>Personalized section</strong>: your saved sessions and RSVPs show up right at the bottom of the home screen.</li>
</ol>
<p>Tap the hamburger menu in the top right and you can see the structure of all 15 features plus the app details at a glance.</p>
<figure class="phone_gNkg"><a href="https://datapopcorn.ai/blog/en/assets/images/03-menu-structure-e032c7e177a8295c08fc8ef838862dce.webp" target="_blank" rel="noreferrer"><img src="https://datapopcorn.ai/blog/en/assets/images/03-menu-structure-e032c7e177a8295c08fc8ef838862dce.webp" alt="In The Loop 2026 full menu drawer" loading="lazy"></a><figcaption><p><strong>Figure 5.</strong> The full 15-item menu drawer, covering networking, maps, gamification, scanner and operations</p></figcaption></figure>
<ul>
<li class=""><strong>Attendee identity &amp; profile</strong>: My QR Code (for badge pickup and networking), My Profile (organization, title, areas of expertise)</li>
<li class=""><strong>Exploring the event</strong>: Agenda (sessions by day and stage), Attendees &amp; Speakers (attendee and speaker directory), Sponsors &amp; Exhibitors (sponsors and booth partners), Interactive Map (Wilhelm Studios floorplan)</li>
<li class=""><strong>Interaction &amp; networking</strong>: Networking (1:1 meetings and the networking concierge), My Scanner (camera scanner for badge QR and node QR codes)</li>
<li class=""><strong>Gamification</strong>: Node Runner (exploring nodes around the venue and building workflows), Merch Quest (swag race and merch redemption)</li>
<li class=""><strong>Operations &amp; session management</strong>: Notifications (on-site announcements), Privacy Policy (external link to n8n's official privacy policy), Log Out (sign out of the attendee session), Exit (return to the main Fliplet Hub container)</li>
<li class=""><strong>Footer</strong>: About this app (Fliplet platform build info and real-time OTA release notes)</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-agenda-and-session-tracks-a-peek-at-n8ns-next-roadmap">3. Agenda and session tracks: a peek at n8n's next roadmap<a href="https://datapopcorn.ai/blog/en/n8n-in-the-loop-fliplet-conference-app-review#3-agenda-and-session-tracks-a-peek-at-n8ns-next-roadmap" class="hash-link" aria-label="Direct link to 3. Agenda and session tracks: a peek at n8n's next roadmap" title="Direct link to 3. Agenda and session tracks: a peek at n8n's next roadmap" translate="no">​</a></h2>
<p>The agenda tab lays out a timeline from October 11 to 15 along with session lists by stage.</p>
<figure class="phone_gNkg"><a href="https://datapopcorn.ai/blog/en/assets/images/04-agenda-tracks-0db5aba87567e8433cb79d99ea31422c.webp" target="_blank" rel="noreferrer"><img src="https://datapopcorn.ai/blog/en/assets/images/04-agenda-tracks-0db5aba87567e8433cb79d99ea31422c.webp" alt="Event agenda timeline and tracks" loading="lazy"></a><figcaption><p><strong>Figure 6.</strong> Sessions sorted by stage: Main Stage, Automation Stage, Builders Stage and more</p></figcaption></figure>
<p>The conference runs on three main stages:</p>
<ul>
<li class=""><strong>Main Stage</strong>: keynotes and strategic product vision</li>
<li class=""><strong>Automation Stage</strong>: business GTM and enterprise solution partner adoption stories</li>
<li class=""><strong>Builders Stage</strong>: hands-on node development and practitioner-focused workflow techniques</li>
</ul>
<p>Among the published session titles, several stood out as hints about where the n8n ecosystem is going:</p>
<ul>
<li class=""><strong>"The n8n Product: Our Roadmap and Vision"</strong>: the next product roadmap, straight from the n8n core team</li>
<li class=""><strong>"Building the Future of the Ambassadors Program"</strong>: the strategy for growing the global community and ambassador ecosystem</li>
<li class=""><strong>"GTM Evolution of n8n"</strong>: the growth formula behind the move from open source into the enterprise B2B market</li>
<li class=""><strong>"Working With n8n: Nodes in Practice"</strong>: practical node use and custom node development</li>
<li class=""><strong>"Lessons From Canva With Gonzalo Rosado"</strong>: how the global design platform Canva uses n8n</li>
</ul>
<p>Tap a session and a bottom sheet slides up with details, track tags and a <strong>shortcut to the venue Floorplan</strong>.</p>
<figure class="phone_gNkg"><a href="https://datapopcorn.ai/blog/en/assets/images/05-session-detail-4fef3c75ab528af0b0b8d416a5ba6cbc.webp" target="_blank" rel="noreferrer"><img src="https://datapopcorn.ai/blog/en/assets/images/05-session-detail-4fef3c75ab528af0b0b8d416a5ba6cbc.webp" alt="Session detail bottom sheet" loading="lazy"></a><figcaption><p><strong>Figure 7.</strong> Session detail card: stage location, time, track tags and a Floorplan shortcut button</p></figcaption></figure>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-dynamic-floorplan-ux-that-ties-sessions-and-the-map-together">4. Dynamic floorplan: UX that ties sessions and the map together<a href="https://datapopcorn.ai/blog/en/n8n-in-the-loop-fliplet-conference-app-review#4-dynamic-floorplan-ux-that-ties-sessions-and-the-map-together" class="hash-link" aria-label="Direct link to 4. Dynamic floorplan: UX that ties sessions and the map together" title="Direct link to 4. Dynamic floorplan: UX that ties sessions and the map together" translate="no">​</a></h2>
<p>The most common annoyance at a conference is opening a separate map and wandering around trying to figure out which stage the session that's starting right now is on.</p>
<p>In the In The Loop app, tapping the <strong>Floorplan</strong> icon on a session card, or <strong>Networking Concierge</strong> on the networking screen, switches straight to the venue map (Hall B, Hall C) and automatically highlights the location with a pulse-ring pin.</p>
<figure class="phone_gNkg"><a href="https://datapopcorn.ai/blog/en/assets/images/08-interactive-floorplan-d57c2c65251752ebe5596b51a05e804a.webp" target="_blank" rel="noreferrer"><img src="https://datapopcorn.ai/blog/en/assets/images/08-interactive-floorplan-d57c2c65251752ebe5596b51a05e804a.webp" alt="Hall B venue floorplan screen" loading="lazy"></a><figcaption><p><strong>Figure 8.</strong> Hall B floorplan: stage locations (such as the Automation Stage), booths and lounge areas are marked with pins.</p></figcaption></figure>
<p>Tabs at the bottom of the map let you move between floors and halls (Hall B ↔ Hall C), and you can zoom and filter for specific facilities (restrooms, coffee stations, the merch booth). Instead of a heavy 3D map renderer, it hooks a lightweight vector-based interactive SVG map into a low-code component, and it ran smoothly without stutter on any device.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-the-highlight-node-runner-and-merch-quest-gamification">5. The highlight: 'Node Runner' and 'Merch Quest' gamification<a href="https://datapopcorn.ai/blog/en/n8n-in-the-loop-fliplet-conference-app-review#5-the-highlight-node-runner-and-merch-quest-gamification" class="hash-link" aria-label="Direct link to 5. The highlight: 'Node Runner' and 'Merch Quest' gamification" title="Direct link to 5. The highlight: 'Node Runner' and 'Merch Quest' gamification" translate="no">​</a></h2>
<p>By far the most interesting parts of this app were the <strong>Node Runner</strong> and <strong>Merch Quest</strong> screens.</p>
<p>Most conferences run an ordinary stamp tour where you collect stamps at booths, or hand out stickers and merch for filling in a survey. n8n instead turned its core identity, <strong>'connecting nodes'</strong>, into an in-person experience.</p>
<figure class="phone_gNkg"><a href="https://datapopcorn.ai/blog/en/assets/images/09-node-runner-4cf60e1759c6128b84fc8ad303a55a16.webp" target="_blank" rel="noreferrer"><img src="https://datapopcorn.ai/blog/en/assets/images/09-node-runner-4cf60e1759c6128b84fc8ad303a55a16.webp" alt="Node Runner gamification screen" loading="lazy"></a><figcaption><p><strong>Figure 9.</strong> Node Runner: an n8n workflow canvas banner drawn in retro 8-bit pixel art</p></figcaption></figure>
<figure class="phone_gNkg"><a href="https://datapopcorn.ai/blog/en/assets/images/10-merch-quest-ec15a1392e114cd21b83e11b14298de4.webp" target="_blank" rel="noreferrer"><img src="https://datapopcorn.ai/blog/en/assets/images/10-merch-quest-ec15a1392e114cd21b83e11b14298de4.webp" alt="Merch Quest swag race screen" loading="lazy"></a><figcaption><p><strong>Figure 10.</strong> Merch Quest: limited-edition swag (cap, hoodie, beanie) unlocked with on-site participation points</p></figcaption></figure>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-node-runner-works">How Node Runner works<a href="https://datapopcorn.ai/blog/en/n8n-in-the-loop-fliplet-conference-app-review#how-node-runner-works" class="hash-link" aria-label="Direct link to How Node Runner works" title="Direct link to How Node Runner works" translate="no">​</a></h3>
<ol>
<li class=""><strong>Find the QR codes</strong>: hunt for the various node QR codes hidden in every corner of the Wilhelm Studios venue.</li>
<li class=""><strong>Level up (Build workflows)</strong>: scan a QR code with the app's built-in camera scanner (<code>My Scanner</code>) and the node is added to your inventory; combining nodes to complete virtual workflows raises your level.</li>
<li class=""><strong>Win merch (Swag Race)</strong>: earn points by leveling up, then unlock and collect limited-edition merch for each tier (ball cap, hoodie, beanie and so on) at the Swag Race booth before it runs out.</li>
</ol>
<p>The <strong>8-bit pixel n8n workflow</strong> in the top banner (<code>Run</code> ➔ <code>Sample Idea</code> ➔ <code>AI Agent Cons</code> ➔ <code>Cons Model</code> / <code>AI Agent Market</code>) nails n8n's particular geeky sensibility.</p>
<p>It's a clever design: instead of wandering the venue as a passive visitor, attendees become "players collecting nodes" and naturally end up visiting every sponsor booth and every corner of every stage.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-the-attendee-directory-and-enterprise-partners">6. The attendee directory and enterprise partners<a href="https://datapopcorn.ai/blog/en/n8n-in-the-loop-fliplet-conference-app-review#6-the-attendee-directory-and-enterprise-partners" class="hash-link" aria-label="Direct link to 6. The attendee directory and enterprise partners" title="Direct link to 6. The attendee directory and enterprise partners" translate="no">​</a></h2>
<p>The networking and sponsor screens also show how mature the n8n ecosystem has become.</p>
<figure class="phone_gNkg"><a href="https://datapopcorn.ai/blog/en/assets/images/06-attendees-networking-bb58167de281e70f60830db20fc8dca9.webp" target="_blank" rel="noreferrer"><img src="https://datapopcorn.ai/blog/en/assets/images/06-attendees-networking-bb58167de281e70f60830db20fc8dca9.webp" alt="Attendee and speaker directory" loading="lazy"></a><figcaption><p><strong>Figure 11.</strong> Attendee and speaker list: a filter for people you've scanned, search, bookmarks and linked profile details</p></figcaption></figure>
<p>In the attendee list, you can scan the badge QR code of someone you talked to on site and collect them under <code>Scanned by Me</code>, then export the full list as CSV/data after the event. Speaker profiles link directly to the sessions that speaker is running and to their LinkedIn profile, which keeps networking going naturally before and after the event.</p>
<figure class="phone_gNkg"><a href="https://datapopcorn.ai/blog/en/assets/images/07-sponsors-partners-fe0ed6bb603be8d6606b08c53b49604e.webp" target="_blank" rel="noreferrer"><img src="https://datapopcorn.ai/blog/en/assets/images/07-sponsors-partners-fe0ed6bb603be8d6606b08c53b49604e.webp" alt="Sponsor and exhibitor list" loading="lazy"></a><figcaption><p><strong>Figure 12.</strong> The global enterprise sponsor lineup (SAP, Accenture, Capgemini, AVANAI, EXXETA, SVA)</p></figcaption></figure>
<p>The sponsor list includes top-tier global enterprise IT and consulting firms such as <strong>SAP</strong>, <strong>Accenture</strong> and <strong>Capgemini</strong> as main partners. It's a strong signal that n8n has grown beyond a local automation tool for individual developers into an orchestration engine for the core business processes of large global companies.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="takeaways-3-lessons-for-designing-conference-and-community-apps">Takeaways: 3 lessons for designing conference and community apps<a href="https://datapopcorn.ai/blog/en/n8n-in-the-loop-fliplet-conference-app-review#takeaways-3-lessons-for-designing-conference-and-community-apps" class="hash-link" aria-label="Direct link to Takeaways: 3 lessons for designing conference and community apps" title="Direct link to Takeaways: 3 lessons for designing conference and community apps" translate="no">​</a></h2>
<p>Here are the practical lessons from exploring the app hands-on.</p>
<ol>
<li class=""><strong>What matters most in an event app is on-site response speed (OTA), not polish</strong>
<ul>
<li class="">Native store apps struggle to reflect same-day changes because of review delays.</li>
<li class="">Low-code webview containers like Fliplet use hardware sensors (camera QR, push notifications) while also enabling updates within seconds via real-time OTA (v43-v51), which drastically lowers on-site operational risk.</li>
</ul>
</li>
<li class=""><strong>Gamification should match the product's core grammar</strong>
<ul>
<li class="">Rather than the usual check-in stamp tour, it applies a grammar unique to n8n, "find node QR codes around the venue and complete a workflow", which maximizes attendee engagement.</li>
</ul>
</li>
<li class=""><strong>Context linking removes breaks in navigation</strong>
<ul>
<li class="">Smooth transitions from session info ➔ stage location pin ➔ real-time wayfinding greatly reduce the fatigue attendees feel at the venue.</li>
</ul>
</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="next-steps">Next steps<a href="https://datapopcorn.ai/blog/en/n8n-in-the-loop-fliplet-conference-app-review#next-steps" class="hash-link" aria-label="Direct link to Next steps" title="Direct link to Next steps" translate="no">​</a></h2>
<ul>
<li class=""><strong>Conference keynote analysis</strong>: I plan to quickly summarize and share the AI agent and official product roadmap n8n presents on site at In The Loop 2026 on October 13-14.</li>
<li class=""><strong>Applying this to hands-on events</strong>: I plan to bring the same kind of QR node-collecting gamification and web-based on-site interaction design into the in-person hands-on AI seminars DataPopcorn runs in Korea (such as Waguwagu Club).</li>
</ul>]]></content:encoded>
            <category>AI work automation</category>
            <category>n8n</category>
            <category>Work tools</category>
        </item>
        <item>
            <title><![CDATA[Why I Left My Job as an 8-Year Data Analyst to Start DataPopcorn]]></title>
            <link>https://datapopcorn.ai/blog/en/why-i-started-datapopcorn</link>
            <guid>https://datapopcorn.ai/blog/en/why-i-started-datapopcorn</guid>
            <pubDate>Fri, 02 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[After working with data at SOCAR, TADA, Zigzag and Coupang, I left to start DataPopcorn. I pictured a 'DataPopcorn Universe' where analysts create content and courses together, but 80% of first-year revenue came from lectures I taught myself, and when the lectures stopped, the company had nothing it could do on its own. This is everything I think about DataPopcorn: why I started it, a look back at year one, what students taught me, the year-two strategy, what I learned running a company on my own money, and why I now want to go back to working at a company.]]></description>
            <content:encoded><![CDATA[<p>I spent close to eight years working with data at SOCAR, TADA, Zigzag and Coupang, then left to start DataPopcorn. The first picture I had in mind was a "DataPopcorn Universe": working analysts writing content and sharing courses together. A year in, though, roughly 80% of revenue (by my own estimate) came from lectures I taught in person, and once the lectures dried up, DataPopcorn had nothing it could do on its own. This post is a portfolio of everything I think about DataPopcorn: why I started it, what actually happened in the first year, what students taught me, the plan for year two, what I learned running a company on my own money for a year and a half, and why I now want to go back to working at a company.</p>
<!-- -->
<p><img decoding="async" loading="lazy" alt="An 8-year analyst leaves the company: DataPopcorn career thumbnail" src="https://datapopcorn.ai/blog/en/assets/images/career-cover-742ca32be7f0ba645c2577dfb63b9b28.webp" width="1600" height="900" class="img_ev3q"></p>
<blockquote>
<p>A year spent getting ready to move beyond growth built on lectures and outsourced projects, and to leap into a scalable business built on DataPopcorn's own platform.</p>
<p>That is how I summed up DataPopcorn's first year in one sentence.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="i-was-a-data-analyst">I was a data analyst<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#i-was-a-data-analyst" class="hash-link" aria-label="Direct link to I was a data analyst" title="Direct link to I was a data analyst" translate="no">​</a></h2>
<p>Before I left, I worked in data analysis and data engineering. Here is where I worked.</p>
<table><thead><tr><th>Industry</th><th>Company</th></tr></thead><tbody><tr><td>Mobility</td><td>SOCAR (Korea's largest car-sharing service), TADA (VCNC, ride hailing)</td></tr><tr><td>E-commerce</td><td>Kakao Style (Zigzag, a fashion shopping app), Coupang (Korea's largest e-commerce company)</td></tr><tr><td>OTT</td><td>Coupang Play</td></tr></tbody></table>
<p>The domain kept changing, from mobility to commerce to OTT to advertising, but the core of the job stayed the same: using the company's data to solve the problems the company had chosen.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="i-left-for-three-reasons">I left for three reasons<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#i-left-for-three-reasons" class="hash-link" aria-label="Direct link to I left for three reasons" title="Direct link to I left for three reasons" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-i-wanted-to-do-wasnt-on-the-companys-priority-list">What I wanted to do wasn't on the company's priority list<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#what-i-wanted-to-do-wasnt-on-the-companys-priority-list" class="hash-link" aria-label="Direct link to What I wanted to do wasn't on the company's priority list" title="Direct link to What I wanted to do wasn't on the company's priority list" translate="no">​</a></h3>
<p>Inside a company, the data team moves within the company's products and roadmap. That's how it should be. But most of what I wanted to try sat outside that, and inside the company its turn never came. The gap between what I was good at and what I wanted to do kept growing.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="i-wanted-to-really-use-ai-in-my-work">I wanted to really use AI in my work<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#i-wanted-to-really-use-ai-in-my-work" class="hash-link" aria-label="Direct link to I wanted to really use AI in my work" title="Direct link to I wanted to really use AI in my work" translate="no">​</a></h3>
<p>As AI and automation tools started pouring out, I wanted to see for myself what changes when you plug them into real work. Not a meeting that ends with "let's consider adopting it," but building something with my own hands, running it, and teaching other people how it works.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="i-could-see-myself-a-few-years-out">I could see myself a few years out<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#i-could-see-myself-a-few-years-out" class="hash-link" aria-label="Direct link to I could see myself a few years out" title="Direct link to I could see myself a few years out" translate="no">​</a></h3>
<p>At year eight, I could picture myself a few years ahead pretty clearly: someone who does the current job a little better. Not a bad future, but I didn't want that to be all there was. And it felt like leaving would only get harder with time.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-first-picture-the-datapopcorn-universe">The first picture: the DataPopcorn Universe<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#the-first-picture-the-datapopcorn-universe" class="hash-link" aria-label="Direct link to The first picture: the DataPopcorn Universe" title="Direct link to The first picture: the DataPopcorn Universe" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-company-i-wanted-to-build">The company I wanted to build<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#the-company-i-wanted-to-build" class="hash-link" aria-label="Direct link to The company I wanted to build" title="Direct link to The company I wanted to build" translate="no">​</a></h3>
<p>When I started DataPopcorn, the company I imagined had three faces.</p>
<ul>
<li class="">An <strong>education company</strong> that teaches practical data and AI automation skills</li>
<li class="">A <strong>group of experts</strong> who raise companies' productivity with no-code tools like n8n and AI</li>
<li class="">A <strong>consultant</strong> who lowers the barrier to technology for individuals and companies</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-i-really-wanted-was-an-mcn">What I really wanted was an MCN<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#what-i-really-wanted-was-an-mcn" class="hash-link" aria-label="Direct link to What I really wanted was an MCN" title="Direct link to What I really wanted was an MCN" translate="no">​</a></h3>
<p>There was a bigger ambition inside that. What I really wanted to build was less a company and more a kind of MCN (multi-channel network). I wanted my own crew, so to speak.</p>
<ul>
<li class="">Recruit working analysts to contribute data content</li>
<li class="">Turn those contributions into videos for YouTube and Instagram</li>
<li class="">Train junior instructors and pass lecture requests on to them when they came in</li>
</ul>
<p>The idea was not just me, but many analysts creating content and courses together, and I called it the "DataPopcorn Universe."</p>
<!-- -->
<p>It's a loop: the crew in the middle writes content and takes on lectures, the people we meet through content and lectures gather in the community, and from there come new people to work with. The universe splits into three pillars.</p>
<table><thead><tr><th>Pillar</th><th>What it does</th></tr></thead><tbody><tr><td>Education · consulting · mentoring</td><td>Corporate training, courses for outside education providers, work automation consulting, mentoring (online and offline)</td></tr><tr><td>Creators</td><td>Publishing data and AI automation content on YouTube and Instagram</td></tr><tr><td>Community</td><td>Wagu Wagu</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-i-first-planned-to-make-money">How I first planned to make money<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#how-i-first-planned-to-make-money" class="hash-link" aria-label="Direct link to How I first planned to make money" title="Direct link to How I first planned to make money" translate="no">​</a></h3>
<p>The revenue model was simple.</p>
<table><thead><tr><th>Category</th><th>Initial plan</th></tr></thead><tbody><tr><td>Lectures</td><td>B2B corporate training, teaching for outside education providers (Learning Spoons, Codeit, Team Sparta and others, all Korean online education companies)</td></tr><tr><td>Consulting</td><td>Custom work automation setups for each company</td></tr><tr><td>Services</td><td>Planning stage</td></tr><tr><td>Products</td><td>Planning stage</td></tr></tbody></table>
<p>I left services and products as "to be planned later." At the time I had no idea how that decision would come back a year later.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-i-actually-did-in-a-year">What I actually did in a year<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#what-i-actually-did-in-a-year" class="hash-link" aria-label="Direct link to What I actually did in a year" title="Direct link to What I actually did in a year" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="lectures-consulting-and-small-experiments">Lectures, consulting and small experiments<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#lectures-consulting-and-small-experiments" class="hash-link" aria-label="Direct link to Lectures, consulting and small experiments" title="Direct link to Lectures, consulting and small experiments" translate="no">​</a></h3>
<p>Most of the first year was lectures and consulting. Here's a summary.</p>
<table><thead><tr><th>Category</th><th>What I did</th><th>Revenue contribution</th><th>What was hard</th><th>Would I do it again?</th></tr></thead><tbody><tr><td>Lectures</td><td>Government-funded training (Woongjin KDT advanced course; KDT is Korea's government-funded K-Digital Training program), corporate B2B data and AI automation tracks</td><td>High</td><td>Time spent on in-person training and resources spread thin</td><td>Yes</td></tr><tr><td>Consulting</td><td>Work automation diagnosis and adoption guides for early-stage companies and students</td><td>Medium</td><td>The effort of proposing and building from scratch every time</td><td>Needs to be packaged after setup</td></tr><tr><td>Service experiments</td><td>Planning our own platform, running an n8n book on WikiDocs (a Korean online publishing platform)</td><td>Medium</td><td>No immediate revenue while still in planning</td><td>Yes (will grow it)</td></tr><tr><td>Products / Smart Store (Naver's online storefront platform)</td><td>None</td><td>Low</td><td>-</td><td>-</td></tr></tbody></table>
<p>Some of the organizations I worked with: LG Electronics, Upstage, Mirae Asset Securities, Lotte Card, LG Uplus, Incheon Sejong Hospital, Kakao Style and Coupang Play.</p>
<p>Along the way I co-authored the book <em>n8n Does It All</em> (《n8n이 다 해줌》) with Hanbit Media (a Korean tech publisher), I run the Korean n8n community as an n8n Global Seoul Ambassador, and I spoke on stage at Google I/O Seoul. This is also when I started building up content on WikiDocs and YouTube.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="best-selling-topics-vs-best-received-topics">Best-selling topics vs. best-received topics<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#best-selling-topics-vs-best-received-topics" class="hash-link" aria-label="Direct link to Best-selling topics vs. best-received topics" title="Direct link to Best-selling topics vs. best-received topics" translate="no">​</a></h3>
<p>The three topics that sold the most lectures and consulting were:</p>
<ul>
<li class="">n8n basics, prerequisites and tool setup</li>
<li class="">Prompt engineering and applying AI to real work</li>
<li class="">Data-driven work automation</li>
</ul>
<p>What got the best reaction was a bit different: <strong>hands-on training where people automate their own real work with no-code (n8n)</strong>, rather than theory, and <strong>workflows for unattended blog and social media posting</strong>. Intro courses sold the most, but the moment students were most engaged was when they saw their own work actually running on its own.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="year-one-in-numbers">Year one in numbers<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#year-one-in-numbers" class="hash-link" aria-label="Direct link to Year one in numbers" title="Direct link to Year one in numbers" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="80-of-revenue-was-lectures">80% of revenue was lectures<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#80-of-revenue-was-lectures" class="hash-link" aria-label="Direct link to 80% of revenue was lectures" title="Direct link to 80% of revenue was lectures" translate="no">​</a></h3>
<p>Looking back on the year, I wrote down my rough sense of the revenue split.</p>
<table><thead><tr><th>Item</th><th style="text-align:right">Share</th><th>Details</th></tr></thead><tbody><tr><td>Lectures</td><td style="text-align:right">80%</td><td>Learning Spoons, short- and long-term corporate B2B training, etc. All taught by me</td></tr><tr><td>Consulting</td><td style="text-align:right">15%</td><td>Follow-up consulting after training and individual requests</td></tr><tr><td>Services / automation</td><td style="text-align:right">5%</td><td>Early tests and building our own workflows</td></tr><tr><td>Products / Smart Store</td><td style="text-align:right">0%</td><td>Plan to expand into selling packages and workflows</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-made-the-most-money-and-why">What made the most money, and why<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#what-made-the-most-money-and-why" class="hash-link" aria-label="Direct link to What made the most money, and why" title="Direct link to What made the most money, and why" translate="no">​</a></h3>
<p>The two things that brought in the most revenue and were the most efficient:</p>
<ol>
<li class="">Long-term teaching for outside education providers (KDT and others) and B2B clients</li>
<li class="">Short corporate seminars and AI literacy and automation training for employees</li>
</ol>
<p>There were reasons it made money.</p>
<ul>
<li class=""><strong>Customers were in a hurry.</strong> Following the AI trend, demand for retraining employees exploded at company after company.</li>
<li class=""><strong>It was something I could do well.</strong> Practical data and n8n expertise, plus the teaching experience I had built up, matched that demand.</li>
<li class=""><strong>The rates were good.</strong> B2B lectures funded by corporate and government training budgets paid well.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="but-this-model-couldnt-last">But this model couldn't last<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#but-this-model-couldnt-last" class="hash-link" aria-label="Direct link to But this model couldn't last" title="Direct link to But this model couldn't last" translate="no">​</a></h3>
<p>It made money, but I felt it would be hard to keep going on this model alone, because most revenue came from in-person training that I, the CEO, taught myself. There were four reasons.</p>
<ol>
<li class="">It took far too much of one person's time.</li>
<li class="">It depended heavily on me. If I didn't teach, revenue was zero.</li>
<li class="">It wasn't recurring revenue. When a course ended, the next month started from zero again.</li>
<li class="">No platform, no asset, was building up inside the company.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-turned-out-differently-than-i-expected">What turned out differently than I expected<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#what-turned-out-differently-than-i-expected" class="hash-link" aria-label="Direct link to What turned out differently than I expected" title="Direct link to What turned out differently than I expected" translate="no">​</a></h2>
<p>Three things surprised me.</p>
<p><strong>First, the business was tied directly to my time and stamina as the instructor.</strong> The more lectures I took on, the more revenue grew, but the less time I had to prepare the next one and the less room to try anything new.</p>
<p><strong>Second, customers wanted working tools, not training.</strong> Demand for workflows and automation tools that actually run was far bigger than demand for one-off training.</p>
<p><strong>Third, teaching how to use the tool wasn't enough.</strong> Students wanted tangible results beyond how to use n8n, such as monetization and automation for their specific role.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-i-learned-from-students">What I learned from students<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#what-i-learned-from-students" class="hash-link" aria-label="Direct link to What I learned from students" title="Direct link to What I learned from students" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-three-questions-i-kept-getting">The three questions I kept getting<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#the-three-questions-i-kept-getting" class="hash-link" aria-label="Direct link to The three questions I kept getting" title="Direct link to The three questions I kept getting" translate="no">​</a></h3>
<p>Almost every question I got in lectures and consulting came down to these three.</p>
<blockquote>
<p>"How do I set up n8n? I got an error, how do I fix it?"</p>
<p>"How can I use what I learned in my role (PM, HR, marketing) right away?"</p>
<p>"Can I use this to automate Shorts or a blog and make money from it?"</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-students-really-wanted">What students really wanted<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#what-students-really-wanted" class="hash-link" aria-label="Direct link to What students really wanted" title="Direct link to What students really wanted" translate="no">​</a></h3>
<p>Putting those three questions side by side, I saw what they had in common. Not one of them asked me to explain the underlying principles more. What students wanted was:</p>
<ul>
<li class="">How to use AI in their own work</li>
<li class="">How to save time with automation</li>
<li class="">Real tools, templates and ready-to-use workflows</li>
</ul>
<p>In one sentence: not studying principles, but <strong>finished workflows that cut their working hours right now, plus maintenance support</strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="problems-worth-paying-for">Problems worth paying for<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#problems-worth-paying-for" class="hash-link" aria-label="Direct link to Problems worth paying for" title="Direct link to Problems worth paying for" translate="no">​</a></h3>
<p>So I separately listed the problems customers would actually pay to solve.</p>
<table><thead><tr><th>Problem</th><th>Customer</th><th>Likelihood to pay</th><th>Potential as a service</th></tr></thead><tbody><tr><td>Adopting n8n workflows tailored to each role</td><td>Solo businesses, startups</td><td>High</td><td>High</td></tr><tr><td>Setting up AI automation for monetization (blogs, Shorts)</td><td>Side hustlers, marketers</td><td>High</td><td>High</td></tr><tr><td>n8n troubleshooting and after-sales support</td><td>Practitioners, company staff in charge</td><td>Medium</td><td>High (sold as a package)</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-went-well-in-year-one">What went well in year one<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#what-went-well-in-year-one" class="hash-link" aria-label="Direct link to What went well in year one" title="Direct link to What went well in year one" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="results">Results<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#results" class="hash-link" aria-label="Direct link to Results" title="Direct link to Results" translate="no">​</a></h3>
<ul>
<li class=""><strong>I generated revenue.</strong> Outside lectures such as KDT and B2B training created stable cash flow.</li>
<li class=""><strong>I built teaching and consulting skills.</strong> After many lectures, I turned the curriculum into a structured path from beginner to practitioner.</li>
<li class=""><strong>I built brand trust.</strong> "n8n no-code automation = DataPopcorn" became a thing, and it led to a book contract with Hanbit Media.</li>
<li class=""><strong>I set a direction for AI and automation.</strong> I finished a concrete roadmap for moving beyond plain education into selling n8n workflows and micro-services.</li>
</ul>
<p>In concrete terms:</p>
<ol>
<li class="">Winning and successfully running major outside training contracts, such as the Woongjin KDT advanced course</li>
<li class="">Finishing plans for building our own platform in 2026 and selling VOD courses and packages</li>
<li class="">Preparing the Hanbit Media n8n book <em>n8n Does It All</em> for publication</li>
<li class="">Starting to build content assets on YouTube, WikiDocs and elsewhere</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-sets-datapopcorn-apart">What sets DataPopcorn apart<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#what-sets-datapopcorn-apart" class="hash-link" aria-label="Direct link to What sets DataPopcorn apart" title="Direct link to What sets DataPopcorn apart" translate="no">​</a></h3>
<p>I think this is why customers chose DataPopcorn: we explain things in a way that fits real work, we can connect AI, data and automation, and there is trust in Inyoung Lee's personal expertise.</p>
<p>If I pick three core strengths:</p>
<ol>
<li class=""><strong>Practical automation skills that combine n8n with AI tools (Claude and others).</strong> I'm confident this is among the best in Korea.</li>
<li class=""><strong>Teaching that explains complex technology at a non-developer's level.</strong> I think this is where I'm strongest by a wide margin.</li>
<li class=""><strong>Execution.</strong> I can build micro-services and workflows myself with vibe coding.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-i-regret-about-year-one">What I regret about year one<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#what-i-regret-about-year-one" class="hash-link" aria-label="Direct link to What I regret about year one" title="Direct link to What I regret about year one" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="i-chased-revenue-and-neglected-branding">I chased revenue and neglected branding<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#i-chased-revenue-and-neglected-branding" class="hash-link" aria-label="Direct link to I chased revenue and neglected branding" title="Direct link to I chased revenue and neglected branding" translate="no">​</a></h3>
<p>Looking back after the first half, this is what I regret most. I started fast, but I was so focused on making DataPopcorn profitable that I never built a proper brand.</p>
<p>Most lectures went through outside education providers and corporate training, and DataPopcorn had almost no channel of its own for meeting customers directly. While things were going well, I didn't see the problem. But the moment the lectures stopped, there was nothing DataPopcorn could do on its own through its own platform.</p>
<p>I also wrote down how customers see DataPopcorn:</p>
<ul>
<li class="">A company built on Inyoung Lee's personal brand</li>
<li class="">An n8n and automation speaking company</li>
<li class="">A practical data education company</li>
</ul>
<p>Of the three, the first was the most accurate. The universe I first pictured had stalled exactly where it was. Far from gathering a crew of analysts, the whole thing only ran if I taught every lecture myself.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-three-most-fragile-parts-of-the-structure">The three most fragile parts of the structure<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#the-three-most-fragile-parts-of-the-structure" class="hash-link" aria-label="Direct link to The three most fragile parts of the structure" title="Direct link to The three most fragile parts of the structure" translate="no">​</a></h3>
<ol>
<li class="">Revenue proportional to time, which doesn't move unless CEO Inyoung Lee teaches in person</li>
<li class="">No platform of our own for meeting customers directly and keeping those relationships</li>
<li class="">Course materials and workflows that get used once and used up, instead of being sold again</li>
</ol>
<p>Heavy dependence on lecture and consulting revenue, dependence on the CEO's time, rebuilding proposals and operating materials from scratch every time, and not enough recurring revenue. In the end it was all the same problem.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-community-failed-once-too">The community failed once, too<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#the-community-failed-once-too" class="hash-link" aria-label="Direct link to The community failed once, too" title="Direct link to The community failed once, too" translate="no">​</a></h3>
<p>Students wanted to stay connected after the lectures, so I opened KakaoTalk (Korea's dominant messaging app) open chat rooms and a Discord server, which at one point reached 950 and 300 members. But I was always the only one starting conversations, and whenever lectures piled up, the community was the first thing to slip. Both eventually went quiet.</p>
<p>Operations were the same. I handled customer management and student support by answering one by one in KakaoTalk open chat rooms, and posting content to Instagram, Threads, LinkedIn and the tech blog was all manual.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="thats-why-i-started-wagu-wagu-club">That's why I started Wagu Wagu Club<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#thats-why-i-started-wagu-wagu-club" class="hash-link" aria-label="Direct link to That's why I started Wagu Wagu Club" title="Direct link to That's why I started Wagu Wagu Club" translate="no">​</a></h2>
<p>After those failures, I started <a href="https://datapopcorn.ai/waguclub" target="_blank" rel="noopener noreferrer" class="">Wagu Wagu Club</a> in Q4.</p>
<p>This time, instead of a room of hundreds of people, I designed a meetup where around 10 people gather offline on a set topic each weekday and spend three hours actually building something. The goal is to build, on the spot, the "results I can use in my own work right away" that students wanted most, such as video automation, repetitive-task automation and building an AI assistant.</p>
<p>An always-open chat room was a structure that wore me out, but a set time where people gather to build together is the thing I do best. And Wagu Wagu Club is the first place where DataPopcorn recruits, takes payment and meets people directly, not through an outside platform. I hope that someday, members of the universe I first pictured will come from the people I meet here.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="datapopcorn-in-year-two">DataPopcorn in year two<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#datapopcorn-in-year-two" class="hash-link" aria-label="Direct link to DataPopcorn in year two" title="Direct link to DataPopcorn in year two" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-to-cut-back-keep-and-grow">What to cut back, keep and grow<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#what-to-cut-back-keep-and-grow" class="hash-link" aria-label="Direct link to What to cut back, keep and grow" title="Direct link to What to cut back, keep and grow" translate="no">​</a></h3>
<p><strong>Cut back</strong></p>
<ol>
<li class="">One-off work that leaves nothing behind in the company once it's done</li>
<li class="">Manual customer support and repetitive guidance that can't be turned into a system</li>
<li class="">Simple time-for-money work that doesn't lead to recurring revenue</li>
</ol>
<p>I'll keep doing lectures and consulting, but change them so each one leaves behind reusable assets.</p>
<p><strong>Keep</strong></p>
<ol>
<li class="">Keep updating WikiDocs and offering high-quality free information (for inbound leads)</li>
<li class="">Regular YouTube and podcast content</li>
<li class="">Strategic partnerships with major outside channels such as JoCoding (a popular Korean coding YouTube channel)</li>
</ol>
<p><strong>Grow</strong></p>
<ol>
<li class=""><strong>DataPopcorn's own platform.</strong> A channel for selling VOD courses and workflow packages</li>
<li class=""><strong>Offline community.</strong> Small, cohort-based study groups and regular meetups</li>
<li class=""><strong>Share of product revenue.</strong> Revenue models beyond lectures, B2C and B2B micro-services</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="courses-to-turn-into-packages">Courses to turn into packages<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#courses-to-turn-into-packages" class="hash-link" aria-label="Direct link to Courses to turn into packages" title="Direct link to Courses to turn into packages" translate="no">​</a></h3>
<table><thead><tr><th>Package candidate</th><th>Audience</th><th>Core content</th><th>Follow-on service</th></tr></thead><tbody><tr><td>n8n intro course (2026 revised edition)</td><td>Beginners, non-developers</td><td>n8n setup and basic logic</td><td>Workflow sales, hands-on course</td></tr><tr><td>n8n hands-on course (monetization, by role)</td><td>Marketers, PMs, HR</td><td>Monetizing Shorts and blogs, role-specific work automation</td><td>Maintenance package, meetup invitation</td></tr><tr><td>Claude Code &amp; Antigravity course</td><td>Intermediate users</td><td>Using the latest AI tools beyond n8n</td><td>Advanced offline study groups and meetups</td></tr></tbody></table>
<p>I also listed products I can offer after a course ends:</p>
<ul>
<li class="">Building and selling n8n workflows I've made</li>
<li class="">Building AI work tools for each department (introducing micro-services)</li>
<li class="">One-month PoC consulting (tied to an after-sales support package)</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-to-turn-into-services">What to turn into services<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#what-to-turn-into-services" class="hash-link" aria-label="Direct link to What to turn into services" title="Direct link to What to turn into services" translate="no">​</a></h3>
<p>Here are work tools I can build quickly with vibe coding:</p>
<ul>
<li class="">Internal automation dashboards built on n8n and vibe coding</li>
<li class="">Unattended content generators and publishers for specific domains such as blogs and YouTube</li>
<li class="">Custom chatbots and automated customer support systems</li>
</ul>
<p>As service candidates:</p>
<table><thead><tr><th>Service candidate</th><th>For whom</th><th>Problem it solves</th><th>MVP feasibility</th></tr></thead><tbody><tr><td>n8n workflows &amp; support package</td><td>Course students, small and solo businesses</td><td>Apply it right away without complex setup</td><td>High</td></tr><tr><td>DataPopcorn VOD subscription/purchase platform</td><td>Our own students</td><td>Watch scattered courses in one place</td><td>High</td></tr><tr><td>n8n + vibe coding micro-services</td><td>B2B, B2C</td><td>Companies and individuals without automation skills</td><td>Medium</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-first-experiment">The first experiment<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#the-first-experiment" class="hash-link" aria-label="Direct link to The first experiment" title="Direct link to The first experiment" translate="no">​</a></h3>
<p>The first service I'll push is <strong>selling n8n workflows and after-sales support packages online</strong>.</p>
<ul>
<li class=""><strong>Why:</strong> It's the surest pipeline for turning the proven demand from lectures straight into product sales, in other words recurring revenue.</li>
<li class=""><strong>First customer candidates:</strong> Existing students from outside training, WikiDocs and YouTube visitors</li>
<li class=""><strong>MVP to build within two weeks:</strong> A product checkout page on our own platform (web) and one package uploaded (for example, an auto-posting workflow)</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="automating-datapopcorns-own-repetitive-work-first">Automating DataPopcorn's own repetitive work first<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#automating-datapopcorns-own-repetitive-work-first" class="hash-link" aria-label="Direct link to Automating DataPopcorn's own repetitive work first" title="Direct link to Automating DataPopcorn's own repetitive work first" translate="no">​</a></h3>
<p>A company that automates other people's work was doing its own work by hand. So I'm starting with DataPopcorn's internal repetitive tasks.</p>
<table><thead><tr><th>Task</th><th>Now</th><th>New approach</th><th>Priority</th></tr></thead><tbody><tr><td>Multi-platform social publishing</td><td>Manual uploads</td><td>One-source, multi-use automated publishing with n8n</td><td>High</td></tr><tr><td>Support and student inquiries</td><td>Manual replies in open chat rooms</td><td>First-line responses with n8n + an AI chatbot</td><td>Medium</td></tr><tr><td>Meetup and webinar announcements</td><td>Emails and texts sent by hand</td><td>Notifications sent by automated workflows</td><td>Medium</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="year-two-goals">Year-two goals<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#year-two-goals" class="hash-link" aria-label="Direct link to Year-two goals" title="Direct link to Year-two goals" translate="no">​</a></h3>
<p>DataPopcorn's three most important goals for year two:</p>
<ol>
<li class=""><strong>Build and launch our own platform.</strong> Make the ecosystem independent with a platform that ties together course and workflow sales.</li>
<li class=""><strong>Publish the book and make the related events a success.</strong> Claim a position in the industry through the book, webinars and online/offline meetups.</li>
<li class=""><strong>Plan and start building micro-services (n8n + vibe coding).</strong> Create product-based recurring revenue through workflow package sales.</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2026-business-plan">2026 business plan<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#2026-business-plan" class="hash-link" aria-label="Direct link to 2026 business plan" title="Direct link to 2026 business plan" translate="no">​</a></h3>
<table><thead><tr><th>Quarter</th><th>Theme</th><th>To do</th></tr></thead><tbody><tr><td>Q1</td><td>Planning and setup</td><td>Plan and build our own platform (web), plan the n8n intro course and workflow packages, plan webinars</td></tr><tr><td>Q2</td><td>Launch and expansion</td><td>Officially launch our platform and sell VODs (intro, monetization, etc.) and packages, regular offline meetups, ramp up YouTube and social media marketing</td></tr><tr><td>Q3</td><td>Content upgrade</td><td>Publish Hanbit Media's <em>n8n Does It All</em> and market the book, offline cohort study groups 1-4, new VODs such as role-specific n8n hands-on courses and Claude Code</td></tr><tr><td>Q4</td><td>Service diversification</td><td>Build and launch micro-services, offline study groups 5-6, meetups and webinars, plan for the next year</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-structure-i-want-by-2027">The structure I want by 2027<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#the-structure-i-want-by-2027" class="hash-link" aria-label="Direct link to The structure I want by 2027" title="Direct link to The structure I want by 2027" translate="no">​</a></h3>
<table><thead><tr><th>Revenue source</th><th>What it looks like in early 2027</th></tr></thead><tbody><tr><td>Lectures</td><td>A larger share of passive income from VODs on our own platform</td></tr><tr><td>Consulting / services</td><td>Structured around n8n support package sales and premium B2B consulting</td></tr><tr><td>Products</td><td>Automated sales of workflow templates and packages</td></tr><tr><td>Operations automation</td><td>A fully automated pipeline from content planning (wiki) to multi-channel social distribution</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-company-i-want-to-be-seen-as-in-year-two">The company I want to be seen as in year two<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#the-company-i-want-to-be-seen-as-in-year-two" class="hash-link" aria-label="Direct link to The company I want to be seen as in year two" title="Direct link to The company I want to be seen as in year two" translate="no">​</a></h3>
<p>Right now DataPopcorn looks like a company built on Inyoung Lee's personal brand. In year two, I want it to look like this:</p>
<blockquote>
<p>DataPopcorn is a technology platform company that hands anyone everything from A to Z, from courses to its own workflows and micro-services, so they can easily adopt AI and automation.</p>
</blockquote>
<p>Rewritten in my own words:</p>
<blockquote>
<p>DataPopcorn is an "AI automation solutions company" that doesn't stop at teaching principles, but provides automation tools (workflows and micro-services) you can use at work right away, cutting real time and cost.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapping-up-the-year">Wrapping up the year<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#wrapping-up-the-year" class="hash-link" aria-label="Direct link to Wrapping up the year" title="Direct link to Wrapping up the year" translate="no">​</a></h2>
<p><strong>Year one in one line.</strong> A period of validating the market and cash flow through lectures and outside training.</p>
<p><strong>Three biggest achievements</strong></p>
<ol>
<li class="">Securing revenue stability through major outside training (KDT and others)</li>
<li class="">Confirming demand for an n8n automation toolkit through lots of feedback from the classroom</li>
<li class="">Setting a concrete 2026 roadmap for moving to our own platform, a book and micro-services</li>
</ol>
<p><strong>Three biggest lessons</strong></p>
<ol>
<li class="">People open their wallets more readily for real time savings (workflows and tools) than for plain knowledge (lectures).</li>
<li class="">A revenue model that depends heavily on the CEO teaching in person has a fatal limit when it comes to scaling up.</li>
<li class="">You need your own platform (your own store) and community for traffic to stay with you as an asset.</li>
</ol>
<p><strong>Three core strategies for year two</strong></p>
<ol>
<li class="">Turn traffic into an asset by launching our own platform (online and offline combined)</li>
<li class="">Expand into a product business by selling n8n workflows and support packages</li>
<li class="">Build DataPopcorn's own automation community ecosystem connecting WikiDocs, the book, meetups and social media</li>
</ol>
<p>DataPopcorn's first year was about creating market touchpoints and cash flow through lectures and consulting, and sharpening our best weapon, n8n automation. In year two, building on that experience, I'll reduce our dependence on outside parties and shift to an ecosystem that sells products (services and workflows) on our own platform. To get there, I'm focusing on launching our own VODs and packages, publishing the Hanbit Media book, and building an online and offline community and meetups.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-i-learned-running-a-company-on-my-own-money">What I learned running a company on my own money<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#what-i-learned-running-a-company-on-my-own-money" class="hash-link" aria-label="Direct link to What I learned running a company on my own money" title="Direct link to What I learned running a company on my own money" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="i-didnt-take-investment">I didn't take investment<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#i-didnt-take-investment" class="hash-link" aria-label="Direct link to I didn't take investment" title="Direct link to I didn't take investment" translate="no">​</a></h3>
<p>DataPopcorn has run on my own money from day one. It's a self-funded company in the most literal sense.</p>
<p>I didn't take investment for two reasons. First, I didn't have an idea clear enough to raise money on. Second, I really wanted to do my own thing freely. Once other people's money comes in, you have to follow the speed and direction that money expects. Before that, I wanted to do what I wanted.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="because-it-was-my-money-i-learned-what-money-is-worth">Because it was my money, I learned what money is worth<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#because-it-was-my-money-i-learned-what-money-is-worth" class="hash-link" aria-label="Direct link to Because it was my money, I learned what money is worth" title="Direct link to Because it was my money, I learned what money is worth" translate="no">​</a></h3>
<p>Running a company on my own money taught me a huge amount about what it takes to run a business. Above all, I learned how precious money is. Money that was a "budget" inside a company was, here, my money down to the last won. Every time I spent, I asked whether it would really come back, and that sense is hard to develop while you're employed.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="i-decided-to-try-things-even-if-they-didnt-make-money">I decided to try things even if they didn't make money<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#i-decided-to-try-things-even-if-they-didnt-make-money" class="hash-link" aria-label="Direct link to I decided to try things even if they didn't make money" title="Direct link to I decided to try things even if they didn't make money" translate="no">​</a></h3>
<p>So I made up my mind to try businesses even if they wouldn't make money. I felt that if not now, I'd never get the chance. These were things I could never have tried while employed.</p>
<p>My 3D printing business was one. So were the Claude Code clicker and the NFC Bible keyring. These are products I make and sell myself, completely different from lectures.</p>
<p>I wasn't being greedy, either. I hoped these products would bring in a steady 1,000,000 won a month. But marketing was incredibly hard. Selling was much harder than making.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="and-why-i-want-to-go-back-to-working-at-a-company">And why I want to go back to working at a company<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#and-why-i-want-to-go-back-to-working-at-a-company" class="hash-link" aria-label="Direct link to And why I want to go back to working at a company" title="Direct link to And why I want to go back to working at a company" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="datapopcorns-goal-was-survival">DataPopcorn's goal was survival<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#datapopcorns-goal-was-survival" class="hash-link" aria-label="Direct link to DataPopcorn's goal was survival" title="Direct link to DataPopcorn's goal was survival" translate="no">​</a></h3>
<p>DataPopcorn's biggest goal was one thing: <strong>could I survive without a company?</strong> I wasn't aiming for the life of a big-time entrepreneur. From the start, it was a challenge focused on survival.</p>
<p>I have my answer. Even now, I can live just fine without a company.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="i-proved-a-lot-in-a-year-and-a-half-and-i-have-no-regrets">I proved a lot in a year and a half, and I have no regrets<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#i-proved-a-lot-in-a-year-and-a-half-and-i-have-no-regrets" class="hash-link" aria-label="Direct link to I proved a lot in a year and a half, and I have no regrets" title="Direct link to I proved a lot in a year and a half, and I have no regrets" translate="no">​</a></h3>
<p>In a year and a half, I achieved much of what I wanted to do. I published a book, taught at companies and bootcamps, ran a community as an n8n Ambassador, stood on the Google I/O stage, built several <a href="https://datapopcorn.ai/portfolio" target="_blank" rel="noopener noreferrer" class="">content channels</a> run by AI agents, and made and sold my own 3D printed products and an NFC service.</p>
<p>I think the fact that I ran a company on my own for a year and a half, even with an employee, proves a lot by itself. I'm disappointed I didn't bring in more revenue. But I have no regrets.</p>
<p>That doesn't mean I've done everything I wanted. I still want to take on more challenges, and there's still work I want to do.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="there-are-limits-to-what-an-individual-not-an-organization-can-do">There are limits to what an individual, not an organization, can do<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#there-are-limits-to-what-an-individual-not-an-organization-can-do" class="hash-link" aria-label="Direct link to There are limits to what an individual, not an organization, can do" title="Direct link to There are limits to what an individual, not an organization, can do" translate="no">​</a></h3>
<p>So why go back? Because there are clear limits to what an individual, rather than an organization, can do.</p>
<p>Even if DataPopcorn grows as far as it can, unless it produces a service or a breakthrough product, growth eventually stops. That's where the "structure that doesn't run unless the CEO teaches in person" I described above ends up. My own time and ability are the company's ceiling.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="building-and-selling-were-different-skills">Building and selling were different skills<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#building-and-selling-were-different-skills" class="hash-link" aria-label="Direct link to Building and selling were different skills" title="Direct link to Building and selling were different skills" translate="no">​</a></h3>
<p>What I regret most is growth. I built products and services, but I failed to grow them.</p>
<p>In the end, building something well with AI and having the sales skills to sell it were different things. My AI skills grew enormously in a year and a half. But the skills of an entrepreneur who makes money are something I'm still developing.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="working-alone-showed-me-how-much-the-people-around-you-matter">Working alone showed me how much the people around you matter<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#working-alone-showed-me-how-much-the-people-around-you-matter" class="hash-link" aria-label="Direct link to Working alone showed me how much the people around you matter" title="Direct link to Working alone showed me how much the people around you matter" translate="no">​</a></h3>
<p>The biggest thing I felt working alone for a year and a half was how much it matters to have an organization and people you can work with.</p>
<p>By "together" I mean less holding onto one task and building it side by side, and more everyone running toward a shared goal. You learn a lot from each other along the way.</p>
<p>Working alone also taught me about myself. I work alone, but I'm someone who feels lonely easily. I realized I like refining my thinking by asking people things and putting questions to them.</p>
<p>Being alone can be comfortable. But to go the distance, I think it's right to belong to a company.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="i-needed-results-beyond-lectures">I needed results beyond lectures<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#i-needed-results-beyond-lectures" class="hash-link" aria-label="Direct link to I needed results beyond lectures" title="Direct link to I needed results beyond lectures" translate="no">​</a></h3>
<p>The AI education market has also changed a lot. The supply of AI instructors has grown sharply, and after more than 100 hours of teaching, I kept feeling that what students really need lies beyond the lecture. As I wrote above, what students wanted was results that work right now.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="now-the-human-job-is-deciding-what-to-build">Now the human job is deciding what to build<a href="https://datapopcorn.ai/blog/en/why-i-started-datapopcorn#now-the-human-job-is-deciding-what-to-build" class="hash-link" aria-label="Direct link to Now the human job is deciding what to build" title="Direct link to Now the human job is deciding what to build" translate="no">​</a></h3>
<p>With Opus 5.5 and GPT 6 Astra, model performance has become overwhelmingly good. I subscribe to both Claude Max x20 and Codex Pro x20, and lately I've even run short of tokens. That's how much more work I can hand off to AI.</p>
<p>I no longer need to study separately for things like prompt engineering or harness and loop engineering (skills). I think there's only one thing left for people to do: <strong>define clearly what to build and why.</strong></p>
<p>Realizing that changed what I think about. Now I think less about how to teach and more about how to build well. That's why I want to go back to working at a company.</p>
<p>When I left, I wanted to do the work I wanted with my own hands, and for a year and a half, I did. I also confirmed I can survive without a company. Now I want to work among people running together toward a shared goal, deciding what to build and building it well.</p>]]></content:encoded>
            <category>Business cases</category>
            <category>Education &amp; career</category>
        </item>
        <item>
            <title><![CDATA[Paste an n8n Workflow Link, Get a Vertical Video of How It Actually Ran]]></title>
            <link>https://datapopcorn.ai/blog/en/n8n-workflow-motion-video</link>
            <guid>https://datapopcorn.ai/blog/en/n8n-workflow-motion-video</guid>
            <pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Give it an n8n workflow link and it collects the execution log, node icons and the real input and output screens, then builds a 9:16 vertical explainer video. I walk through it with two examples, a comic-translation bot and a weather alert. The key is a structure that splits the collected information into spec.json so you don't rewrite code for every workflow.]]></description>
            <content:encoded><![CDATA[<p>After you build an n8n workflow, you still have to show the result on a blog, on YouTube or in a course, and redoing screen recordings or slide captures takes a lot of effort. In this post I share how I turn a single n8n workflow link into a vertical explainer video of how that workflow actually ran. Both videos below were made without any human editing, just by passing in the workflow link. It scrapes the execution log, node icons and real result screens directly from n8n, then renders the video from a single intermediate file called <code>spec.json</code>. The key is not rewriting code for every workflow.</p>
<!-- -->
<div style="display:flex;flex-wrap:wrap;gap:20px;justify-content:center;margin:8px 0 28px"><figure style="flex:1 1 260px;max-width:340px;margin:0"><video src="/blog/en/assets/medias/calvin-workflow-2e333e69354c3c5b472022fa6a8ce01d.mp4" poster="/blog/en/assets/images/calvin-poster-678d1ea666e180ccd86ad07c038ea33e.webp" autoplay="" loop="" muted="" playsinline="" controls="" preload="metadata" style="width:100%;height:auto;aspect-ratio:9 / 16;object-fit:contain;display:block;border-radius:12px;background:#10292C" aria-label="A 9:16 explainer video made from the real execution log of a workflow that translates Calvin and Hobbes comics and posts them to Slack"></video><figcaption>Comic translation → Slack (10 nodes, execution #63854, 44 seconds). The comic actually received appears as the input, and the actual Slack message as the result.</figcaption></figure><figure style="flex:1 1 260px;max-width:340px;margin:0"><video src="/blog/en/assets/medias/morning-brief-d8a61a1a9e1530378b513afdd3dbce95.mp4" poster="/blog/en/assets/images/morning-brief-poster-7060dfb8fc4c42ea951193e6f18766c2.webp" autoplay="" loop="" muted="" playsinline="" controls="" preload="metadata" style="width:100%;height:auto;aspect-ratio:9 / 16;object-fit:contain;display:block;border-radius:12px;background:#10292C" aria-label="A 9:16 explainer video made from the real execution log of a morning briefing workflow"></video><figcaption>Weather → Slack morning briefing (4 nodes, execution #68974, 20 seconds). The error path that didn't run is shown dimmed.</figcaption></figure></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="background">Background<a href="https://datapopcorn.ai/blog/en/n8n-workflow-motion-video#background" class="hash-link" aria-label="Direct link to Background" title="Direct link to Background" translate="no">​</a></h2>
<p>Once you have a few automations running, there's one question you always get: "So how does this thing actually work right now?"</p>
<p>Most of the time the answer is screenshots. You capture the n8n canvas, then crop a few shots of the execution view. That explains it, but the result is always static images. When someone asks to see it as a video, you have to record all over again.</p>
<p>So I kept running into the same three problems.</p>
<p>First, <strong>you have to redraw the logos.</strong> If you roughly mock up the OpenWeatherMap or Slack logo as text, the video ends up looking less like an explainer and more like "a deck someone forced together".</p>
<p>Second, <strong>you have to make up the description of the output.</strong> If you don't know the actual text that gets sent, you end up with generic captions like "passes it to the next step".</p>
<p>Third, <strong>you repeat the same work for every workflow.</strong> If you made a video for a 3-node workflow, you have to rewrite the script for a 7-node one.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="goal">Goal<a href="https://datapopcorn.ai/blog/en/n8n-workflow-motion-video#goal" class="hash-link" aria-label="Direct link to Goal" title="Direct link to Goal" translate="no">​</a></h2>
<p>The goal is to get rid of all three at once. It fits in one sentence:</p>
<blockquote>
<p>If a workflow has already run, make a 9:16 video explaining that run from nothing but the workflow ID.</p>
</blockquote>
<p>The "already run" condition matters. This tool doesn't trigger a new run. If the workflow sends a Slack message, a new run would actually send that message again. Instead, it only reads execution logs that already exist in n8n.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works">How it works<a href="https://datapopcorn.ai/blog/en/n8n-workflow-motion-video#how-it-works" class="hash-link" aria-label="Direct link to How it works" title="Direct link to How it works" translate="no">​</a></h2>
<p>The whole thing has three layers. Collect, turn it into <code>spec.json</code>, then read that file and render.</p>
<!-- -->
<p>Splitting this into two parts was the key decision. The collector only knows about n8n, and the renderer only knows about <code>spec.json</code>. So no matter how many workflows you add, you only ever change the renderer code once.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-a-workflow-isnt-a-document-its-an-api-response">1. A workflow isn't a document, it's an API response<a href="https://datapopcorn.ai/blog/en/n8n-workflow-motion-video#1-a-workflow-isnt-a-document-its-an-api-response" class="hash-link" aria-label="Direct link to 1. A workflow isn't a document, it's an API response" title="Direct link to 1. A workflow isn't a document, it's an API response" translate="no">​</a></h3>
<p>In a logged-in browser session, n8n serves its own REST API directly. So there's no need to open the workflow and read it by eye.</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// Workflow definition: node coordinates, connections, node types</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> wf </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">fetch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">/rest/workflows/</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">workflowId</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">then</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">r</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> r</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">then</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">r</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> r</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">data</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Only successful runs from the execution list</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> runs </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">fetch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">/rest/executions?limit=25&amp;filter=</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation function" style="color:#d73a49">encodeURIComponent</span><span class="token template-string interpolation punctuation" style="color:#393A34">(</span><span class="token template-string interpolation"></span><br></div><div class="token-line" style="color:#393A34"><span class="token template-string interpolation">  </span><span class="token template-string interpolation known-class-name class-name">JSON</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation method function property-access" style="color:#d73a49">stringify</span><span class="token template-string interpolation punctuation" style="color:#393A34">(</span><span class="token template-string interpolation punctuation" style="color:#393A34">{</span><span class="token template-string interpolation"> workflowId </span><span class="token template-string interpolation punctuation" style="color:#393A34">}</span><span class="token template-string interpolation punctuation" style="color:#393A34">)</span><span class="token template-string interpolation"></span><br></div><div class="token-line" style="color:#393A34"><span class="token template-string interpolation"></span><span class="token template-string interpolation punctuation" style="color:#393A34">)</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">then</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">r</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> r</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Expand a single execution to get per-node execution times</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> detail </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">fetch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">/rest/executions/</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">id</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">then</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">r</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> r</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// detail.data.runData = { 'nodeName': [{ executionTime, startTime, data: { main: [[...]] } }] }</span><br></div></code></pre></div></div>
<p>There's one thing to watch out for. n8n sends execution data in a flattened (flatted) format. All the values live in one array and point to each other by string index, so if you read it as-is, you see numeric strings like <code>"12"</code> instead of node output. You have to resolve the indexes recursively to get the real JSON. If you don't, you'll wrongly conclude that "the node has no output".</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-picking-which-execution-to-explain">2. Picking which execution to explain<a href="https://datapopcorn.ai/blog/en/n8n-workflow-motion-video#2-picking-which-execution-to-explain" class="hash-link" aria-label="Direct link to 2. Picking which execution to explain" title="Direct link to 2. Picking which execution to explain" translate="no">​</a></h3>
<p>If a workflow is turned on, executions pile up. You can't just pick any of them for the video. The rule is "the execution where the most nodes actually ran".</p>
<p>That's because that execution captures the real flow end to end. Pick a run that errored out or got blocked at a branch, and the graph comes out empty. For the morning briefing, I picked the scheduled run at 06:00 on 2026-09-29 (#68974). In that run, 3 of the 4 nodes ran and 1 (the error alert) didn't.</p>
<p>I decided it was better to show that as-is. For a workflow with an error branch, showing "no error this time, so this path wasn't taken" helps even someone seeing the workflow for the first time understand its structure.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-pull-the-logos-from-the-canvas-instead-of-redrawing-them">3. Pull the logos from the canvas instead of redrawing them<a href="https://datapopcorn.ai/blog/en/n8n-workflow-motion-video#3-pull-the-logos-from-the-canvas-instead-of-redrawing-them" class="hash-link" aria-label="Direct link to 3. Pull the logos from the canvas instead of redrawing them" title="Direct link to 3. Pull the logos from the canvas instead of redrawing them" translate="no">​</a></h3>
<p>The first version drew node icons on the canvas by hand. They looked similar but weren't the real app logos, so viewers couldn't tell at a glance which service each one was.</p>
<p>If you inspect the n8n UI in the DOM, every node already has an SVG icon rendered. I loop over <code>[data-test-id="canvas-node"]</code> and grab each SVG wholesale. If the color comes through as <code>currentColor</code>, I compute the actual rendered color and substitute it as a literal.</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// .n8n-node-icon &gt; svg inside canvas-node</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> svg </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> icon</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">outerHTML</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">replaceAll</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'currentColor'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> actualRenderedColor</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>The result is pixel-identical to the n8n UI. It's no longer "a similar logo"; it's the actual icon. For nodes that can't be found on screen, it falls back to the <code>iconUrl</code> in <code>/types/nodes.json</code>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-inputs-and-outputs-are-already-in-the-execution-log">4. Inputs and outputs are already in the execution log<a href="https://datapopcorn.ai/blog/en/n8n-workflow-motion-video#4-inputs-and-outputs-are-already-in-the-execution-log" class="hash-link" aria-label="Direct link to 4. Inputs and outputs are already in the execution log" title="Direct link to 4. Inputs and outputs are already in the execution log" translate="no">​</a></h3>
<p>This is the most important part. What makes an explainer video convincing is showing <strong>what actually went out in that run</strong>, not a sample screen.</p>
<p>The input comes from <code>binary</code> in <code>runData</code>. If a file flowed through the workflow, you can read its mimeType, size and filename directly. In the Calvin and Hobbes workflow, the comic GIF downloaded that day was picked up as the input, and that comic appears as-is in the opening scene and on the card for the node that handles the image. For the output, if the last node is Slack, the message contains <code>channel</code> and <code>ts</code>. From those two values I build the Slack message URL, find the message in a real browser and screenshot it.</p>
<p>I refined one thing here. Slack messages can be long. Capturing a message taller than the screen as-is cut off the Slack header (search bar, channel name). So I added three things:</p>
<ul>
<li class="">Scroll aligned to the top of the panel so the header isn't cut off</li>
<li class="">If the message is taller than the screen, reduce the page zoom so it fits entirely</li>
<li class="">If there's empty space with no message on the right, crop it out pixel by pixel</li>
</ul>
<p>When the capture fails, the screen honestly says "Expected result (reconstructed)". Passing off a reconstructed screen as the real result turns it into marketing material nobody can verify.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-merge-everything-into-one-specjson">5. Merge everything into one spec.json<a href="https://datapopcorn.ai/blog/en/n8n-workflow-motion-video#5-merge-everything-into-one-specjson" class="hash-link" aria-label="Direct link to 5. Merge everything into one spec.json" title="Direct link to 5. Merge everything into one spec.json" translate="no">​</a></h3>
<p>Once collection is done, everything known about the workflow is extracted into <code>spec.json</code>.</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"meta"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"workflowId"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"gdtZQi48u2rUx0c3"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"workflowName"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"[AI Assistant] Morning Briefing (Weather, News, FX AI Summary → Slack)"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"executionId"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"68974"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"executedAt"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"2026-09-28T21:00:58.066Z"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"nodes"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"n1"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Fetch Seoul Weather"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"n8n-nodes-base.openWeatherMap"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"typeLabel"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"OpenWeatherMap"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"x"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">544</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"y"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">336</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"w"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">96</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"h"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">96</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"outputs"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"outLabels"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"success"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"error"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"ran"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"icon"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"icons/n1.svg"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"result"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"label"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Actual result · Slack capture"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"src"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"media/output-slack.png"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>The renderer knows nothing about OpenWeatherMap or Slack. It only reads this file. Node positions use the canvas coordinates as-is, branches are drawn from <code>outputs</code> and <code>outLabels</code>, and nodes with <code>ran: false</code> are dimmed. When I explain a different workflow later, only the numbers in this file change.</p>
<p>This structure also made it easy to change the output size. The first render with the same settings was 19.9MB. That was too big to put in the repo, so I added an output-size option to the engine and re-rendered at 720×1280 / 1.15Mbps. The layout stays in 1080×1920 logical coordinates; only the output is scaled down. The result is 3.0MB.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-encode-right-in-the-browser">6. Encode right in the browser<a href="https://datapopcorn.ai/blog/en/n8n-workflow-motion-video#6-encode-right-in-the-browser" class="hash-link" aria-label="Direct link to 6. Encode right in the browser" title="Direct link to 6. Encode right in the browser" translate="no">​</a></h3>
<p>The renderer doesn't use a separate build tool. All it needs is to draw each frame onto a browser canvas. Encoding uses WebCodecs, and the container uses an mp4 muxer.</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> enc </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">VideoEncoder</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">output</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">c</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> m</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> mux</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">addVideoChunk</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">c</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> m</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">enc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">configure</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">codec</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'avc1.640033'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">width</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">720</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">height</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1280</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">bitrate</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1150000</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">framerate</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">30</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword control-flow" style="color:#00009f">for</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> i </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> i </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain"> frameCount</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> i</span><span class="token operator" style="color:#393A34">++</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">draw</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">i </span><span class="token operator" style="color:#393A34">/</span><span class="token plain"> fps</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> px </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> ctx</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">getImageData</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">720</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1280</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> frame </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">VideoFrame</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">px</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">data</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">format</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'RGBA'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">codedWidth</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">720</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">codedHeight</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1280</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  enc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">encode</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">frame</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">keyFrame</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> i </span><span class="token operator" style="color:#393A34">%</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">60</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  frame</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">close</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>There was a reason for this too. I tried rendering with the Remotion CLI, but launching Chromium was blocked in this environment. Rendering in a browser tab makes that problem disappear entirely, and lets me preview and verify frames in the same code.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-dont-trust-a-successful-render">7. Don't trust a successful render<a href="https://datapopcorn.ai/blog/en/n8n-workflow-motion-video#7-dont-trust-a-successful-render" class="hash-link" aria-label="Direct link to 7. Don't trust a successful render" title="Direct link to 7. Don't trust a successful render" translate="no">​</a></h3>
<p>This is the part I most want to stress. "Render complete" is not verification. Even if the code finishes without errors, if the animation timing is off, the screen can freeze for seconds or go black.</p>
<p>So right after rendering, two checks run automatically:</p>
<ul>
<li class="">Sample a frame every 0.5 seconds; <strong>if two consecutive samples are nearly identical, it's a freeze</strong>, and <strong>if overall brightness is below a threshold, it's a blackout</strong></li>
<li class="">Check a contact sheet (many frames stitched into one image) by eye</li>
</ul>
<!-- -->
<p>Without this step, you report "0 errors when running the code" as success. That's not verification, that's reading a log.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="results">Results<a href="https://datapopcorn.ai/blog/en/n8n-workflow-motion-video#results" class="hash-link" aria-label="Direct link to Results" title="Direct link to Results" translate="no">​</a></h2>
<p>Both workflows were made with the same code.</p>
<table><thead><tr><th></th><th>Comic translation → Slack</th><th>Morning briefing (weather → Slack)</th></tr></thead><tbody><tr><td>Nodes</td><td>9 of 10 ran</td><td>3 of 4 ran</td></tr><tr><td>Node shown as not run</td><td>Image-extraction failure alert</td><td>Weather-lookup failure alert</td></tr><tr><td>Reference execution</td><td>#63854 (2026-09-19)</td><td>#68974 (2026-09-29 06:00)</td></tr><tr><td>Processing time shown</td><td>9 steps, 7.645s</td><td>3 steps, 0.240s</td></tr><tr><td>Input screen</td><td>The actual comic GIF received</td><td>None (scheduled run)</td></tr><tr><td>Result screen</td><td>Actual Slack capture</td><td>Actual Slack capture</td></tr><tr><td>Web MP4</td><td>44s · 720×1280 · 5.8MB</td><td>20s · 720×1280 · 3.0MB</td></tr></tbody></table>
<p>Neither video had frozen or black frames.</p>
<p>I originally built this tool around a workflow that translates Calvin and Hobbes comics and posts them to Slack. The morning briefing was the second case, run afterwards by passing in nothing but the workflow ID. I didn't modify the collection code for that workflow; the only refinements were to the Slack capture's scrolling and whitespace handling, shared by both. In other words, the "all you need is the ID" goal held up on the second workflow too.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-i-learned">What I learned<a href="https://datapopcorn.ai/blog/en/n8n-workflow-motion-video#what-i-learned" class="hash-link" aria-label="Direct link to What I learned" title="Direct link to What I learned" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="showing-the-paths-that-didnt-run-reveals-the-structure">Showing the paths that didn't run reveals the structure<a href="https://datapopcorn.ai/blog/en/n8n-workflow-motion-video#showing-the-paths-that-didnt-run-reveals-the-structure" class="hash-link" aria-label="Direct link to Showing the paths that didn't run reveals the structure" title="Direct link to Showing the paths that didn't run reveals the structure" translate="no">​</a></h3>
<p>The morning briefing has an error path that sends an alert if the weather lookup fails. That path wasn't taken in this run. At first I considered showing only the nodes that ran, but leaving the un-run node visible and dimmed worked better. That way viewers also understand the structure: "if it fails, it goes this way".</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="say-which-execution-the-timings-come-from">Say which execution the timings come from<a href="https://datapopcorn.ai/blog/en/n8n-workflow-motion-video#say-which-execution-the-timings-come-from" class="hash-link" aria-label="Direct link to Say which execution the timings come from" title="Direct link to Say which execution the timings come from" translate="no">​</a></h3>
<p>The 7.6-second total shown in the Calvin and Hobbes video didn't come from a single run. It was a manual execution, so the earlier nodes reused results from a previous run, and times from different executions were mixed together. You can't tell that from the screen alone. Now I always add "based on execution #number" under the timing, and mark any value that didn't come from the same execution.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="auto-generated-captions-need-one-human-read">Auto-generated captions need one human read<a href="https://datapopcorn.ai/blog/en/n8n-workflow-motion-video#auto-generated-captions-need-one-human-read" class="hash-link" aria-label="Direct link to Auto-generated captions need one human read" title="Direct link to Auto-generated captions need one human read" translate="no">​</a></h3>
<p>I set the default caption template to <code>결과를 ${서비스}(으)로 보냅니다</code> ("sends the result to <code>${service}</code>"), and it produced awkward sentences like "결과를 Slack(으)로 보냅니다". In Korean, the particle for "to" depends on whether the previous word ends in a final consonant, and because the service name was in English, the code couldn't decide. I fixed it by switching the particle to "에". This is the kind of thing only a human read catches. Treat auto-generated captions as a way to set direction, nothing more.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-resolution-can-be-right-while-the-picture-is-wrong">The resolution can be right while the picture is wrong<a href="https://datapopcorn.ai/blog/en/n8n-workflow-motion-video#the-resolution-can-be-right-while-the-picture-is-wrong" class="hash-link" aria-label="Direct link to The resolution can be right while the picture is wrong" title="Direct link to The resolution can be right while the picture is wrong" translate="no">​</a></h3>
<p>When I first uploaded the web version, it came out zoomed into just the top-left corner of the frame. The file really was 720×1280 and had shrunk to 3MB, but the drawing code was resetting the scale to 1:1 on every frame. Only the 720×1280 region of a screen drawn at 1080×1920 was being cropped out. I had only checked the video resolution and file size, so I didn't notice until after publishing. Now I extract frames directly from the rendered MP4 and compare them side by side with the original layout.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-you-can-use-this">How you can use this<a href="https://datapopcorn.ai/blog/en/n8n-workflow-motion-video#how-you-can-use-this" class="hash-link" aria-label="Direct link to How you can use this" title="Direct link to How you can use this" translate="no">​</a></h2>
<p>Since it's split into three layers, it's also clear how to assemble it in your own environment.</p>
<p><strong>Layer 1: collector.</strong> From the workflow ID, scrape four things: the workflow definition, the list of successful executions, the actual execution data, and the node icons pulled from the canvas. If the workflow ends in Slack, also pull <code>channel</code> and <code>ts</code> from the last node's output.</p>
<p><strong>Layer 2: spec.json.</strong> Write what you collected above as JSON. The skeleton of the picture is four keys, <code>nodes</code>, <code>edges</code>, <code>steps</code> and <code>result</code>, while <code>meta</code>, <code>intro</code> and <code>outro</code> hold the source attribution and on-screen text. Use the n8n canvas coordinates for node positions as-is.</p>
<p><strong>Layer 3: renderer.</strong> Set up a 9:16 canvas, write JS that draws each frame, and encode with WebCodecs + an mp4 muxer. Add the frozen/black-frame checks at the end.</p>
<p>There are three prerequisites: a browser session logged in to your n8n instance, an agent that can drive a browser, and an environment to run Node locally. For the server, a short static server using only the Node standard library is enough.</p>
<p>I've bundled these three layers into a skill for my browser agent (Aside). So the request is just one line with the workflow link. It reads the n8n host and workflow ID from the link, so it works the same way with n8n Cloud or a self-hosted n8n, as long as you're logged in in the browser.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">https://your-n8n-host/workflow/WORKFLOW_ID Make an explainer video from this workflow</span><br></div></code></pre></div></div>
<p>I haven't released this skill as an installable package yet. For now, the approach is to use the structure and code snippets above as a reference and reassemble them for your own environment.</p>
<p>To sum up, the value of this approach is in the structure, not the video. Once collection, organization and rendering are cut apart by a single <code>spec.json</code>, "making one more explainer video" becomes regenerating JSON rather than changing code. The more things you need to explain, the bigger that difference gets.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="next-steps">Next steps<a href="https://datapopcorn.ai/blog/en/n8n-workflow-motion-video#next-steps" class="hash-link" aria-label="Direct link to Next steps" title="Direct link to Next steps" translate="no">​</a></h2>
<ul>
<li class="">Extend the Slack capture to other destinations like Gmail and Notion, so the "result posted to a channel" can be attached automatically</li>
<li class="">Switch auto-captions to rules that draw from the execution data. Right now most of them are plain sentences like "Here's how it arrives in Slack"</li>
<li class="">Add voice narration and caption timing to the videos. Right now there are only sound effects</li>
<li class="">Add aspect ratio to the engine so a 16:9 version can be generated from the same <code>spec.json</code></li>
</ul>]]></content:encoded>
            <category>AI work automation</category>
            <category>n8n</category>
            <category>Guide</category>
        </item>
        <item>
            <title><![CDATA[The Same 540K Rows Can Be 3.5MB or 102.7MB: How to Choose Between CSV, Excel, JSON and Parquet]]></title>
            <link>https://datapopcorn.ai/blog/en/file-formats-parquet</link>
            <guid>https://datapopcorn.ai/blog/en/file-formats-parquet</guid>
            <pubDate>Tue, 29 Sep 2026 01:20:00 GMT</pubDate>
            <description><![CDATA[I saved 540,000 rows of transactions from a UK online store as CSV, JSON and Parquet. The files came out at 45.0MB, 102.7MB and 3.5MB, and with CSV and JSON the types of the date and customer ID columns changed the moment I read them back. Using all 151 Pokémon from PokeAPI, I also cover the pitfalls of flattening nested JSON into a table and monthly partitioning, all with real measurements.]]></description>
            <content:encoded><![CDATA[<p><strong>I saved the same 540,000 rows of online store transactions and got 3.5MB as Parquet, 45.0MB as CSV and 102.7MB as JSON Lines.</strong> Reading them back took 0.1 seconds for Parquet, 0.24 seconds for CSV, 1.3 seconds for JSON and around 20 seconds for the original Excel file. The bigger difference is types. With CSV and JSON, the moment I read the data back, dates turned into strings and customer ID <code>17850</code> turned into <code>17850.0</code>, while Parquet kept them as they were. On the other hand, with a small 151-row dataset, CSV was smaller than Parquet. You don't choose a format by file size alone. You choose it by <strong>who reads it, how often it's read, and whether the types need to survive</strong>.</p>
<!-- -->
<p>This post uses the same UCI Online Retail II data as the <a href="https://datapopcorn.ai/blog/total-sales-four-answers" target="_blank" rel="noopener noreferrer" class="">total sales post</a> and the <a href="https://datapopcorn.ai/blog/pandas-dtype-memory" target="_blank" rel="noopener noreferrer" class="">pandas types post</a>, and practices JSON with PokeAPI.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-the-four-formats-are-like">What the four formats are like<a href="https://datapopcorn.ai/blog/en/file-formats-parquet#what-the-four-formats-are-like" class="hash-link" aria-label="Direct link to What the four formats are like" title="Direct link to What the four formats are like" translate="no">​</a></h2>
<table><thead><tr><th>Format</th><th>Shape</th><th>Type info</th><th>Easy for people to open</th><th>Typical use</th></tr></thead><tbody><tr><td>CSV</td><td>Table, row-based text</td><td>None</td><td>Easy</td><td>Sharing with people, when it needs to open in any tool</td></tr><tr><td>Excel</td><td>Table + formatting and formulas</td><td>Partial</td><td>Easiest</td><td>When people view and edit it directly</td></tr><tr><td>JSON</td><td>Text that can be nested</td><td>Roughly strings and numbers</td><td>Possible</td><td>API responses, data where each item has a different number of children</td></tr><tr><td>Parquet</td><td>Table, column-based binary</td><td>Preserved</td><td>Hard</td><td>Intermediate saves during analysis, data read repeatedly, large data</td></tr></tbody></table>
<p>Parquet stores data <strong>by column</strong>, not by row. Similar values from the same column sit next to each other, so it compresses well, and you can read just the columns you need. It also records each column's type inside the file. All three show up clearly in the measurements below.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="stuck-at-the-save-step-parquet-rejects-mixed-types">Stuck at the save step: Parquet rejects mixed types<a href="https://datapopcorn.ai/blog/en/file-formats-parquet#stuck-at-the-save-step-parquet-rejects-mixed-types" class="hash-link" aria-label="Direct link to Stuck at the save step: Parquet rejects mixed types" title="Direct link to Stuck at the save step: Parquet rejects mixed types" translate="no">​</a></h2>
<p>As in the <a href="https://datapopcorn.ai/blog/pandas-dtype-memory" target="_blank" rel="noopener noreferrer" class="">previous post</a>, I read the data with explicit types for the invoice, product and customer ID columns, then saved it in three formats. CSV and JSON saved without a problem, but Parquet threw an error.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Excel read: 22.6s, 541,910 rows</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Parquet save failed: ArrowTypeError - ("Expected bytes, got a 'int' object", 'Conversion failed for column Description with type</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Python types of Description values: {'str': 540455, 'float': 1454, 'int': 1} | numeric examples: [20713]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Saved after casting Description to string</span><br></div></code></pre></div></div>
<p>The product description (Description) column held 540,455 strings and 1,454 empty values (<code>NaN</code>, which is a float in Python), plus <strong>exactly one number, <code>20713</code></strong>. It's a value Excel read as a number. CSV silently saves this kind of mix as text, while Parquet requires "one type per column," so it refuses to save.</p>
<p>It looks like an annoying error, but it actually caught, at the save step, the "silently wrong" problem from the previous post. Once I set the description column to string, it saved.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="file-size-and-read-time">File size and read time<a href="https://datapopcorn.ai/blog/en/file-formats-parquet#file-size-and-read-time" class="hash-link" aria-label="Direct link to File size and read time" title="Direct link to File size and read time" translate="no">​</a></h2>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Original xlsx (2 sheets): 43.5 MB</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">CSV size: 45.0 MB</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">JSON Lines size: 102.7 MB</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Parquet size: 3.5 MB</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">CSV read: 0.242s (best of 3)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">JSON Lines read: 1.295s (best of 3)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Parquet read: 0.099s (best of 3)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Parquet (2 columns only) read: 0.003s (best of 3)</span><br></div></code></pre></div></div>
<p>Each read time is the fastest of three runs after one warm-up read. In this script I timed Excel only once: 22.6 seconds in the run above, and between 18 and 23 seconds when I reran the same script several times.</p>
<table><thead><tr><th>Format</th><th style="text-align:right">Size</th><th style="text-align:right">Read time</th></tr></thead><tbody><tr><td>Excel (original, 2 sheets)</td><td style="text-align:right">43.5 MB</td><td style="text-align:right">About 18-23s (one sheet)</td></tr><tr><td>CSV</td><td style="text-align:right">45.0 MB</td><td style="text-align:right">0.242s</td></tr><tr><td>JSON Lines</td><td style="text-align:right">102.7 MB</td><td style="text-align:right">1.295s</td></tr><tr><td>Parquet</td><td style="text-align:right">3.5 MB</td><td style="text-align:right">0.099s</td></tr><tr><td>Parquet, only the 2 needed columns</td><td style="text-align:right">(same file)</td><td style="text-align:right">0.003s</td></tr></tbody></table>
<p>Parquet is one-thirteenth the size of CSV. This data has 540,000 rows with 38 country names and the same product descriptions repeated over and over, so column-based compression works especially well. JSON Lines writes the column names again on every row, so it's more than twice the size of CSV.</p>
<p>What stands out in the read times is that for data this size, CSV is plenty fast too. Parquet's real strength is in the last line. If you only need the quantity and price columns, it pulls just those columns off the disk, so it's done in 0.003 seconds. The more columns a real-world table has (hundreds, say), the bigger this gap gets. For reference, the original xlsx is already a compressed format, so its size is similar to the CSV, but it took 70 to 90 times longer to read than CSV.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="do-the-types-survive-a-round-trip">Do the types survive a round trip?<a href="https://datapopcorn.ai/blog/en/file-formats-parquet#do-the-types-survive-a-round-trip" class="hash-link" aria-label="Direct link to Do the types survive a round trip?" title="Direct link to Do the types survive a round trip?" translate="no">​</a></h2>
<p>I read the saved files back without any options and compared the types.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">[CSV] InvoiceDate=object, Customer ID=float64, Invoice=object, StockCode=object, Customer ID sample=np.float64(17850.0), values equal (Customer ID)=False</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">[JSON Lines] InvoiceDate=object, Customer ID=float64, Invoice=object, StockCode=object, Customer ID sample=np.float64(17850.0), values equal (Customer ID)=False</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">[Parquet] InvoiceDate=datetime64[ns], Customer ID=string, Invoice=object, StockCode=object, Customer ID sample='17850', values equal (Customer ID)=True</span><br></div></code></pre></div></div>
<table><thead><tr><th>Column</th><th>Original type</th><th>Read back from CSV</th><th>Read back from JSON Lines</th><th>Read back from Parquet</th></tr></thead><tbody><tr><td>InvoiceDate</td><td>Date</td><td>String</td><td>String</td><td>Date</td></tr><tr><td>Customer ID</td><td>String <code>'17850'</code></td><td>Float <code>17850.0</code></td><td>Float <code>17850.0</code></td><td>String <code>'17850'</code></td></tr></tbody></table>
<p>CSV and JSON can't carry information like "this column is a date" or "this column is a string ID." When reading them back, pandas guesses from the values alone: a customer ID that looks like a number becomes a number, and because empty values are mixed in, it guesses float. The type problem you barely fixed at read time comes back after a single save. Dates become strings, so you have to convert them again before any monthly aggregation.</p>
<p>If you have to use CSV, you need to specify <code>dtype</code> and <code>parse_dates</code> every time you read it. If you're saving intermediate results during analysis, Parquet does this for you.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="split-data-that-grows-every-day-partitioning">Split data that grows every day: partitioning<a href="https://datapopcorn.ai/blog/en/file-formats-parquet#split-data-that-grows-every-day-partitioning" class="hash-link" aria-label="Direct link to Split data that grows every day: partitioning" title="Direct link to Split data that grows every day: partitioning" translate="no">​</a></h2>
<p>If you dump data that piles up daily, like orders, into a single file, you have to read the whole thing even when you only want "this month." So you save it in folders split by something like date. This is called <strong>partitioning</strong>. If you name the folders in the form <code>column=value</code>, most tools read the name and restore it as a column.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"ym"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"InvoiceDate"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">dt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">strftime</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"%Y-%m"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">to_parquet</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"by_month"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> partition_cols</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"ym"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> index</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">False</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">          </span><span class="token comment" style="color:#999988;font-style:italic"># one folder per month</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">nov </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> pd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">read_parquet</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"by_month"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> filters</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"ym"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"=="</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"2011-11"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">   </span><span class="token comment" style="color:#999988;font-style:italic"># read only the November folder</span><br></div></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Partition folders: 13 total, ym=2010-12 … ym=2011-12</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Read 2011-11 only: 84,711 rows, 0.02s, ym dtype=category</span><br></div></code></pre></div></div>
<p>It created 13 folders, from <code>ym=2010-12</code> to <code>ym=2011-12</code>, and read only the 84,711 rows for November 2011 in 0.02 seconds. With partitioning, if a particular month was collected incorrectly, you can delete just that folder and rewrite it, which also helps automation where running the same job several times should give the same result.</p>
<p>There are two things to watch out for. First, the <code>ym</code> you read back is <code>category</code>, not string. Partition columns are restored from the folder names, not from inside the files. So here too, you need to check types after reading. Second, splitting too finely actually makes things slower. If a day's data is only a few KB and you split by day, all the time goes into opening and closing thousands of small files. In this post the data is a few thousand rows per day, so I split by month for the experiment. Whether daily or monthly is better depends on your actual query patterns and file sizes.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="json-data-that-doesnt-fit-neatly-into-a-table">JSON: data that doesn't fit neatly into a table<a href="https://datapopcorn.ai/blog/en/file-formats-parquet#json-data-that-doesnt-fit-neatly-into-a-table" class="hash-link" aria-label="Direct link to JSON: data that doesn't fit neatly into a table" title="Direct link to JSON: data that doesn't fit neatly into a table" translate="no">​</a></h2>
<p>CSV, Excel and Parquet are all tables with fixed rows and columns. But the data APIs return often isn't a table. If you pull the 151 first-generation Pokémon from PokeAPI, each Pokémon can have one type or two.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> requests</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> pandas </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> pd</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">rows </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> i </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> </span><span class="token builtin">range</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">152</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    p </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> requests</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"https://pokeapi.co/api/v2/pokemon/</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">i</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> timeout</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">30</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    rows</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">append</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"dex_no"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:#e3116c">f"</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">p</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">[</span><span class="token string-interpolation interpolation string" style="color:#e3116c">'id'</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">]</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">:</span><span class="token string-interpolation interpolation format-spec">04d</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">                              </span><span class="token comment" style="color:#999988;font-style:italic"># four-digit string like 0025</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"name"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> p</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"name"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"weight"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> p</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"weight"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">                                  </span><span class="token comment" style="color:#999988;font-style:italic"># unit: 0.1kg</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"types"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">t</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"type"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"name"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> t </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> p</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"types"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">       </span><span class="token comment" style="color:#999988;font-style:italic"># keep the list as is</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token operator" style="color:#393A34">**</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">s</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"stat"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"name"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> s</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"base_stat"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> s </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> p</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"stats"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">df </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> pd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">DataFrame</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rows</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Rows: 151 | 2 types: 67 | 1 type: 84</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Charizard: ['fire', 'flying'] | Pikachu: ['electric']</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Rows after explode: 218</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Total weight: originally 6,938.7kg → after explode 10,650.7kg</span><br></div></code></pre></div></div>
<p>67 of the 151 Pokémon have two types. This is where a common mistake comes in. If you use <code>explode</code> to spread the list into rows for a per-type analysis, 151 rows become 218, and Pokémon with two types appear twice. Sum the weights in that state and 6,938.7kg inflates to 10,650.7kg. It's the same problem as total sales getting inflated when you sum without checking what one row represents. <strong>The moment you explode, the meaning of one row changes from "one Pokémon" to "one type of one Pokémon."</strong> Do per-type analysis on the exploded table, and per-Pokémon totals on the original table.</p>
<p>Nested dictionaries in JSON, like <code>{"stats": {"hp": 35, "attack": 55}}</code>, can be flattened into <code>stats.hp</code> and <code>stats.attack</code> columns with <code>pd.json_normalize()</code>. The part that needs a decision is a list like <code>types</code> above, in other words a one-to-many relationship.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="with-small-data-csv-is-smaller">With small data, CSV is smaller<a href="https://datapopcorn.ai/blog/en/file-formats-parquet#with-small-data-csv-is-smaller" class="hash-link" aria-label="Direct link to With small data, CSV is smaller" title="Direct link to With small data, CSV is smaller" translate="no">​</a></h2>
<p>I saved the same 151 Pokémon in three formats and read them back.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">gen1.csv 7.7 KB</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">gen1.json 23.0 KB</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">gen1.parquet 10.7 KB</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Reread from CSV | Pikachu dex_no: np.int64(25) | Bulbasaur types: "['grass', 'poison']"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Reread from Parquet | Pikachu dex_no: '0025' | Bulbasaur types: ndarray ['grass', 'poison']</span><br></div></code></pre></div></div>
<p>At 151 rows, <strong>CSV (7.7KB) is smaller than Parquet (10.7KB).</strong> Parquet attaches schema and statistics to every file, so with small data that fixed cost outweighs the rest. The benefit of compression only shows when the data is large, like the 540,000 rows earlier. If someone says "Parquet is always smaller," AI or human, they said it without actually measuring.</p>
<p>Even so, Parquet is still the only one that gives the types back intact when read with default options (with CSV, you can prevent this by specifying <code>dtype</code> when reading). Read it back from CSV and Pikachu's Pokédex number <code>0025</code> becomes the number <code>25</code>, and Bulbasaur's list of types becomes a single string, <code>"['grass', 'poison']"</code>. From Parquet, <code>'0025'</code> and <code>['grass', 'poison']</code> come back as they were (strictly speaking, as an array, not a Python list).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="when-to-use-what">When to use what<a href="https://datapopcorn.ai/blog/en/file-formats-parquet#when-to-use-what" class="hash-link" aria-label="Direct link to When to use what" title="Direct link to When to use what" translate="no">​</a></h2>
<table><thead><tr><th>Situation</th><th>Format</th></tr></thead><tbody><tr><td>Sending results to people when you don't know what program they'll use</td><td>CSV</td></tr><tr><td>When people open and edit it directly, or you need colors and formulas</td><td>Excel</td></tr><tr><td>Receiving API responses, or when each item has a different number of children</td><td>JSON</td></tr><tr><td>Saving intermediate results during analysis and reading them repeatedly, or when the data is large</td><td>Parquet</td></tr><tr><td>Data that piles up every day</td><td>Parquet + date partitions</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="when-you-ask-ai-to-do-the-conversion">When you ask AI to do the conversion<a href="https://datapopcorn.ai/blog/en/file-formats-parquet#when-you-ask-ai-to-do-the-conversion" class="hash-link" aria-label="Direct link to When you ask AI to do the conversion" title="Direct link to When you ask AI to do the conversion" translate="no">​</a></h2>
<p>Format conversion is a good job to hand to AI. Just ask it to also verify that the converted result matches the original.</p>
<blockquote>
<p>Convert this CSV to Parquet. After converting, compare the file size and read time of the two files, and when reading it back, check that the row count, column types, dates and code values with leading zeros match the original.</p>
</blockquote>
<p>When you get the result, check three things: Is the <strong>row count</strong> the same? Are the <strong>column types</strong> right, with dates as dates and IDs as strings? In <strong>sample values</strong>, are leading zeros and date formats intact? Summary statistics alone make it easy to miss that <code>0025</code> became <code>25</code>, so compare a few rows by eye.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="questions-you-might-get-in-an-interview">Questions you might get in an interview<a href="https://datapopcorn.ai/blog/en/file-formats-parquet#questions-you-might-get-in-an-interview" class="hash-link" aria-label="Direct link to Questions you might get in an interview" title="Direct link to Questions you might get in an interview" translate="no">​</a></h2>
<p><strong>Explain the difference between CSV and Parquet, and when you'd use Parquet.</strong> CSV is row-based text, so it opens anywhere, but it has no type information and you read the whole thing every time. Parquet is column-based binary, so you read only the columns you need, it compresses well and it preserves types. Use Parquet for intermediate saves during analysis and for large data, and CSV for handing things to people. With the 540,000 rows in this post, it was 45.0MB vs. 3.5MB, and 0.003 seconds to read just two columns.</p>
<p><strong>What are Parquet's downsides?</strong> People can't open it directly, it's hard to edit a single row, and with small data it's actually bigger than CSV. At 151 rows, CSV was 7.7KB and Parquet was 10.7KB.</p>
<p><strong>What do you watch out for when turning nested JSON into a table?</strong> Dictionaries can be flattened with <code>json_normalize</code>, but if you spread a list (one-to-many) into rows with <code>explode</code>, the meaning of a row changes and totals get double-counted. First decide what one row represents, and if needed, split the one-to-many information into a separate table.</p>
<p><strong>What's the difference between partitioning and sharding?</strong> Partitioning splits data into sections within one storage location by something like date, so you read only the part you need. Sharding splits data across multiple servers by a key such as a hash of the user ID, to spread the load. Analysts run into partitioning often; for sharding, knowing the concept is enough.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="full-code">Full code<a href="https://datapopcorn.ai/blog/en/file-formats-parquet#full-code" class="hash-link" aria-label="Direct link to Full code" title="Direct link to Full code" translate="no">​</a></h2>
<p>Run <code>pip install pandas openpyxl pyarrow requests</code> first. The first script compares formats on the online store data; run it in the same folder as <code>online_retail_II.xlsx</code>.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> os</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> time</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> shutil</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> pandas </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> pd</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> pyarrow  </span><span class="token comment" style="color:#999988;font-style:italic"># load it up front so import time doesn't leak into the first read time</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">XLSX </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> os</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">environ</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"RETAIL_XLSX"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"online_retail_II.xlsx"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">OUT </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"formats"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">shutil</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">rmtree</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">OUT</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ignore_errors</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> os</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">makedirs</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">OUT</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">timed</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">fn</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    t </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> time</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">perf_counter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> r </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> fn</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> r</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> time</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">perf_counter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> t</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># The original, read with explicit types</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> t_xlsx </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> timed</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">lambda</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> pd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">read_excel</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    XLSX</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> sheet_name</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"Year 2010-2011"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    dtype</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">{</span><span class="token string" style="color:#e3116c">"Invoice"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"StockCode"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Customer ID"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Int64"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"Customer ID"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"Customer ID"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">astype</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"string"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># IDs as strings</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"Excel read: </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">t_xlsx</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">:</span><span class="token string-interpolation interpolation format-spec">.1f</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">s, </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation builtin">len</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">(</span><span class="token string-interpolation interpolation">df</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">)</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">:</span><span class="token string-interpolation interpolation format-spec">,</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c"> rows"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># 1) Save in three formats</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">paths </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token string" style="color:#e3116c">"CSV"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:#e3116c">f"</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">OUT</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">/retail.csv"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"JSON Lines"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:#e3116c">f"</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">OUT</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">/retail.jsonl"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Parquet"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:#e3116c">f"</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">OUT</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">/retail.parquet"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">to_csv</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">paths</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"CSV"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> index</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">False</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">to_json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">paths</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"JSON Lines"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> orient</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"records"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> lines</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> date_format</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"iso"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> force_ascii</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">False</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">try</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    df</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">to_parquet</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">paths</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"Parquet"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> index</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">False</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">except</span><span class="token plain"> Exception </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> e</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Parquet save failed:"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">type</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">__name__</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"-"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">90</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    odd </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"Description"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token builtin">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token builtin">type</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">value_counts</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">to_dict</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Python types of Description values:"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">t</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">__name__</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> n </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> t</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> n </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> odd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">items</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"| numeric examples:"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> df</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">loc</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"Description"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token builtin">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">lambda</span><span class="token plain"> v</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">isinstance</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">v</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token builtin">int</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">float</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">and</span><span class="token plain"> pd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">notna</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">v</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Description"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">head</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">3</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">tolist</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"Description"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"Description"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">astype</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"string"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    df</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">to_parquet</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">paths</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"Parquet"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> index</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">False</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Saved after casting Description to string"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">xlsx_mb </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> os</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">path</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">getsize</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">XLSX</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">/</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1024</span><span class="token operator" style="color:#393A34">**</span><span class="token number" style="color:#36acaa">2</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"Original xlsx (2 sheets): </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">xlsx_mb</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">:</span><span class="token string-interpolation interpolation format-spec">.1f</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c"> MB"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> k</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> p </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> paths</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">items</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">k</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c"> size: </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">os</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">.</span><span class="token string-interpolation interpolation">path</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">.</span><span class="token string-interpolation interpolation">getsize</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">(</span><span class="token string-interpolation interpolation">p</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">)</span><span class="token string-interpolation interpolation"> </span><span class="token string-interpolation interpolation operator" style="color:#393A34">/</span><span class="token string-interpolation interpolation"> </span><span class="token string-interpolation interpolation number" style="color:#36acaa">1024</span><span class="token string-interpolation interpolation operator" style="color:#393A34">**</span><span class="token string-interpolation interpolation number" style="color:#36acaa">2</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">:</span><span class="token string-interpolation interpolation format-spec">.1f</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c"> MB"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># 2) Time to read back</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">readers </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"CSV"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">lambda</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> pd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">read_csv</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">paths</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"CSV"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"JSON Lines"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">lambda</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> pd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">read_json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">paths</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"JSON Lines"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> lines</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"Parquet"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">lambda</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> pd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">read_parquet</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">paths</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"Parquet"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"Parquet (2 columns only)"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">lambda</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> pd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">read_parquet</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">paths</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"Parquet"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> columns</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"Quantity"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Price"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">back </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> k</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> fn </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> readers</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">items</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    fn</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># one warm-up run</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    runs </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">timed</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">fn</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> _ </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> </span><span class="token builtin">range</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">3</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    back</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">k</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> runs</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">k</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c"> read: </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation builtin">min</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">(</span><span class="token string-interpolation interpolation">sec </span><span class="token string-interpolation interpolation keyword" style="color:#00009f">for</span><span class="token string-interpolation interpolation"> _</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">,</span><span class="token string-interpolation interpolation"> sec </span><span class="token string-interpolation interpolation keyword" style="color:#00009f">in</span><span class="token string-interpolation interpolation"> runs</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">)</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">:</span><span class="token string-interpolation interpolation format-spec">.3f</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">s (best of 3)"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># 3) Do the types survive the round trip?</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> k </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"CSV"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"JSON Lines"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Parquet"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    b </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> back</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">k</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"[</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">k</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">] InvoiceDate=</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">b</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">[</span><span class="token string-interpolation interpolation string" style="color:#e3116c">'InvoiceDate'</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">]</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">.</span><span class="token string-interpolation interpolation">dtype</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">, Customer ID=</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">b</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">[</span><span class="token string-interpolation interpolation string" style="color:#e3116c">'Customer ID'</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">]</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">.</span><span class="token string-interpolation interpolation">dtype</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">, "</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string-interpolation string" style="color:#e3116c">f"Invoice=</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">b</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">[</span><span class="token string-interpolation interpolation string" style="color:#e3116c">'Invoice'</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">]</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">.</span><span class="token string-interpolation interpolation">dtype</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">, StockCode=</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">b</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">[</span><span class="token string-interpolation interpolation string" style="color:#e3116c">'StockCode'</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">]</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">.</span><span class="token string-interpolation interpolation">dtype</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">, "</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string-interpolation string" style="color:#e3116c">f"Customer ID sample=</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">b</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">[</span><span class="token string-interpolation interpolation string" style="color:#e3116c">'Customer ID'</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">]</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">.</span><span class="token string-interpolation interpolation">dropna</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">(</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">)</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">.</span><span class="token string-interpolation interpolation">iloc</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">[</span><span class="token string-interpolation interpolation number" style="color:#36acaa">0</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">]</span><span class="token string-interpolation interpolation conversion-option punctuation" style="color:#393A34">!r</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">, "</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string-interpolation string" style="color:#e3116c">f"values equal (Customer ID)=</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">b</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">[</span><span class="token string-interpolation interpolation string" style="color:#e3116c">'Customer ID'</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">]</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">.</span><span class="token string-interpolation interpolation">astype</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">(</span><span class="token string-interpolation interpolation string" style="color:#e3116c">'string'</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">)</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">.</span><span class="token string-interpolation interpolation">fillna</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">(</span><span class="token string-interpolation interpolation string" style="color:#e3116c">''</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">)</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">.</span><span class="token string-interpolation interpolation">equals</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">(</span><span class="token string-interpolation interpolation">df</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">[</span><span class="token string-interpolation interpolation string" style="color:#e3116c">'Customer ID'</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">]</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">.</span><span class="token string-interpolation interpolation">fillna</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">(</span><span class="token string-interpolation interpolation string" style="color:#e3116c">''</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">)</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">)</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># 4) Monthly partitions</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"ym"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"InvoiceDate"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">dt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">strftime</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"%Y-%m"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">to_parquet</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">OUT</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">/by_month"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> partition_cols</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"ym"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> index</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">False</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">folders </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token builtin">sorted</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">os</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">listdir</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">OUT</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">/by_month"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Partition folders:"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">len</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">folders</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"total,"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> folders</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"…"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> folders</span><span class="token punctuation" style="color:#393A34">[</span><span class="token operator" style="color:#393A34">-</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">nov</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> sec </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> timed</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">lambda</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> pd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">read_parquet</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">OUT</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">/by_month"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> filters</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"ym"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"=="</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"2011-11"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"Read 2011-11 only: </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation builtin">len</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">(</span><span class="token string-interpolation interpolation">nov</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">)</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">:</span><span class="token string-interpolation interpolation format-spec">,</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c"> rows, </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">sec</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">:</span><span class="token string-interpolation interpolation format-spec">.2f</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">s, ym dtype=</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">nov</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">[</span><span class="token string-interpolation interpolation string" style="color:#e3116c">'ym'</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">]</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">.</span><span class="token string-interpolation interpolation">dtype</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">"</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>The second is the PokeAPI script. It sends one request per Pokémon, 151 requests in total.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> os</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> requests</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> pandas </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> pd</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">rows </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> i </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> </span><span class="token builtin">range</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">152</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    p </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> requests</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"https://pokeapi.co/api/v2/pokemon/</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">i</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> timeout</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">30</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    rows</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">append</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"dex_no"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:#e3116c">f"</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">p</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">[</span><span class="token string-interpolation interpolation string" style="color:#e3116c">'id'</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">]</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">:</span><span class="token string-interpolation interpolation format-spec">04d</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"name"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> p</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"name"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"weight"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> p</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"weight"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># unit: 0.1kg</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"types"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">t</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"type"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"name"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> t </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> p</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"types"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token operator" style="color:#393A34">**</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">s</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"stat"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"name"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> s</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"base_stat"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> s </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> p</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"stats"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">df </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> pd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">DataFrame</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rows</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Rows:"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">len</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"| 2 types:"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"types"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">.</span><span class="token builtin">len</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token builtin">sum</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"| 1 type:"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"types"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">.</span><span class="token builtin">len</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token builtin">sum</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Charizard:"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> df</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">loc</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"name"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"charizard"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"types"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">iloc</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"| Pikachu:"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> df</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">loc</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"name"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"pikachu"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"types"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">iloc</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># Spreading the one-to-many relationship into rows</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token builtin">long</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> df</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">explode</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"types"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Rows after explode:"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">len</span><span class="token punctuation" style="color:#393A34">(</span><span class="token builtin">long</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"Total weight: originally </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">df</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">[</span><span class="token string-interpolation interpolation string" style="color:#e3116c">'weight'</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">]</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">.</span><span class="token string-interpolation interpolation builtin">sum</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">(</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">)</span><span class="token string-interpolation interpolation"> </span><span class="token string-interpolation interpolation operator" style="color:#393A34">/</span><span class="token string-interpolation interpolation"> </span><span class="token string-interpolation interpolation number" style="color:#36acaa">10</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">:</span><span class="token string-interpolation interpolation format-spec">,.1f</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">kg → after explode </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation builtin">long</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">[</span><span class="token string-interpolation interpolation string" style="color:#e3116c">'weight'</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">]</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">.</span><span class="token string-interpolation interpolation builtin">sum</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">(</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">)</span><span class="token string-interpolation interpolation"> </span><span class="token string-interpolation interpolation operator" style="color:#393A34">/</span><span class="token string-interpolation interpolation"> </span><span class="token string-interpolation interpolation number" style="color:#36acaa">10</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">:</span><span class="token string-interpolation interpolation format-spec">,.1f</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">kg"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># Save, then read back</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">os</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">makedirs</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"poke"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> exist_ok</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">to_csv</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"poke/gen1.csv"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> index</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">False</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">to_json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"poke/gen1.json"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> orient</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"records"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> force_ascii</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">False</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">to_parquet</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"poke/gen1.parquet"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> index</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">False</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> f </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"gen1.csv"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"gen1.json"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"gen1.parquet"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">f</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:#e3116c">f"</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">os</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">.</span><span class="token string-interpolation interpolation">path</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">.</span><span class="token string-interpolation interpolation">getsize</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">(</span><span class="token string-interpolation interpolation string" style="color:#e3116c">'poke/'</span><span class="token string-interpolation interpolation"> </span><span class="token string-interpolation interpolation operator" style="color:#393A34">+</span><span class="token string-interpolation interpolation"> f</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">)</span><span class="token string-interpolation interpolation"> </span><span class="token string-interpolation interpolation operator" style="color:#393A34">/</span><span class="token string-interpolation interpolation"> </span><span class="token string-interpolation interpolation number" style="color:#36acaa">1024</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">:</span><span class="token string-interpolation interpolation format-spec">.1f</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c"> KB"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">c </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> pd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">read_csv</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"poke/gen1.csv"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">q </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> pd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">read_parquet</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"poke/gen1.parquet"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Reread from CSV | Pikachu dex_no:"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">repr</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">c</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">loc</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">c</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"name"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"pikachu"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"dex_no"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">iloc</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"| Bulbasaur types:"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">repr</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">c</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">loc</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">c</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"name"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"bulbasaur"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"types"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">iloc</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">b </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> q</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">loc</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">q</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"name"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"bulbasaur"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"types"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">iloc</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Reread from Parquet | Pikachu dex_no:"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">repr</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">q</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">loc</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">q</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"name"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"pikachu"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"dex_no"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">iloc</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"| Bulbasaur types:"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">type</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">b</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">__name__</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">list</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">b</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="references">References<a href="https://datapopcorn.ai/blog/en/file-formats-parquet#references" class="hash-link" aria-label="Direct link to References" title="Direct link to References" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://archive.ics.uci.edu/dataset/502/online+retail+ii" target="_blank" rel="noopener noreferrer" class="">UCI Online Retail II</a>: Chen, D. (2012). UCI Machine Learning Repository. <a href="https://doi.org/10.24432/C5CG6D" target="_blank" rel="noopener noreferrer" class="">https://doi.org/10.24432/C5CG6D</a> (CC BY 4.0)</li>
<li class=""><a href="https://pokeapi.co/" target="_blank" rel="noopener noreferrer" class="">PokeAPI</a>: public API for Pokémon data (accessed September 29, 2026)</li>
<li class=""><a href="https://parquet.apache.org/docs/overview/" target="_blank" rel="noopener noreferrer" class="">Apache Parquet official docs</a>: the design background of the columnar format</li>
</ul>
<p>File sizes and times were measured on macOS with Python 3.9.6, pandas 2.3.3 and pyarrow 21.0.0. Times vary by environment, so read them as relative differences between formats.</p>]]></content:encoded>
            <category>Data analysis</category>
            <category>Career</category>
        </item>
        <item>
            <title><![CDATA[Japan's Seafood Exports to China Vanished by 99.5%. The US, Vietnam and Thailand Picked Up Less Than a Third]]></title>
            <link>https://datapopcorn.ai/blog/en/japan-seafood-export-reallocation</link>
            <guid>https://datapopcorn.ai/blog/en/japan-seafood-export-reallocation</guid>
            <pubDate>Sun, 27 Sep 2026 13:32:00 GMT</pubDate>
            <description><![CDATA[Japan's seafood (HS03) exports to China were $538.4 million in 2022 and $2.6 million in reported value in 2024. The extra $150 million bought by the US, Vietnam and Thailand covered less than a third of what was lost, and in the five months right after the ban, exports to other destinations grew by only $0.8 million.]]></description>
            <content:encoded><![CDATA[<p><strong>In 2022, Japan sold $538.4 million of seafood (HS03) to China. In 2024, reported exports to China came to $2.6 million. 99.5% had vanished.</strong> This happened after China halted all seafood imports from Japan on August 24, 2023.</p>
<p>A line you heard a lot back then was "if China won't buy, just sell to other countries." And compared with 2022, in 2024 the US bought $65.84 million more, Vietnam $60.56 million more and Thailand $25.83 million more. That adds up to $152.23 million. <strong>But that's less than a third of what was lost from China alone.</strong></p>
<p>The period right after the ban is even more striking. <strong>Over the five months from August to December 2023, extra sales to countries other than China and Hong Kong totaled just $0.8 million.</strong> That's 0.3% of the $238.17 million that disappeared. I used raw UN trade data to trace how slowly, and how partially, "just sell somewhere else" actually works in the real world.</p>
<!-- -->
<p><img decoding="async" loading="lazy" alt="Change in Japan&amp;#39;s HS03 exports by destination before and after China halted imports" src="https://datapopcorn.ai/blog/en/assets/images/chart-ff68623c92bc3e80ed071e877c88acfb.png" width="2800" height="1640" class="img_ev3q"></p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>The short version</div><div class="admonitionContent_BuS1"><ul>
<li class=""><strong>For the first five months, the lost exports went practically nowhere.</strong> From August to December 2023, exports to China and Hong Kong were $238.17 million lower than in the same months of 2022, while other destinations grew by only $0.8 million. Japan's world exports fell by the same amount, down 30.8%.</li>
<li class=""><strong>Looking only at the second half a year later, it seems like much of the gap was filled, but on an annual basis it was about one-seventh.</strong> From August to December 2024, destinations other than China and Hong Kong were up $175.63 million (+37.2%) from the same months of 2022, covering about 71% of the drop. But across all of 2024, the $81.62 million increase in other destinations was about 14% of the $575.58 million decline in China and Hong Kong, and world exports were 25.3% below 2022.</li>
<li class=""><strong>So here's how to read it.</strong> When a market that took a third of exports was closed off, alternative outlets didn't open right away, and more than a year later most of the gap was still there. It's a measured case showing how slowly, and how partially, the assumption "if one market closes, sell somewhere else" works in reality.</li>
</ul></div></div>
<div class="wrapper_Aefj vertical_a1Rk"><iframe src="https://www.youtube.com/embed/I_E6JSmZmk8" title="Japan's seafood exports to China fell 99.5%. Did other countries buy in its place?" loading="lazy" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="its-hs03-not-all-seafood">It's HS03, not all seafood<a href="https://datapopcorn.ai/blog/en/japan-seafood-export-reallocation#its-hs03-not-all-seafood" class="hash-link" aria-label="Direct link to It's HS03, not all seafood" title="Direct link to It's HS03, not all seafood" translate="no">​</a></h2>
<p>First, we need to narrow down what exactly is being analyzed. HS03 is the HS chapter covering fish, crustaceans, molluscs and other aquatic invertebrates, a classification centered on unprocessed items. So it is not the same as "all seafood" in everyday language. It can also include items that don't line up exactly with live fish or with what is legally banned.</p>
<p>Prepared or preserved fish (HS1604) and prepared or preserved crustaceans and molluscs (HS1605) are separate baskets. I did not add these two baskets to HS03 or merge them into a single all-seafood indicator. Stating up front that the legal ban and the statistical classification may differ keeps the numbers that follow from being overstated as a policy effect.</p>
<p>This analysis covers Japan, exports, monthly, January 2022 through December 2024. For the raw data, I pulled every destination row for Japan plus the World aggregate from the UN Comtrade public preview API. World's partner code 0 is the sum of all destinations, not an additional country, so I did not add it back into the country totals.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="lining-up-the-key-results-by-the-same-months">Lining up the key results by the same months<a href="https://datapopcorn.ai/blog/en/japan-seafood-export-reallocation#lining-up-the-key-results-by-the-same-months" class="hash-link" aria-label="Direct link to Lining up the key results by the same months" title="Direct link to Lining up the key results by the same months" translate="no">​</a></h2>
<p>China announced its halt on Japanese seafood imports and it took effect on August 24, 2023. Because the measure landed mid-month, August 2023 is a transition month, not a clean pre- or post-period month. Even so, to avoid seasonality, I compared August-December 2022 with August-December 2023, month for month.</p>
<p>In the table below, <code>m</code> means millions of US dollars.</p>
<table><thead><tr><th>Destination or total</th><th style="text-align:right">Aug-Dec 2022</th><th style="text-align:right">Aug-Dec 2023</th><th style="text-align:right">Change</th><th>How to read it</th></tr></thead><tbody><tr><td>World</td><td style="text-align:right">$770.264m</td><td style="text-align:right">$532.889m</td><td style="text-align:right">-30.82%</td><td>Decline in world exports</td></tr><tr><td>China, reported rows only</td><td style="text-align:right">$229.009m, 5/5 months</td><td style="text-align:right">$18.769m, 4/5 months</td><td style="text-align:right">Full-period rate not calculated</td><td>November 2023 missing</td></tr><tr><td>Hong Kong</td><td style="text-align:right">$69.458m</td><td style="text-align:right">$41.524m</td><td style="text-align:right">-40.22%</td><td>5/5 months observed</td></tr><tr><td>Destinations excluding China and Hong Kong</td><td style="text-align:right">$471.797m</td><td style="text-align:right">$472.596m</td><td style="text-align:right">+0.17%</td><td>Descriptive statistic for early reallocation</td></tr></tbody></table>
<p>World compares five fully observed months each. China, on the other hand, has no row for November 2023, so there are only four months of reported values. Turning that gap into $0 would create a completely different claim: "exports to China were zero in November." So I did not calculate a five-month change rate for China, and recorded the reported subtotal and whether each row exists separately.</p>
<p>In a matched-month comparison, which keeps only pairs where the same month exists in both years, China fell 90.07%, from $189.054 million to $18.769 million on a four-month basis. This too doesn't plug in zeros for the missing value; it matches August, September, October and December 2022 with the same months of 2023. But this result also can't be read as the pure effect of a single legal measure.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="it-was-replaced-versus-it-changed-at-the-same-time">"It was replaced" versus "it changed at the same time"<a href="https://datapopcorn.ai/blog/en/japan-seafood-export-reallocation#it-was-replaced-versus-it-changed-at-the-same-time" class="hash-link" aria-label="Direct link to &quot;It was replaced&quot; versus &quot;it changed at the same time&quot;" title="Direct link to &quot;It was replaced&quot; versus &quot;it changed at the same time&quot;" translate="no">​</a></h2>
<p>When exports to China fall and exports to other destinations rise, people often say "China's volume was replaced by the US or Vietnam." In trade, reallocation means a supplier redirecting existing volume to other markets. But the fact that exports to different destinations moved together doesn't let you trace where individual China-bound shipments went.</p>
<p>Causal identification would require examining what path each destination and world exports would have taken without the ban, whether comparison with other countries is possible, and whether price, product, logistics and exchange-rate shocks were the same. These results don't estimate such a counterfactual; they are before-and-after descriptive statistics. The 0.17% rise in destinations other than China and Hong Kong means that, at least in the first same-month window, there was no visible large-scale additional absorption overall. It is not evidence that the $200 million lost from China moved intact to other countries.</p>
<p>August-December 2024 paints a different picture. World was 9.29% below the same months of 2022, but destinations excluding China and Hong Kong were up 37.23% to $647.427 million. The US rose by $83.878 million, Vietnam by $53.201 million and Thailand by $33.709 million. This is consistent with destination diversification growing over time, but you can't count the 2024 rise as the causal replacement volume for China.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-i-checked-a-september-window-separately">Why I checked a September window separately<a href="https://datapopcorn.ai/blog/en/japan-seafood-export-reallocation#why-i-checked-a-september-window-separately" class="hash-link" aria-label="Direct link to Why I checked a September window separately" title="Direct link to Why I checked a September window separately" translate="no">​</a></h2>
<p>Since August is the transition month that includes the effective date, it's intuitive to look at September through December on its own. The raw data and result files include comparison windows for January-July 2023 and September-December 2023. But the windows differ in length (7 months versus 4), so only monthly averages can be compared, not totals, and the seasonal comparison isn't perfectly matched either.</p>
<p>In the stricter same-month comparison of September-December 2022 versus September-December 2023, world exports fell 32.26%, from $612.367 million to $414.810 million. China fell 96.98% looking only at the three reported months common to both years, and destinations excluding China and Hong Kong fell 3.80%. The direction matches the August-December results, but a different comparison window gives different numbers. If you don't fix the analysis period and just pick whichever window looks biggest, conclusions get inflated easily.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-i-used-only-dollar-export-values">Why I used only dollar export values<a href="https://datapopcorn.ai/blog/en/japan-seafood-export-reallocation#why-i-used-only-dollar-export-values" class="hash-link" aria-label="Direct link to Why I used only dollar export values" title="Direct link to Why I used only dollar export values" translate="no">​</a></h2>
<p>In the 2,012 rows of HS03 raw data, net weight was either zero or carried an estimation flag, so I couldn't build a reliable kg total. The main analysis therefore uses only nominal US dollar export values. This is the declared export value, not volume. It isn't adjusted for exchange rates or inflation, and it can also reflect changes in the mix of species, quality and processing, as well as changes in export prices.</p>
<p>So the right phrasing is not "export volume fell 30.8%" but "HS03 export value in nominal US dollars fell 30.8%." HS1604 and HS1605 do have separately valid net weight rows, but I didn't mix those figures with HS03 dollar exports. Combining indicators with different units into one total can produce a plausible-looking number that has lost its meaning.</p>
<p>Hong Kong can't be treated as a simple substitute destination either. From August 24, 2023, Hong Kong announced a full restriction on seafood from 10 Japanese prefectures and inspection requirements for products from other areas. So Hong Kong's -40.22% should be read as the change in a market hit by an external shock in the same direction as China, and it can't be used as a clean control group to fill in China's missing data.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="71-for-the-second-half-14-for-the-full-year">71% for the second half, 14% for the full year<a href="https://datapopcorn.ai/blog/en/japan-seafood-export-reallocation#71-for-the-second-half-14-for-the-full-year" class="hash-link" aria-label="Direct link to 71% for the second half, 14% for the full year" title="Direct link to 71% for the second half, 14% for the full year" translate="no">​</a></h2>
<p>The same data produced two numbers because January-July 2024 was weak. Annual world exports in 2024 were $1,455.52 million, $493.96 million less than the $1,949.48 million in 2022. Destinations other than China and Hong Kong rose 6.5% for the year, from $1,259.80 million to $1,341.40 million, but most of that increase was concentrated in the second half. From January to June 2024, exports to destinations other than China and Hong Kong were actually 4-30% lower each month than in the same months of 2022, and from July 2024, about 11 months after the ban, they were 13-60% higher each month.</p>
<p>This gap is an important warning for anyone reading data. "Other markets filled 70% of China's gap" and "they filled only one-seventh" can both come from the same raw data, and which period you pick changes the conclusion. Any "replacement complete" claim that doesn't also show the full year deserves some skepticism.</p>
<p>Note that all of these values are in nominal US dollars. The yen was weaker in 2024 than in 2022, so even at the same yen price, the dollar value comes out smaller. Part of the -25.3% in world exports may be exchange-rate movement. Comparisons across destinations, however, face the same exchange rate, so the direction "other markets grew far less than China's decline" can't be explained by currency.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="so-where-can-you-use-this-case">So where can you use this case?<a href="https://datapopcorn.ai/blog/en/japan-seafood-export-reallocation#so-where-can-you-use-this-case" class="hash-link" aria-label="Direct link to So where can you use this case?" title="Direct link to So where can you use this case?" translate="no">​</a></h2>
<p><strong>If you're an exporter or policymaker</strong>, you can use it as a benchmark for checking dependence on a single market. In 2022, China and Hong Kong together accounted for 35.4% of Japan's HS03 exports. When that market closed, it took nearly a year for alternative outlets to grow noticeably, and on an annual basis most of it wasn't recovered. It puts numbers behind the idea that alternative outlets need to be built before a market closes, not after.</p>
<p><strong>When you read news coverage</strong>, check which period a "replaced by other countries" report looked at, whether it's value or volume, and whether it also looked at the full year. Exports to the US, Vietnam and Thailand did increase, but that doesn't mean they filled China's gap.</p>
<p><strong>For a more precise view</strong>, you'd need to look at HS 6-digit exports by species (scallops, for example), yen-denominated values and re-export routes after processing. A key question in particular is whether volume that used to be processed in China moved to processing plants in other countries.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="rules-to-follow-when-reproducing-this">Rules to follow when reproducing this<a href="https://datapopcorn.ai/blog/en/japan-seafood-export-reallocation#rules-to-follow-when-reproducing-this" class="hash-link" aria-label="Direct link to Rules to follow when reproducing this" title="Direct link to Rules to follow when reproducing this" translate="no">​</a></h2>
<p>First, don't add World back into the destination list. Second, when a partner-month row is missing, leave it missing and don't replace it with zero. Third, don't call August 2023 the first complete post-ban month. Fourth, look at the same-month and matched-month comparisons first, and present 7-month versus 1-month or 7-month versus 4-month windows only as monthly-average reference figures. Fifth, don't mix nominal US dollars with kg.</p>
<p>I downloaded the raw data from the <a href="https://comtradeapi.un.org/public/v1/preview/C/M/HS" target="_blank" rel="noopener noreferrer" class="">UN Comtrade public preview API</a> and followed the <a href="https://uncomtrade.org/docs/un-comtrade-api/" target="_blank" rel="noopener noreferrer" class="">UN Comtrade API documentation</a> for dimension definitions. I checked the effective date against the <a href="https://english.scio.gov.cn/pressroom/2023-08/24/content_107089279.htm" target="_blank" rel="noopener noreferrer" class="">announcement from China's State Council Information Office</a>, and Hong Kong's separate measures against the <a href="https://www.info.gov.hk/gia/general/202308/28/P2023082800761.htm" target="_blank" rel="noopener noreferrer" class="">Hong Kong government notice</a>.</p>
<p><a href="https://datapopcorn.ai/blog/en/assets/files/study-551812a09d1c3049315c2b9a1a941ba7.zip" target="_blank" class="">Raw data and reproduction code (ZIP)</a></p>
<p>The archive contains monthly country rows for HS03, HS1604 and HS1605 from January 2022 to December 2024, the request manifest, validation results, and analyze.mjs. Running the script on Node.js 18 or later recalculates the same-month totals for World, China, Hong Kong and destinations excluding China and Hong Kong, along with the missing-row checks. annual_offset.mjs recalculates the annual and August-December replacement ratios in the summary box without filling in missing values.</p>
<p><a href="https://datapopcorn.ai/blog/en/assets/files/chart-85c1ea7b85c36c5f92147ad30bf58e0a.svg" target="_blank" class="">Original chart SVG</a></p>]]></content:encoded>
            <category>Data analysis</category>
        </item>
    </channel>
</rss>