Upgrade
PicX StudioPicX Studio
Create
Generate
Explore
Templates
Skills
Prompts
Product Video Studio
Product Image Studio
Ad Templates
Media
DirectorLive
Creditsup to 80%

Buy Credits−80%CreditsLoading…up to 80% off yearly
  • Overview
  • Docs
  • API keys
  • Playground
  • MCP
  • CLI
Getting Started
  • Quick Start
  • Authentication
  • Rate Limits
MCP
  • Overview
  • Tools Reference
  • Connect Claude Desktop
  • Connect Cursor
CLI
  • Overview
  • Command Reference
  • Recipes
API Reference
  • Generate Image
  • Edit Image
  • Generate Video
  • List Models
  • Managed Assets
  • Templates
Developer Tools
  • API Keys
  • Webhooks
  • Usage Tracking
  • Playground
  • Async Image Generation
Code Examples
  • cURL Examples
  • Python SDK
  • JavaScript SDK
DevelopersDocsCode Examples

JavaScript SDK

Call the PicX Studio API from Node and TypeScript with the official picx-ai package.

View as Markdown

The official JavaScript SDK wraps the PicX Studio REST API in a typed client: picx.images.generate(...), picx.video.create(...), picx.models.list(...). It handles auth headers, JSON encoding, retries with backoff, timeouts, and video job polling, and it throws typed errors you can branch on. It ships its own TypeScript types, so requests and responses are checked at compile time.

The SDK is not the CLI. The PicX CLI is a separate tool you install globally and run in your terminal for one-off jobs and scripting. The SDK is a library you add as a dependency to call the API from inside your own application. They are independent — use either, or both.

Install#

npm install picx-ai
pnpm add picx-ai

Quickstart#

Pass your API key to the constructor. Read it from an environment variable — never hardcode it.

import { PicX } from "picx-ai";

const picx = new PicX(process.env.PICX_API_KEY);

const asset = await picx.images.generate({
  prompt: "a beautiful sunset over the ocean, cinematic lighting",
  size: "2K",
  aspect_ratio: "16:9",
});

console.log(asset.url);
console.log(asset.id, asset.model, asset.credits_used);

Never use an API key in a browser or any client-side bundle. pxsk_… keys are server-side credentials that carry your billing and full key scopes; anything shipped to a browser is public, and bundlers will inline the value. Call the SDK only from a server, a serverless function, or a background worker, and expose your own endpoint to the frontend. A key that reaches a browser must be treated as leaked and revoked.

Generate an image#

images.generate is synchronous — it resolves to the finished asset. Only prompt is required.

const asset = await picx.images.generate({
  prompt: "a red leather sneaker on white marble, studio lighting",
  model: "gemini-3.1-flash-image-preview",
  size: "2K",
  aspect_ratio: "16:9",
});

console.log(asset.url);         // hosted CDN URL
console.log(asset.credits_used); // credits billed for this call
Option Type Required Description
prompt string Yes What to generate.
model string No Model ID. Defaults to the current image model.
size string No Output size, for example 1K, 2K, 4K.
aspect_ratio string No Aspect ratio, for example 16:9, 1:1.

Edit an image#

images.edit takes an instruction plus one to five source image URLs. It is also synchronous.

const edited = await picx.images.edit({
  instruction: "make the colors more vibrant and add a subtle glow effect",
  image_urls: ["https://cdn.picxstudio.com/api/generated/image_abc123.png"],
  model: "gemini-3.1-flash-image-preview",
  size: "1K",
});

console.log(edited.url);

Pass several URLs to composite or use additional references:

const composite = await picx.images.edit({
  instruction: "place the product from the second image onto the background of the first",
  image_urls: [
    "https://example.com/background.jpg",
    "https://example.com/product.png",
  ],
});

image_urls accepts 1–5 URLs. More than five is rejected with a validation error before the request is sent, so you fail fast instead of burning a round trip.

Upload and manage assets#

Edit and video calls accept public HTTPS URLs, not files that only exist on your server or device. Upload the file through managed assets first. Creating and deleting assets requires the uploads:write scope; listing and fetching assets require a valid API key.

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

const bytes = await readFile("./portrait.jpg");
const source = await picx.assets.create({
  file: new Blob([bytes], { type: "image/jpeg" }),
  filename: "portrait.jpg",
  metadata: { source: "onboarding" },
});

const avatar = await picx.images.edit({
  instruction: "turn this into a naive doodle avatar",
  image_urls: [source.url],
});

console.log(source.id, source.url, avatar.url);

In a browser or Worker, pass a File or Blob directly:

const source = await picx.assets.create({ file: selectedFile });

Managed uploads accept images up to 20 MB, video up to 50 MB, and audio up to 15 MB. Use the resource methods to list, recover, or soft-delete assets:

const { assets, total } = await picx.assets.list({ kind: "image", limit: 50 });
const recovered = await picx.assets.get(source.id);
await picx.assets.delete(recovered.id);

Generate a video#

Video generation is asynchronous. video.create returns a job, not an asset. Await job.wait() to poll until the video is ready and get the finished asset back.

const job = await picx.video.create({
  prompt: "a cat walking in the rain, cinematic",
  duration: 5,
  resolution: "720p",
  sound: true,
});

console.log(job.id, job.status); // "pending"

const asset = await job.wait();  // polls with backoff until terminal
console.log(asset.url);

job.wait() resolves when the generation completes and throws if it fails, so you do not need to inspect status yourself.

Option Type Required Description
prompt string Yes What to generate.
model string No Video model ID.
duration number No Length in seconds.
resolution string No For example 720p, 1080p.
sound boolean No Generate an audio track.

Bind a webhook to a video job#

Receive a notification when the job finishes instead of holding a connection open:

const job = await picx.video.create({
  prompt: "a cat jumping off a table",
  duration: 5,
  resolution: "480p",
  webhook: { url: "https://your-server.com/webhook" },
});
// job.raw.webhook.mode === "inline"

Or bind by a registered webhook ID:

const job = await picx.video.create({
  prompt: "a cat jumping",
  webhook: { id: "your-webhook-uuid" },
});

Poll manually#

If you would rather not hold a connection open — for example you store the job ID and check it from a queue worker or a webhook handler — poll generations.get yourself.

const job = await picx.video.create({ prompt: "a cat walking in the rain" });

// Persist job.id, then later, from anywhere:
let generation = await picx.generations.get(job.id);

while (generation.status !== "completed" && generation.status !== "failed") {
  await new Promise((r) => setTimeout(r, 5000));
  generation = await picx.generations.get(job.id);
}

if (generation.status === "failed") throw new Error(generation.error);
console.log(generation.url);

Video modes#

mode selects how a video is sourced. text is the default; the other modes take existing media as input. Each mode has its own required fields, and the SDK checks them client-side before the request goes out — a missing audio_url on a lipsync call throws a TypeError locally with a message naming the field, so you never spend a round trip on a 422. Requires picx-ai 0.4.0 or later.

mode Required fields Needs prompt?
text (default) — Yes
image image_url Yes
reference reference_urls Yes
frames start_frame_url (end_frame_url optional) Yes
extend source_video_url Yes
lipsync source_video_url and audio_url No
edit source_video_url and image_url Yes

Animate between two frames (frames)#

Give a start frame and, optionally, an end frame. The model interpolates a clip between them.

const job = await picx.video.create({
  mode: "frames",
  prompt: "smooth dolly-in on the product",
  start_frame_url: "https://cdn.example.com/first.png",
  end_frame_url: "https://cdn.example.com/last.png", // optional
  duration: 5,
});

const asset = await job.wait();
console.log(asset.url);

Extend an existing clip (extend)#

Continue a video past its last frame.

const job = await picx.video.create({
  mode: "extend",
  prompt: "the camera keeps pulling back to reveal the skyline",
  source_video_url: "https://cdn.example.com/clip.mp4",
});

const asset = await job.wait();
console.log(asset.url);

Lip-sync a clip to audio (lipsync)#

Drive the lips in a source video from an audio track. This is the one mode that does not take a prompt — the spoken content comes from audio_url.

const job = await picx.video.create({
  mode: "lipsync",
  source_video_url: "https://cdn.example.com/face.mp4",
  audio_url: "https://cdn.example.com/voice.mp3",
});

const asset = await job.wait();
console.log(asset.url);

Edit a clip with a reference image (edit)#

Apply the content of an image to a source video.

const job = await picx.video.create({
  mode: "edit",
  prompt: "restyle the jacket to match the reference",
  source_video_url: "https://cdn.example.com/clip.mp4",
  image_url: "https://cdn.example.com/reference.png",
});

const asset = await job.wait();
console.log(asset.url);

Client-side validation is a real time saver here: picx.video.create({ mode: "edit", prompt: "…", source_video_url: "…" }) throws immediately because image_url is missing, rather than queuing a job that the server would reject. The message names the exact field the mode needs.

Prompt templates#

The template catalogue is a searchable library of about 50,000 ready-made image and video prompts, tuned per model and tagged by topic. templates.list searches it and templates.get fetches one by id. Both need a valid API key like the rest of /v1, but return public catalogue content. Requires picx-ai 0.4.0 or later.

const { templates, total } = await picx.templates.list({
  q: "sneaker",
  media_type: "video",
  featured: true,
  limit: 20,
});

for (const t of templates) {
  console.log(t.id, t.title, t.target_model);
}

All filters are optional and combine with AND. tags is matched as a set — a template must carry every tag you pass.

Option Type Description
q string Free-text search across the catalogue.
media_type "image" | "video" Restrict to one output type.
topic string Restrict to a single topic (see the note below).
tags string[] Match templates carrying every tag.
target_model string Restrict to templates tuned for one model.
featured boolean Only editorially featured templates.
trending boolean Only currently trending templates.
limit number Page size, 1–100. Defaults to 30.
offset number Rows to skip. Defaults to 0.

Fetch one template by id:

const template = await picx.templates.get("tpl_abc123");
// Feed it straight into a generation:
if (template.prompt) {
  const asset = await picx.images.generate({
    prompt: template.prompt,
    model: template.target_model ?? undefined,
  });
  console.log(asset.url);
}

Three behaviours of the catalogue will bite you if you assume otherwise:

  • total is an estimate, not an exact count. Do not page to offset >= total and expect the last page to line up. Page until a request returns fewer rows than limit — that short (or empty) page is the real end.
  • The topic filter works, but the topic field is always null. You can filter by topic, but you cannot read back which topic a returned template belongs to.
  • A null prompt means the row is premium/gated, not that data is missing. Skip such rows (or surface them as locked) rather than treating them as broken.

Paging correctly, given that total is only an estimate:

let offset = 0;
const pageSize = 100;
for (;;) {
  const page = await picx.templates.list({ q: "cinematic", limit: pageSize, offset });
  for (const t of page.templates) {
    if (t.prompt === null) continue; // gated/premium row
    process(t);
  }
  if (page.templates.length < pageSize) break; // short page = real end
  offset += pageSize;
}

Account tier and limits#

account.tier() returns the rate limits and quotas for your key's plan. Read it at start-up to size your own client-side throttling, or to check which model families the key may use before you submit a generation.

const tier = await picx.account.tier();

console.log(tier.plan_code);            // e.g. "free", "pro"
console.log(tier.requests_per_minute);  // per-minute request cap
console.log(tier.requests_per_day);     // per-day request cap
console.log(tier.concurrent_limit);     // max generations running at once
console.log(tier.max_credits_per_day);  // daily credit ceiling
console.log(tier.allowed_model_types);  // e.g. ["image", "video"]

// Gate a video submit on the plan before spending a request.
if (!tier.allowed_model_types.includes("video")) {
  throw new Error(`Plan ${tier.plan_code} cannot generate video.`);
}

Stream generation progress#

Rather than polling generations.get in a loop, subscribe to the live Server-Sent Events stream. generations.events(id) returns an async iterable that yields one event per frame as the server pushes status updates, then a terminal completed/failed event. It is cheaper and more responsive than polling. Requires picx-ai 0.4.0 or later and the generations:read scope.

import { isTerminalStatus } from "picx-ai";

const job = await picx.video.create({ prompt: "a cat walking in the rain" });

for await (const event of picx.generations.events(job.id)) {
  const status = event.data?.status;
  console.log(event.event, status);
  if (isTerminalStatus(status)) break;
}

Each event carries the SSE frame's fields:

Field Type Description
event string SSE event name, e.g. status, completed, failed, ping. "message" when the frame carried no event: line.
data Record<string, unknown> | null Parsed JSON payload, or null when the frame's data: text was not JSON (e.g. a ping keep-alive).
rawData string The undecoded data: text — always present, so a non-JSON frame is never lost.
id string? The SSE id: field, when the frame carried one.
retry number? The SSE retry: reconnection hint in ms, when present.

The connection stays open while you iterate, so consume it to the end or break out — either releases it. Streaming requests are never retried; abort mid-stream with a signal, and use timeout: 0 to disable the read-idle timeout on a long render:

const controller = new AbortController();
for await (const event of picx.generations.events(job.id, {
  signal: controller.signal,
  timeout: 0,
})) {
  if (event.data?.status === "completed") {
    console.log("done:", event.data.output_url);
    break;
  }
}

List and manage generations#

// List recent generations, optionally filtered
const { generations, total } = await picx.generations.list({
  type: "video",
  limit: 10,
});

for (const gen of generations) {
  console.log(gen.id, gen.status, gen.type);
}

// Cancel a pending generation
await picx.generations.cancel("generation-uuid");

// View webhook deliveries for a specific generation
const { deliveries } = await picx.generations.deliveries("generation-uuid");

List models#

models.list is public — it needs no API key, which makes it a convenient connectivity check.

import { PicX } from "picx-ai";

const picx = new PicX(); // no key required for this call

const { models } = await picx.models.list();
const videoModels = await picx.models.list({ type: "video" });

for (const model of videoModels.models) {
  console.log(model.id, model.type, model.credits);
}

Async images and webhooks#

Both image calls are synchronous by default. Pass callback_url (or webhook) and the API returns 202 immediately, the SDK hands back a GenerationJob, and the finished image is POSTed to your URL. Requires picx-ai 0.3.0 or later.

import { PicX, GenerationJob } from "picx-ai";

const picx = new PicX(process.env.PICX_API_KEY);

// No target -> ImageResult, exactly as before.
const image = await picx.images.generate({ prompt: "a cat" });
console.log(image.url);

// With a target -> GenerationJob. Nothing is held open.
const job = await picx.images.generate({
  prompt: "a cat",
  callback_url: "https://your-server.com/hooks/picx",
});
console.log(job instanceof GenerationJob, job.id, job.status);

// Editing takes the same fields.
const editJob = await picx.images.edit({
  instruction: "replace the sky with a sunset",
  image_urls: ["https://example.com/photo.jpg"],
  webhook: { url: "https://your-server.com/hooks/picx" },
});

// Always have a fallback — poll if a delivery is missed.
const asset = await job.wait({ pollIntervalMs: 3000, timeoutMs: 600_000 });
console.log(asset.url);

TypeScript picks the return type from the call: overloads keep an existing picx.images.generate({ prompt }) typed as Promise<ImageResult>, and only widen to Promise<GenerationJob> when a delivery target is actually present. No casts needed.

See Async Image Generation for the payload shape, signature verification and retry behaviour.

Webhook management and deliveries#

create, list, test and delete are session-authenticated in the console and reject an API key. The delivery-inspection methods below do work with an API key, from picx-ai 0.3.1.

// Registered-webhook management — console session only, see the note above.
const wh = await picx.webhooks.create({
  url: "https://your-server.com/webhook",
  events: ["generation.completed", "generation.failed"],
});
console.log(wh.id, wh.secret); // save the secret
await picx.webhooks.list();
await picx.webhooks.test(wh.id);
await picx.webhooks.delete(wh.id);

Delivery inspection works with an API key — this is how you debug a webhook that never arrived:

// Every delivery attempted for one generation, including inline/callback_url ones.
const { deliveries, total } = await picx.generations.deliveries(job.id);
for (const d of deliveries) {
  console.log(d.event_type, d.outcome, d.status_code, d.target_url);
  console.log(d.attempts); // per-attempt history
}

// Per-webhook history, filtered to the failures.
const failed = await picx.webhooks.deliveries(webhookId, {
  outcome: "exhausted",
  limit: 50,
});

// Replay one after fixing your endpoint. Reuses the original payload and event id.
const result = await picx.webhooks.redeliver(deliveryId);
console.log(result.outcome, result.status_code);

Binding a generation to an already-registered webhook by id also works with an API key:

const job = await picx.images.generate({
  prompt: "a cat",
  webhook: { id: "0b9c1e42-…" },
});

Check usage and account#

const me = await picx.account.me();
console.log(me.name, me.credits.balance);

const usage = await picx.account.usage({ period: "30d" });
console.log(usage);

Handle errors#

Every failure throws a subclass of PicXError, so you can branch on the failure mode instead of parsing status codes.

import {
  PicX,
  PicXError,
  AuthenticationError,
  PermissionDeniedError,
  NotFoundError,
  ValidationError,
  RateLimitError,
  ServerError,
  NetworkError,
} from "picx-ai";

const picx = new PicX(process.env.PICX_API_KEY);

try {
  const asset = await picx.images.generate({ prompt: "a sunset" });
  console.log(asset.url);
} catch (err) {
  if (err instanceof AuthenticationError) {
    // 401 — key missing, malformed, or revoked
  } else if (err instanceof PermissionDeniedError) {
    // 403 — the key is valid but lacks the required scope
  } else if (err instanceof NotFoundError) {
    // 404 — no such generation or model
  } else if (err instanceof ValidationError) {
    // 422 — bad parameters; err.message says which
  } else if (err instanceof RateLimitError) {
    // 429 — already retried; wait err.retryAfter seconds
    console.log(`Rate limited, retry in ${err.retryAfter}s`);
  } else if (err instanceof ServerError) {
    // 5xx — already retried
  } else if (err instanceof NetworkError) {
    // connection failure or timeout
  } else if (err instanceof PicXError) {
    // anything else from the SDK
  } else {
    throw err;
  }
}
Error Status Meaning
AuthenticationError 401 Key missing, malformed, or revoked.
PermissionDeniedError 403 Key lacks the scope for this operation.
NotFoundError 404 Generation, model, or resource does not exist.
ValidationError 422 Invalid parameters. Not retried.
RateLimitError 429 Limit exceeded. Exposes retryAfter in seconds.
ServerError 5xx Upstream failure. Retried.
NetworkError — Connection failure or timeout. Retried.

RateLimitError and ServerError only surface after the SDK has exhausted its retries. If you catch one, backing off further is the right move — see Rate Limits.

Configure retries and timeouts#

The constructor takes options alongside the key.

const picx = new PicX(process.env.PICX_API_KEY, {
  baseUrl: "https://api.picxstudio.com/v1",
  timeout: 120_000, // ms, per request
  maxRetries: 3,
});
Option Default Description
baseUrl https://api.picxstudio.com/v1 Override for staging or a proxy.
timeout SDK default Per-request timeout in milliseconds.
maxRetries 3 Retry attempts before throwing. Set 0 to disable.

The SDK retries only on 429, 5xx, and network errors, with exponential backoff that honors Retry-After. It never retries 400, 401, 403, 404, or 422 — those will not succeed on a second attempt, so retrying only wastes time.

Idempotency keys#

Pass a per-call idempotency key on any POST so a retry — the SDK's or yours — cannot bill you twice for the same generation.

const asset = await picx.images.generate(
  { prompt: "a red sneaker on marble" },
  { idempotencyKey: "order-4417-hero" },
);

Reusing a key returns the original result instead of generating again. Use a stable value derived from your own data, such as an order or row ID.

Prefer raw HTTP?#

The SDK is optional. Every endpoint is a plain REST call, so if you would rather not add a dependency — a tiny edge function, a language without an SDK, or a strict dependency policy — call the API directly with fetch.

const res = await fetch("https://api.picxstudio.com/v1/images/generate", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PICX_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    prompt: "a beautiful sunset over the ocean",
    size: "2K",
    aspect_ratio: "16:9",
  }),
});
if (!res.ok) throw new Error(`API error: ${res.status}`);
const data = await res.json();
console.log(data.url);

The SDK intentionally mirrors the API's snake_case field names (aspect_ratio, image_urls, credits_used). Going direct means handling retries, backoff, and video polling yourself. See cURL Examples for every endpoint, and Rate Limits for the retry rules to implement.

Previous
Python SDK
On this page
  • Install
  • Quickstart
  • Generate an image
  • Edit an image
  • Upload and manage assets
  • Generate a video
  • Bind a webhook to a video job
  • Poll manually
  • Video modes
  • Animate between two frames (frames)
  • Extend an existing clip (extend)
  • Lip-sync a clip to audio (lipsync)
  • Edit a clip with a reference image (edit)
  • Prompt templates
  • Account tier and limits
  • Stream generation progress
  • List and manage generations
  • List models
  • Async images and webhooks
  • Webhook management and deliveries
  • Check usage and account
  • Handle errors
  • Configure retries and timeouts
  • Idempotency keys
  • Prefer raw HTTP?
PicX Studio

Fuel your creativity, frame your story.

Studio

  • 1985 Snapshot
  • Templates
  • Skills
  • Tools
  • Prompts
  • Discover
  • PicX TV
  • Pricing
  • Blog

Skills

  • 1985 Flash Snapshot
  • 90s Album Snapshot
  • Storyboard to Video Workflow
  • H3 Max Director Live
  • GPT Image 2 Prompting
  • Character Continuity

Video models

  • MiniMax H3
  • Seedance 2.5
  • Seedance 2.0
  • Kling 3.0 Pro
  • FLUX 3
  • Grok Imagine
  • What is Seedance?
  • Higgsfield alternative

Image models

  • Nano Banana 2
  • Nano Banana Pro
  • GPT Image 2
  • Seedream 5 Pro
  • Nano Banana 2 vs Pro
  • What is Nano Banana?
  • Prompt Generator

E-commerce

  • AI tools for e-commerce
  • AI product photography
  • AI product video generator
  • AI UGC video ads
  • AI video ad generator
  • AI dropshipping

Free Tools

  • Background Remover
  • Image Upscaler
  • Image Compressor
  • Image to Text
  • Meme Generator
  • Instagram grid maker
  • AI face generator
  • Explore all tools

Resources

  • Compare
  • Prompt Roundups
  • Guides
  • Docs
  • API
  • CLI
  • FAQ

Company

  • About Us
  • Team
  • Careers
  • Brand
  • Partners
  • Sponsors
  • Roadmap
  • Status
  • Domain Rating
PicX Studio

© 2026 PicX Studio. All rights reserved.

Monitor your Domain Rating with FrogDR
[email protected]
  • Terms of Service
  • Privacy Policy
  • Security