npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

popnow

v0.3.0

Published

Official TypeScript SDK for the PopNow API: AI video, image, music and speech generation with Kling, Seedance, Wan, Nano Banana, Flux and more.

Readme

PopNow is a browser-based AI studio for videos, images and music: image-to-video, text-to-video, AI images, music and speech, one-tap trending video effects and image styles. This SDK gives Node.js, Deno, Bun, Cloudflare Workers and edge runtimes typed access to the same models as popnow.ai, at the same price for the same settings, billed in USD from your separate API balance. It has zero runtime dependencies and uses the global fetch.

Quickstart

1. Install, create an API key at popnow.ai/api/console/keys and add funds in API billing (the API balance starts at $0 and is separate from website credits):

npm install popnow
export POPNOW_API_KEY="sk-popnow-..."

Also on JSR for Deno and Bun: deno add jsr:@popnow/popnow or npx jsr add @popnow/popnow.

2. Generate an image (a fast model, about $0.01):

import { PopNow } from 'popnow';

const popnow = new PopNow(); // reads POPNOW_API_KEY

const task = await popnow.jobs.createAndWait({
  model: 'alibaba/z-image-turbo/text-to-image',
  input: { prompt: 'A small red fox curled up in a teacup, soft morning light' },
});

3. Use the result. output.assets[0].url is a signed link that works for about 20 minutes:

console.log(task.state, task.costUsd, task.output?.assets?.[0]?.url); // costUsd: exact USD charge, e.g. "0.01"

Features

| Area | What the SDK does | | --- | --- | | Tasks | quote, create, get, list / listAll, retry, purge, wait (backoff + AbortSignal), createAndWait | | Models | list (filter by modality, provider, task, search; optional input schemas and examples) and get | | Files | upload a Blob, File, Buffer, Uint8Array or ArrayBuffer (ticket, PUT, commit in one call), import LoRA weights from a link, downloadUrl | | Assets | Long-term asset library: create, list / listAll, get, update, delete, usage, characters.list / characters.get | | Account | balance() (availableUsd, heldUsd, totalUsd) and usage() (totalSpend), as exact USD strings | | Reliability | Automatic retries for connection errors, timeouts, 429, 500, 503, 50301 with Retry-After; an Idempotency-Key is always sent on task creation so retries never charge twice | | Errors | Typed error classes from the {code,msg,data,request_id} envelope, with code, status, requestId and reason | | Webhooks | verifyWebhook (task callbacks) and verifyStandardWebhook (account endpoints), using Web Crypto | | Runtimes | Node.js 18+, Deno, Bun, Cloudflare Workers, Vercel / Next.js edge and Node runtimes; ESM and CommonJS builds with type definitions |

Supported models

Every image, video and audio model on PopNow is callable with the same jobs.create call. A model ID is <maker>/<model>/<task>. A few examples:

| Kind | Example model IDs | | --- | --- | | Image to video | alibaba/wan-3.0/image-to-video, kling/3.0/image-to-video, bytedance/seedance-2.0/image-to-video, minimax/h3/image-to-video, vidu/q3/image-to-video | | Text to video | kling/3.0/text-to-video, bytedance/seedance-2.0/text-to-video, alibaba/wan-3.0/text-to-video | | Text to image | alibaba/z-image-turbo/text-to-image, google/nano-banana-2.1/text-to-image, openai/gpt-image-2/text-to-image, black-forest-labs/flux-2-pro/text-to-image | | Image editing | google/nano-banana-2.1/edit, bytedance/seedream-5.0-pro/edit, popnow/background-remover-v1/edit | | Music and speech | suno/v6/text-to-audio, minimax/music-3.0/text-to-audio, google/gemini-3.8-flash-tts/text-to-speech |

The live list, prices and input schemas come from popnow.models.list() or popnow.ai/docs/models.

Usage

Configuration

const popnow = new PopNow({
  apiKey: process.env.POPNOW_API_KEY, // default
  baseURL: 'https://api.popnow.ai',   // default (or POPNOW_BASE_URL)
  timeout: 60_000,                    // per attempt, in ms
  maxRetries: 2,                      // connection errors, timeouts, 429, 500, 503, 50301
  language: 'en',                     // Accept-Language for error messages; codes never change
});

Every method takes a last options argument with signal, timeout, maxRetries and headers.

Create a video from an image

const created = await popnow.jobs.create(
  {
    model: 'alibaba/wan-3.0/image-to-video',
    input: {
      image_url: 'https://media.popnow.ai/movie/lighthouse.webp',
      prompt: 'Waves roll in slowly, the camera drifts toward the lighthouse',
      resolution: '480p',
      duration_seconds: 5,
    },
    callBackUrl: 'https://your-app.example.com/hooks/popnow', // optional
  },
  { idempotencyKey: 'order-8814-render-1' },
);
// created: { taskId, state: 'queued', estimatedCostUsd, deadlineAt }  (estimatedCostUsd: the USD hold)

const controller = new AbortController();
const task = await popnow.jobs.wait(created.taskId, {
  signal: controller.signal, // stops waiting; the task keeps running
  pollInterval: 10_000,      // grows x1.5 up to maxPollInterval (15 s)
  onPoll: (t) => console.log(t.state),
});

wait returns the terminal record whatever its state (succeeded, failed, expired). Pass throwOnFailure: true to get a TaskFailedError instead. Unknown states are treated as still running.

Quote first, then lock the price

const params = { model: 'alibaba/wan-3.0/image-to-video', input: { image_url, resolution: '720p', duration_seconds: 5 } };
const quote = await popnow.jobs.quote(params); // nothing is held
console.log(`$${quote.expectedChargeUsd} typical, $${quote.maxChargeUsd} at most`);
await popnow.jobs.create({ ...params, quoteId: quote.quoteId, expectedCostUsd: quote.maxChargeUsd });
// A changed price or an expired quote (five minutes) fails with ConflictError code 40901 and holds nothing.

Confirm with maxChargeUsd, the amount that is held, never with the typical expectedChargeUsd. Sending quoteId alone also works.

Money amounts

Every amount is an exact USD decimal string with up to nine decimal places, for example "0.0048": maxChargeUsd and expectedChargeUsd on a quote, estimatedCostUsd when a task is accepted, costUsd on the task record (the hold while settled is false, the final charge once it is true), availableUsd, heldUsd and totalUsd on the balance, and spend / totalSpend in usage. Keep them as strings, or use a decimal library, for accounting; do not round them through parseFloat.

The integer credit fields (maxCredits, estimatedCredits, credits, availableCredits and the rest) are deprecated display projections at 1 credit = $0.001. They are still typed and returned for compatibility, but new code should read the USD fields.

Upload your own files

import { readFile } from 'node:fs/promises';

const file = await popnow.files.upload(await readFile('reference.png')); // type detected from the bytes
// or: await popnow.files.upload(blob)  /  { contentType: 'video/mp4' }
await popnow.jobs.create({ model: 'google/nano-banana-2.1/edit', input: { prompt: 'Make it night', image_urls: [file.uri] } });

Uploads are temporary. To reuse a file for a long time, save it to the asset library:

const asset = await popnow.assets.create({ source: 'task_output', taskId: task.taskId, index: 0, name: 'Hero shot' });
// asset.uri === 'popnow://a/ast_...' works in any matching media field

LoRA weights can be imported from a Hugging Face repo or a direct link:

const lora = await popnow.files.import({ url: 'your-name/your-lora-repo', token: process.env.HF_TOKEN });
await popnow.jobs.create({
  model: 'alibaba/qwen-image-2.1-lora/text-to-image',
  input: { prompt: 'A cozy reading nook in watercolor style', loras: [{ path: lora.uri, scale: 1 }] },
});

Lists and pagination

const page = await popnow.jobs.list({ state: 'failed', from: '2026-10-01', limit: 50 });
for await (const item of popnow.jobs.listAll({ model: 'alibaba/wan-3.0/image-to-video' })) {
  console.log(item.taskId, item.state, item.cost); // cost: exact USD (final if settled, otherwise the hold)
}

Balance and usage

const balance = await popnow.account.balance(); // availableUsd, heldUsd, totalUsd: exact USD strings
const usage = await popnow.account.usage({ from: '2026-10-01', to: '2026-10-16' }); // totalSpend, per day and per model

The balance is the API's own USD balance. Website credits are separate and never count toward it; add API funds in API billing.

Error handling

Errors carry the business code (branch on it, never on the message), the HTTP status, the requestId to quote to support, and reason when the API adds a machine-readable data.reason.

import { APIError, PaymentRequiredError, RateLimitError, TaskFailedError } from 'popnow';

try {
  await popnow.jobs.createAndWait(params, { throwOnFailure: true });
} catch (err) {
  if (err instanceof PaymentRequiredError) {
    // 40201: the API balance cannot cover the hold, 40202: a key spend cap was reached
  } else if (err instanceof RateLimitError) {
    console.log('retry after', err.retryAfter, 'seconds');
  } else if (err instanceof TaskFailedError) {
    console.log(err.errorCode, err.task.errorMessage); // e.g. content_rejected
  } else if (err instanceof APIError) {
    console.log(err.code, err.status, err.requestId, err.reason);
  }
}

| Class | HTTP | Business codes | | --- | --- | --- | | BadRequestError | 400 | 400, 40003, 40004 | | AuthenticationError | 401 | 401 | | PaymentRequiredError | 402 | 40201, 40202 | | PermissionDeniedError | 403 | 403, 40301, 40302, 40303, 40310 | | NotFoundError | 404 | 404 | | ConflictError | 409 | 409, 40901 | | PayloadTooLargeError | 413 | 413 | | RateLimitError | 429 | 429 | | InternalServerError | 5xx | 500 | | ServiceUnavailableError | 503 | 503, 50301, 50302 | | APIConnectionError / APITimeoutError | — | no response | | APIUserAbortError | — | your AbortSignal fired | | WaitTimeoutError, TaskFailedError, WebhookVerificationError | — | SDK helpers |

Webhooks

Task callbacks (callBackUrl) are signed with your account webhook secret: base64(HMAC-SHA256(secret, "<taskId>.<timestamp>.<sha256_hex(raw_body)>")). Pass the raw body.

import { verifyWebhook } from 'popnow';

export async function POST(request: Request) {
  const event = await verifyWebhook({
    payload: await request.text(),
    headers: request.headers,
    secret: process.env.POPNOW_WEBHOOK_SECRET!,
  }); // throws WebhookVerificationError; rejects timestamps older than 5 minutes
  // event.request_id is the delivery id (dedupe on it); event.data is the task record
  return new Response(null, { status: 200 });
}

Account endpoints use the Standard Webhooks format: verifyStandardWebhook({ payload, headers, secret: 'whsec_...' }) returns { type, data: { object: TaskRecord } }.

Browsers

Do not ship your API key to a browser: anyone who opens the page can read it and spend your API balance. The client prints a warning when it runs in a browser. Call PopNow from your own server (for example a Next.js route handler) and pass dangerouslyAllowBrowser: true only for local experiments.

Examples

FAQ

How do I generate a video from an image with an API?

Upload the image (popnow.files.upload) or pass a public HTTPS URL, then call popnow.jobs.create({ model: 'alibaba/wan-3.0/image-to-video', input: { image_url, prompt, duration_seconds: 5 } }) and popnow.jobs.wait(taskId). Video usually takes a few minutes, so use wait or a webhook.

How do I call Kling, Seedance or Nano Banana from Node.js?

Install the SDK (npm install popnow), set POPNOW_API_KEY, and pass the model ID, for example kling/3.0/text-to-video, bytedance/seedance-2.0/image-to-video or google/nano-banana-2.1/edit. All models share one request shape.

How much does the PopNow API cost?

The API is prepaid in USD from its own API balance, with no subscription. A model costs the same on the API as on popnow.ai for the same settings, but website credits and the API balance are separate and neither can pay for the other. Failed or expired tasks release their hold, so you pay nothing for them. Call popnow.jobs.quote(params) for the exact USD price of one request, see popnow.ai/api/pricing, and add funds in API billing.

Does the SDK work on Cloudflare Workers, Deno, Bun and the Next.js edge runtime?

Yes. It has no dependencies and only uses fetch, AbortController and Web Crypto, so it runs anywhere those exist. Pass apiKey explicitly where there is no process.env.

How do I avoid paying twice when a request times out?

jobs.create always sends an Idempotency-Key, and the SDK reuses it on its own retries. For retries across process restarts, pass your own key derived from your job ID, such as order-8814-render-1.

How do I verify PopNow webhook signatures in JavaScript?

Use verifyWebhook({ payload: rawBody, headers, secret }) for task callbacks or verifyStandardWebhook for account endpoints. Always pass the raw request body, not parsed JSON.

Can I use the OpenAI SDK instead?

For video, PopNow also exposes an OpenAI-compatible /v1/videos API: set the OpenAI SDK's baseURL to https://api.popnow.ai/v1. This SDK covers the full native API (images, audio, uploads, assets, quotes).

Links

License

MIT © PopNow

Model catalog update — 2026-11-03

Model IDs and input fields come from the live PopNow catalog. This release covers the new GPT chat families, Gemini Omni and Veo 3.1 video variants, GPT/Whisper transcription, TTS-1/HD, realtime voice task models, and the updated Tripo, Topaz and editing models. Fetch the selected model schema before submitting; use the normal quote, create and poll flow for task models.

Embedding tasks return output.embeddings.data (input index plus the numeric embedding) and measured output.embeddings.inputTokens, with no media files required. OpenAI-compatible embeddings use POST /v1/embeddings; binary speech uses POST /v1/audio/speech. The API wallet and quotes are in USD.