<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[TanLabs]]></title><description><![CDATA[TanLabs]]></description><link>https://tanlabs.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 07 Nov 2026 11:35:59 GMT</lastBuildDate><atom:link href="https://tanlabs.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Freelance Cash-Flow Spreadsheet: A Practical Guide for Irregular Income]]></title><description><![CDATA[A freelancer can be profitable and still run short of cash between invoices. Before importing a year of history, spend five minutes testing three…]]></description><link>https://tanlabs.hashnode.dev/freelance-cash-flow-spreadsheet-irregular-income</link><guid isPermaLink="true">https://tanlabs.hashnode.dev/freelance-cash-flow-spreadsheet-irregular-income</guid><category><![CDATA[excel]]></category><category><![CDATA[Freelancing]]></category><category><![CDATA[personal finance]]></category><category><![CDATA[Productivity]]></category><dc:creator><![CDATA[TanLabs]]></dc:creator><pubDate>Fri, 06 Nov 2026 06:00:58 GMT</pubDate><content:encoded><![CDATA[<p>A freelancer can be profitable and still run short of cash between invoices. Before importing a year of history, spend five minutes testing three transactions: one invoice, one current-month expense and one next-month expense. This guide shows how to choose a cash-flow spreadsheet, check tax reserve and runway, and decide whether a transparent local workbook or a connected budgeting app fits the job.</p>
<h2>Compare the three common choices</h2>
<table>
<thead>
<tr>
<th>Tool</th>
<th>Strongest use case</th>
<th>Trade-off</th>
<th>Choose the local workbook when…</th>
</tr>
</thead>
<tbody><tr>
<td>Tiller</td>
<td>Automatic daily bank feeds into Sheets or Excel</td>
<td>Subscription and bank connection</td>
<td>You want editable formulas and no account credentials</td>
</tr>
<tr>
<td>YNAB</td>
<td>Guided zero-based planning and goals</td>
<td>App-first workflow</td>
<td>You need prompts and envelope-style assignment</td>
</tr>
<tr>
<td>Vertex42</td>
<td>Free general-purpose templates</td>
<td>Less tailored to irregular client work</td>
<td>You need a simple manual starting point</td>
</tr>
</tbody></table>
<p>The local workbook is deliberately narrower: it does not sync a bank, predict local taxes or replace an accountant. In exchange, every formula is visible, the assumptions are editable and the file can be regenerated from Python.</p>
<h2>Minimum fields that prevent surprises</h2>
<p>Use one row per event, rather than one tab per client:</p>
<ul>
<li><strong>Income:</strong> date, client, invoice status and gross amount.</li>
<li><strong>Expenses:</strong> date, category and amount.</li>
<li><strong>Setup:</strong> tax reserve rate, monthly baseline salary, opening buffer and warning runway.</li>
<li><strong>Dashboard:</strong> gross income, tax reserve, net cash, expenses, ending buffer and runway months.</li>
</ul>
<p>For monthly totals, use a half-open interval (<code>&gt;= first day</code> and <code>&lt; first day of next month</code>). That keeps a payment at 23:59 on the last day in the correct month and avoids hidden time-of-day bugs.</p>
<h2>Five-minute verification before you rely on it</h2>
<ol>
<li>Enter one $2,000 invoice on the 5th and one $500 expense on the 12th.</li>
<li>With a 25% tax reserve, confirm tax reserve is $500 and net invoice cash is $1,500.</li>
<li>Add a $300 expense dated in the next month; confirm only the next month changes.</li>
<li>Change the reserve to 30%; confirm the dashboard changes by $100 without editing a formula.</li>
<li>Set baseline salary to $1,000; confirm runway equals ending buffer divided by $1,000.</li>
</ol>
<p>These checks test timing, assumptions and formula transparency. If one fails, fix the workbook before adding historical transactions.</p>
<h2>Printable six-check smoke test</h2>
<p>Copy this compact checklist into a note or print the page before importing historical data. It turns the five checks above into a repeatable handoff for a client, bookkeeper or future you.</p>
<ul>
<li><input disabled type="checkbox" /> One invoice is entered with the expected date, client and gross amount.</li>
<li><input disabled type="checkbox" /> One current-month expense changes this month only.</li>
<li><input disabled type="checkbox" /> One next-month expense lands in the next month, not the current month.</li>
<li><input disabled type="checkbox" /> Tax reserve changes by the expected amount when the rate is edited.</li>
<li><input disabled type="checkbox" /> Baseline spending is editable without changing a formula.</li>
<li><input disabled type="checkbox" /> Runway equals ending buffer divided by the current baseline.</li>
</ul>
<p>If any box fails, fix the workbook before adding historical transactions.</p>
<h2>If a check fails, isolate the cause</h2>
<p>Use the failing check to narrow the problem before changing formulas:</p>
<table>
<thead>
<tr>
<th>Symptom</th>
<th>Most likely cause</th>
<th>Quick isolation step</th>
</tr>
</thead>
<tbody><tr>
<td>Tax reserve is not $500</td>
<td>The rate is stored as <code>25</code> instead of <code>25%</code>, or the invoice is not included</td>
<td>Set the rate to 25%, filter to the invoice date, and recalculate</td>
</tr>
<tr>
<td>November includes the December expense</td>
<td>The month filter uses <code>&lt;= month end</code> with a time value</td>
<td>Replace the upper bound with <code>&lt; first day of next month</code></td>
</tr>
<tr>
<td>Runway is blank or infinite</td>
<td>Baseline spending is zero or stored as text</td>
<td>Enter a positive numeric baseline such as $1,000 and confirm the cell is numeric</td>
</tr>
<tr>
<td>Changing the rate does nothing</td>
<td>The dashboard contains a pasted value or a hard-coded formula</td>
<td>Trace the reserve cell back to the setup rate and refresh calculations</td>
</tr>
</tbody></table>
<p>Save a copy of the workbook before correcting a formula. After the correction, rerun all six checks and remove the demo rows. This gives a clear before/after record without mixing test data into real cash flow.</p>
<h2>Copyable starter rows</h2>
<p>If you want a reproducible first run, paste these rows into the income and expenses sheets before importing real history. They are deliberately small enough to audit by eye:</p>
<pre><code class="language-csv">sheet,date,description,amount
income,2026-11-05,Demo invoice,2000
expenses,2026-11-12,Demo current-month expense,500
expenses,2026-12-02,Demo next-month expense,300
</code></pre>
<p>With a 25% reserve and a $1,000 monthly baseline, the expected checks are: $500 tax reserve on the invoice, $1,500 net invoice cash, the $500 expense in November only, the $300 expense in December only, and runway based on the ending buffer divided by $1,000. Delete the demo rows after the smoke test so they cannot be mistaken for real transactions.</p>
<h2>Reproducible starter file</h2>
<p>The exact three-row smoke-test input is also kept as <code>50_devto/assets/freelance_cashflow_starter_rows.csv</code>. Copy it into the income and expenses sheets, run the six checks, then delete the demo rows before adding real history. Keeping the CSV beside the article prevents transcription differences when a client or bookkeeper repeats the handoff.</p>
<h2>A ready-to-use starting point</h2>
<p>The <a href="https://tanlabs.hashnode.dev/budget-spreadsheets-python-openpyxl-formula-logic?utm_source=seo&amp;utm_medium=referral&amp;utm_campaign=freelance_cashflow_12">freelancer cash-flow dashboard tutorial</a> includes the four-sheet layout and openpyxl formulas. For a finished editable bundle, see the <a href="https://tanlabs.gumroad.com/l/zlynlp?utm_source=seo&amp;utm_medium=referral&amp;utm_campaign=freelance_cashflow_12">Budget &amp; Bookkeeping Toolkit</a>. You can also sanity-check allocations in the free <a href="https://calc.funmagi.com/50-30-20-budget-calculator?utm_source=seo&amp;utm_medium=referral&amp;utm_campaign=freelance_cashflow_12">50/30/20 calculator</a>.</p>
<p>The goal is a repeatable decision: record events, reserve tax money, pay the baseline and watch runway. A spreadsheet cannot make an uncertain invoice arrive, but it can show the gap early enough to act.</p>
<h2>Quick decision guide</h2>
<p>Choose the local workbook if you need to inspect every formula, keep bank credentials out of the workflow, or change the reserve rate for a specific contract. Choose Tiller if automatic transaction import is the deciding feature. Choose YNAB if guided category assignment and goal prompts matter more than an editable workbook. Choose Vertex42 if a free, general template is enough and irregular invoice timing is not the main problem.</p>
<h2>FAQ: freelance cash-flow spreadsheets</h2>
<p><strong>Is this a replacement for accounting software?</strong> No. It is a small cash-flow view for timing, reserves and runway. Keep tax filings and statutory records in the system required by your accountant or jurisdiction.</p>
<p><strong>Can I use it without connecting my bank?</strong> Yes. Enter invoice and expense events manually. That keeps the inputs reviewable, but it also means the result is only as complete as the entries you add.</p>
<p><strong>What should I test first?</strong> Use one invoice, one current-month expense and one next-month expense. Then change the tax reserve rate. The dashboard should change only in the expected month and the runway should use the updated reserve and baseline assumptions.</p>
<p><strong>Why use a half-open month (<code>&gt;= start</code> and <code>&lt; next start</code>)?</strong> It includes every timestamp on the first day and excludes the next month cleanly, so a late-night payment on the last day is not dropped or double-counted.</p>
]]></content:encoded></item><item><title><![CDATA[I Built 4 Budget Spreadsheets With Python (openpyxl): The Formula Logic Behind Each One]]></title><description><![CDATA[Build four practical budget spreadsheets with Python and openpyxl. Learn the formula logic for irregular income, debt payoff, bookkeeping P&L, and…]]></description><link>https://tanlabs.hashnode.dev/budget-spreadsheets-python-openpyxl-formula-logic</link><guid isPermaLink="true">https://tanlabs.hashnode.dev/budget-spreadsheets-python-openpyxl-formula-logic</guid><category><![CDATA[Python]]></category><category><![CDATA[excel]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[TanLabs]]></dc:creator><pubDate>Mon, 02 Nov 2026 13:09:18 GMT</pubDate><content:encoded><![CDATA[<p>Most budget templates are hand-built in Excel, so when you find a bug you fix it in one file and forget the other three. I generated mine from a single Python script with <a href="https://openpyxl.readthedocs.io/">openpyxl</a> instead. Every formula is a Python f-string, so the logic lives in one place, sits in version control and gets rebuilt in a second.</p>
<p>This post covers the formula logic of the four sheets: an irregular-income budget for freelancers, a snowball-vs-avalanche debt planner, a bookkeeping sheet whose P&amp;L fills itself in, and a printable binder. You can copy the formulas into your own sheets. If you'd rather not, the finished files are linked at the end.</p>
<p>Status, so nobody is misled: the templates are on Gumroad. I have no sales numbers worth sharing yet, so this post is about the build, not revenue. AI tools helped me draft and double-check some formulas. I tested every sheet by hand in Excel and Google Sheets.</p>
<h2>Where this toolkit fits (compared with the popular options)</h2>
<p>Before choosing a template, decide whether you need bank sync or a repeatable workbook you own. Tiller is the strongest spreadsheet-first option when automatic daily bank feeds matter; YNAB is the stronger app when zero-based planning and one-tap assigning are the priority; Vertex42 is the simplest free, manual template for a general household budget. This toolkit sits between those three: no bank connection, no subscription, and more specialised sheets for irregular freelance income, debt payoff, bookkeeping P&amp;L and print.</p>
<table>
<thead>
<tr>
<th>Option</th>
<th>Best at</th>
<th>Trade-off</th>
<th>Why this toolkit can be a better fit</th>
</tr>
</thead>
<tbody><tr>
<td>Tiller</td>
<td>Daily bank feeds into Sheets/Excel</td>
<td>Subscription and account connection</td>
<td>Manual, local workbook with no bank credentials; one-time purchase</td>
</tr>
<tr>
<td>YNAB</td>
<td>Zero-based planning and guided workflow</td>
<td>App-first workflow and recurring subscription</td>
<td>Keeps Excel/Google Sheets formulas visible and editable</td>
</tr>
<tr>
<td>Vertex42</td>
<td>Free general-purpose templates</td>
<td>Less specialised for freelancers and debt comparisons</td>
<td>Includes irregular-income buffer, snowball/avalanche and P&amp;L in one bundle</td>
</tr>
</tbody></table>
<p>That positioning also sets the right expectation: this is a transparent workbook for people who want to inspect and change formulas, not an account-syncing app.</p>
<h3>Five-minute smoke test</h3>
<ol>
<li>Open the free 50/30/20 sheet and replace the three sample income/expense values.</li>
<li>Open the debt planner and change one balance and APR; confirm the priority and payoff month change.</li>
<li>Open the browser calculators first if you want to test the method without downloading anything.</li>
<li>Only then decide whether the four-sheet bundle saves enough setup time for your workflow.</li>
</ol>
<h2>Why generate spreadsheets with code?</h2>
<ul>
<li><strong>One source of truth.</strong> Column letters, ranges and number formats come from helper functions, so a renamed sheet can't silently break a formula three tabs away.</li>
<li><strong>Big pre-built ranges.</strong> The income log has 500 formula rows and the debt schedule has 360 months. Nobody wants to drag-fill those by hand, and nobody wants to check that someone did.</li>
<li><strong>Formulas, not values.</strong> openpyxl writes formulas as strings and Excel or Google Sheets calculates them on open. Buyers get a live sheet, not a static report.</li>
</ul>
<p>A cell is just this:</p>
<pre><code class="language-python">ws.cell(row=r, column=4, value=f'=IF(C{r}="","",C{r}*Setup!$B$5)').number_format = MONEY
</code></pre>
<h2>1. Irregular income: pay yourself a fixed salary out of a buffer</h2>
<p>Freelance income arrives in lumps. The fix is to stop treating each payment as spending money:</p>
<ol>
<li>Every payment goes into a <strong>buffer</strong> (a separate account).</li>
<li>A fixed share of each payment is set aside for <strong>tax</strong> (25% by default, editable).</li>
<li>Once a month you pay yourself a <strong>baseline salary</strong> equal to your must-pay costs.</li>
<li>The buffer carries you through the lean months. <strong>Runway</strong> = buffer ÷ salary.</li>
</ol>
<p>On the <code>Setup</code> tab you list your must-pay costs (rent, utilities, insurance, debt minimums, savings goal…). The salary is simply their sum:</p>
<pre><code class="language-text">Baseline salary  = SUM(Setup!B9:B24)
</code></pre>
<p>Each row in the <code>Income Log</code> splits a payment into tax and net:</p>
<pre><code class="language-text">Tax set-aside = IF(C4="", "", C4 * Setup!$B$5)
Net to buffer = IF(C4="", "", C4 - D4)
</code></pre>
<p>The <code>Dashboard</code> rolls the logs up by month with <code>SUMIFS</code> on a date window and carries the buffer forward:</p>
<pre><code class="language-text">Gross income (month) = SUMIFS('Income Log'!$C$4:$C$503,
                        'Income Log'!$A$4:$A$503, "&gt;="&amp;A5,
                        'Income Log'!$A$4:$A$503, "&lt;"&amp;EOMONTH(A5,0)+1)
Buffer balance       = previous buffer + net income - salary
Runway (months)      = IF(salary=0, "", buffer / salary)
</code></pre>
<p>Two conditional formats do most of the work: the buffer cell turns red when it drops below zero, and runway turns yellow under 3 months. The <code>&gt;=</code> month-start and <code>&lt;</code> next-month-start window also handles timestamps safely, so you never need a "month" helper column in the log. Just type the date.</p>
<h2>2. Snowball vs avalanche in plain formulas (no macros)</h2>
<p>The difference between the two methods is only the <strong>order</strong> in which debts get the extra money:</p>
<ul>
<li><strong>Snowball</strong>: smallest balance first (quick wins).</li>
<li><strong>Avalanche</strong>: highest APR first (least interest).</li>
</ul>
<p><strong>Ranking without SORT().</strong> <code>SORT</code> doesn't exist in older Excel, so the priority column uses <code>COUNTIFS</code>. A debt's rank is the number of debts that beat it, plus a tie-breaker:</p>
<pre><code class="language-text">Snowball  rank = COUNTIFS($B$10:$B$17,"&lt;"&amp;B10, $B$10:$B$17,"&gt;0") + COUNTIF($B$10:B10, B10)
Avalanche rank = COUNTIFS($C$10:$C$17,"&gt;"&amp;C10, $B$10:$B$17,"&gt;0") + COUNTIFS($C$10:C10, C10, $B$10:B10, "&gt;0")
Priority       = IF(N(B10)&lt;=0, 99, IF($B$5="Avalanche", &lt;avalanche&gt;, &lt;snowball&gt;))
</code></pre>
<p>The running <code>COUNTIF($B$10:B10, B10)</code> makes two equal balances get different ranks, so no money is ever split twice.</p>
<p><strong>The monthly schedule with the rollover.</strong> Each month and each debt:</p>
<pre><code class="language-text">owed      = last balance * (1 + APR/12)
min part  = MIN(owed, minimum payment)
left      = total monthly budget - SUM(all min parts)
ahead     = SUMPRODUCT((priority &lt; my priority) * MAX(0, owed - min part))   ← what higher-priority debts still need
pay       = min part + MIN(owed - min part, MAX(0, left - ahead))
balance   = ROUND(MAX(0, owed - pay), 2)
</code></pre>
<p>The total budget stays fixed at <em>extra + all original minimums</em>. So when a debt hits zero, its minimum doesn't disappear. It becomes part of <code>left</code> and flows to the next debt in line. That <em>is</em> the snowball rollover, and it needs no VBA.</p>
<p>The <code>Summary</code> tab counts months with a balance above 1 cent to get the payoff date. It also flags any debt whose minimum is <strong>at or below its monthly interest</strong> (<code>min &lt;= balance * APR / 12</code>), because that debt does not decline and people rarely notice. Flip the method dropdown to compare total interest for both orders on your own numbers.</p>
<h2>3. A P&amp;L that builds itself with SUMIFS</h2>
<p>For the bookkeeping sheet, the rule was "one row per transaction, nothing else". Each row is a date, a description, a type (Income/Expense) and a category from a dropdown (a <code>DataValidation</code> list fed by the <code>Setup</code> tab), plus an amount.</p>
<p>The P&amp;L is a grid of categories × 12 months, and each cell is a single four-condition <code>SUMIFS</code>:</p>
<pre><code class="language-text">=IF($A6="", "", SUMIFS(Transactions!$E$4:$E$2003,
      Transactions!$C$4:$C$2003, "Income",          ← type
      Transactions!$D$4:$D$2003, $A6,               ← category
      Transactions!$A$4:$A$2003, "&gt;="&amp;B$4,          ← month start
      Transactions!$A$4:$A$2003, "&lt;"&amp;EOMONTH(B$4,0)+1))
</code></pre>
<p>Then come totals, net profit and margin (<code>IF(income=0,"",profit/income)</code>), and a simple tax set-aside tab: <code>MAX(0, monthly profit) * rate</code>, with a running total so you can see what you should have put aside so far. It's labelled as an estimate, not tax advice.</p>
<p>The details that took longest:</p>
<ul>
<li><code>$A6</code> / <code>B$4</code> mixed references, so one formula string fills the whole grid.</li>
<li>The category list on the P&amp;L is itself a formula (<code>=IF(Setup!A9="","",Setup!A9)</code>). Rename a category once and it updates everywhere.</li>
</ul>
<h2>4. Printables: HTML + CSS → PDF</h2>
<p>The undated budget binder (12 pages, A4 and US Letter) isn't a spreadsheet at all. A small Python script builds an HTML document: a cover, a monthly budget, a bill tracker, savings and debt trackers, all as CSS-grid tables. Then it prints that to PDF once per paper size. CSS is much nicer than a PDF library for "a grid of boxes with rounded corners", and changing the paper size is a single argument.</p>
<h2>Lessons I'd keep</h2>
<ol>
<li><strong>Test in Google Sheets as well as Excel.</strong> Both handle <code>SUMIFS</code>, <code>EOMONTH</code> and <code>COUNTIFS</code> the same way. Newer functions like <code>SORT</code>/<code>FILTER</code> are where they differ, which is why I avoided them.</li>
<li><strong>Put example data in every log.</strong> An empty dashboard full of zeros looks broken. A realistic year of sample payments shows how it works in five seconds.</li>
<li><strong>Shade the input cells.</strong> The buyer should know exactly which cells are theirs to type in, and that everything else is formulas.</li>
<li><strong>Write the edge-case warnings into the sheet</strong> ("minimum below interest", "runway under 3 months") instead of the README nobody reads.</li>
</ol>
<h2>Get the files</h2>
<p>If you just want to use them:</p>
<ul>
<li><strong>Free:</strong> <a href="https://tanlabs.gumroad.com/l/free-budget-sheet?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn10_budget_sheets">50/30/20 budget sheet (Google Sheets + Excel)</a> and a <a href="https://tanlabs.gumroad.com/l/eypunu?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn10_budget_sheets">5-page large-print coloring sampler</a> (yes, unrelated, but people keep asking for it for their parents).</li>
<li><strong>Single templates:</strong> <a href="https://tanlabs.gumroad.com/l/gvfjft?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn10_budget_sheets">Freelancer irregular-income budget</a> ($12), <a href="https://tanlabs.gumroad.com/l/kubsdr?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn10_budget_sheets">Debt snowball vs avalanche planner</a> ($9), <a href="https://tanlabs.gumroad.com/l/qrfhah?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn10_budget_sheets">Bookkeeping with automatic P&amp;L</a> ($15), <a href="https://tanlabs.gumroad.com/l/pgigm?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn10_budget_sheets">Undated budget binder PDF</a> ($7).</li>
<li><strong>All four:</strong> <a href="https://tanlabs.gumroad.com/l/zlynlp?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn10_budget_sheets">Budget &amp; Bookkeeping Toolkit</a> for $29 instead of $43 bought separately.</li>
</ul>
<p>If you'd rather not download anything, the same maths runs in the browser on <a href="https://calc.funmagi.com/?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn10_budget_sheets">calc.funmagi.com</a>: <a href="https://calc.funmagi.com/debt-snowball-calculator?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn10_budget_sheets">debt snowball</a>, <a href="https://calc.funmagi.com/50-30-20-budget-calculator?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn10_budget_sheets">50/30/20 budget</a> and <a href="https://calc.funmagi.com/freelance-rate-calculator?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn10_budget_sheets">freelance rate</a> calculators, free and with no sign-up.</p>
<p>Questions about any of the formulas are welcome in the comments. Not financial advice: these are templates, and you enter your own numbers.</p>
]]></content:encoded></item><item><title><![CDATA[Turn a Doll or Plush Photo into a 3D Model with Free Hugging Face Spaces — TRELLIS vs Hunyuan3D Tested]]></title><description><![CDATA[I sell handmade-doll-style 3D models, and every model starts as a photo of a physical doll. Paid image-to-3D tools work, but they cost credits for every…]]></description><link>https://tanlabs.hashnode.dev/turn-a-doll-or-plush-photo-into-a-3d-model-with-free-hugging-face-spaces-trellis-vs-hunyuan3d-tested</link><guid isPermaLink="true">https://tanlabs.hashnode.dev/turn-a-doll-or-plush-photo-into-a-3d-model-with-free-hugging-face-spaces-trellis-vs-hunyuan3d-tested</guid><category><![CDATA[AI]]></category><category><![CDATA[3d]]></category><category><![CDATA[Python]]></category><category><![CDATA[GameDev]]></category><dc:creator><![CDATA[TanLabs]]></dc:creator><pubDate>Sun, 01 Nov 2026 09:40:25 GMT</pubDate><content:encoded><![CDATA[<p>I sell handmade-doll-style 3D models, and every model starts as a photo of a physical doll. Paid image-to-3D tools work, but they cost credits for every attempt. So I tested whether <strong>free Hugging Face Spaces</strong> could do the job: TRELLIS and Hunyuan3D-2, called from a Python script on the free ZeroGPU quota. Below are the real results from my test log, including the failures.</p>
<h2>The setup</h2>
<ul>
<li><strong>Input:</strong> two 800×800 photos of the same doll (#392): one full-body front shot, one close-up crop.</li>
<li><strong>Client:</strong> a small <code>gradio_client</code> script that sends each photo to a Space and saves the returned GLB.</li>
<li><strong>Engines:</strong><ul>
<li><code>trellis-community/TRELLIS</code> with <code>mesh_simplify 0.95</code> and a 1024 texture</li>
<li><code>tencent/Hunyuan3D-2</code> via its <code>/generation_all</code> endpoint</li>
</ul>
</li>
<li><strong>Preview:</strong> each GLB rendered from 4 angles in Blender 3.0.1 (EEVEE) so I could judge the back and sides, not only the front.</li>
</ul>
<pre><code class="language-python">from gradio_client import Client, handle_file
client = Client("trellis-community/TRELLIS")
# upload the photo, run the image-to-3D endpoint, then extract the GLB
# (endpoint names and parameters differ per Space; check "Use via API" on the Space page)
</code></pre>
<h2>Round 1: does it even run?</h2>
<table>
<thead>
<tr>
<th>Engine</th>
<th>Result</th>
<th>Time per photo</th>
</tr>
</thead>
<tbody><tr>
<td>TRELLIS</td>
<td>2/2 succeeded, textured GLB files</td>
<td>43 s / 37 s</td>
</tr>
<tr>
<td>Hunyuan3D-2 (<code>/generation_all</code>)</td>
<td>0/2, the Space returned <code>AppError NameError</code></td>
<td>~17 s</td>
</tr>
</tbody></table>
<p>The Hunyuan3D-2 failure was on the Space side, not the input. Public Spaces break from time to time. I haven't retried it yet (the <code>/shape_generation</code> endpoint or the Hunyuan3D-2.1 Space are the next things to try), so this is <strong>not</strong> a quality verdict on Hunyuan3D, only on that Space on that day.</p>
<h2>Round 2: is the TRELLIS output usable?</h2>
<table>
<thead>
<tr>
<th>Input</th>
<th>Faces / vertices</th>
<th>Texture</th>
<th>What the 4 views showed</th>
</tr>
</thead>
<tbody><tr>
<td>Full-body front photo</td>
<td>25,695 / 19,392</td>
<td>1 material, 1024²</td>
<td>Shape is complete and recognisable: ears, face, layered skirt, separate arms and legs. Front colours are right; back and sides are a dark guess with blurry detail</td>
</tr>
<tr>
<td>Close-up crop</td>
<td>29,289 / 21,821</td>
<td>1 material, 1024²</td>
<td>Failed: a skirt-like fragment with no head or limbs</td>
</tr>
</tbody></table>
<p><strong>Lesson 1:</strong> feed it a <strong>full-body, front-facing photo with the whole subject in frame</strong>. Crops and close-ups don't work.</p>
<p><strong>Lesson 2:</strong> a 1024² texture and a guessed back side are fine for a preview, not for a product.</p>
<h2>Round 3: free vs paid, side by side</h2>
<p>To see how far free gets you, I compared a second doll (#426: green pompom, orange knit hat, black top with "Boo!" embroidery) made from the same photo with a paid Meshy production model and with TRELLIS.2, rendered with identical settings.</p>
<table>
<thead>
<tr>
<th></th>
<th>Meshy (paid)</th>
<th>TRELLIS.2 (free)</th>
<th>TRELLIS v1 (HF Space, doll #392)</th>
</tr>
</thead>
<tbody><tr>
<td>Faces / vertices</td>
<td>458,972 / 246,692</td>
<td>278,318 / 245,134</td>
<td>25,695 / 19,392</td>
</tr>
<tr>
<td>Textures</td>
<td>4096²×2 + 2048²×2 (PBR)</td>
<td>2048²×2</td>
<td>1024²×1</td>
</tr>
<tr>
<td>Colour accuracy</td>
<td>Green pompom and orange hat correct</td>
<td><strong>Pompom turned grey</strong></td>
<td>Dark, guessed back</td>
</tr>
<tr>
<td>Text / fine detail</td>
<td>"Boo!" embroidery readable</td>
<td>Embroidery blurred into noise</td>
<td>Blurry</td>
</tr>
<tr>
<td>Back side</td>
<td>Knit texture, pompom, skirt all there</td>
<td>Clean shape, flatter texture</td>
<td>Dark, blurry</td>
</tr>
<tr>
<td>Cost</td>
<td>Paid credits</td>
<td>Free (own GPU or HF free quota)</td>
<td>Free</td>
</tr>
</tbody></table>
<p>One practical gotcha: TRELLIS.2 stored its textures as WebP, which Blender 3.0 couldn't read. I converted them to PNG first.</p>
<h2>My verdict</h2>
<ul>
<li><strong>Free TRELLIS is real and useful</strong> for previews, sample renders and triage (deciding which doll is worth spending paid credits on). In my test it succeeded 2/2 on the Space, in about 40 seconds per photo.</li>
<li><strong>It is not the final product.</strong> A wrong colour (the grey pompom) means a buyer's listing would be wrong, so finished models still go through the paid pipeline, plus a manual colour fix if I use TRELLIS.2.</li>
<li><strong>Hunyuan3D-2:</strong> 0/2 because the Space was failing that day. It's worth a retry, not a conclusion.</li>
</ul>
<h2>See a finished model</h2>
<p>If you want to see what the paid "finished" end looks like, my <strong>#27 Teddy on Skates</strong> doll model is on Gumroad: <a href="https://tanlabs.gumroad.com/l/spkxd?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn08">Teddy on Skates 3D model</a>. It's <strong>$9.99 during launch (regular $14.99)</strong>.</p>
<p>Need 2D art instead? The <a href="https://tanlabs.itch.io/cozy-chibi-doll-character-pack?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn08">Cozy Chibi Doll Character Pack</a> on itch.io has transparent PNG characters in matching poses, <strong>pay what you want from $4.99</strong>.</p>
<p><strong>Try it yourself:</strong> take one full-body front photo of a doll or plush, run it through the free TRELLIS Space, and compare your result with the Teddy model above. If your photo works, it's a free preview. If you want a finished product, <a href="https://tanlabs.gumroad.com/l/spkxd?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn08">grab Teddy on Skates for $9.99</a> while the launch price lasts.</p>
]]></content:encoded></item><item><title><![CDATA[How I Built a Free Christmas Coloring Pages Site With Free-Tier AI, OpenCV and a 2-Pages-a-Day Pipeline]]></title><description><![CDATA[Every December, parents, teachers and care-home staff search for the same thing: printable Christmas coloring pages they can print right now, without an…]]></description><link>https://tanlabs.hashnode.dev/free-christmas-coloring-pages-site-free-tier-ai-opencv</link><guid isPermaLink="true">https://tanlabs.hashnode.dev/free-christmas-coloring-pages-site-free-tier-ai-opencv</guid><category><![CDATA[webdev]]></category><category><![CDATA[AI]]></category><category><![CDATA[Python]]></category><category><![CDATA[indiehackers]]></category><category><![CDATA[opencv]]></category><dc:creator><![CDATA[TanLabs]]></dc:creator><pubDate>Sat, 31 Oct 2026 17:38:59 GMT</pubDate><content:encoded><![CDATA[<p>Every December, parents, teachers and care-home staff search for the same thing: printable Christmas coloring pages they can print <em>right now</em>, without an account. I built <a href="https://color.funmagi.com/?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn09_xmas_coloring">FunMagi Color</a> to answer that query, and this post is how it works: the image pipeline, the cleanup step that makes AI output actually printable, the daily publishing loop and how the site pays for itself.</p>
<p>Status, so nobody is misled: the site went public on October 31, 2026. It has 20+ theme pages and two paid bundles on Gumroad. I have no sales numbers worth sharing yet, so this post is about the build, not revenue.</p>
<h2>What the site is</h2>
<ul>
<li><strong>Free single pages, no sign-up.</strong> Every page has a direct PDF download in US Letter, with A4 offered too.</li>
<li><strong>Themes people actually search for.</strong> <a href="https://color.funmagi.com/christmas-dog-coloring-pages/?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn09_xmas_coloring">Christmas dog</a>, <a href="https://color.funmagi.com/christmas-cat-coloring-pages/?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn09_xmas_coloring">Christmas cat</a>, <a href="https://color.funmagi.com/santa-paws-coloring-pages/?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn09_xmas_coloring">Santa Paws (pets in Santa hats)</a>, <a href="https://color.funmagi.com/reindeer-pet-coloring-pages/?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn09_xmas_coloring">reindeer pets</a>, <a href="https://color.funmagi.com/easy-christmas-coloring-pages-for-toddlers/?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn09_xmas_coloring">easy pages for toddlers</a> and <a href="https://color.funmagi.com/large-print-coloring-pages-for-seniors/?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn09_xmas_coloring">large-print pages for seniors</a>, plus winter birds, bees and garden sets.</li>
<li><strong>Difficulty badges</strong> (Easy / Medium / Detailed), so a toddler page and an adult page aren't mixed up.</li>
<li><strong>Two optional bundles</strong> for people who don't want to click 24 times: a $2.99 Christmas Pets pack (24 pages) and a $6.99 Holiday Mega Bundle (88 pages). Both are on the <a href="https://color.funmagi.com/bundles/?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn09_xmas_coloring">bundles page</a>.</li>
</ul>
<h2>Step 1: generate on the free tier</h2>
<p>Images come from <strong>Cloudflare Workers AI</strong> (a FLUX.2 Klein 4B model) at 896Ã—1152. The style prompt does most of the work:</p>
<pre><code class="language-text">black and white coloring book page, clean bold uniform black outlines,
pure white background, no shading, no grey, no color, no text,
large simple shapes suitable for kids and adults, full subject centered, line art: &lt;subject&gt;
</code></pre>
<p>The subject is one short line, for example <em>"a golden retriever puppy sleeping by the fireplace with Christmas stockings"</em>. A generation takes about 10 seconds.</p>
<p>The rule I set early: <strong>batch content never goes to a paid model.</strong> Calls go through a small quota gate that caps this site at 30% of the daily free allocation. If the provider ever returns a quota error, the gate locks the site out for the rest of the day, so the coloring site can't starve the other projects sharing the account.</p>
<h2>Step 2: make AI line art actually printable</h2>
<p>Raw model output is never truly black-and-white. It has grey anti-aliasing, faint shading and the odd speckle, and on a home printer that turns into muddy lines that crayons don't cover well. The fix is five lines of OpenCV:</p>
<pre><code class="language-python">g = cv2.imdecode(np.frombuffer(out, np.uint8), 0)             # grayscale
g = cv2.resize(g, (g.shape[1]*2, g.shape[0]*2), interpolation=cv2.INTER_CUBIC)
g = cv2.GaussianBlur(g, (5, 5), 0)                            # kill speckles
_, b = cv2.threshold(g, 150, 255, cv2.THRESH_BINARY)          # pure 1-bit
b = cv2.erode(b, np.ones((3, 3), np.uint8), 1)                # thicken lines
cv2.imwrite(f"samples/{slug}.png", b, [cv2.IMWRITE_PNG_BILEVEL, 1])
</code></pre>
<p>Upscaling before thresholding keeps curves smooth. Eroding the white area thickens the black outlines, which is what makes a page feel like a "real" coloring book. The PNG is saved as true bilevel, so it's tiny and crisp.</p>
<p>Then Pillow places the art on a 1275Ã—1650 canvas (US Letter at 150 dpi) with a light grey footer line, and saves a PDF and a thumbnail. Print a few pages before you trust any pipeline: the threshold and erode values above came from test prints, not from staring at a screen.</p>
<h2>Step 3: automatic QC with one number</h2>
<p>I log a <strong>black-pixel ratio</strong> for every page (the share of pixels that are ink). That one number catches most bad generations:</p>
<ul>
<li><strong>Too low</strong> means faint, broken outlines or an empty page.</li>
<li><strong>Too high</strong> means the model ignored "no shading" and filled big black areas, which wastes ink and is no fun to color.</li>
</ul>
<p>The fireplace puppy above came in at 0.085, comfortably in range. Out-of-range pages are flagged and never auto-published. Every candidate also lands on a daily contact sheet for a quick human look.</p>
<h2>Step 4: a boring daily publishing loop</h2>
<p>The Christmas queue is a TSV of 108 subjects, and the plan is <strong>two new pages a day until December 24</strong>. One script, <code>daily.py</code>, does it in stages:</p>
<ol>
<li><code>gen</code> takes the next rows and generates them (only after the daily quota resets), then runs QC.</li>
<li><code>approve</code> inserts the pages into the site's YAML config, rebuilds the static site, deploys it and HTTP-checks each new URL.</li>
<li>It then pings <strong>IndexNow</strong> with the new page URLs, their theme pages and the sitemap.</li>
<li><code>auto</code> chains all of this and is idempotent per day, so a cron retry can't double-publish.</li>
</ol>
<p>The whole site is static HTML built by a Python script from one <code>site.yaml</code>. Adding a theme means editing YAML, not touching templates.</p>
<p><strong>A Cloudflare gotcha worth knowing:</strong> if you visit a new URL before it exists, Cloudflare can cache the 404 for hours. After launch, the real page still shows "not found" to everyone. I now check new pages only with a cache-buster (<code>/new-page/?cb=1730390000</code>) until they are deployed.</p>
<h2>How it (hopefully) pays for itself</h2>
<p>Free pages are the product. The search traffic they bring has two optional upsells:</p>
<ul>
<li><strong>Bundles on Gumroad.</strong> Gumroad handles checkout (card or PayPal), instant delivery and re-downloads, so I didn't build any of that myself. The <a href="https://color.funmagi.com/license/?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn09_xmas_coloring">license page</a> keeps terms simple: free for personal, family, therapy-group and classroom use; no reselling or re-uploading.</li>
<li><strong>"Your pet as a coloring page."</strong> Each theme page links to <a href="https://petmagi.com/craft/?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn09_xmas_coloring">PetMagi</a>, my other project, where you upload a photo of your own cat or dog and get printable line art of <em>your</em> pet. Someone printing Christmas dog pages is about the best audience that feature will ever have.</li>
</ul>
<h2>What I'd do the same way again</h2>
<ul>
<li>Free-tier generation behind a hard quota gate, never paid APIs for bulk content.</li>
<li>Deterministic cleanup (threshold + erode) instead of hoping the prompt gives clean lines.</li>
<li>One QC metric that a script can act on.</li>
<li>Small daily releases plus IndexNow, instead of one big dump of 100 pages.</li>
</ul>
<p>If you have kids, a classroom or a grandparent who likes large-print pages, grab a few at <a href="https://color.funmagi.com/?utm_source=hashnode&amp;utm_medium=blog&amp;utm_campaign=hn09_xmas_coloring">color.funmagi.com</a>. New Christmas pages are added every day through December. I'd love feedback on which themes are missing.</p>
]]></content:encoded></item><item><title><![CDATA[robots.txt for AI crawlers in 2026: which bots to block, which to allow (copy-paste templates)]]></title><description><![CDATA[A lot of sites added a "block all AI bots" robots.txt snippet in 2024 and never looked at it again. The problem: the same snippet often blocks the bots…]]></description><link>https://tanlabs.hashnode.dev/robots-txt-for-ai-crawlers-in-2026-which-bots-to-block-which-to-allow-copy-paste-templates</link><guid isPermaLink="true">https://tanlabs.hashnode.dev/robots-txt-for-ai-crawlers-in-2026-which-bots-to-block-which-to-allow-copy-paste-templates</guid><category><![CDATA[SEO]]></category><category><![CDATA[webdev]]></category><category><![CDATA[AI]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[TanLabs]]></dc:creator><pubDate>Sat, 31 Oct 2026 10:08:44 GMT</pubDate><content:encoded><![CDATA[<p>A lot of sites added a "block all AI bots" robots.txt snippet in 2024 and never looked at it again. The problem: the same snippet often blocks the bots that <strong>cite</strong> you in ChatGPT, Perplexity and Claude answers, not just the ones that collect training data. Here is how to tell them apart and three templates you can copy.</p>
<h2>Two kinds of AI crawlers</h2>
<p><strong>Training crawlers</strong> collect pages to train future models. Blocking them costs you nothing in visibility today:</p>
<table>
<thead>
<tr>
<th>User-agent</th>
<th>Company</th>
</tr>
</thead>
<tbody><tr>
<td>GPTBot</td>
<td>OpenAI (training)</td>
</tr>
<tr>
<td>ClaudeBot</td>
<td>Anthropic (training)</td>
</tr>
<tr>
<td>Google-Extended</td>
<td>Google (Gemini training control token)</td>
</tr>
<tr>
<td>Applebot-Extended</td>
<td>Apple (training control token)</td>
</tr>
<tr>
<td>CCBot</td>
<td>Common Crawl</td>
</tr>
<tr>
<td>Bytespider</td>
<td>ByteDance</td>
</tr>
<tr>
<td>meta-externalagent</td>
<td>Meta</td>
</tr>
</tbody></table>
<p><strong>Search / answer crawlers</strong> fetch pages so an assistant can quote and link them. Block these and you simply don't show up as a source:</p>
<table>
<thead>
<tr>
<th>User-agent</th>
<th>Used for</th>
</tr>
</thead>
<tbody><tr>
<td>OAI-SearchBot</td>
<td>ChatGPT search results</td>
</tr>
<tr>
<td>ChatGPT-User</td>
<td>Pages a ChatGPT user asks it to open</td>
</tr>
<tr>
<td>Claude-SearchBot, Claude-User</td>
<td>Claude search and user-requested fetches</td>
</tr>
<tr>
<td>PerplexityBot, Perplexity-User</td>
<td>Perplexity answers</td>
</tr>
</tbody></table>
<p>Check each vendor's crawler docs before relying on this list; names change every few months.</p>
<h2>Template 1: allow everything (most small sites)</h2>
<pre><code>User-agent: *
Allow: /

Sitemap: https://example.com/sitemap.xml
</code></pre>
<p>If you sell something and want to be recommended by assistants, this is usually the right default.</p>
<h2>Template 2: no training, yes citations</h2>
<pre><code>User-agent: GPTBot
Disallow: /

User-agent: ClaudeBot
Disallow: /

User-agent: Google-Extended
Disallow: /

User-agent: Applebot-Extended
Disallow: /

User-agent: CCBot
Disallow: /

User-agent: Bytespider
Disallow: /

User-agent: meta-externalagent
Disallow: /

User-agent: *
Allow: /

Sitemap: https://example.com/sitemap.xml
</code></pre>
<p>OAI-SearchBot, ChatGPT-User, Claude-SearchBot and PerplexityBot fall through to <code>User-agent: *</code> and stay allowed.</p>
<h2>Template 3: block all AI (and accept the trade-off)</h2>
<p>Add the search bots from the second table to Template 2. Your pages will stop appearing as sources in AI answers.</p>
<h2>The mistakes I see most often</h2>
<ol>
<li><strong><code>User-agent: *</code> + <code>Disallow: /</code> left over from staging.</strong> Blocks every crawler, AI or not.</li>
<li><strong>Blocking ChatGPT-User "because it's OpenAI".</strong> That bot only fetches a page when a person asked for it.</li>
<li><strong>Content rendered only by JavaScript.</strong> Most AI fetchers don't run JS, so even an allowed bot sees an empty page. View source (not DevTools) and check that your main text is there.</li>
<li><strong>No sitemap line.</strong> Cheap to add, helps every crawler.</li>
<li><strong>No <code>/llms.txt</code>.</strong> It's an optional, Markdown summary of your site for language models. It isn't a standard yet, but it takes five minutes to write.</li>
</ol>
<h2>Checking many sites at once</h2>
<p>Doing this by hand for one site is fine. For a client list, I built an <a href="https://apify.com/tanlabs/ai-search-readiness-audit">AI Search Readiness Audit</a> actor: you paste URLs and get, per page, which of 14 AI crawlers robots.txt blocks (split into training vs search bots), whether <code>/llms.txt</code> and a sitemap exist, JSON-LD types, server-rendered word count, a 0–100 score with prioritized fixes, and a ready-to-publish llms.txt draft. It's pay-per-page, and robots/llms/sitemap fetches are free.</p>
<p>If you only have one site, the tables above are all you need.</p>
<hr />
<p><em>More small tools from TanLabs: <a href="https://apify.com/tanlabs/social-preview-meta-checker">Social Preview &amp; Meta Tag Checker</a>, <a href="https://apify.com/tanlabs/app-store-review-insights">App Store Review Insights</a>, <a href="https://apify.com/tanlabs/app-store-top-charts">App Store Top Charts &amp; Rank Tracker</a>.</em></p>
]]></content:encoded></item><item><title><![CDATA[What It Actually Costs to Run an AI Pet Holiday-Portrait Site (Stack, Per-Order Math, and the $4.99 Trap)]]></title><description><![CDATA[The stack, per-order model costs (fal.ai list prices) and payment fees behind a small AI pet holiday-portrait site, and why $4.99 tiers fail with ads.]]></description><link>https://tanlabs.hashnode.dev/what-it-actually-costs-to-run-an-ai-pet-holiday-portrait-site-stack-per-order-math-and-the-4-99-trap</link><guid isPermaLink="true">https://tanlabs.hashnode.dev/what-it-actually-costs-to-run-an-ai-pet-holiday-portrait-site-stack-per-order-math-and-the-4-99-trap</guid><category><![CDATA[Web Development]]></category><category><![CDATA[AI]]></category><category><![CDATA[Python]]></category><category><![CDATA[indie hackers]]></category><dc:creator><![CDATA[TanLabs]]></dc:creator><pubDate>Fri, 30 Oct 2026 14:14:44 GMT</pubDate><content:encoded><![CDATA[<p>I'm building <a href="https://petmagi.com">PetMagi</a>, a small site where you upload a photo of your dog or cat and get holiday portraits, a short animated clip and a share card back. It's the kind of "small payment, instant digital delivery" product that looks trivial until you price it. This post is the stack I chose, the real per-order model costs, and the pricing mistake I almost made.</p>
<p>Status, so nobody is misled: the site is live, but checkout is still in test mode while merchant onboarding finishes. The numbers below are list prices from the providers' own pages (checked October 30, 2026), not revenue.</p>
<h2>The stack (boring on purpose)</h2>
<table>
<thead>
<tr>
<th>Layer</th>
<th>What I use</th>
<th>Why</th>
</tr>
</thead>
<tbody><tr>
<td>Pages</td>
<td>Static HTML built by a Python script (one YAML file per site)</td>
<td>One template powers several holiday/niche sites; static pages are fast and cheap</td>
</tr>
<tr>
<td>API</td>
<td>One Python stdlib HTTP process under systemd</td>
<td>Upload, preview, checkout, webhook, delivery. No framework, ~zero idle cost</td>
</tr>
<tr>
<td>Hosting</td>
<td>A small cloud VM I already run for other jobs, Caddy for HTTPS</td>
<td>Capped with <code>MemoryMax=500M</code> and <code>CPUQuota=50%</code> so a traffic spike can't take down neighbours</td>
</tr>
<tr>
<td>Image + video models</td>
<td>fal.ai queue API</td>
<td>Pay per call, many models behind one key, easy to swap</td>
</tr>
<tr>
<td>Payments</td>
<td>A Merchant of Record (Creem/Waffo-style) with PayPal as an extra button</td>
<td>MoR handles VAT/sales tax and chargebacks, which matters when you sell small orders to buyers in many countries</td>
</tr>
<tr>
<td>Video glue</td>
<td>ffmpeg (static binary, 1 thread)</td>
<td>Turns stills into a 9:16 MP4 and a 1080×1080 share image</td>
</tr>
<tr>
<td>Safety</td>
<td>Local face detector (YuNet ONNX) + prompt blocklist</td>
<td>Rejects photos of people before any paid model is called</td>
</tr>
</tbody></table>
<p>The one rule that saved the most work: <strong>every site is a YAML file</strong>. Brand, domain, languages, theme, price tiers, model names and a daily spend cap all live there, so a new seasonal site is a copy-and-edit, not a fork.</p>
<h2>Model costs per call (fal.ai list prices)</h2>
<ul>
<li><strong>Free preview</strong> – FLUX.2 Flash edit: $0.005 per megapixel of input <em>and</em> output. A 1 MP photo in and 1 MP preview out is about <strong>$0.01</strong>.</li>
<li><strong>HD portrait</strong> – Seedream 4.5 edit: <strong>$0.04 per image</strong>, about a minute per edit, up to 4 MP output.</li>
<li><strong>Animated clip</strong> – PixVerse v4.5 image-to-video, 5 seconds: <strong>$0.15 at 540p</strong>, $0.20 at 720p, $0.40 at 1080p.</li>
</ul>
<p>Free previews are the hidden cost. If 1 in 20 visitors who make a preview actually buys, those 20 previews cost about $0.20 on their own. That's why the config has a hard daily cap (<code>daily_spend_cap_usd: 5</code>) and a separate ceiling for how much of it free previews may use (40%).</p>
<h2>Per-order math</h2>
<p>Three tiers. Payment fee uses a typical MoR rate of 3.9% + $0.40 per transaction (Creem's published rate; yours will differ).</p>
<table>
<thead>
<tr>
<th>Tier</th>
<th>What's delivered</th>
<th>Model cost</th>
<th>Payment fee</th>
<th>Left before ads, previews, refunds</th>
</tr>
</thead>
<tbody><tr>
<td>$4.99</td>
<td>2 HD portraits + square image</td>
<td>2 × $0.04 = $0.08</td>
<td>$0.60</td>
<td><strong>$4.31</strong></td>
</tr>
<tr>
<td>$9.99</td>
<td>4 HD + one 5 s clip + share card</td>
<td>4 × $0.04 + $0.15 = $0.31</td>
<td>$0.79</td>
<td><strong>$8.89</strong></td>
</tr>
<tr>
<td>$14.99</td>
<td>8 HD + two clips + a "letter from your pet" card</td>
<td>8 × $0.04 + 2 × $0.15 = $0.62</td>
<td>$0.99</td>
<td><strong>$13.38</strong></td>
</tr>
</tbody></table>
<p>Two things jump out:</p>
<ol>
<li><strong>Model cost is not the problem.</strong> Even the premium tier spends well under a dollar on inference.</li>
<li><strong>The fixed $0.40 fee is.</strong> On $4.99 it eats about 12% of the sale; on $14.99 it's under 7%.</li>
</ol>
<h2>The $4.99 trap</h2>
<p>The tempting plan is: run ads to a $4.99 offer, make it up on volume. The math above says each $4.99 order leaves about $4.31 <em>before</em> acquisition. If a paid click-to-buyer costs more than that — and for a cold audience it easily can — every sale loses money. Volume makes it worse, not better.</p>
<p>What I did instead:</p>
<ul>
<li><strong>$9.99 is the "most popular" default</strong>, visually highlighted; $4.99 exists as an anchor and for people arriving from free/organic links.</li>
<li><strong>The premium tier adds things that cost almost nothing to produce</strong> (a second clip, a written card) but feel like a different product.</li>
<li><strong>Packaging beats features.</strong> "AI pet portrait" on its own doesn't sell. "Your dog as Santa, delivered as a card you can send tonight" does. Seasonal landing pages (Christmas, Lunar New Year, a Japanese New Year card version) reuse the same pipeline with different copy.</li>
</ul>
<h2>Small engineering details that mattered</h2>
<ul>
<li><strong>Refuse bad input before paying for it.</strong> Face detection runs locally before any fal call; photos with people are rejected (the site is pets only, and real-person content is a policy minefield).</li>
<li><strong>Queue, don't block.</strong> fal's queue API returns a request id; the browser polls. One stdlib process handles it fine because nothing waits on the model inside a request.</li>
<li><strong>Delete by default.</strong> Uploaded photos and results are auto-deleted after 7 days. Less storage, less liability.</li>
<li><strong>Watermark previews, not HD.</strong> Previews are low-res and watermarked; HD files are only generated after the payment webhook, so a failed checkout costs one $0.01 preview, not $0.31.</li>
<li><strong>Log UTM first-touch and last-touch with each checkout.</strong> When you do test ads, you'll want to know which creative produced the buyer, not just the click.</li>
</ul>
<h2>Same pipeline, different products</h2>
<p>Once you have "photo in → styled images + clip out", it reuses well. On the same stack I've built a casual browser game, <a href="https://tanlabs.itch.io/blob-drop-merge-puzzle">Blob Drop</a>, whose character art came out of the same image workflow.</p>
<h2>Free stuff if you're building something similar</h2>
<p>If you need cute placeholder or final art for a game or app UI, I packaged a set of transparent-PNG characters as the <a href="https://tanlabs.itch.io/cozy-chibi-doll-character-pack">Cozy Chibi Doll Character Pack</a> on itch.io: matching poses, two sizes, pay-what-you-want above a small minimum. And if you're testing a printable product (colouring pages are a common first one), there's a <a href="https://tanlabs.gumroad.com/l/eypunu">free 5-page Birds &amp; Blooms colouring sampler</a> you can download to see the line weight and page layout I use, with the <a href="https://tanlabs.gumroad.com/l/yecdag">full 32-page large-print Birds &amp; Blooms pack</a> if you want more.</p>
<h2>The short version</h2>
<p>Inference is cheap (cents per order). Payment fees and acquisition are what decide whether a small-ticket AI product works. Price the default tier around $10, cap your free-preview spend, reject bad inputs before you pay a model, and use seasonal packaging rather than "AI" as the hook.</p>
<p><em>Disclosure: I built PetMagi, Blob Drop, the character pack and the colouring packs linked above. The art and portraits are generated with AI models (FLUX.2, Seedream 4.5, PixVerse) and then curated by me. Prices quoted are the providers' public list prices on October 30, 2026.</em></p>
]]></content:encoded></item><item><title><![CDATA[Shipping a Browser Game Without an Artist: Cheap Character Art, Store Pages and Share Cards That Don't Look Broken]]></title><description><![CDATA[Most small browser games die in the same place: the code works, but the game still has programmer art, the store page has no proper cover, and the link…]]></description><link>https://tanlabs.hashnode.dev/shipping-a-browser-game-without-an-artist-cheap-character-art-store-pages-and-share-cards-that-don-t-look-broken</link><guid isPermaLink="true">https://tanlabs.hashnode.dev/shipping-a-browser-game-without-an-artist-cheap-character-art-store-pages-and-share-cards-that-don-t-look-broken</guid><category><![CDATA[GameDev]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[indiehackers]]></category><category><![CDATA[HTML5]]></category><dc:creator><![CDATA[TanLabs]]></dc:creator><pubDate>Fri, 30 Oct 2026 12:16:18 GMT</pubDate><content:encoded><![CDATA[<p>Most small browser games die in the same place: the code works, but the game still has programmer art, the store page has no proper cover, and the link shared on Discord shows a blank grey card. I just shipped a small HTML5 merge puzzle, <a href="https://tanlabs.itch.io/blob-drop-merge-puzzle">Blob Drop</a>, and this is the checklist I used to get from "it runs" to "it looks finished" without hiring an artist.</p>
<h2>1. Pick one consistent art style before anything else</h2>
<p>Mixing three free asset packs is what makes a prototype look cheap. Pick one style (pixel, flat vector, or soft "chibi" characters) and stick to it for sprites, UI and the cover.</p>
<ul>
<li>Keep a single palette of 6–10 colours and reuse it everywhere, including the UI.</li>
<li>Export sprites at 2× the size you draw them on screen, so they stay sharp on phones.</li>
<li>Use transparent PNGs with a little padding, so your physics or hit-box shapes don't clip the outline.</li>
</ul>
<p>If you want a ready set in the soft cute style, I put together a <a href="https://tanlabs.itch.io/cozy-chibi-doll-character-pack">Cozy Chibi Doll Character Pack</a> on itch.io: transparent PNG characters in matching poses that work as placeholder or final art for casual games, visual novels and UI mascots.</p>
<h2>2. Make the store page images at the sizes portals actually use</h2>
<p>Each portal crops differently, so make the images once at the right sizes:</p>
<table>
<thead>
<tr>
<th>Where</th>
<th>Image</th>
<th>Size</th>
</tr>
</thead>
<tbody><tr>
<td>itch.io</td>
<td>Cover</td>
<td>630×500 (shown at 315×250)</td>
</tr>
<tr>
<td>itch.io</td>
<td>Screenshots</td>
<td>3–5, same aspect as the game</td>
</tr>
<tr>
<td>Web portals (e.g. CrazyGames)</td>
<td>Thumbnails</td>
<td>16:9 landscape plus a portrait variant</td>
</tr>
<tr>
<td>Anywhere you share a link</td>
<td><code>og:image</code></td>
<td>1200×630</td>
</tr>
</tbody></table>
<p>Put the main character big and centred on the cover; at thumbnail size, nobody can read a busy scene.</p>
<h2>3. Package the HTML5 build so it runs on the first click</h2>
<ul>
<li>Zip the <strong>contents</strong> of your build folder, with <code>index.html</code> at the root of the zip.</li>
<li>Use relative paths only; most portals serve your game from a sub-path inside an iframe.</li>
<li>Test the zip on a phone in portrait mode. If the canvas does not resize, players leave in seconds.</li>
<li>Don't autoplay sound before the first tap; browsers will block it anyway.</li>
</ul>
<h2>4. Check the share card before you post the link</h2>
<p>When you post your game page on X, Reddit, Discord or Slack, the preview comes from <code>og:title</code>, <code>og:description</code> and <code>og:image</code>. Check that the image returns 200 at 1200×630 and that the title isn't cut off. To check a list of URLs in one go, <a href="https://apify.com/tanlabs/social-preview-meta-checker">Social Preview &amp; Meta Tag Checker</a> scores each page and gives you the missing tags to paste in.</p>
<h2>5. Bonus: a printable for your players or your kids</h2>
<p>A surprisingly nice launch freebie is a printable colouring page of your game's characters; parents and teachers share them. If you'd like to see what that looks like, there's a <a href="https://tanlabs.gumroad.com/l/eypunu">free cute colouring sampler</a> you can download, and the full <a href="https://tanlabs.gumroad.com/l/yecdag">cute colouring pack</a> if you want more pages.</p>
<h2>The short version</h2>
<p>One style, one palette, covers at the sizes portals crop to, a zip with <code>index.html</code> at the root, and a share card that isn't grey. That is an afternoon of work, and it is the difference between "prototype" and "game" for most players.</p>
<p><em>Disclosure: I made the Blob Drop game, the character pack, the colouring packs and the Apify tool linked above; the character and colouring art was created with AI assistance and then curated and edited by me.</em></p>
]]></content:encoded></item><item><title><![CDATA[The 20-Minute Pre-Launch Checklist for Indie Apps and Sites (Share Previews, AI Search, Reviews, Rankings)]]></title><description><![CDATA[Launch day goes wrong in boring ways. The link you post on X shows a blank grey card. ChatGPT and Perplexity can't read your landing page, so they never…]]></description><link>https://tanlabs.hashnode.dev/the-20-minute-pre-launch-checklist-for-indie-apps-and-sites-share-previews-ai-search-reviews-rankings</link><guid isPermaLink="true">https://tanlabs.hashnode.dev/the-20-minute-pre-launch-checklist-for-indie-apps-and-sites-share-previews-ai-search-reviews-rankings</guid><category><![CDATA[startup]]></category><category><![CDATA[SEO]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[indiehackers]]></category><dc:creator><![CDATA[TanLabs]]></dc:creator><pubDate>Thu, 29 Oct 2026 11:24:06 GMT</pubDate><content:encoded><![CDATA[<p>Launch day goes wrong in boring ways. The link you post on X shows a blank grey card. ChatGPT and Perplexity can't read your landing page, so they never mention you. You find out about the crash everyone complained about two weeks later, in a one-star review. And nobody on the team knows whether the launch actually moved your chart position.</p>
<p>None of these need an expensive tool. Here's the checklist I run before (and the week after) shipping something, with the free data source behind each step.</p>
<h2>1. Share previews: check every link you'll post (5 minutes)</h2>
<p>When someone pastes your URL into X, LinkedIn, Slack or iMessage, the preview comes from a handful of tags: <code>og:title</code>, <code>og:description</code>, <code>og:image</code>, <code>twitter:card</code>, plus a reachable image of the right size.</p>
<p>Check, for every page you plan to share (home, pricing, launch post, docs):</p>
<ul>
<li><code>og:image</code> returns <strong>200</strong>, is at least <strong>1200×630</strong>, and is under ~5 MB.</li>
<li><code>twitter:card</code> is <code>summary_large_image</code> if you want the big card.</li>
<li>The title is under ~60 characters and the description under ~160.</li>
<li>There's a <code>canonical</code> URL, and the page is not accidentally <code>noindex</code>.</li>
</ul>
<p>To do this for a list of URLs at once, I use <a href="https://apify.com/tanlabs/social-preview-meta-checker">Social Preview &amp; Meta Tag Checker</a>: it scores each page 0–100 and gives a fix list plus ready-to-paste tags.</p>
<h2>2. AI search readiness: can assistants read and cite you? (5 minutes)</h2>
<p>More and more people ask an AI assistant instead of Google. The basics that decide whether you get cited:</p>
<ul>
<li><code>robots.txt</code> doesn't block <code>GPTBot</code>, <code>PerplexityBot</code>, <code>ClaudeBot</code> or <code>Google-Extended</code> (unless you mean to).</li>
<li>Your main content is in the HTML, not only rendered by JavaScript.</li>
<li>You have clear headings, a short "what is this" paragraph near the top, and structured data (<code>Organization</code>, <code>Product</code>, <code>FAQPage</code>).</li>
<li>Optional but cheap: an <code>/llms.txt</code> summarizing your key pages.</li>
</ul>
<p><a href="https://apify.com/tanlabs/ai-search-readiness-audit">AI Search Readiness Audit</a> checks these per page and drafts an <code>llms.txt</code> for you.</p>
<h2>3. Reviews: read what users actually say (5 minutes, then weekly)</h2>
<p>For iOS apps, Apple publishes recent customer reviews per country as a public feed. In launch week, read them daily and sort into buckets: crashes, missing features, pricing complaints, praise. The first two buckets are your next update.</p>
<p><a href="https://apify.com/tanlabs/app-store-review-insights">App Store Reviews Scraper &amp; Insights</a> pulls the reviews for one or many countries and groups the complaints and ratings into a short report, for your app or a competitor's.</p>
<h2>4. Rankings: know whether launch moved anything (5 minutes, then daily)</h2>
<p>Apple's public chart feeds give the top 100 per country, chart and category. Pick 3–5 countries that matter, record your position every day, and treat "not in the top 100" as a data point rather than a missing row.</p>
<p><a href="https://apify.com/tanlabs/app-store-top-charts">App Store Top Charts &amp; Rank Tracker</a> snapshots the charts and compares them with the previous run, so you get a rank history without an ASO subscription.</p>
<h2>The whole thing on one page</h2>
<table>
<thead>
<tr>
<th>When</th>
<th>Check</th>
<th>Pass when</th>
</tr>
</thead>
<tbody><tr>
<td>Before launch</td>
<td>Share previews</td>
<td>Every shared URL scores 90+ and shows a large card</td>
</tr>
<tr>
<td>Before launch</td>
<td>AI search readiness</td>
<td>AI crawlers allowed, content in HTML, llms.txt present</td>
</tr>
<tr>
<td>Launch week</td>
<td>Reviews</td>
<td>No unanswered crash complaints</td>
</tr>
<tr>
<td>Launch week +</td>
<td>Rankings</td>
<td>Daily position recorded for your key countries</td>
</tr>
</tbody></table>
<p>Twenty minutes before launch, five minutes a day after. Cheap insurance against the boring failures.</p>
<p><em>Disclosure: I built the four Apify tools linked above. Each runs on pay-per-use pricing of a fraction of a cent per page or report, and all the underlying data sources are public.</em></p>
]]></content:encoded></item><item><title><![CDATA[Track Your App's App Store Ranking Every Day (Any Country, Any Category) Without Paying for an ASO Suite]]></title><description><![CDATA["What rank are we in Productivity in Germany today?" sounds like a simple question, but most indie developers answer it by opening the App Store on a…]]></description><link>https://tanlabs.hashnode.dev/track-your-app-s-app-store-ranking-every-day-any-country-any-category-without-paying-for-an-aso-suite</link><guid isPermaLink="true">https://tanlabs.hashnode.dev/track-your-app-s-app-store-ranking-every-day-any-country-any-category-without-paying-for-an-aso-suite</guid><category><![CDATA[appstore]]></category><category><![CDATA[marketing]]></category><category><![CDATA[automation]]></category><category><![CDATA[iOS]]></category><dc:creator><![CDATA[TanLabs]]></dc:creator><pubDate>Thu, 29 Oct 2026 09:08:41 GMT</pubDate><content:encoded><![CDATA[<p>"What rank are we in Productivity in Germany today?" sounds like a simple question, but most indie developers answer it by opening the App Store on a phone, scrolling, and guessing. ASO suites answer it properly, usually for $50 to $300 a month.</p>
<p>If all you need is <strong>daily chart positions for your app and a few competitors</strong>, Apple already publishes the data for free. This post shows where it comes from, what to watch out for, and how to turn it into a daily rank history with almost no setup.</p>
<h2>Where the chart data comes from</h2>
<p>Apple serves public RSS/JSON feeds of the App Store charts: top free, top paid, top grossing (iPhone and iPad) and new apps, per country and per category, up to 100 apps each. A second public endpoint, the iTunes Lookup API, returns an app's rating, rating count, current version, release date and size by its numeric id.</p>
<p>Together they are enough for honest rank tracking:</p>
<ul>
<li><strong>Rank</strong>: the position of your app id in a given country × chart × category feed.</li>
<li><strong>Context</strong>: rating and version from the Lookup API, so you can see whether a rank drop lines up with a bad update.</li>
<li><strong>History</strong>: store yesterday's snapshot and compare it with today's.</li>
</ul>
<h2>Three things that trip people up</h2>
<ol>
<li><strong>Charts are per country.</strong> Your app can be #8 in Finance in the US and absent in the UK. Pick the 3 to 5 storefronts that actually matter for revenue.</li>
<li><strong>"Not in the top 100" is a data point.</strong> Record it explicitly, otherwise a missing row looks like a broken job.</li>
<li><strong>Charts move during the day.</strong> Apple refreshes several times a day. One or two snapshots at fixed times give a cleaner trend than random manual checks.</li>
</ol>
<h2>A daily rank tracker in five minutes</h2>
<p>I packaged the steps above as an Apify Actor: <a href="https://apify.com/tanlabs/app-store-top-charts">App Store Top Charts &amp; Rank Tracker</a>. You choose:</p>
<ul>
<li><strong>Countries</strong>: two-letter codes such as <code>us</code>, <code>gb</code>, <code>de</code>, <code>jp</code>.</li>
<li><strong>Charts</strong>: top-free, top-paid, top-grossing, iPad variants, or new.</li>
<li><strong>Categories</strong>: overall plus 24 App Store categories (games, finance, productivity, health-fitness and more).</li>
<li><strong>Track my apps</strong>: the numeric App Store ids of your app and competitors.</li>
<li><strong>Save only tracked apps</strong>: keeps the output (and the cost) small when you only care about your own ids.</li>
<li><strong>Compare with previous run</strong>: remembers each chart in a named key-value store and reports rank changes, new entries and drop-outs since the last run.</li>
</ul>
<p>Schedule it once a day in Apify Schedules and every run adds one row per tracked app per chart, with <code>rank</code>, <code>previousRank</code>, <code>change</code>, rating and version. Export to CSV or Google Sheets, or send a webhook to Slack, Zapier or Make when an app enters or leaves a chart.</p>
<p>Pricing is pay per event: $0.005 per chart fetched and $0.0003 per saved app row. Tracking 3 apps across 5 country/category charts every day costs well under a dollar a month, and Apify's free plan credit covers it.</p>
<h2>What to do with the numbers</h2>
<ul>
<li><strong>After every release</strong>, check the next 2 to 3 days of rank in your main category. A drop together with a falling rating usually means a bad update; a drop with a stable rating usually means a competitor push.</li>
<li><strong>Before a launch in a new country</strong>, pull that storefront's category chart for a week to see how volatile it is and what rank range realistic download numbers buy.</li>
<li><strong>Watch the "new" chart</strong> in your category to spot competitors early.</li>
</ul>
<h2>Related tools</h2>
<ul>
<li><a href="https://apify.com/tanlabs/app-store-review-insights">App Store Reviews Scraper &amp; Insights</a> turns reviews into complaint themes and rating trends, which pairs well with a rank drop investigation.</li>
<li><a href="https://apify.com/tanlabs/ai-search-readiness-audit">AI Search Readiness Audit</a> checks whether your app's landing page is visible to ChatGPT and Perplexity.</li>
</ul>
<p>If you track ranks a different way, or need a chart the feeds don't cover, tell me in the comments.</p>
]]></content:encoded></item><item><title><![CDATA[Is Your Site Invisible to ChatGPT and Perplexity? A 5-Minute AI Search Readiness Check (plus a free llms.txt draft)]]></title><description><![CDATA[More and more people ask ChatGPT, Claude, Perplexity or Gemini instead of typing into Google. Those assistants only cite pages their crawlers can reach…]]></description><link>https://tanlabs.hashnode.dev/is-your-site-invisible-to-chatgpt-and-perplexity-a-5-minute-ai-search-readiness-check-plus-a-free-llms-txt-draft</link><guid isPermaLink="true">https://tanlabs.hashnode.dev/is-your-site-invisible-to-chatgpt-and-perplexity-a-5-minute-ai-search-readiness-check-plus-a-free-llms-txt-draft</guid><category><![CDATA[SEO]]></category><category><![CDATA[AI]]></category><category><![CDATA[webdev]]></category><category><![CDATA[llm]]></category><dc:creator><![CDATA[TanLabs]]></dc:creator><pubDate>Thu, 29 Oct 2026 07:12:03 GMT</pubDate><content:encoded><![CDATA[<p>More and more people ask ChatGPT, Claude, Perplexity or Gemini instead of typing into Google. Those assistants only cite pages their crawlers can <strong>reach, read and understand</strong>. Many sites quietly fail on at least one of the three, often because of a single robots.txt line copied from a "block AI" blog post.</p>
<p>Here is a practical checklist, and a way to run it on many URLs at once.</p>
<h2>1. Check robots.txt against the <em>right</em> AI bots</h2>
<p>There are two kinds of AI crawlers:</p>
<ul>
<li><strong>Training bots</strong> (GPTBot, CCBot, Google-Extended, Applebot-Extended, Bytespider, meta-externalagent). Blocking these keeps your content out of model training. That is a legitimate choice.</li>
<li><strong>Search / answer bots</strong> (OAI-SearchBot, ChatGPT-User, Claude-SearchBot, Claude-User, PerplexityBot, Perplexity-User). Blocking these means the assistant <strong>cannot fetch your page when a user asks</strong>, so you will not be cited or linked.</li>
</ul>
<p>A blanket <code>User-agent: * / Disallow: /</code> for "AI", or a copy-pasted block list, often hits both groups. Decide per group.</p>
<h2>2. Publish an <code>/llms.txt</code></h2>
<p><code>llms.txt</code> is a short Markdown file at your site root that tells language models what your site is and which pages matter most: a title, a one-line summary, and a list of key links with descriptions. It takes ten minutes to write and gives assistants a clean map instead of guessing from navigation menus.</p>
<h2>3. Make sure the content is in the HTML</h2>
<p>Most AI crawlers do not run JavaScript. If your product description or pricing table only appears after client-side rendering, the bot sees an empty shell. Check the server-rendered word count and text-to-HTML ratio of your key pages.</p>
<h2>4. Give machines structure</h2>
<ul>
<li>A sitemap (listed in robots.txt or at <code>/sitemap.xml</code>).</li>
<li>Schema.org JSON-LD: <code>Organization</code>, <code>Product</code>, <code>Article</code>, <code>FAQPage</code> where relevant.</li>
<li>One clear H1, logical H2s, a real meta description, canonical URL, <code>lang</code> attribute.</li>
<li>Visible dates on articles, and question-and-answer style sections for common queries.</li>
</ul>
<h2>5. Watch for accidental "do not use" signals</h2>
<p><code>noindex</code>, <code>noai</code> or <code>noimageai</code> meta tags left over from staging are surprisingly common.</p>
<h2>Running the check on many pages</h2>
<p>I packaged this checklist into an Apify Actor: <a href="https://apify.com/tanlabs/ai-search-readiness-audit">AI Search Readiness Audit</a>. Paste a list of URLs and it returns, per page, a 0–100 score with a letter grade, the robots.txt verdict for 14 AI crawlers (training vs search separated), llms.txt / sitemap / schema / rendering checks, a prioritized fix list, and a <strong>ready-to-publish llms.txt draft</strong> if you do not have one.</p>
<pre><code class="language-json">{ "urls": ["https://example.com/", "https://example.com/pricing"], "generateLlmsTxt": true }
</code></pre>
<p>It costs fractions of a cent per page audited (robots.txt, llms.txt and sitemap are fetched once per site and are free), which makes it practical for agencies auditing a whole client list.</p>
<p>Related tools: <a href="https://apify.com/tanlabs/social-preview-meta-checker">Social Preview &amp; Meta Tag Checker</a> for Open Graph and X cards, and <a href="https://apify.com/tanlabs/app-store-review-insights">App Store Review Insights</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Turn App Store reviews into a product insights report (no scraping, official Apple feeds)]]></title><description><![CDATA[App Store reviews are one of the best free sources of product feedback, but reading hundreds of them is slow. Here is a way to pull them in bulk and get a…]]></description><link>https://tanlabs.hashnode.dev/turn-app-store-reviews-into-a-product-insights-report-no-scraping-official-apple-feeds</link><guid isPermaLink="true">https://tanlabs.hashnode.dev/turn-app-store-reviews-into-a-product-insights-report-no-scraping-official-apple-feeds</guid><category><![CDATA[iOS]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[api]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[TanLabs]]></dc:creator><pubDate>Wed, 28 Oct 2026 19:37:17 GMT</pubDate><content:encoded><![CDATA[<p>App Store reviews are one of the best free sources of product feedback, but reading hundreds of them is slow. Here is a way to pull them in bulk and get a summary of what users actually complain about.</p>
<h2>Where the data comes from</h2>
<p>Apple exposes public endpoints for this, so you don't need to scrape HTML:</p>
<ul>
<li>the iTunes Lookup API (<code>https://itunes.apple.com/lookup?id=APP_ID</code>) for app metadata and the store rating</li>
<li>the customer reviews RSS feed (<code>https://itunes.apple.com/{country}/rss/customerreviews/page=1/id={APP_ID}/sortby=mostrecent/json</code>) for recent reviews, up to 10 pages per country</li>
</ul>
<p>You can call these yourself with a few lines of Python. The harder part is turning raw reviews into something useful.</p>
<h2>Getting a report instead of raw rows</h2>
<p>I packaged this as an Apify Actor: <a href="https://apify.com/tanlabs/app-store-review-insights">App Store Review Insights</a>. Give it one or more App Store links or app IDs and countries, and it returns:</p>
<ul>
<li>every review (rating, title, text, version, date)</li>
<li>an app-level summary: average rating of recent reviews vs. the overall store rating</li>
<li>recurring themes such as pricing and subscriptions, bugs after an update, ads, login problems</li>
<li>the most negative and most positive reviews to read first</li>
</ul>
<p>For example, a run on 20 recent US reviews of a large video app showed an average of 2.75 stars against a 4.66 store rating, with pricing/subscription, a disliked UI update and ads as the top themes. That gap between recent and lifetime rating is often the first sign a release went badly.</p>
<p>Example input:</p>
<pre><code class="language-json">{
  "apps": ["https://apps.apple.com/us/app/youtube-watch-listen-stream/id544007664"],
  "countries": ["us"],
  "maxReviewsPerCountry": 100
}
</code></pre>
<p>Pricing is $0.02 per app report plus $0.0003 per review.</p>
<h2>Related tools</h2>
<ul>
<li><a href="https://apify.com/tanlabs/app-store-top-charts">App Store Top Charts &amp; Rank Tracker</a>: daily top free/paid/grossing charts for any category and country, with rank changes since the last run.</li>
<li><a href="https://apify.com/tanlabs/social-preview-meta-checker">Social Preview &amp; Meta Tag Checker</a>: checks how your app's landing page looks when shared.</li>
</ul>
<p>Happy to answer questions in the comments.</p>
]]></content:encoded></item><item><title><![CDATA[How to bulk-check Open Graph and X card tags before you share a link]]></title><description><![CDATA[You ship a landing page, paste the link into Slack, X or LinkedIn, and the preview shows the wrong title, a cropped image, or nothing at all. Usually the…]]></description><link>https://tanlabs.hashnode.dev/how-to-bulk-check-open-graph-and-x-card-tags-before-you-share-a-link</link><guid isPermaLink="true">https://tanlabs.hashnode.dev/how-to-bulk-check-open-graph-and-x-card-tags-before-you-share-a-link</guid><category><![CDATA[SEO]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[openGraph]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[TanLabs]]></dc:creator><pubDate>Wed, 28 Oct 2026 19:35:03 GMT</pubDate><content:encoded><![CDATA[<p>You ship a landing page, paste the link into Slack, X or LinkedIn, and the preview shows the wrong title, a cropped image, or nothing at all. Usually the cause is one of a handful of meta tag problems:</p>
<ul>
<li><code>og:image</code> points to a URL that returns 404 or redirects to a login page</li>
<li>the image is too small (under 1200×630 looks blurry on most networks) or too heavy</li>
<li><code>og:title</code> / <code>og:description</code> are missing, so the network falls back to random page text</li>
<li><code>twitter:card</code> is missing, so X shows a tiny summary card instead of a large image</li>
<li>the page has no <code>canonical</code>, or it is accidentally <code>noindex</code></li>
</ul>
<p>Checking this by hand for one page is fine. Checking it for 50 product pages or a whole blog is not.</p>
<h2>A quick manual check</h2>
<p>For a single URL you can look at the raw tags:</p>
<pre><code class="language-bash">curl -sL https://example.com | grep -Eio '&lt;meta[^&gt;]+(og:|twitter:)[^&gt;]+&gt;'
</code></pre>
<p>That shows whether tags exist, but not whether the image actually loads, what size it is, or whether the title is too long to display.</p>
<h2>Checking many pages at once</h2>
<p>I built a small tool for this: <a href="https://apify.com/tanlabs/social-preview-meta-checker">Social Preview &amp; Meta Tag Checker</a> on Apify. You give it a list of URLs and it returns, per page:</p>
<ul>
<li>every Open Graph and X/Twitter card tag it found</li>
<li>whether <code>og:image</code> loads, its HTTP status, pixel size and file size</li>
<li>title and description length checks</li>
<li>canonical, favicon, viewport, <code>h1</code> and <code>noindex</code> checks</li>
<li>a 0–100 score, a prioritized fix list, and ready-to-paste meta tags</li>
</ul>
<p>Example input:</p>
<pre><code class="language-json">{
  "urls": ["https://example.com", "https://github.com"],
  "checkImage": true
}
</code></pre>
<p>You can run it from the Apify console, or call it from code:</p>
<pre><code class="language-bash">curl -X POST "https://api.apify.com/v2/acts/tanlabs~social-preview-meta-checker/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls":["https://example.com"]}'
</code></pre>
<p>It costs $0.004 per page checked, so auditing 250 pages is about $1.</p>
<h2>What to fix first</h2>
<p>From the pages I tested, the most common problems were, in order: no <code>og:image</code> at all, a missing <code>twitter:card</code>, and descriptions longer than ~200 characters. Fix those three and most link previews look right.</p>
<p>If you want a wider check of how ready a site is for AI search engines (robots.txt rules for AI crawlers, <code>llms.txt</code>, structured data), there is also an <a href="https://apify.com/tanlabs/ai-search-readiness-audit">AI Search Readiness Audit</a> from the same author.</p>
<p>Questions or feature requests are welcome in the comments.</p>
]]></content:encoded></item></channel></rss>