<?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: Ivanvolt</title>
    <description>The latest articles on DEV Community by Ivanvolt (@ivanvolt815).</description>
    <link>https://dev.to/ivanvolt815</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%2F3416642%2F02c60694-6078-48ed-ab61-cac4f0cec491.jpg</url>
      <title>DEV Community: Ivanvolt</title>
      <link>https://dev.to/ivanvolt815</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ivanvolt815"/>
    <language>en</language>
    <item>
      <title>Charging credits for async AI jobs without double-spending</title>
      <dc:creator>Ivanvolt</dc:creator>
      <pubDate>Thu, 27 Aug 2026 03:59:18 +0000</pubDate>
      <link>https://dev.to/ivanvolt815/charging-credits-for-async-ai-jobs-without-double-spending-33pe</link>
      <guid>https://dev.to/ivanvolt815/charging-credits-for-async-ai-jobs-without-double-spending-33pe</guid>
      <description>&lt;p&gt;Image and video jobs do not finish in the same HTTP request. They take long enough that a dropped response, a double-click, or a refresh will retry. If you charge on "done", you eat unpaid work. If you charge on "start" and the job dies, you owe people money back. Both bugs showed up in our first design, so we moved the money and the task into one write.&lt;/p&gt;

&lt;p&gt;This is how Epochal does it today: a &lt;code&gt;202&lt;/code&gt; on submit, a durable task, a client request id, and a refund flag that can only flip once. No customer webhooks. The browser polls.&lt;/p&gt;

&lt;p&gt;I am writing this as the person who has to explain a missing refund.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure that forced the design
&lt;/h2&gt;

&lt;p&gt;A user hits Generate. The server creates a job and charges the quoted cost. The tab dies before the JSON comes back. The client retries with the same payload.&lt;/p&gt;

&lt;p&gt;Without a request id you get two tasks and twice the spend. With a request id but a late charge, the retry can create a second task that never got paid. The only version that stayed sane for us:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The client sends &lt;code&gt;clientRequestId&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;One database transaction locks the user, checks the in-flight job cap, spends credits, inserts a &lt;code&gt;pending&lt;/code&gt; task.&lt;/li&gt;
&lt;li&gt;The HTTP response is &lt;code&gt;202&lt;/code&gt; with the task id. The file is not ready.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Same user + same &lt;code&gt;clientRequestId&lt;/code&gt; returns the existing task. No second spend.&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="c1"&gt;// Same user + same clientRequestId → existing row, created: false&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existing&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;findTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientRequestId&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="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;created&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;consumeCredits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;insertTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;creditsReserved&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The column is unique per user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reserve credits and create the task atomically
&lt;/h2&gt;

&lt;p&gt;Spend happens inside the same transaction as the task insert, not after the model returns.&lt;/p&gt;

&lt;p&gt;The user row is locked (&lt;code&gt;FOR UPDATE&lt;/code&gt;) so two tabs cannot both pass the balance check. Credits live in expiring lots. We spend the lots that expire first, and a cron expires any unused balance and records the change in the ledger.&lt;/p&gt;

&lt;p&gt;If the balance is short, we throw before insert. The generate routes map that to &lt;strong&gt;402&lt;/strong&gt;. No empty task, no "reserved 0 then fail."&lt;/p&gt;

&lt;p&gt;Local safety or text-moderation blocks write a failed task with nothing reserved. Those never touch the ledger.&lt;/p&gt;

&lt;h2&gt;
  
  
  202 means accepted, not completed
&lt;/h2&gt;

&lt;p&gt;Synchronous image generation is deprecated on our API. Submit returns immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;202&lt;/span&gt; &lt;span class="ne"&gt;Accepted&lt;/span&gt;
&lt;span class="s"&gt;{&lt;/span&gt;
&lt;span class="s"&gt;  "creation": { "id": "…", "status": "pending" },&lt;/span&gt;
&lt;span class="s"&gt;  "remainingCredits": 142&lt;/span&gt;
&lt;span class="s"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Statuses are only &lt;code&gt;pending&lt;/code&gt;, &lt;code&gt;running&lt;/code&gt;, &lt;code&gt;completed&lt;/code&gt;, &lt;code&gt;failed&lt;/code&gt;. A completed or failed row cannot move back. That sounds obvious until two workers both try to "fix" a stuck job.&lt;/p&gt;

&lt;p&gt;The workbench polls the task endpoint while it is active. Each GET asks the server to sync that task before it reads. A slower task-center poll lets users navigate away without losing visibility.&lt;/p&gt;

&lt;p&gt;We do not offer "POST this URL when the video is done." Billing uses Stripe webhooks. Generation completion is our problem: client poll, a server-side follow-up after the &lt;code&gt;202&lt;/code&gt;, and a cron that rescans stale &lt;code&gt;pending&lt;/code&gt; / &lt;code&gt;running&lt;/code&gt; rows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refund exactly once
&lt;/h2&gt;

&lt;p&gt;Failure does two things in order: mark the task &lt;code&gt;failed&lt;/code&gt; (only if it is not already terminal), then refund if the row is eligible.&lt;/p&gt;

&lt;p&gt;The refund is its own transaction. The refund flag is not a record that a refund was attempted. It is a claim that the refund transaction completed.&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;claimed&lt;/span&gt;&lt;span class="p"&gt;]&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;tx&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;generationTask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;creditsRefunded&lt;/span&gt;&lt;span class="p"&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;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;generationTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;generationTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;generationTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;creditsRefunded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;returning&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;generationTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;claimed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// another worker already refunded&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;addCredits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;creditsCost&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;insertLedgerRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REFUND&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;creditsCost&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the add-credits step throws, the transaction rolls back and &lt;code&gt;creditsRefunded&lt;/code&gt; stays false. The next retry can try again. That comment is in the orchestrator because we shipped the opposite bug: flag flipped, money never added.&lt;/p&gt;

&lt;p&gt;A second cron looks for &lt;code&gt;failed&lt;/code&gt; + reserved + not refunded and runs the same claim. I do not trust a single in-process path after a deploy mid-job.&lt;/p&gt;

&lt;p&gt;Not every terminal failure is refundable, so refund eligibility is stored explicitly rather than inferred from &lt;code&gt;status = failed&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Completed means we own the output
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;running&lt;/code&gt; means we handed the job off. &lt;code&gt;completed&lt;/code&gt; means we downloaded the result, stored it on our object storage, and linked it to the task. That last write is also claimed so two finishers cannot create two library rows.&lt;/p&gt;

&lt;p&gt;Upstream success is not a durable result. If persist fails after the file exists, a later sync or the recovery cron can finish the row. Users see the same object in the workbench, the task center, and the library because both tables map to one list item.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concurrency belongs inside the same lock
&lt;/h2&gt;

&lt;p&gt;In-flight means &lt;code&gt;pending&lt;/code&gt; or &lt;code&gt;running&lt;/code&gt;. We enforce the user's concurrency limit under the same user lock as the credit spend. That prevents two simultaneous requests from both passing the limit check before either inserts its task.&lt;/p&gt;

&lt;p&gt;Over the cap is &lt;strong&gt;429&lt;/strong&gt;. The pricing UI reads the same plan-cap resolver as the server, so the displayed limit and the enforced limit cannot drift independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would not do again
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Charge after success.&lt;/strong&gt; You will eat unpaid GPU time on every abandoned tab, and you will invent a "pending charge" state that nobody can explain in support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Refund with &lt;code&gt;UPDATE … SET refunded = true&lt;/code&gt; then a second query for the money.&lt;/strong&gt; One of those queries will fail. Claim the slot and move the balance in one transaction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customer webhooks as the only completion signal.&lt;/strong&gt; We would have to sign payloads, retry 4xx, and still keep polling for the workbench. Poll + server sync + cron is enough for a product that is a website, not an API company.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treat "the model finished" as "the user has a file."&lt;/strong&gt; Until the object is on our storage and linked, the task is not done. People refresh. The row has to survive that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try the failure path
&lt;/h2&gt;

&lt;p&gt;Open a model or tool on &lt;a href="https://epochal.app" rel="noopener noreferrer"&gt;epochal.app&lt;/a&gt;, submit once, then refresh mid-job. The same &lt;code&gt;clientRequestId&lt;/code&gt; should not take credits twice.&lt;/p&gt;

&lt;p&gt;If you run a similar queue: put the request id, the spend, and the &lt;code&gt;pending&lt;/code&gt; row in one lock. Everything after that is catching up.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>architecture</category>
      <category>postgres</category>
      <category>backend</category>
    </item>
    <item>
      <title>Best AI Video Generators in 2026 (That Actually Work for Real Content)</title>
      <dc:creator>Ivanvolt</dc:creator>
      <pubDate>Thu, 16 Apr 2026 08:59:41 +0000</pubDate>
      <link>https://dev.to/ivanvolt815/best-ai-video-generators-in-2026-that-actually-work-for-real-content-46oe</link>
      <guid>https://dev.to/ivanvolt815/best-ai-video-generators-in-2026-that-actually-work-for-real-content-46oe</guid>
      <description>&lt;p&gt;AI video generation has improved fast.&lt;/p&gt;

&lt;p&gt;Most models can now generate impressive clips.&lt;/p&gt;

&lt;p&gt;But here’s the real problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Most AI video tools don’t work well for actual content creation.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After testing multiple models and tools, I realized something important:&lt;/p&gt;

&lt;p&gt;👉 The best AI video generator is not just about quality&lt;br&gt;&lt;br&gt;
👉 It’s about how well it fits your workflow&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Veo 3.1 → best for cinematic quality&lt;/li&gt;
&lt;li&gt;Seedance 2.0 → best for fast iteration &amp;amp; volume&lt;/li&gt;
&lt;li&gt;Kling 3.0 → best balance&lt;/li&gt;
&lt;li&gt;WAN → interesting for open workflows&lt;/li&gt;
&lt;li&gt;Workflow &amp;gt; model quality&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The biggest mistake people make
&lt;/h2&gt;

&lt;p&gt;Most comparisons focus only on output quality.&lt;/p&gt;

&lt;p&gt;That’s not how real usage works.&lt;/p&gt;

&lt;p&gt;In practice, you need to ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can I reuse outputs?&lt;/li&gt;
&lt;li&gt;Can I iterate quickly?&lt;/li&gt;
&lt;li&gt;Can I keep consistency across generations?&lt;/li&gt;
&lt;li&gt;Can I move from image → video easily?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 This is a workflow problem, not just a model problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  Veo 3.1 — best for cinematic output
&lt;/h2&gt;

&lt;p&gt;Best for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cinematic shots&lt;/li&gt;
&lt;li&gt;brand visuals&lt;/li&gt;
&lt;li&gt;high-quality scenes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tradeoff:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;slower iteration&lt;/li&gt;
&lt;li&gt;not ideal for volume&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 Great for “hero content”, not for rapid testing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Seedance 2.0 — best for iteration
&lt;/h2&gt;

&lt;p&gt;Best for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fast testing&lt;/li&gt;
&lt;li&gt;social content&lt;/li&gt;
&lt;li&gt;generating multiple variations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why it works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;speed &amp;gt; perfection&lt;/li&gt;
&lt;li&gt;easier to iterate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 In real production, this often matters more than peak quality.&lt;/p&gt;




&lt;h2&gt;
  
  
  Kling 3.0 — balanced option
&lt;/h2&gt;

&lt;p&gt;Good for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;text-to-video&lt;/li&gt;
&lt;li&gt;image-to-video&lt;/li&gt;
&lt;li&gt;multiple formats&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 Not always #1 in one area, but solid across everything.&lt;/p&gt;




&lt;h2&gt;
  
  
  WAN — worth watching
&lt;/h2&gt;

&lt;p&gt;Why it matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;more transparent&lt;/li&gt;
&lt;li&gt;open-weight ecosystem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Best for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;experimentation&lt;/li&gt;
&lt;li&gt;research workflows&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The real insight: workflow &amp;gt; model
&lt;/h2&gt;

&lt;p&gt;A real AI video workflow looks like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with prompt or image&lt;/li&gt;
&lt;li&gt;Generate candidates&lt;/li&gt;
&lt;li&gt;Save strong outputs&lt;/li&gt;
&lt;li&gt;Reuse references&lt;/li&gt;
&lt;li&gt;Iterate variations&lt;/li&gt;
&lt;li&gt;Move into video&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;👉 This is why image-to-video is becoming critical.&lt;/p&gt;

&lt;p&gt;Text alone is unstable.&lt;br&gt;&lt;br&gt;
Images create consistency.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where most tools fail
&lt;/h2&gt;

&lt;p&gt;Most tools treat every generation as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;a fresh start&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;inconsistent visuals&lt;/li&gt;
&lt;li&gt;no continuity&lt;/li&gt;
&lt;li&gt;random outputs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What actually works better
&lt;/h2&gt;

&lt;p&gt;A better approach is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reuse strong frames&lt;/li&gt;
&lt;li&gt;build on previous outputs&lt;/li&gt;
&lt;li&gt;compare variations&lt;/li&gt;
&lt;li&gt;keep everything in one loop&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 This is where workflow tools start to matter.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Epochal fits
&lt;/h2&gt;

&lt;p&gt;While testing, I came across a tool called &lt;strong&gt;Epochal&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of focusing on one model, it focuses on workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;text → video&lt;/li&gt;
&lt;li&gt;image → video&lt;/li&gt;
&lt;li&gt;model comparison&lt;/li&gt;
&lt;li&gt;saved outputs&lt;/li&gt;
&lt;li&gt;iteration loop&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 It’s closer to a &lt;strong&gt;workspace for AI video creation&lt;/strong&gt; than a single generator.&lt;/p&gt;

&lt;p&gt;You can check it here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://epochal.app?ref=devto" rel="noopener noreferrer"&gt;https://epochal.app?ref=devto&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  My practical takeaway
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use Veo for quality&lt;/li&gt;
&lt;li&gt;Use Seedance for speed&lt;/li&gt;
&lt;li&gt;Use Kling for flexibility&lt;/li&gt;
&lt;li&gt;Focus on workflow if you care about real output&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;The future of AI video is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;one perfect prompt&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It’s:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;generate → compare → reuse → iterate&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s where consistency and real content creation start to happen.&lt;/p&gt;




&lt;p&gt;If you're curious, I also wrote a deeper breakdown here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://epochal.app/blog/best-ai-video-generator-2026?ref=devto" rel="noopener noreferrer"&gt;https://epochal.app/blog/best-ai-video-generator-2026?ref=devto&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>startup</category>
      <category>productivity</category>
    </item>
    <item>
      <title>We built an SDK to submit AI tools to directories</title>
      <dc:creator>Ivanvolt</dc:creator>
      <pubDate>Fri, 06 Mar 2026 14:41:11 +0000</pubDate>
      <link>https://dev.to/ivanvolt815/we-built-an-sdk-to-submit-ai-tools-to-directories-3fgb</link>
      <guid>https://dev.to/ivanvolt815/we-built-an-sdk-to-submit-ai-tools-to-directories-3fgb</guid>
      <description>&lt;p&gt;Launching an AI tool is easy today.&lt;/p&gt;

&lt;p&gt;Getting people to discover it is much harder.&lt;/p&gt;

&lt;p&gt;Most founders eventually start submitting their tools to directories, product listings, and communities. The problem is that the process is repetitive.&lt;/p&gt;

&lt;p&gt;You open a form, paste your URL, write a description, upload an icon, and repeat the same steps across different platforms.&lt;/p&gt;

&lt;p&gt;As developers, we automate almost everything else.&lt;/p&gt;

&lt;p&gt;So we asked a simple question:&lt;/p&gt;

&lt;p&gt;Why are tool submissions still manual?&lt;/p&gt;

&lt;p&gt;That’s why we built the Aidirs SDK.&lt;/p&gt;

&lt;p&gt;Instead of filling out forms, developers can submit their AI tools programmatically.&lt;/p&gt;

&lt;p&gt;All you need is a URL.&lt;/p&gt;

&lt;p&gt;The SDK can automatically generate:&lt;/p&gt;

&lt;p&gt;• tool name&lt;br&gt;
• description&lt;br&gt;
• icon&lt;br&gt;
• metadata&lt;/p&gt;

&lt;p&gt;This means submissions can become part of a developer workflow instead of a manual task.&lt;/p&gt;

&lt;p&gt;If you’re curious, you can check the docs here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aidirs.best/api-docs" rel="noopener noreferrer"&gt;https://aidirs.best/api-docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you’re launching an AI tool, you can submit it here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aidirs.best/submit" rel="noopener noreferrer"&gt;https://aidirs.best/submit&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>npm</category>
      <category>webdev</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Aidirs is now listed on Toolify</title>
      <dc:creator>Ivanvolt</dc:creator>
      <pubDate>Thu, 05 Mar 2026 06:24:56 +0000</pubDate>
      <link>https://dev.to/ivanvolt815/aidirs-is-now-listed-on-toolify-4jmf</link>
      <guid>https://dev.to/ivanvolt815/aidirs-is-now-listed-on-toolify-4jmf</guid>
      <description>&lt;p&gt;Aidirs, a curated AI tools directory, has recently been listed on Toolify.&lt;/p&gt;

&lt;p&gt;You can view the listing here:&lt;br&gt;
&lt;a href="https://toolify.ai/tool/aidirs" rel="noopener noreferrer"&gt;https://toolify.ai/tool/aidirs&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How Indie Makers Discover AI Tools in 2026 (And Why Directories Still Matter)</title>
      <dc:creator>Ivanvolt</dc:creator>
      <pubDate>Wed, 25 Feb 2026 14:41:48 +0000</pubDate>
      <link>https://dev.to/ivanvolt815/how-indie-makers-discover-ai-tools-in-2026-and-why-directories-still-matter-4mkm</link>
      <guid>https://dev.to/ivanvolt815/how-indie-makers-discover-ai-tools-in-2026-and-why-directories-still-matter-4mkm</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;AI tools are launching faster than ever, but discovery hasn’t changed as much as people think.&lt;br&gt;
After building an AI tools directory, I started to see clear patterns in how indie makers actually get discovered — and what doesn’t work anymore.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  AI tools are everywhere — discovery is not
&lt;/h2&gt;

&lt;p&gt;In 2026, launching an AI tool is easy.&lt;/p&gt;

&lt;p&gt;Getting it &lt;strong&gt;discovered&lt;/strong&gt; is not.&lt;/p&gt;

&lt;p&gt;Product Hunt spikes fade fast. Social posts disappear in hours.&lt;br&gt;&lt;br&gt;
And most indie makers eventually realize the same thing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Discovery is not about launching louder.&lt;br&gt;&lt;br&gt;
It’s about being findable &lt;em&gt;after&lt;/em&gt; the launch.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I learned this the hard way while building an AI tools directory.&lt;/p&gt;




&lt;h2&gt;
  
  
  What actually drives long-term discovery
&lt;/h2&gt;

&lt;p&gt;After reviewing hundreds of AI tools and submissions, a few patterns became very clear.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Search intent beats launch traffic
&lt;/h3&gt;

&lt;p&gt;Most users don’t search for your product name.&lt;br&gt;&lt;br&gt;
They search for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“AI writing tools”&lt;/li&gt;
&lt;li&gt;“best AI tools for developers”&lt;/li&gt;
&lt;li&gt;“AI directories”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your product doesn’t live on pages that rank for these intents, it becomes invisible very quickly.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Directories still work — if they’re curated
&lt;/h3&gt;

&lt;p&gt;Not all directories help.&lt;/p&gt;

&lt;p&gt;The ones that &lt;em&gt;do&lt;/em&gt; share common traits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear categories&lt;/li&gt;
&lt;li&gt;Real descriptions (not auto-generated spam)&lt;/li&gt;
&lt;li&gt;Clean outbound links&lt;/li&gt;
&lt;li&gt;Pages that are indexed and updated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Search engines still treat these as &lt;strong&gt;trust signals&lt;/strong&gt;, not shortcuts.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Backlinks matter less than context
&lt;/h3&gt;

&lt;p&gt;A single backlink from a relevant, well-structured page often beats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10 random links&lt;/li&gt;
&lt;li&gt;1 viral tweet&lt;/li&gt;
&lt;li&gt;1 short-term launch spike&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Contextual placement + relevance &amp;gt; raw volume.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I built an AI tools directory
&lt;/h2&gt;

&lt;p&gt;I didn’t build it to “collect links”.&lt;/p&gt;

&lt;p&gt;I built it to answer a simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Where do founders actually go &lt;em&gt;after&lt;/em&gt; they launch?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer was obvious:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They submit to directories&lt;/li&gt;
&lt;li&gt;They compare tools&lt;/li&gt;
&lt;li&gt;They look for places users already trust&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s how &lt;strong&gt;Aidirs&lt;/strong&gt; was born — a curated place for AI tools, directories, and rankings.&lt;/p&gt;

&lt;p&gt;No growth hacks.&lt;br&gt;&lt;br&gt;
Just structured discovery.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;Most AI products don’t fail because they’re bad.&lt;/p&gt;

&lt;p&gt;They fail because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They disappear after launch&lt;/li&gt;
&lt;li&gt;They aren’t searchable&lt;/li&gt;
&lt;li&gt;They aren’t listed where users look&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Discovery is not a moment.&lt;br&gt;
It’s infrastructure.&lt;/p&gt;

&lt;p&gt;I document these patterns while running a curated AI tools directory: &lt;a href="https://aidirs.best" rel="noopener noreferrer"&gt;https://aidirs.best&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you’re building an AI product, think less about the launch day — and more about where your product will still be visible six months later.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>sass</category>
      <category>indiehacking</category>
      <category>seo</category>
    </item>
    <item>
      <title>From Frustration to Fix: How Aluo is Helping Sellers Design Smarter</title>
      <dc:creator>Ivanvolt</dc:creator>
      <pubDate>Wed, 06 Aug 2025 08:52:10 +0000</pubDate>
      <link>https://dev.to/ivanvolt815/from-frustration-to-fix-how-aluo-is-helping-sellers-design-smarter-2lj3</link>
      <guid>https://dev.to/ivanvolt815/from-frustration-to-fix-how-aluo-is-helping-sellers-design-smarter-2lj3</guid>
      <description>&lt;p&gt;To be honest, when I started working on Aluo.ai, there wasn’t some grand plan. I just got really tired of how frustrating product photos were in e-commerce.&lt;/p&gt;

&lt;p&gt;Every seller wants the same thing — images that look clean, professional, and high-converting. But getting there usually means hiring a designer, endlessly tweaking PSD files, or wrestling with some clunky template editor that only gives you so much control.&lt;/p&gt;

&lt;p&gt;As a developer, I kept thinking: can’t AI just handle all this by now?&lt;/p&gt;

&lt;p&gt;That’s how Aluo started.&lt;/p&gt;

&lt;p&gt;You can think of it as a product image generator — but it’s more than that. Upload your product photo (even if it has a messy background), and the system automatically removes the background. Then, with one click, you can apply clean, realistic scene templates: wood surfaces, studio lighting, soft gradients, lifestyle setups, and more.&lt;/p&gt;

&lt;p&gt;The best part? You can still make changes. Move things around, tweak colors, add shadows, or just type your edits in plain English and let AI handle the rest.&lt;/p&gt;

&lt;p&gt;It’s not meant to replace designers. It’s meant to take away the boring parts — the resizing, masking, recoloring, re-exporting — and let you focus on the vision.&lt;/p&gt;

&lt;p&gt;I know there are tools out there that claim to do this. I’ve tried most of them. But I found them either too rigid, too slow, or just… not convincing. Aluo aims to live in that sweet spot: easy enough for non-designers, but good enough that you’d actually want to use the images.&lt;/p&gt;

&lt;p&gt;We’ve worked hard on the small things: image clarity, fast rendering, high resolution, soft lighting, realistic reflections, clean cutouts — things that make your product feel real. We also support global sellers, multiple languages, and different style presets depending on the platform you’re selling on.&lt;/p&gt;

&lt;p&gt;And yes — it’s already helping real sellers. Cross-border teams, Amazon folks, Shopify brands — they’re saving hours every week and finally getting marketing visuals that don’t look like generic catalog shots.&lt;/p&gt;

&lt;p&gt;In future posts, I’ll be sharing more behind-the-scenes — how we made certain tech choices, what surprised us about user behavior, and how I’m building Aluo using modern AI coding tools without even touching much code myself these days.&lt;/p&gt;

&lt;p&gt;There’s no magic formula. Just trial, error, and a lot of stubborn optimism. If you’re building something for the ecommerce space, or if you just want to create faster without compromising quality — maybe give Aluo a try.&lt;/p&gt;

&lt;p&gt;No pressure. Just a quiet tool built by someone who got tired of Photoshop.&lt;/p&gt;

&lt;p&gt;— Ivan, creator of &lt;a href="https://aluo.ai" rel="noopener noreferrer"&gt;Aluo.ai&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
  </channel>
</rss>
