<?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: PopNow</title>
    <description>The latest articles on DEV Community by PopNow (@popnow).</description>
    <link>https://dev.to/popnow</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%2F4198501%2F1f505864-f127-483e-91da-3a9a6366d967.png</url>
      <title>DEV Community: PopNow</title>
      <link>https://dev.to/popnow</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/popnow"/>
    <language>en</language>
    <item>
      <title>Generate AI images, videos and music from Claude Code, Cursor or VS Code with one remote MCP server</title>
      <dc:creator>PopNow</dc:creator>
      <pubDate>Thu, 05 Nov 2026 10:38:45 +0000</pubDate>
      <link>https://dev.to/popnow/generate-ai-images-videos-and-music-from-claude-code-cursor-or-vs-code-with-one-remote-mcp-server-309i</link>
      <guid>https://dev.to/popnow/generate-ai-images-videos-and-music-from-claude-code-cursor-or-vs-code-with-one-remote-mcp-server-309i</guid>
      <description>&lt;p&gt;Coding agents are good at writing the landing page. They're bad at the parts that aren't code: the hero image, the 5-second product loop, the background track for the demo video. Usually you leave the editor, open three different generator sites, download files, and drag them back into &lt;code&gt;public/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This tutorial connects one remote MCP server to your agent so it can make those assets itself, inside the same session. I'll use &lt;a href="https://popnow.ai" rel="noopener noreferrer"&gt;PopNow&lt;/a&gt;'s hosted server because it covers image, video, music and speech behind one sign-in, but most of what follows (OAuth discovery, quote-then-confirm, async jobs) applies to any paid, hosted MCP server.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclosure: I work on PopNow. Everything below works with a normal PopNow account; prices are in the table near the end with sources.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What you'll have at the end
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A remote MCP server connected to Claude Code, Cursor or VS Code (no local process, no API key in a config file)&lt;/li&gt;
&lt;li&gt;An agent that can call &lt;code&gt;create_image&lt;/code&gt;, &lt;code&gt;create_video&lt;/code&gt;, &lt;code&gt;create_music&lt;/code&gt; and &lt;code&gt;create_speech&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A small workflow: "build a landing page and make its hero video" in one prompt&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. Connect the server
&lt;/h2&gt;

&lt;p&gt;The server URL is &lt;code&gt;https://api.popnow.ai/mcp&lt;/code&gt;. It speaks &lt;strong&gt;Streamable HTTP&lt;/strong&gt; and signs you in with &lt;strong&gt;OAuth 2.1&lt;/strong&gt;, so there is nothing to install.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Code&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http popnow https://api.popnow.ai/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;code&gt;/mcp&lt;/code&gt; inside Claude Code, pick &lt;code&gt;popnow&lt;/code&gt; and authenticate. A browser tab opens, you sign in and press &lt;strong&gt;Allow&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cursor&lt;/strong&gt; (&lt;code&gt;.cursor/mcp.json&lt;/code&gt; in the project, or &lt;code&gt;~/.cursor/mcp.json&lt;/code&gt; globally)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"popnow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.popnow.ai/mcp"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;VS Code&lt;/strong&gt; (&lt;code&gt;.vscode/mcp.json&lt;/code&gt;; note the top-level key is &lt;code&gt;servers&lt;/code&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"popnow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.popnow.ai/mcp"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Claude on the web or desktop&lt;/strong&gt;: Customize → Connectors → Add custom connector, name it &lt;code&gt;PopNow&lt;/code&gt;, paste the URL, leave the advanced OAuth fields empty.&lt;/p&gt;

&lt;p&gt;Configs for Windsurf, Cline, Codex CLI and others are in the &lt;a href="https://github.com/popnow-ai/popnow-mcp" rel="noopener noreferrer"&gt;popnow-mcp repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What actually happens during sign-in
&lt;/h2&gt;

&lt;p&gt;If you've only used local stdio servers, remote OAuth can feel like magic. It isn't, and knowing the steps makes debugging much easier. You can replay them with &lt;code&gt;curl&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: the client calls the server without a token and gets a 401.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.popnow.ai/mcp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'content-type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'accept: application/json, text/event-stream'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'&lt;/span&gt;
&lt;span class="c"&gt;# HTTP/2 401&lt;/span&gt;
&lt;span class="c"&gt;# www-authenticate: Bearer resource_metadata="https://api.popnow.ai/.well-known/oauth-protected-resource/mcp", scope="generate read"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: it follows &lt;code&gt;resource_metadata&lt;/code&gt; from that header&lt;/strong&gt; to find out who issues tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://api.popnow.ai/.well-known/oauth-protected-resource/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.popnow.ai/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"authorization_servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"https://api.popnow.ai"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scopes_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"generate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"read"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bearer_methods_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"header"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: it reads the authorization server metadata:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://api.popnow.ai/.well-known/oauth-authorization-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parts that matter: &lt;code&gt;registration_endpoint&lt;/code&gt; (so the client can register itself, RFC 7591 dynamic client registration), &lt;code&gt;code_challenge_methods_supported: ["S256"]&lt;/code&gt; (PKCE is required) and &lt;code&gt;client_id_metadata_document_supported: true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: the client registers, opens the authorize page with a PKCE challenge, and swaps the code for a token.&lt;/strong&gt; From then on every MCP request carries &lt;code&gt;Authorization: Bearer ...&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's why you never paste a key: the client does all four steps on its own. If a client claims it "can't connect", check which step fails. A missing &lt;code&gt;www-authenticate&lt;/code&gt; header or a metadata URL that 404s is the usual culprit when you build your own server.&lt;/p&gt;

&lt;p&gt;The two scopes are worth copying in your own servers: &lt;code&gt;read&lt;/code&gt; (balance, history, model list) and &lt;code&gt;generate&lt;/code&gt; (spend money). A connection approved without &lt;code&gt;generate&lt;/code&gt; can look around but can't charge you.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. First generation
&lt;/h2&gt;

&lt;p&gt;Ask in plain words:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Make a 16:9 picture of a paper boat on a rainy window sill, soft morning light.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent calls &lt;code&gt;create_image&lt;/code&gt;. Images return inline as a preview plus a download link, and every result also gets a permanent &lt;code&gt;popnow://&lt;/code&gt; link you can feed to the next tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Now turn that picture into a 5-second video with a slow push-in.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's &lt;code&gt;create_video&lt;/code&gt; with &lt;code&gt;image_url&lt;/code&gt; set to the &lt;code&gt;popnow://&lt;/code&gt; link from the previous step. No downloading and re-uploading.&lt;/p&gt;

&lt;p&gt;Useful read-only tools while you explore:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list_models&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every image, video and audio model with its price (&lt;code&gt;category&lt;/code&gt;: &lt;code&gt;image&lt;/code&gt;, &lt;code&gt;video&lt;/code&gt;, &lt;code&gt;audio&lt;/code&gt;, &lt;code&gt;effects&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;check_balance&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Credits left, and how many are held for running jobs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list_my_creations&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Recent results with links&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_job&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Collect a result that's still rendering&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  4. The two patterns that make paid tools safe for agents
&lt;/h2&gt;

&lt;p&gt;Giving an autonomous agent a tool that spends money is the part people worry about. Two patterns handle it, and they're worth stealing for any MCP server that costs money.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quote first, confirm above a threshold
&lt;/h3&gt;

&lt;p&gt;Every generation tool takes an optional &lt;code&gt;max_credits&lt;/code&gt;. Small jobs (up to 300 credits, i.e. $0.30) start right away. Anything bigger doesn't start: the tool answers with the price and your balance instead.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You:        Make a 9:16 video of a vintage car on a coast road at golden hour, 10 seconds.
Assistant:  (create_video → "Needs confirmation: costs 370 credits, balance 8450")
            That's 370 credits. Go ahead?
You:        Yes.
Assistant:  (create_video again with max_credits: 370 → job_id)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The confirmation is enforced by the server, not by the model being polite. If the price moved between the quote and the retry, nothing is charged and you get the new price. Failed jobs are refunded automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Async jobs instead of long-held requests
&lt;/h3&gt;

&lt;p&gt;Most videos take one to three minutes, much longer than you want an HTTP request to hang. The tool waits up to about 40 seconds; if the result isn't ready it returns a &lt;code&gt;job_id&lt;/code&gt;, and the agent calls &lt;code&gt;get_job&lt;/code&gt; (which can wait up to 50 seconds per call) until it's done.&lt;/p&gt;

&lt;p&gt;One more detail that saves money: identical calls from the same account within ten seconds become one job, so an agent that retries after a timeout doesn't pay twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. A real workflow: landing page plus hero video
&lt;/h2&gt;

&lt;p&gt;Here's the prompt I use in Claude Code when scaffolding a marketing page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build a single-page landing page for "Driftwood", a note-taking app, in Next.js.
For the hero:
1. Use PopNow create_image to make a 16:9 still of a calm desk by a window, notebook open, morning light.
2. Turn it into a 5-second looping video with create_video (slow push-in, no text).
3. Download both into public/hero/ and use the image as the &amp;lt;video&amp;gt; poster.
4. Make a 30-second calm instrumental with create_music for the demo page and save it to public/audio/.
Show me the price before anything over 300 credits.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What to expect on the default models:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;create_image&lt;/code&gt;: about 48 credits, returned inline&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;create_video&lt;/code&gt; with the &lt;code&gt;popnow://&lt;/code&gt; link from step 1: about 185 credits for 5 seconds, returns a &lt;code&gt;job_id&lt;/code&gt; that the agent polls with &lt;code&gt;get_job&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;create_music&lt;/code&gt; with &lt;code&gt;instrumental: true&lt;/code&gt;: about 72 credits&lt;/li&gt;
&lt;li&gt;The agent writes the files into &lt;code&gt;public/&lt;/code&gt; and wires up a &lt;code&gt;&amp;lt;video autoplay muted loop playsinline poster=...&amp;gt;&lt;/code&gt; hero&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Total: about 305 credits, roughly $0.31, without leaving the editor. Each step is under the 300-credit threshold, so nothing pauses for confirmation unless you ask for a longer or higher-resolution clip.&lt;/p&gt;

&lt;p&gt;Tip: tell the agent which model to use when it matters ("use Seedance 2.5 for the video", "use Nano Banana for the still"). Every generation tool takes a &lt;code&gt;model&lt;/code&gt; id from &lt;code&gt;list_models&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. What it costs, compared with going direct
&lt;/h2&gt;

&lt;p&gt;The same model costs different amounts on different platforms. Here's one model at one setting, from the &lt;a href="https://popnow.ai/ai-price-index" rel="noopener noreferrer"&gt;AI Video &amp;amp; Image Price Index&lt;/a&gt; (November 2026, public self-serve list prices, every row links to its source):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seedance 2.5, text-to-video, 720p with audio, one 5-second clip&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PopNow (pay-as-you-go)&lt;/td&gt;
&lt;td&gt;$1.39&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runway API&lt;/td&gt;
&lt;td&gt;$1.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Higgsfield API&lt;/td&gt;
&lt;td&gt;$2.31&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fal&lt;/td&gt;
&lt;td&gt;$2.37&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Prices change monthly; the &lt;a href="https://popnow.ai/ai-price-index" rel="noopener noreferrer"&gt;index page&lt;/a&gt; has the full table, plus a &lt;a href="https://popnow.ai/price-index/2026-11.csv" rel="noopener noreferrer"&gt;CSV&lt;/a&gt; and &lt;a href="https://popnow.ai/price-index/2026-11.json" rel="noopener noreferrer"&gt;JSON&lt;/a&gt; under CC BY 4.0 if you want to build your own comparison. PopNow credits are $0.001 each with no subscription; bigger packs add bonus credits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"Please connect your PopNow account first."&lt;/td&gt;
&lt;td&gt;The tool ran before sign-in finished. Reconnect from &lt;code&gt;/mcp&lt;/code&gt; (Claude Code) or the client's MCP settings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"This connection wasn't given permission to do that."&lt;/td&gt;
&lt;td&gt;You approved without the &lt;code&gt;generate&lt;/code&gt; scope. Disconnect, reconnect, press &lt;strong&gt;Allow&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;405 Method Not Allowed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The server is stateless: &lt;code&gt;POST&lt;/code&gt; only. Any MCP client does this for you; only hand-rolled clients hit it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cline connects but no tools work&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;"type": "streamableHttp"&lt;/code&gt;, otherwise Cline falls back to legacy SSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Too many creations at once"&lt;/td&gt;
&lt;td&gt;More than 20 new jobs in a minute. Nothing was charged; wait the seconds in the message&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Remote MCP + OAuth means no keys in dotfiles and no local process to keep alive.&lt;/li&gt;
&lt;li&gt;For any tool that spends money, put the confirmation in the server (quote, threshold, &lt;code&gt;max_credits&lt;/code&gt;), not in the prompt.&lt;/li&gt;
&lt;li&gt;Return job ids for anything slow, and de-duplicate retries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Setup docs for every client: &lt;a href="https://popnow.ai/mcp" rel="noopener noreferrer"&gt;popnow.ai/mcp&lt;/a&gt;. Source for the configs: &lt;a href="https://github.com/popnow-ai/popnow-mcp" rel="noopener noreferrer"&gt;github.com/popnow-ai/popnow-mcp&lt;/a&gt;. If you build something with it, I'd like to see it in the comments.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>claude</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>What AI video and image generation really costs: same model on PopNow, Higgsfield, Runway and fal</title>
      <dc:creator>PopNow</dc:creator>
      <pubDate>Wed, 04 Nov 2026 12:57:51 +0000</pubDate>
      <link>https://dev.to/popnow/what-ai-video-and-image-generation-really-costs-same-model-on-popnow-higgsfield-runway-and-fal-6do</link>
      <guid>https://dev.to/popnow/what-ai-video-and-image-generation-really-costs-same-model-on-popnow-higgsfield-runway-and-fal-6do</guid>
      <description>&lt;p&gt;If you generate AI video regularly, the bill adds up fast, and the same model can cost very different amounts depending on where you run it. I checked the public prices for the same models at the same settings on four platforms: Higgsfield, Runway and fal, which many creators and developers use, and PopNow.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure up front: I work on PopNow, so read the numbers with that in mind. Every competitor price below comes from that platform's own public pricing page, and you can check each one yourself.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How the comparison works
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;One row is one generation: a 5-second clip for video models, one image for image models, same model, same resolution, same audio setting.&lt;/li&gt;
&lt;li&gt;Only public, self-serve prices count. Runway and fal use their public API prices. For Higgsfield I take the higher of its API list price and its cheapest monthly plan that includes the model.&lt;/li&gt;
&lt;li&gt;PopNow is priced with its biggest-bonus credit pack: the Pro pack, $100 for 112,000 credits, which includes 12,000 bonus credits.&lt;/li&gt;
&lt;li&gt;Prices were checked on October 31, 2026. They change often, so confirm on each site before you buy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Video: one 5-second clip
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj7w5agbbq7d55896vokr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj7w5agbbq7d55896vokr.png" alt="Price of one 5-second AI video clip on PopNow, Higgsfield, Runway and fal" width="800" height="632"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model · setting&lt;/th&gt;
&lt;th&gt;PopNow&lt;/th&gt;
&lt;th&gt;Higgsfield&lt;/th&gt;
&lt;th&gt;Runway&lt;/th&gt;
&lt;th&gt;fal&lt;/th&gt;
&lt;th&gt;Saving up to&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MiniMax H3 (Hailuo) 2K&lt;/td&gt;
&lt;td&gt;$0.26&lt;/td&gt;
&lt;td&gt;$0.65&lt;/td&gt;
&lt;td&gt;$0.75&lt;/td&gt;
&lt;td&gt;$0.65&lt;/td&gt;
&lt;td&gt;65%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MiniMax H3 768p&lt;/td&gt;
&lt;td&gt;$0.17&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;$0.50&lt;/td&gt;
&lt;td&gt;$0.30&lt;/td&gt;
&lt;td&gt;67%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seedance 2.0 Mini 480p&lt;/td&gt;
&lt;td&gt;$0.11&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;$0.35&lt;/td&gt;
&lt;td&gt;$0.36&lt;/td&gt;
&lt;td&gt;69%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seedance 2.0 Fast 720p&lt;/td&gt;
&lt;td&gt;$0.43&lt;/td&gt;
&lt;td&gt;$0.59&lt;/td&gt;
&lt;td&gt;$1.20&lt;/td&gt;
&lt;td&gt;$1.21&lt;/td&gt;
&lt;td&gt;64%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seedance 2.0 720p&lt;/td&gt;
&lt;td&gt;$0.71&lt;/td&gt;
&lt;td&gt;$1.51&lt;/td&gt;
&lt;td&gt;$1.50&lt;/td&gt;
&lt;td&gt;$1.52&lt;/td&gt;
&lt;td&gt;53%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seedance 2.0 1080p&lt;/td&gt;
&lt;td&gt;$1.74&lt;/td&gt;
&lt;td&gt;$3.40&lt;/td&gt;
&lt;td&gt;$2.00&lt;/td&gt;
&lt;td&gt;$3.41&lt;/td&gt;
&lt;td&gt;49%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seedance 2.5 720p&lt;/td&gt;
&lt;td&gt;$1.24&lt;/td&gt;
&lt;td&gt;$2.31&lt;/td&gt;
&lt;td&gt;$1.50&lt;/td&gt;
&lt;td&gt;$2.36&lt;/td&gt;
&lt;td&gt;47%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seedance 2.5 1080p&lt;/td&gt;
&lt;td&gt;$3.05&lt;/td&gt;
&lt;td&gt;$5.69&lt;/td&gt;
&lt;td&gt;$3.40&lt;/td&gt;
&lt;td&gt;$5.82&lt;/td&gt;
&lt;td&gt;47%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kling 3.0 1080p&lt;/td&gt;
&lt;td&gt;$0.60&lt;/td&gt;
&lt;td&gt;$0.84&lt;/td&gt;
&lt;td&gt;$0.85&lt;/td&gt;
&lt;td&gt;$0.84&lt;/td&gt;
&lt;td&gt;29%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"—" means the platform does not offer that model or setting.&lt;/p&gt;

&lt;p&gt;What stands out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MiniMax H3&lt;/strong&gt; has the widest gap: a 2K five-second clip is $0.26 on PopNow and $0.65 or more elsewhere, so the same budget buys about 2.5 times as many clips.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seedance 2.0&lt;/strong&gt; costs about $1.50 per 720p clip on the other three platforms; PopNow is under half that. &lt;strong&gt;Seedance 2.5&lt;/strong&gt; is over 40% below Higgsfield and fal.&lt;/li&gt;
&lt;li&gt;Not every model is half price. Runway is relatively close on Seedance 2.5 (10 to 20% apart) and &lt;strong&gt;Kling 3.0&lt;/strong&gt; is 29% apart, so check the models you actually use.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Images: one image
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwuim7i1xuqslewehy6mw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwuim7i1xuqslewehy6mw.png" alt="Price of one AI image on PopNow, Higgsfield, Runway and fal" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model · setting&lt;/th&gt;
&lt;th&gt;PopNow&lt;/th&gt;
&lt;th&gt;Higgsfield&lt;/th&gt;
&lt;th&gt;Runway&lt;/th&gt;
&lt;th&gt;fal&lt;/th&gt;
&lt;th&gt;Saving up to&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Nano Banana 2 · 1K&lt;/td&gt;
&lt;td&gt;$0.048&lt;/td&gt;
&lt;td&gt;$0.098&lt;/td&gt;
&lt;td&gt;$0.070&lt;/td&gt;
&lt;td&gt;$0.080&lt;/td&gt;
&lt;td&gt;51%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nano Banana Pro · 4K&lt;/td&gt;
&lt;td&gt;$0.17&lt;/td&gt;
&lt;td&gt;$0.20&lt;/td&gt;
&lt;td&gt;$0.40&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;57%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seedream 5.0 Pro · 1K&lt;/td&gt;
&lt;td&gt;$0.032&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;$0.050&lt;/td&gt;
&lt;td&gt;$0.068&lt;/td&gt;
&lt;td&gt;52%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seedream 5.0 Pro · 2K&lt;/td&gt;
&lt;td&gt;$0.064&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;$0.090&lt;/td&gt;
&lt;td&gt;$0.14&lt;/td&gt;
&lt;td&gt;52%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT Image 2.5 · 4K medium&lt;/td&gt;
&lt;td&gt;$0.032&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;$0.050&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;35%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few cents per image sounds trivial until you batch hundreds of product shots or thumbnails.&lt;/p&gt;

&lt;h2&gt;
  
  
  A monthly example
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2hhzx056vn0q830rsxhe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2hhzx056vn0q830rsxhe.png" alt="Monthly bill for 30 videos and 300 images on Higgsfield, fal and PopNow" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Say you make 30 Seedance 2.0 720p clips (5 seconds each) and 300 Nano Banana 2 images at 1K every month:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fal: 30 × $1.52 + 300 × $0.080 = &lt;strong&gt;$69.60 a month&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Higgsfield: 30 × $1.51 + 300 × $0.098 = &lt;strong&gt;$74.70 a month&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;PopNow: 30 × $0.71 + 300 × $0.048 = &lt;strong&gt;$35.70 a month&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is roughly $400 a year compared with fal. PopNow also sells one-time credit packs with no subscription, and credits do not expire, so a quiet month costs nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Price protection
&lt;/h2&gt;

&lt;p&gt;PopNow calls itself the first AI platform with price protection: if you find the same generation cheaper on Higgsfield, Runway or fal, it pays you twice the difference. The terms are specific, and worth reading before you count on it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only those three platforms, and only their standard public self-serve prices. Promotions, coupon codes, subscription bonus credits and enterprise rates do not count.&lt;/li&gt;
&lt;li&gt;Same model, same version and same settings (resolution, duration, audio).&lt;/li&gt;
&lt;li&gt;Claim within 7 days with your task ID, the other platform's pricing page and a dated snapshot. Every claim is checked by a person.&lt;/li&gt;
&lt;li&gt;Paid as bonus credits (or API balance for API generations) valid for 90 days, up to $20 a month and no more than 10% of what you paid that month. Only successful generations paid with purchased credits count.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Same model, same setting: up to &lt;strong&gt;69%&lt;/strong&gt; less on PopNow in this snapshot. The biggest gaps are on MiniMax H3 and Seedance 2.0, often half or more.&lt;/li&gt;
&lt;li&gt;Seedance 2.5 and Kling 3.0 users save less; check your own tier.&lt;/li&gt;
&lt;li&gt;The live table with every source link is at &lt;a href="https://popnow.ai/compare" rel="noopener noreferrer"&gt;popnow.ai/compare&lt;/a&gt;, and the data is on Hugging Face as &lt;a href="https://huggingface.co/datasets/popnow/ai-model-prices" rel="noopener noreferrer"&gt;popnow/ai-model-prices&lt;/a&gt; if you want to run your own numbers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I work on PopNow. Competitor prices are their own published prices; please verify them yourself.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>video</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Nano Banana 2.1 vs GPT Image 2.5 vs Seedream 5.0: one prompt, first try, four tests</title>
      <dc:creator>PopNow</dc:creator>
      <pubDate>Wed, 04 Nov 2026 12:57:35 +0000</pubDate>
      <link>https://dev.to/popnow/nano-banana-21-vs-gpt-image-25-vs-seedream-50-one-prompt-first-try-four-tests-3j9g</link>
      <guid>https://dev.to/popnow/nano-banana-21-vs-gpt-image-25-vs-seedream-50-one-prompt-first-try-four-tests-3j9g</guid>
      <description>&lt;p&gt;Three of the most talked-about image models shipped new versions this autumn: Google's &lt;strong&gt;Nano Banana 2.1&lt;/strong&gt;, OpenAI's &lt;strong&gt;GPT Image 2.5&lt;/strong&gt; and ByteDance's &lt;strong&gt;Seedream 5.0&lt;/strong&gt;. Most comparisons use English prompts and pick the best of several tries. I wanted something closer to everyday use, so I ran the same prompt once on each model and kept the first result, including two prompts that ask for Chinese text inside the image, which is where models still slip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Same prompt for all three, portrait 3:4, 1K resolution, one generation each, no retries, no editing.&lt;/li&gt;
&lt;li&gt;All three run with the same parameters on one platform, so default settings do not differ between sites.&lt;/li&gt;
&lt;li&gt;Price per image at these settings: Nano Banana 2.1 about $0.048, GPT Image 2.5 (Sunburst, high quality) about $0.031, Seedream 5.0 Pro about $0.036.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In every comparison image, left to right: &lt;strong&gt;Nano Banana 2.1 | GPT Image 2.5 | Seedream 5.0&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test 1: a poster with exact Chinese text
&lt;/h2&gt;

&lt;p&gt;Prompt (translated): a portrait grand-opening poster for a bubble tea shop, warm illustration style, big title "开业大吉" (Grand Opening), a line "全场第二杯半价" (second cup half price) and a small footer "11月8日 · 人民路88号" (Nov 8 · 88 Renmin Road), clean layout, no typos.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvj8viefz7gnud7hidad4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvj8viefz7gnud7hidad4.jpg" alt="Bubble tea grand-opening poster from Nano Banana 2.1, GPT Image 2.5 and Seedream 5.0" width="799" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nano Banana 2.1&lt;/strong&gt; produced a lovely poster but translated every line into English. It ignored the instruction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPT Image 2.5&lt;/strong&gt; rendered all three Chinese lines correctly and the layout looks like a real commercial poster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seedream 5.0&lt;/strong&gt; also got every character right, in a softer, cuter style, and invented a "Dear Tea" cup design.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Test 2: a hand-drawn infographic with Chinese labels
&lt;/h2&gt;

&lt;p&gt;Prompt (translated): a hand-drawn infographic titled "手冲咖啡四步法" (four steps to pour-over coffee), four steps left to right with sketches: grind (medium-fine), wet the filter, bloom 30 seconds, pour in stages, a short Chinese caption under each, beige paper background.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F49i4x3bl0qvr3tq5lfqx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F49i4x3bl0qvr3tq5lfqx.jpg" alt="Pour-over coffee infographic from the three models" width="799" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nano Banana 2.1&lt;/strong&gt; went bilingual with English first, in a 2×2 grid instead of left to right.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPT Image 2.5&lt;/strong&gt; gave the cleanest result: four numbered steps in a row, correct and readable Chinese captions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seedream 5.0&lt;/strong&gt; has the strongest hand-drawn feel with correct titles and steps; one small caption has a redundant annotation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Test 3: an e-commerce product shot
&lt;/h2&gt;

&lt;p&gt;Prompt: an amber glass perfume bottle on wet dark slate after rain, a few damp green leaves, soft side light, shallow depth of field, premium commercial photography.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyqgj081whesl4v64nb5f.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyqgj081whesl4v64nb5f.jpg" alt="Amber perfume bottle product shot from the three models" width="799" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All three are usable. &lt;strong&gt;Nano Banana 2.1&lt;/strong&gt; looks the most like a real photo (and made up a label). &lt;strong&gt;GPT Image 2.5&lt;/strong&gt; looks the most "luxury ad". &lt;strong&gt;Seedream 5.0&lt;/strong&gt; is clean and minimal. For your own product, use image-to-image with a real photo instead of asking a model to invent the bottle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test 4: imagination
&lt;/h2&gt;

&lt;p&gt;Prompt: a corgi in a white spacesuit sitting on the Moon eating Sichuan hot pot, red chili broth steaming, blue Earth in the distance, cinematic light.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faujukbmyf0lcsylxqngd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faujukbmyf0lcsylxqngd.jpg" alt="Corgi astronaut eating hot pot on the Moon from the three models" width="799" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nano Banana 2.1&lt;/strong&gt;: good composition, but the corgi is small and took its helmet off on the Moon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPT Image 2.5&lt;/strong&gt;: the most striking close-up, though it drew a NASA logo on the suit, which matters if you use it commercially.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seedream 5.0&lt;/strong&gt;: helmet on, the richest details, and the best grasp of "Sichuan hot pot".&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Nano Banana 2.1&lt;/th&gt;
&lt;th&gt;GPT Image 2.5&lt;/th&gt;
&lt;th&gt;Seedream 5.0&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Non-English text&lt;/td&gt;
&lt;td&gt;Weak, tends to switch to English&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Photorealism&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Following instructions&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 1K image&lt;/td&gt;
&lt;td&gt;~$0.048&lt;/td&gt;
&lt;td&gt;~$0.031&lt;/td&gt;
&lt;td&gt;~$0.036&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Posters, cards and diagrams with exact text, especially non-English: start with &lt;strong&gt;GPT Image 2.5&lt;/strong&gt;, then Seedream 5.0.&lt;/li&gt;
&lt;li&gt;Photoreal products and portraits: &lt;strong&gt;Nano Banana 2.1&lt;/strong&gt; is still excellent; just don't rely on it for Chinese text.&lt;/li&gt;
&lt;li&gt;Scenes with Chinese cultural context: &lt;strong&gt;Seedream 5.0&lt;/strong&gt; understood them best.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No model won everything. At two or three cents an image, running one prompt on all three and picking the best is faster than rewriting the prompt again and again. I ran these on &lt;a href="https://popnow.ai" rel="noopener noreferrer"&gt;PopNow&lt;/a&gt;, where the three models sit behind one account and the price shows before each run.&lt;/p&gt;

&lt;p&gt;All images are first generations with no edits, and the prompts are above if you want to reproduce them.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I work on PopNow.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>design</category>
      <category>productivity</category>
    </item>
    <item>
      <title>A URL is not an asset record: keep a manifest beside generated media</title>
      <dc:creator>PopNow</dc:creator>
      <pubDate>Wed, 28 Oct 2026 12:19:44 +0000</pubDate>
      <link>https://dev.to/popnow/a-url-is-not-an-asset-record-keep-a-manifest-beside-generated-media-5g8j</link>
      <guid>https://dev.to/popnow/a-url-is-not-an-asset-record-keep-a-manifest-beside-generated-media-5g8j</guid>
      <description>&lt;p&gt;A media-generation prototype can get surprisingly far with an array of output URLs. Then someone asks which reference image produced a clip, whether two results used the same settings, or why a saved link no longer opens. The array cannot answer those questions.&lt;/p&gt;

&lt;p&gt;A useful next step is a small asset manifest. Give the result an application-owned identity, and store its recipe separately from its current delivery address.&lt;/p&gt;

&lt;p&gt;This article proposes an application data model. The TypeScript is illustrative, not a provider SDK or a description of an existing production schema. This tutorial was drafted by an AI agent and reviewed for accuracy by PopNow Team before publication. The team confirmed that it reflects knowledge they are familiar with. No remote generation or benchmark was run for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate identity, recipe, and delivery
&lt;/h2&gt;

&lt;p&gt;These three concepts change for different reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Identity:&lt;/strong&gt; the asset your application and its users refer to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recipe:&lt;/strong&gt; the inputs and settings that explain one generation attempt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delivery:&lt;/strong&gt; where an output can currently be obtained.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A refreshed signed URL does not necessarily mean a new creative result. A changed prompt does mean a new recipe. The application should not infer either fact from string equality between URLs.&lt;/p&gt;

&lt;p&gt;Here is one possible TypeScript model:&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;type&lt;/span&gt; &lt;span class="nx"&gt;Recipe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Readonly&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;modelRef&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="nl"&gt;promptRef&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="nl"&gt;referenceAssetIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Readonly&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&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;type&lt;/span&gt; &lt;span class="nx"&gt;Delivery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;remote&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;url&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="nl"&gt;observedAt&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="nl"&gt;expiresAt&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="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stored&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;objectKey&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="nl"&gt;contentType&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="nl"&gt;checksum&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="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;MediaAsset&lt;/span&gt; &lt;span class="o"&gt;=&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;projectId&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="nl"&gt;recipeId&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="nl"&gt;providerTaskRef&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="nl"&gt;createdAt&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="nl"&gt;delivery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Delivery&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;These names belong to the application. An adapter translates a provider's actual response into this representation; the type definitions do not imply that any particular API returns these fields.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;promptRef&lt;/code&gt; is a reference to an access-controlled record, not a reason to copy prompts into analytics events. Similarly, a storage object key belongs in the manifest; public access can be decided separately when someone asks to view the asset.&lt;/p&gt;

&lt;h2&gt;
  
  
  Snapshot the recipe before starting work
&lt;/h2&gt;

&lt;p&gt;Suppose a user selects a reference image and adjusts the duration. Save that approved configuration as a recipe before submitting the generation request. Associate the returned task reference with that attempt when it becomes available.&lt;/p&gt;

&lt;p&gt;Do not let a later edit mutate the historical recipe. If a user revises the prompt, create another recipe and associate the next attempt with it. Otherwise an old clip will appear to have been generated from today's settings.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Readonly&lt;/code&gt; types above help express intent to TypeScript callers. They do not freeze objects at runtime or enforce database immutability. A production implementation still needs its own write rules, versioning strategy, and authorization checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep delivery updates narrow
&lt;/h2&gt;

&lt;p&gt;When a provider supplies a replacement delivery link for an existing result, the adapter may update the delivery information associated with that asset. It should not overwrite the recipe or invent a new creative revision merely because the address changed.&lt;/p&gt;

&lt;p&gt;Conversely, do not assume that any two URLs pointing to apparently similar images identify the same output. Deduplication needs evidence appropriate to the application. A provider's stable output identifier can be useful; a checksum of downloaded bytes answers a narrower question about byte identity.&lt;/p&gt;

&lt;p&gt;An expiry field should be filled only when its meaning is documented or otherwise known. An absent &lt;code&gt;expiresAt&lt;/code&gt; means "unknown in this record," not "available forever." Decide what the UI should do when a remote link is unavailable, instead of hiding that state behind a permanent thumbnail.&lt;/p&gt;

&lt;p&gt;If your application copies an output into its own storage, record that completed storage operation separately. A task reaching a completed state is not proof that your archive copy succeeded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build comparisons from the manifest
&lt;/h2&gt;

&lt;p&gt;A comparison view can now show a thumbnail with its model reference, recipe, and selected settings. It can keep a selected result tied to the exact attempt a reviewer approved.&lt;/p&gt;

&lt;p&gt;For a fictional product-photo comparison, a reviewer might record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;asset-101 -&amp;gt; recipe-31 -&amp;gt; keep
asset-102 -&amp;gt; recipe-32 -&amp;gt; revise: label changed
asset-103 -&amp;gt; recipe-33 -&amp;gt; reject: wrong object shape
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The review belongs to the asset and its recorded recipe. Replacing a thumbnail in a grid should not silently move that decision to a different result.&lt;/p&gt;

&lt;p&gt;This also makes exports clearer. A project can export selected assets together with a manifest that explains their provenance, without pretending that the manifest itself grants rights to the input references or generated content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the boundaries with local fixtures
&lt;/h2&gt;

&lt;p&gt;You do not need a paid generation call to exercise this data model. Construct fictional provider responses and check that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Updating a delivery link preserves the asset and recipe identities.&lt;/li&gt;
&lt;li&gt;A deliberate input revision creates a new recipe.&lt;/li&gt;
&lt;li&gt;Unknown expiry remains unknown.&lt;/li&gt;
&lt;li&gt;A failed archive copy does not mark the output as stored.&lt;/li&gt;
&lt;li&gt;A project cannot resolve another project's private recipe through an asset reference.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are suggested application tests, not claims that a remote service has passed them. The point is to make the relationships explicit enough that a test can describe the behavior you intend.&lt;/p&gt;

&lt;p&gt;An asset manifest is a modest addition to a prototype. Its value is that "which result is this?", "how was it requested?", and "where can I retrieve it?" become separate questions with separate answers.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
      <category>typescript</category>
    </item>
    <item>
      <title>How to Generate AI Videos from Code with One API (Node.js + Python Examples)</title>
      <dc:creator>PopNow</dc:creator>
      <pubDate>Wed, 21 Oct 2026 13:51:50 +0000</pubDate>
      <link>https://dev.to/popnow/how-to-generate-ai-videos-from-code-with-one-api-nodejs-python-examples-34je</link>
      <guid>https://dev.to/popnow/how-to-generate-ai-videos-from-code-with-one-api-nodejs-python-examples-34je</guid>
      <description>&lt;p&gt;Generating an image from code is usually one request: you send a prompt and get a picture back a few seconds later. Video is different. A five-second clip can take minutes to render, so a single HTTP call that blocks until the file is ready is the wrong shape. What you need is a small job flow: submit the work, track it, collect the result.&lt;/p&gt;

&lt;p&gt;This tutorial builds that flow end to end with plain &lt;code&gt;fetch&lt;/code&gt; in Node.js and &lt;code&gt;requests&lt;/code&gt; in Python. The examples use the &lt;a href="https://popnow.ai" rel="noopener noreferrer"&gt;PopNow&lt;/a&gt; API, which puts many video models (Wan, MiniMax, Kling, Seedance, Vidu and others) behind one request format, so switching models is a one-line change. The pattern itself — create a task, poll or receive a webhook, download before the link expires — carries over to most async generation APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll build
&lt;/h2&gt;

&lt;p&gt;A script that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Asks for the price of a clip before running it.&lt;/li&gt;
&lt;li&gt;Creates a text-to-video task.&lt;/li&gt;
&lt;li&gt;Polls until the task is finished.&lt;/li&gt;
&lt;li&gt;Downloads the MP4 to disk.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After that we'll animate an image instead, switch to a second model, and look at what changes when you move from a script to production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 18+ or Python 3 with &lt;code&gt;requests&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A PopNow account and an API key. Keys are created in the API console and start with &lt;code&gt;sk-popnow-&lt;/code&gt;. The API spends the same credits as the website; there is no separate API plan and no subscription. You buy a one-time credit pack (packs start at $9) and pay as you go. One credit is $0.001.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;POPNOW_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sk-popnow-..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the key on your server. It spends real credits, so it should never end up in a browser bundle or a mobile app.&lt;/p&gt;

&lt;h2&gt;
  
  
  The request shape
&lt;/h2&gt;

&lt;p&gt;Every generation — image, video or audio — goes through the same endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST https://api.popnow.ai/api/v1/jobs/createTask
Authorization: Bearer $POPNOW_API_KEY
Idempotency-Key: &amp;lt;unique id for this job&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The body has two required fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alibaba/wan-3.0/text-to-video"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A paper boat drifts down a rain-soaked street at dusk, neon signs reflecting in the water, slow tracking shot at ground level"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"resolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"480p"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"duration_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"aspect_ratio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"16:9"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;model&lt;/code&gt;&lt;/strong&gt; picks both the model and the job: &lt;code&gt;text-to-video&lt;/code&gt;, &lt;code&gt;image-to-video&lt;/code&gt;, &lt;code&gt;video-extend&lt;/code&gt; and so on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;input&lt;/code&gt;&lt;/strong&gt; is validated against that model's own schema. Unknown fields are rejected, so a typo like &lt;code&gt;duraton_seconds&lt;/code&gt; fails immediately instead of being silently ignored.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Idempotency-Key&lt;/code&gt;&lt;/strong&gt; makes retries safe. If a request times out and you resend it with the same key, you get the original task back instead of a second charge.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every JSON response uses the same envelope: &lt;code&gt;{ "code", "msg", "data", "request_id" }&lt;/code&gt;. Note that &lt;code&gt;code&lt;/code&gt; is a business code, not the HTTP status. &lt;code&gt;200&lt;/code&gt; means success; anything else is an error. Branch on &lt;code&gt;code&lt;/code&gt;, never on the wording of &lt;code&gt;msg&lt;/code&gt;, and log &lt;code&gt;request_id&lt;/code&gt; so you can quote it if you need support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Ask for the price first
&lt;/h2&gt;

&lt;p&gt;Video is priced per second of output, and the rate depends on resolution. For Wan 3.0 text-to-video it is 45 credits per second at 480p, 90 at 720p and 180 at 1080p. Instead of hard-coding a price table, ask the API. &lt;code&gt;POST /jobs/quote&lt;/code&gt; takes exactly the body you would send to &lt;code&gt;createTask&lt;/code&gt; and returns the price without creating a task or holding any credits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.popnow.ai/api/v1/jobs/quote"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$POPNOW_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"model":"alibaba/wan-3.0/text-to-video","input":{"prompt":"A paper boat drifts down a rain-soaked street","resolution":"480p","duration_seconds":5}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response carries &lt;code&gt;maxCredits&lt;/code&gt; (the most the request can cost, which is the amount that will be held from your balance) and &lt;code&gt;expectedCredits&lt;/code&gt; (what it usually costs). Five seconds at 480p is 5 × 45 = 225 credits, or $0.225.&lt;/p&gt;

&lt;p&gt;If you show that price to an end user and want it to stick, pass the &lt;code&gt;quoteId&lt;/code&gt; and the &lt;code&gt;maxCredits&lt;/code&gt; you showed (as &lt;code&gt;expectedCredits&lt;/code&gt;) in &lt;code&gt;createTask&lt;/code&gt; within five minutes. If anything changed in between, the API answers &lt;code&gt;40901&lt;/code&gt; and holds nothing, so you can quote again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Create, poll and download in Node.js
&lt;/h2&gt;

&lt;p&gt;Save this as &lt;code&gt;video.mjs&lt;/code&gt; and run it with &lt;code&gt;node video.mjs&lt;/code&gt;. It uses only built-in modules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// video.mjs: text-to-video, end to end&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;randomUUID&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;writeFile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:fs/promises&lt;/span&gt;&lt;span class="dl"&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;API&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.popnow.ai/api/v1&lt;/span&gt;&lt;span class="dl"&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;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&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;POPNOW_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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="s1"&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="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alibaba/wan-3.0/text-to-video&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A paper boat drifts down a rain-soaked street at dusk, neon signs &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reflecting in the water, slow tracking shot at ground level&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;480p&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;duration_seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;aspect_ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;16:9&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="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Small helper: unwrap the { code, msg, data, request_id } envelope.&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;init&lt;/span&gt; &lt;span class="o"&gt;=&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="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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="nx"&gt;init&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&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="nx"&gt;body&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (request &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&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="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 1. Price check.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;quote&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;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/jobs/quote&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="s1"&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;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="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`at most &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxCredits&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; credits ($&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxCharge&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Create the task. In a real app, store this key with your own job record&lt;/span&gt;
&lt;span class="c1"&gt;//    so a retry after a timeout returns the same task.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;created&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;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/jobs/createTask&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="s1"&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="s1"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&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="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`task &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;created&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="s2"&gt; is &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;created&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Poll until the state is final. Unknown states count as "not final yet".&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FINAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;succeeded&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="s1"&gt;failed&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="s1"&gt;expired&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;task&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;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/jobs/recordInfo?taskId=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;created&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`\r&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;state&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;...   `&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;FINAL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&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;state&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;succeeded&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`task &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;state&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &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;errorCode&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &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;errorMessage&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 4. Download right away: the signed link is short-lived.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;asset&lt;/span&gt; &lt;span class="o"&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;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assets&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&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="nx"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&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;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;clip.mp4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`saved clip.mp4 (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;durationSeconds&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;s, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;), charged &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;credits&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; credits`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth knowing about what just happened:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;createTask&lt;/code&gt; returns HTTP &lt;code&gt;202&lt;/code&gt;, not the video.&lt;/strong&gt; &lt;code&gt;202&lt;/code&gt; means accepted. The response has a &lt;code&gt;taskId&lt;/code&gt;, &lt;code&gt;state: "queued"&lt;/code&gt; and &lt;code&gt;estimatedCredits&lt;/code&gt;, which is the hold: the most this task can cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You pay for what was used.&lt;/strong&gt; When the task succeeds, you are charged the actual amount, rounded up to whole credits and never more than the hold; the rest goes back to your balance. When a task fails or expires, the hold is released.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;States.&lt;/strong&gt; A task moves from &lt;code&gt;queued&lt;/code&gt; to &lt;code&gt;running&lt;/code&gt; and ends as &lt;code&gt;succeeded&lt;/code&gt;, &lt;code&gt;failed&lt;/code&gt; or &lt;code&gt;expired&lt;/code&gt;. On failure, branch on &lt;code&gt;errorCode&lt;/code&gt; and show &lt;code&gt;errorMessage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signed links expire.&lt;/strong&gt; &lt;code&gt;output.assets[0].url&lt;/code&gt; works for about 20 minutes and needs no API key. The file itself is still there afterwards: read the task again with &lt;code&gt;recordInfo&lt;/code&gt; to get a fresh link. Store the &lt;code&gt;taskId&lt;/code&gt;, not the URL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For images you can skip the polling loop: add &lt;code&gt;?wait=60&lt;/code&gt; to &lt;code&gt;createTask&lt;/code&gt; and the connection stays open for up to 60 seconds, so a quick image comes back in the same response. Video almost always takes longer than that, which is why this script polls.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same flow in Python
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# video.py: pip install requests, then python video.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.popnow.ai/api/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;HEADERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POPNOW_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;FINAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;succeeded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expired&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Send a request and unwrap the {code, msg, data, request_id} envelope.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})}&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&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;if&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;msg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (request &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;request_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alibaba/wan-3.0/text-to-video&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A paper boat drifts down a rain-soaked street at dusk, neon signs &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reflecting in the water, slow tracking shot at ground level&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resolution&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;480p&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duration_seconds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aspect_ratio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;16:9&lt;/span&gt;&lt;span class="sh"&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="n"&gt;quote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/jobs/quote&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;at most &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxCredits&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; credits ($&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxCharge&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;created&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/jobs/createTask&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;())})&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;taskId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/jobs/recordInfo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;taskId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;taskId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;FINAL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;succeeded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;task &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;errorCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;errorMessage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;asset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assets&lt;/span&gt;&lt;span class="sh"&gt;"&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="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;clip.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iter_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;saved clip.mp4, charged&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;credits&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;credits&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Animate an image instead
&lt;/h2&gt;

&lt;p&gt;Image-to-video is the same code with a different &lt;code&gt;model&lt;/code&gt; and &lt;code&gt;input&lt;/code&gt;:&lt;br&gt;
&lt;/p&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;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alibaba/wan-3.0/image-to-video&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;image_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://media.popnow.ai/movie/lighthouse.webp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Waves roll in slowly, clouds drift, the camera pushes gently toward the lighthouse&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;480p&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;duration_seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;image_url&lt;/code&gt; is the opening frame. Many image-to-video models also accept &lt;code&gt;last_image_url&lt;/code&gt;: the clip then ends exactly on that image. Use the same picture for both and you get a smooth loop, which is handy for product shots and background videos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Switch models with one line
&lt;/h2&gt;

&lt;p&gt;Because every model sits behind the same endpoint, trying another one is a matter of changing &lt;code&gt;model&lt;/code&gt;. Here is MiniMax H3's text-to-video:&lt;br&gt;
&lt;/p&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;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;minimax/h3/text-to-video&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A paper boat drifts down a rain-soaked street at dusk, slow tracking shot at ground level&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;768p&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;duration_seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the &lt;code&gt;resolution&lt;/code&gt; values are different: H3 takes &lt;code&gt;768p&lt;/code&gt; or &lt;code&gt;2k&lt;/code&gt; and 5 to 15 seconds, while Wan 3.0 takes &lt;code&gt;480p&lt;/code&gt;, &lt;code&gt;720p&lt;/code&gt; or &lt;code&gt;1080p&lt;/code&gt; and up to 30 seconds. That is where schema validation pays off. If you swap the model and forget to change &lt;code&gt;480p&lt;/code&gt;, the request is rejected up front instead of running with a setting you didn't ask for. H3 costs 37 credits per second at 768p, so this clip is 185 credits.&lt;/p&gt;

&lt;p&gt;To see every model ID with its price from code, call &lt;code&gt;GET /api/v1/models&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going to production: webhooks instead of polling
&lt;/h2&gt;

&lt;p&gt;Polling every few seconds is fine for a script. In an app with many users, let the API call you instead. Add a &lt;code&gt;callBackUrl&lt;/code&gt; (a public HTTPS URL) to the &lt;code&gt;createTask&lt;/code&gt; body, and the finished task is POSTed to it.&lt;/p&gt;

&lt;p&gt;Deliveries are signed with your account's webhook secret, which you generate in the API console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signature = base64( HMAC-SHA256( secret, "&amp;lt;taskId&amp;gt;.&amp;lt;timestamp&amp;gt;.&amp;lt;sha256_hex(raw_body)&amp;gt;" ) )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The signature arrives in &lt;code&gt;X-Webhook-Signature&lt;/code&gt; and the timestamp in &lt;code&gt;X-Webhook-Timestamp&lt;/code&gt;. Hash the raw bytes you received, not a re-serialized object. Here is a compact Express receiver:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&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;SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&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;POPNOW_WEBHOOK_SECRET&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/hooks/popnow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raw&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="s1"&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;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;10mb&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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;ts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-webhook-timestamp&lt;/span&gt;&lt;span class="dl"&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;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-webhook-signature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&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="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&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;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;300&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&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;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&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="k"&gt;catch&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;end&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="nx"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&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;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SECRET&lt;/span&gt;&lt;span class="p"&gt;)&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&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;taskId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&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;ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timingSafeEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sig&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;ok&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&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;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                   &lt;span class="c1"&gt;// acknowledge first...&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&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;taskId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&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;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ...then queue the real work&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few rules make webhook handlers robust:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Answer fast.&lt;/strong&gt; Any 2xx within 15 seconds counts as received. Download and transcode in a background job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expect duplicates.&lt;/strong&gt; Failed deliveries are retried, so the same task can arrive twice. &lt;code&gt;request_id&lt;/code&gt; in the body is the delivery ID and stays the same across retries; store it with a unique index and ignore repeats.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The webhook says "delivered", not "succeeded".&lt;/strong&gt; &lt;code&gt;code&lt;/code&gt; is &lt;code&gt;200&lt;/code&gt; even for a failed task. Read &lt;code&gt;data.state&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reconcile.&lt;/strong&gt; Once a day, list recent tasks with &lt;code&gt;GET /api/v1/jobs&lt;/code&gt; and read any whose webhook you never received.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And a few that apply whether you poll or not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Derive the &lt;code&gt;Idempotency-Key&lt;/code&gt; from your own job ID, so a retry can never create a second task.&lt;/li&gt;
&lt;li&gt;Copy finished files into your own storage. Outputs are kept for a limited time; the task record says when in &lt;code&gt;retention.outputsExpireAt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Tune prompts at the lowest resolution and a short duration, then raise both for the final render.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A note on prompts
&lt;/h2&gt;

&lt;p&gt;Video models reward prompts that describe &lt;strong&gt;motion&lt;/strong&gt;, not just the scene. "A beautiful lighthouse" gives the model nothing to do. "Waves roll in slowly, clouds drift, the camera pushes gently toward the lighthouse" tells it what moves, how fast, and what the camera does. With image-to-video this matters even more: the picture already shows what things look like, so spend your words on what changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The whole integration comes down to three calls — &lt;code&gt;jobs/quote&lt;/code&gt;, &lt;code&gt;jobs/createTask&lt;/code&gt; and &lt;code&gt;jobs/recordInfo&lt;/code&gt; — plus a webhook once you go to production. Since the request shape is the same for every model, you can write this code once and compare models by changing a single string.&lt;/p&gt;

&lt;p&gt;The full reference, including uploads for your own images, error codes and per-model parameters, is in the &lt;a href="https://popnow.ai/docs/quickstart" rel="noopener noreferrer"&gt;PopNow API docs&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
