PopNowAPI Docs
Integrations
Main content

Vercel AI SDK provider

Use PopNow models with the Vercel AI SDK 7. generateImage for text to image and image editing, generateSpeech, transcribe and chat models, billed in USD with one API key.

@popnow/ai-sdk-provider connects PopNow models to the Vercel AI SDK. With one API key you call image generation and image editing models through generateImage, text to speech through generateSpeech, speech to text through transcribe, and chat models through generateText and streamText. Everything is billed in USD from your separate API balance.

The package is open source under the MIT license (GitHub). It requires AI SDK 7 (ai@^7) and Node.js 22 or newer.

At a glance

AI SDK functionPopNow modelsFactory
generateImageText to image and image editing: Nano Banana, GPT Image, Seedream, FLUX, Qwen Image and morepopnow.image(id)
generateSpeechText to speechpopnow.speech(id)
transcribeSpeech to textpopnow.transcription(id)
generateText, streamTextChat models through the OpenAI-compatible endpointpopnow(id), popnow.chat(id)

Set up

Install the packages

npm install @popnow/ai-sdk-provider ai

Create an API key

Create a key in the API console. Keys start with sk-popnow-. The API balance starts at $0; add funds in API billing before the first paid call.

Put the key in your environment

The default popnow instance reads POPNOW_API_KEY:

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

Keep the key on your server. Anyone who can read it can spend your API balance.

Generate an image

TypeScript
import { popnow } from '@popnow/ai-sdk-provider';
import { generateImage } from 'ai';
import fs from 'node:fs';

const { image } = await generateImage({
  model: popnow.image('google/nano-banana-2.1/text-to-image'),
  prompt: 'A cozy reading nook beside a rain-streaked window, warm lamp light',
  aspectRatio: '16:9',
  providerOptions: {
    popnow: { resolution: '2k' },
  },
});

await fs.promises.writeFile('nook.png', image.uint8Array);

Provider settings

For other settings, create your own instance with createPopNow:

TypeScript
import { createPopNow } from '@popnow/ai-sdk-provider';

const popnow = createPopNow({
  apiKey: process.env.POPNOW_API_KEY, // default: POPNOW_API_KEY
  baseURL: 'https://api.popnow.ai', // default: POPNOW_BASE_URL or https://api.popnow.ai
  headers: { 'X-PopNow-Tags': 'my-app' }, // optional, sent with every request
});

createPopNow also takes a custom fetch function.

Edit an image

Edit models have IDs that end in /edit. Pass the source images, and a mask where the model takes one, in the prompt:

TypeScript
import { popnow } from '@popnow/ai-sdk-provider';
import { generateImage } from 'ai';

const { image } = await generateImage({
  model: popnow.image('google/nano-banana-2.1/edit'),
  prompt: {
    text: 'Change the jacket to deep forest green; keep the face and background',
    images: ['https://example.com/portrait.jpg'],
  },
});

URLs are passed through as they are. Images given as bytes or base64 are sent inline when they are JPEG, PNG, WebP or GIF of at most 1 MiB, and uploaded to PopNow otherwise.

How settings map to PopNow

Every PopNow model validates its input against its own JSON Schema and rejects unknown fields. The provider reads the model's schema (GET /api/v1/models/{model}, cached for ten minutes) and only sends a setting when the model has the matching field:

AI SDK settingPopNow input field
promptprompt
aspectRatioaspect_ratio
sizewidth and height; otherwise reduced to aspect_ratio, with a compatibility warning
seedseed
nnum_outputs or num_images; for models without either, each image is its own task and the AI SDK runs several
prompt imagesimage_urls, or image_url for single-image models
prompt maskmask_url

A setting the model does not have is left out and reported in warnings as unsupported. Any other model field goes in providerOptions.popnow, spelled exactly as in the model's schema (snake_case), for example resolution, output_format or quality. These values override the mapped ones.

Text to speech

TypeScript
import { popnow } from '@popnow/ai-sdk-provider';
import { generateSpeech } from 'ai';
import fs from 'node:fs';

const { audio } = await generateSpeech({
  model: popnow.speech('openai/tts-1/text-to-speech'),
  text: 'Hello from PopNow.',
  voice: 'nova',
  speed: 1.1,
});

await fs.promises.writeFile('hello.mp3', audio.uint8Array);

voice maps to voice_id, outputFormat to output_format, and instructions, speed and language to fields of the same name, when the model has them. Each model's schema lists the voices it accepts.

Transcription

TypeScript
import { popnow } from '@popnow/ai-sdk-provider';
import { transcribe } from 'ai';
import fs from 'node:fs';

const result = await transcribe({
  model: popnow.transcription('openai/whisper-1/speech-to-text'),
  audio: await fs.promises.readFile('meeting.mp3'),
  providerOptions: { popnow: { language: 'en' } },
});

console.log(result.text, result.words);

Audio bytes are uploaded to PopNow first (MP3 and WAV, up to 90 MiB). A public https:// URL (audio: new URL('https://...')) is passed to PopNow directly without downloading it.

Chat models

PopNow serves chat models through an OpenAI-compatible API at https://api.popnow.ai/v1. The provider uses @ai-sdk/openai-compatible for them, with the same API key and balance:

TypeScript
import { popnow } from '@popnow/ai-sdk-provider';
import { generateText } from 'ai';

const { text } = await generateText({
  model: popnow('anthropic/claude-haiku-5.5/chat'),
  prompt: 'Suggest three names for a kite shop by the sea.',
});

streamText works with the same model:

TypeScript
import { popnow } from '@popnow/ai-sdk-provider';
import { streamText } from 'ai';

const result = streamText({
  model: popnow('anthropic/claude-haiku-5.5/chat'),
  prompt: 'Write a short welcome message for a kite shop.',
});

for await (const delta of result.textStream) process.stdout.write(delta);

Chat models are priced per token, with no per-request minimum. Chat models lists every model and the parameters it accepts.

Model IDs

Model IDs have the form <maker>/<model>/<task>, for example google/nano-banana-2.1/text-to-image, openai/tts-1/text-to-speech or openai/whisper-1/speech-to-text. The TypeScript types suggest a few; any ID your key can call works. Models lists every model with its parameters and prices. To list them from code, with input schemas:

curl "https://api.popnow.ai/api/v1/models?modality=image&includeSchema=1" \
  -H "Authorization: Bearer $POPNOW_API_KEY"

modality is image, video or audio. Chat models are listed at GET https://api.popnow.ai/v1/models.

Pricing

Prices are in USD per image, per second or per 1,000 characters, depending on the model, and shown on Models and each model's page. The AI price index lists them too. Each task holds its maximum price from your API balance when it is accepted and settles the actual charge when it finishes. Tasks that fail or expire are not charged. Details: Billing and quotes.

Each image entry in providerMetadata.popnow.images carries the taskId, its costUsd (a USD decimal string) and whether the charge is settled. Speech and transcription results carry the same fields in providerMetadata.popnow.

Tasks, polling and retries

PopNow generation is asynchronous. Each call creates a task, waits up to 60 seconds on the create request, then polls the task (every 2 seconds at first, growing to every 15 seconds) until it is final, and downloads the output files from their signed links. The API key is never sent to those links.

These options go in providerOptions.popnow and are not sent to the model:

OptionDefaultMeaning
pollIntervalMs2000First delay between polls
pollTimeoutMsTask deadline plus two minutesStop waiting after this long; the task keeps running
waitSeconds60How long the create request is held open (0 to 60)
idempotencyKeyGenerated per callStable key for one logical generation
metadataNoneYour own labels, stored with the task

Every create request carries an Idempotency-Key. Connection errors, 429 and retryable 5xx responses are retried twice with the same key, so a retry never starts a second task. When you pass your own idempotencyKey, the AI SDK's own retries (maxRetries) reuse it too. See Idempotency.

abortSignal stops waiting at once. A task that was already accepted keeps running on PopNow.

Errors

  • APICallError (from @ai-sdk/provider) for API errors. The message carries the PopNow business code, such as 40201 (not enough API balance) or 40301 (the key may not call this model). See Errors.
  • PopNowTaskError when a task ends as failed or expired. It has taskId, state, errorCode (for example content_rejected), errorMessage and costUsd. These tasks are not charged.
  • PopNowTaskTimeoutError when pollTimeoutMs runs out. It has taskId; the task keeps running.
TypeScript
import { popnow, PopNowTaskError } from '@popnow/ai-sdk-provider';
import { generateImage } from 'ai';

try {
  await generateImage({
    model: popnow.image('google/nano-banana-2.1/text-to-image'),
    prompt: 'A lighthouse at dawn, watercolor',
  });
} catch (error) {
  if (PopNowTaskError.isInstance(error)) console.log(error.taskId, error.errorCode);
}

Not included

  • Video. The AI SDK's video model interface is still experimental (experimental_generateVideo), so this package does not implement it yet. PopNow video models are available through the task API, the official SDKs and the OpenAI Videos compatible endpoints.
  • Embeddings. embeddingModel throws NoSuchModelError.

Which AI SDK version does it need?

AI SDK 7 (ai@^7) on Node.js 22 or newer. Install both with npm install @popnow/ai-sdk-provider ai.

Can I call it from the browser?

Not with your API key in the page: anyone who can read the key can spend your API balance. Call the AI SDK from a server route, such as a Next.js route handler or a server action, and send the result to the browser.

Does it support video models?

Not yet, because the AI SDK's video interface is still experimental. Use the task API or the JavaScript SDK for video.

How do I see what a call cost?

Read providerMetadata.popnow: each task reports its taskId, costUsd and whether the charge is settled. The API console shows usage per key.

On this page