<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sifat Ahmed</title>
    <description>The latest articles on DEV Community by Sifat Ahmed (@sifat_ahmed_3cc666203e7b8).</description>
    <link>https://dev.to/sifat_ahmed_3cc666203e7b8</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4090356%2F4ae60a96-0c30-4bc9-82f3-5b62d886acec.png</url>
      <title>DEV Community: Sifat Ahmed</title>
      <link>https://dev.to/sifat_ahmed_3cc666203e7b8</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sifat_ahmed_3cc666203e7b8"/>
    <language>en</language>
    <item>
      <title>A counting-stat animation in HyperFrames, full working code (free recipe)</title>
      <dc:creator>Sifat Ahmed</dc:creator>
      <pubDate>Sun, 06 Sep 2026 05:33:01 +0000</pubDate>
      <link>https://dev.to/sifat_ahmed_3cc666203e7b8/a-counting-stat-animation-in-hyperframes-full-working-code-free-recipe-3on4</link>
      <guid>https://dev.to/sifat_ahmed_3cc666203e7b8/a-counting-stat-animation-in-hyperframes-full-working-code-free-recipe-3on4</guid>
      <description>&lt;p&gt;Following up on the guide I posted about a couple days ago (HyperFrames — renders video from plain HTML/CSS/GSAP instead of a timeline editor). Here's one full recipe from it, in full, free — no teaser cut-off.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;p&gt;Turning a plain number into a small performance reads as far more "designed" than just displaying a static figure — genuinely effective for anything results/data-driven: a metric, a price, a countdown.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"clip"&lt;/span&gt; &lt;span class="na"&gt;data-start=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;data-duration=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;data-track-index=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"stat"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;customers served&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nx"&gt;tl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ease&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;power1.inOut&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;onUpdate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why it's built this way
&lt;/h2&gt;

&lt;p&gt;Tweening a plain JS object (not the DOM element itself) and writing the formatted number into the element on every tick is the reliable way to do this in a render pipeline that has to seek to arbitrary timestamps and always get the same pixels. It stays perfectly deterministic — the exact displayed number at any timestamp is a pure function of that timestamp — and it avoids animating a text property GSAP doesn't natively tween.&lt;/p&gt;

&lt;p&gt;This is one of six recipes in the full guide (title reveal, logo intro, scene crossfade, CTA morph, Ken Burns background drift are the other five), plus a "five pitfalls" section on the mistakes that produce a blank render or frozen frame instead of a helpful error message. If the rest is useful: &lt;a href="https://sidheart.gumroad.com/l/lyudd" rel="noopener noreferrer"&gt;https://sidheart.gumroad.com/l/lyudd&lt;/a&gt; ($19).&lt;/p&gt;

&lt;p&gt;Happy to answer questions about the pattern itself here for free either way.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Made a paid guide from an open-source Claude skill (without reselling it)</title>
      <dc:creator>Sifat Ahmed</dc:creator>
      <pubDate>Sun, 06 Sep 2026 05:10:26 +0000</pubDate>
      <link>https://dev.to/sifat_ahmed_3cc666203e7b8/made-a-paid-guide-from-an-open-source-claude-skill-without-reselling-it-5b3n</link>
      <guid>https://dev.to/sifat_ahmed_3cc666203e7b8/made-a-paid-guide-from-an-open-source-claude-skill-without-reselling-it-5b3n</guid>
      <description>&lt;p&gt;Quick build-in-public post, same vein as my earlier n8n automation service posts.&lt;/p&gt;

&lt;p&gt;I wanted to package something adjacent to that work: a guide to HyperFrames, an open-source framework (from HeyGen) that renders video from plain HTML/CSS/GSAP instead of a timeline editor. There's a Claude Code skill that teaches an AI agent this framework - genuinely useful, but not something I built, so selling a copy of it would've been selling someone else's open-source work as my own.&lt;/p&gt;

&lt;p&gt;What I did instead: wrote an original guide teaching the technique - the actual mental model (why the render has to be deterministic and seekable, and what that means for how you write animation code), plus six complete working recipes (title reveals, counting stats, crossfades, that kind of thing) with real code, checked against the framework's actual contract rather than simplified examples that fall apart the moment you try to render them for real. Same rule paid Docker/React courses run on: the tool itself is free, the technique you package around it is legitimately yours to sell.&lt;/p&gt;

&lt;p&gt;$19 on Gumroad if it's useful to anyone building video-from-code pipelines: &lt;a href="https://sidheart.gumroad.com/l/lyudd" rel="noopener noreferrer"&gt;https://sidheart.gumroad.com/l/lyudd&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer anything about HyperFrames or the deterministic-rendering mental model if useful - not trying to oversell it, just sharing what I built.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>opensource</category>
      <category>buildinpublic</category>
      <category>video</category>
    </item>
    <item>
      <title>OpenAI Shipped a Hard Spend Limit - Here's the Gap It Still Leaves Open</title>
      <dc:creator>Sifat Ahmed</dc:creator>
      <pubDate>Fri, 04 Sep 2026 04:32:03 +0000</pubDate>
      <link>https://dev.to/sifat_ahmed_3cc666203e7b8/openai-still-wont-let-you-set-a-hard-spend-limit-heres-what-actually-works-19d2</link>
      <guid>https://dev.to/sifat_ahmed_3cc666203e7b8/openai-still-wont-let-you-set-a-hard-spend-limit-heres-what-actually-works-19d2</guid>
      <description>&lt;p&gt;For most of 2026, this was true: OpenAI's budget field was alert-only. Cross your number, get an email, keep getting billed. That changed on July 23, when OpenAI rolled out real hard spend limits to every API account, free, no enterprise tier required. Set a cap at the org or project level, and once tracked spend reaches it, further requests fail with a 429 until the next billing cycle.&lt;/p&gt;

&lt;p&gt;That's a genuine fix for the worst version of this problem - a leaked key or a runaway retry loop can no longer bill you into the ground forever. If you haven't turned it on, it's worth doing today: project settings, Limits, Edit spend limit, done in two minutes.&lt;/p&gt;

&lt;p&gt;But a few weeks of watching this land tells me it doesn't close the gap most solo devs actually have. Two things stayed true even after the feature shipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's a kill switch, not a warning
&lt;/h2&gt;

&lt;p&gt;OpenAI's own guidance is to only use hard limits on dev/CI keys, not production - because the cutoff is account-wide and immediate. It doesn't stop the one customer or workflow that's spending; it stops everything behind that key. For a side project that's fine. For anything with real users hitting it, flipping that switch on means a runaway loop doesn't just cost you money anymore, it takes your app down completely, with no warning first.&lt;/p&gt;

&lt;p&gt;So the same developers this feature was built for mostly end up leaving it off in production, for the same reason they left the old alert-only budget too high to matter: the downside of it firing is worse than the upside of it existing. That's not a flaw in OpenAI's implementation - a blunt, request-blocking cap has to work that way. It just means "OpenAI now has a hard limit" and "I can safely turn it on" are two different sentences.&lt;/p&gt;

&lt;h2&gt;
  
  
  A cap tells you after, not before
&lt;/h2&gt;

&lt;p&gt;Even set up correctly, a hard limit is silent until it fires. You don't get a "hey, you're at 80%, might want to look" - you get a working app, then a 429. The dashboard shows you the number if you go check it, but nothing pushes a signal to you before the wall.&lt;/p&gt;

&lt;p&gt;That's the part I built Fusebox around originally, and it's still true regardless of whether the hard limit exists: &lt;a href="https://fusebox.sifatsrk.workers.dev?ref=devto" rel="noopener noreferrer"&gt;Fusebox&lt;/a&gt; watches your usage against a ceiling you set and emails you at 50%, 80%, and 100% of it, using a read-only Admin key scoped to &lt;code&gt;api.usage.read&lt;/code&gt; - it can see your numbers, it can't spend or touch your account. Free tier emails you once you're over. Paid ($7/mo) adds the earlier warnings, which is the part that actually lets you catch a loop while it's still cheap to fix.&lt;/p&gt;

&lt;p&gt;The honest way to put it: OpenAI's hard limit is the backstop you hope you never need. Fusebox is the thing that tells you early enough that you never do.&lt;/p&gt;

&lt;p&gt;If you've turned on OpenAI's hard limit in production, I'd like to know - did you actually leave it on, or did the account-wide blast radius talk you out of it? That's the real open question this feature raised for me.&lt;/p&gt;

</description>
      <category>openai</category>
      <category>api</category>
      <category>ai</category>
      <category>saas</category>
    </item>
    <item>
      <title>Bot protection and unsubscribe: the two features I built before Fusebox had a single user</title>
      <dc:creator>Sifat Ahmed</dc:creator>
      <pubDate>Sat, 29 Aug 2026 16:02:07 +0000</pubDate>
      <link>https://dev.to/sifat_ahmed_3cc666203e7b8/bot-protection-and-unsubscribe-the-two-features-i-built-before-fusebox-had-a-single-user-eja</link>
      <guid>https://dev.to/sifat_ahmed_3cc666203e7b8/bot-protection-and-unsubscribe-the-two-features-i-built-before-fusebox-had-a-single-user-eja</guid>
      <description>&lt;p&gt;Fusebox emails you before your OpenAI bill blows past a number you set. Before writing that pitch anywhere, I built two things nobody asks for in a demo: a bot check on signup, and a real unsubscribe link. Neither was optional, and neither was hard - but skipping them is exactly the kind of thing a solo project quietly does when there's no user yet to complain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why bot protection on day one
&lt;/h2&gt;

&lt;p&gt;The signup form asks for an OpenAI Admin key. That's the one part of Fusebox that can't be undone if it goes wrong - a scraped or brute-forced signup endpoint means someone else's key sitting encrypted in your database, or a flood of junk rows with no real owner. reCAPTCHA was the obvious default, but it ships a tracking script and a visible "I'm not a robot" checkbox that doesn't fit a tool whose whole pitch is "we ask for less than you'd expect." Cloudflare Turnstile does the same job - a managed challenge that mostly resolves invisibly - without the tracking baggage, and it's free.&lt;/p&gt;

&lt;p&gt;The actual wiring is small: a widget on the client that calls back with a token once it resolves, a submit button that stays disabled until that token exists, and a server-side check against Cloudflare's own siteverify endpoint before the signup handler does anything else:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;verifyTurnstile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secretKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://challenges.cloudflare.com/turnstile/v0/siteverify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;secretKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({}));&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that check fails, the request never reaches the part that touches an API key or the database. One rule worth calling out: on any submit attempt - success or failure - the client resets the token and re-disables the button. A stale token from a previous attempt is exactly the kind of thing that gets missed until someone abuses it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the unsubscribe link isn't a preference toggle
&lt;/h2&gt;

&lt;p&gt;The simplest version of "let people leave" is a settings page with a checkbox. Fusebox doesn't have an account system at all - just an email, an encrypted key, and a ceiling - so there's no login to put a checkbox behind. The unsubscribe link that goes out with every threshold email carries a random token generated once at signup and stored next to that subscriber's row. Clicking it is a single GET that flips one column:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`UPDATE subscribers SET active = 0 WHERE unsubscribe_token = ? AND active = 1`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AND active = 1 matters more than it looks. Without it, a link that's clicked twice - or forwarded, or hit by an email client's link-prefetcher - just re-runs the same update harmlessly. With it, the second click tells the truth: "already used," not a fake success. Small difference, but it's the kind of thing that's invisible right up until someone checks it, and getting it wrong is exactly the kind of bug that erodes trust in a tool whose entire pitch is trustworthiness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why write this down
&lt;/h2&gt;

&lt;p&gt;Neither of these features is interesting on its own, and neither shows up in a screenshot or a pitch. They're the parts of a "trust us with your API key" tool that only matter when something goes wrong - a bot, a forwarded email, a link clicked twice - and by then it's too late to have skipped them. Building them before the first real signup, instead of after the first incident, was the point.&lt;/p&gt;

&lt;p&gt;Fusebox is free, early access, live now: &lt;a href="https://fusebox.sifatsrk.workers.dev" rel="noopener noreferrer"&gt;https://fusebox.sifatsrk.workers.dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've built something similar and handled either of these differently, I'd like to hear about it in the comments.&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>cloudflare</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I'm running a done-for-you automation service on self-hosted n8n — looking for a first pilot client</title>
      <dc:creator>Sifat Ahmed</dc:creator>
      <pubDate>Sun, 23 Aug 2026 11:08:03 +0000</pubDate>
      <link>https://dev.to/sifat_ahmed_3cc666203e7b8/im-running-a-done-for-you-automation-service-on-self-hosted-n8n-looking-for-a-first-pilot-client-4c1n</link>
      <guid>https://dev.to/sifat_ahmed_3cc666203e7b8/im-running-a-done-for-you-automation-service-on-self-hosted-n8n-looking-for-a-first-pilot-client-4c1n</guid>
      <description>&lt;p&gt;Quick build-in-public post. I've been setting up a small service: I build and run n8n automations for people — the repetitive stuff that eats an hour a day (copying data between tools, replying to the same kind of message, chasing follow-ups) — self-hosted on my own infrastructure, so you never touch the software or pay for a seat. You just get the automation working.&lt;/p&gt;

&lt;p&gt;Stack is self-hosted n8n + Postgres, monitored (I run my own uptime checks on it so I know before you do if something's down), fronted through Cloudflare. Zero infra cost on my end at this stage, so pricing stays honest rather than padded to cover a cloud bill.&lt;/p&gt;

&lt;p&gt;I'm looking for a first real pilot — something small and concrete (a workflow you're doing by hand right now that shouldn't need to be). I'll build it free or heavily discounted in exchange for honest feedback and, if it goes well, a testimonial. Not trying to oversell this: it's early, I have zero client track record yet, and I'd rather say that upfront than pretend otherwise.&lt;/p&gt;

&lt;p&gt;If you've got something repetitive eating your time, or you know someone who does, I'd genuinely like to hear about it — reply here or DM.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Why Fusebox doesn't support Claude yet</title>
      <dc:creator>Sifat Ahmed</dc:creator>
      <pubDate>Sun, 23 Aug 2026 02:50:06 +0000</pubDate>
      <link>https://dev.to/sifat_ahmed_3cc666203e7b8/why-fusebox-doesnt-support-claude-yet-196f</link>
      <guid>https://dev.to/sifat_ahmed_3cc666203e7b8/why-fusebox-doesnt-support-claude-yet-196f</guid>
      <description>&lt;p&gt;Fusebox emails you before your OpenAI bill blows past a number you set. Before writing any of that code, I spent an afternoon checking one thing: what kind of API key would I actually need to ask people for?&lt;/p&gt;

&lt;p&gt;That question mattered more than it sounds like it should. A tool that reads your spend needs some credential from your account. The honest version of that credential can &lt;em&gt;only&lt;/em&gt; read - it can't touch billing, can't create or revoke other keys, can't add or remove people from your org. The dishonest version asks for more than it needs and hopes you don't check.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenAI: genuinely scoped
&lt;/h2&gt;

&lt;p&gt;OpenAI lets you create an Admin key and restrict it to specific permissions per resource - None, Read, or Write. For usage and cost data specifically, that's a scope called &lt;code&gt;api.usage.read&lt;/code&gt;. Grant only that, and the key can pull your spend numbers and do nothing else. That's exactly the shape of trust a tool like this should ask for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anthropic: not yet, for individual accounts
&lt;/h2&gt;

&lt;p&gt;I checked the same thing for Claude, expecting a similar scoped option. Anthropic's own docs say otherwise, for Claude Console (the product individual developers actually use):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Claude Console keys do not have selectable scopes; every key carries full access to all endpoints that accept Admin API keys."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Scoped read-only keys do exist on Anthropic's side (&lt;code&gt;read:spend_limits&lt;/code&gt;, &lt;code&gt;read:analytics&lt;/code&gt;) but only for &lt;strong&gt;Claude Enterprise&lt;/strong&gt; organizations, not individual Console accounts. For everyone else, the only key that can read usage data can also manage your organization's members and revoke other people's API keys.&lt;/p&gt;

&lt;p&gt;Asking a solo developer to hand that over, in service of a feature as small as "email me at 80% of my budget," is a bad trade. So Fusebox doesn't do it yet. Claude support is built and waiting - it's a small amount of code - but it stays off until Anthropic ships the same kind of scoping for individual accounts that OpenAI already has.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is worth writing down
&lt;/h2&gt;

&lt;p&gt;Most tools that ask for API access don't explain what they're actually asking for, and most users don't check. I'd rather the constraint be visible than quietly worked around.&lt;/p&gt;

&lt;p&gt;Fusebox is free, early access, OpenAI-only for now: &lt;a href="https://fusebox.sifatsrk.workers.dev" rel="noopener noreferrer"&gt;https://fusebox.sifatsrk.workers.dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've found a cleaner way to do the Claude side of this safely, I'd genuinely like to hear about it in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>security</category>
      <category>buildinpublic</category>
    </item>
  </channel>
</rss>
