API Docs, PixelFireman
API REFERENCE

PixelFireman API

A cheaper, drop-in image-generation API. Send a prompt, get back an image. Pay as you go with credits bought in your dashboard, no subscriptions, no free tier, credits never expire.

For AI agents, MCP in one line

Building an agent on Claude? PixelFireman ships as an MCP server, so a Claude Desktop or Claude Code agent can generate images and video as a native tool. Add this to your MCP config, drop in your key, and the agent gets three tools instantly:

{
  "mcpServers": {
    "pixelfireman": {
      "command": "npx",
      "args": ["-y", "pixelfireman-mcp"],
      "env": { "PF_API_KEY": "pf_live_xxx" }
    }
  }
}
ToolWhat it does
generate_imageprompt → image URL (or SVG). Args: prompt, size, mode.
generate_videoprompt → short clip URL. Args: prompt, tier.
check_balancereturns remaining image credits and video minutes.

Example agent prompt: “Generate a wide, bright image of a modern kitchen renovation and give me the URL.”, the agent calls generate_image and returns the link. Machine-readable spec: openapi.json.

Download .md

Paste it into your AI assistant and it will know how to call PixelFireman.


Coming from OpenAI’s image API? See the migration guide for a side-by-side request/response mapping.

Getting an API key

  1. Sign up at pixelfireman.com/account.
  2. Buy a credit pack (see Credits & pricing).
  3. Copy your API key from the account dashboard. Keys look like pf_live_xxxxx….

Keep your key secret. Treat it like a password, anyone with it can spend your credits.


Authentication

Every request must include your API key. Two header forms are accepted, use either one:

Authorization: Bearer YOUR_API_KEY

or

x-api-key: YOUR_API_KEY

Requests without a valid key return 401.

Generate an image

POST/v1/images

Send a JSON body with a Content-Type: application/json header.

Request parameters

FieldTypeRequiredDefaultDescription
promptstringYes,What to generate.
modestringNo"auto"One of "auto", "generate", "draw". generate = realistic photo/image (fast diffusion model). draw = clean flat SVG vector art (logos/icons). auto = let the service pick the best engine.
sizestringNo"1024x1024"Output dimensions. Examples: "1024x1024" (square), "1024x1536" (portrait), "1536x1024" (landscape).
modelstringNo,Generate with one of your custom style models. Pass the model id returned by POST /v1/models/train. The model must be ready. The image comes out in your trained look; the model's trigger word is applied automatically. Billed at the normal per-image rate. Omit it for standard generation.

Response

Lead on creditsUsed (what this call cost you) and usage.creditsLeft (your remaining balance). Those two fields are your source of truth for billing.

FieldTypeDescription
creditsUsedintegerWhat you were charged for this call, in image credits. A 1024×1024 image is 1 credit; credits scale with megapixels (rounded up, minimum 1). This is the number that matters for your spend.
usage.creditsLeftintegerYour image-credit balance after this call. null if the key is not attached to an account wallet.
formatstring"url" or "svg". Tells you how to read the image field.
imagestringIf format is "url": an https URL to the PNG/JPG, fetch/download it. If format is "svg": the raw SVG markup string.
enginestringWhich engine served the request. Informational only, not needed to use the result.
modelstringPresent only when you generated with one of your custom style models, echoes the model id used.
usage.imagesintegerLifetime image count on this key (not a charge). Use creditsUsed for per-call spend.
providerCostnumberInternal: our raw provider cost in USD for this call, surfaced for transparency only. It is not your price — you pay in credits (creditsUsed). Safe to ignore.

Example response (generate mode):

{
  "creditsUsed": 1,
  "format": "url",
  "image": "https://pixelfireman.com/i/abc123.png",
  "engine": "flux-serverless",
  "usage": { "images": 42, "creditsLeft": 1358 },
  "providerCost": 0.003
}

Example response (draw mode):

{
  "creditsUsed": 1,
  "format": "svg",
  "image": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1024 1024\">…</svg>",
  "engine": "draw",
  "usage": { "images": 43, "creditsLeft": 1357 },
  "providerCost": 0
}

A larger image uses more credits: a 1536×1536 request returns "creditsUsed": 3 (credits ≈ megapixels, rounded up). Always read creditsUsed from the response rather than assuming 1.

Generate a video

POST/v1/videos

Generate a short (~5-second) video clip. Same Bearer auth as /v1/images. Send a JSON body with a Content-Type: application/json header.

Video is billed from a separate “video minutes” wallet, it does not spend image credits. See Video minutes & pricing.

Request parameters

FieldTypeRequiredDefaultDescription
promptstringYes,What to generate (a short ~5-second clip).
tierstringNo"economy"One of "economy", "standard", "premium". Higher tiers cost more minutes and look better.

Response

FieldTypeDescription
tierstringThe tier that served the request.
formatstringAlways "url" for video.
videostringAn https URL to the generated MP4, fetch/download it.
minutesUsednumberVideo minutes spent on this clip.
usageobject{ minutesLeft }, the video-minutes wallet balance after this request.

Example response:

{
  "tier": "economy",
  "format": "url",
  "video": "https://pixelfireman.com/v/abc123.mp4",
  "minutesUsed": 30,
  "usage": { "minutesLeft": 470 }
}

“Minutes” here are wallet credits, not the clip length, every clip is about 5 seconds long regardless of tier.

Async rendering (premium / slow clips)

Fast tiers (economy, standard) usually finish within the request and return the response above. Premium (Kling) and any slow render instead return HTTP 202 with a job you poll, so the connection never times out:

{
  "status": "processing",
  "tier": "premium",
  "aspect": "16:9",
  "jobId": "vj_8f3k2x9a",
  "poll": "https://pixelfireman.com/v1/videos/vj_8f3k2x9a"
}

Poll GET /v1/videos/:id (with your Authorization header) every few seconds until status is completed:

# GET https://pixelfireman.com/v1/videos/vj_8f3k2x9a
{ "status": "processing" }          # keep polling
{ "status": "completed", "video": "https://pixelfireman.com/v/abc.mp4", "minutesUsed": 150 }
{ "status": "failed", "error": "..." }   # on error

Minutes are billed only when the job completes, never on a failed render. A given tier always returns either the inline response or a 202 job, so branch on the HTTP status.

Custom style models

Train a model on your own images so every generation comes out in your look, your product, or your character. One-time training fee, then normal per-image generation. See the custom model page for the full overview.

Train a model

POST/v1/models/train

Same Bearer auth as /v1/images. Send a JSON body with a Content-Type: application/json header. Training is a long-running job, the call returns immediately with a model id you poll, and the one-time training fee is charged on submit (refunded if training fails).

FieldTypeRequiredDefaultDescription
namestringYes,A label for the model, shown in your account.
imagesstring[]Yes,10 to 30 training images as base64 data (or https URLs). Fewer than 10 or more than 30 is rejected with 400.
isStylebooleanNotruetrue for a brand or style look, false for a specific subject, product or face.
triggerWordstringNoautoThe word that activates your model in a prompt. Auto-generated if omitted; applied for you when you pass model to /v1/images.

Example request and response:

curl -s https://pixelfireman.com/v1/models/train \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"My Brand Look","isStyle":true,"images":["data:image/jpeg;base64,...", "..."]}'

# →
{
  "modelId": "mdl_8f3k2x9a",
  "status": "training",
  "poll": "https://pixelfireman.com/v1/models/mdl_8f3k2x9a"
}

List and poll your models

GET/v1/models

Lists all models on your account with their status. Send the same Authorization header, no body.

GET/v1/models/:id

Poll one model until status is ready, then use its id as the model param on /v1/images:

# GET https://pixelfireman.com/v1/models/mdl_8f3k2x9a
{ "modelId": "mdl_8f3k2x9a", "status": "training" }     # keep polling
{ "modelId": "mdl_8f3k2x9a", "status": "ready", "name": "My Brand Look", "triggerWord": "mybrandlook" }
{ "modelId": "mdl_8f3k2x9a", "status": "failed", "error": "..." }   # training fee refunded

Generate with your model

Once status is ready, pass the id as model to /v1/images. The trigger word is applied automatically, so prompt it like normal:

curl -s https://pixelfireman.com/v1/images \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"a plated pasta dish on a marble table","model":"mdl_8f3k2x9a"}'

Generating with a model bills at the normal per-image rate, the one-time training fee is charged only once, at training. A model id that is not ready, or belongs to another account, is rejected.

Code examples

Replace YOUR_API_KEY with your real key (pf_live_…) in each example.

cURL

# 1. Generate the image
curl -s https://pixelfireman.com/v1/images \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"a red fox sitting in autumn leaves","mode":"generate","size":"1024x1024"}' \
  -o response.json

# 2. If format is "url", pull the URL out and download the file
IMG_URL=$(grep -o '"image":"[^"]*"' response.json | sed 's/"image":"//;s/"//')
curl -s "$IMG_URL" -o output.png
echo "Saved output.png"

Node.js (fetch, Node 18+)

import fs from "node:fs";

const API_KEY = "YOUR_API_KEY";

const res = await fetch("https://pixelfireman.com/v1/images", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    prompt: "a red fox sitting in autumn leaves",
    mode: "generate",
    size: "1024x1024",
  }),
});

if (!res.ok) {
  const err = await res.json().catch(() => ({}));
  throw new Error(`HTTP ${res.status}: ${err.error || "request failed"}`);
}

const data = await res.json();
console.log("credits used:", data.creditsUsed, "credits left:", data.usage.creditsLeft);

if (data.format === "url") {
  // Download and save the PNG/JPG
  const img = await fetch(data.image);
  const buf = Buffer.from(await img.arrayBuffer());
  fs.writeFileSync("output.png", buf);
  console.log("Saved output.png");
} else {
  // SVG markup, save directly
  fs.writeFileSync("output.svg", data.image);
  console.log("Saved output.svg");
}

Python (requests)

import requests

API_KEY = "YOUR_API_KEY"

res = requests.post(
    "https://pixelfireman.com/v1/images",
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    },
    json={
        "prompt": "a red fox sitting in autumn leaves",
        "mode": "generate",
        "size": "1024x1024",
    },
    timeout=120,
)

if not res.ok:
    raise SystemExit(f"HTTP {res.status_code}: {res.json().get('error', 'request failed')}")

data = res.json()
print("credits used:", data["creditsUsed"], "credits left:", data["usage"]["creditsLeft"])

if data["format"] == "url":
    # Download and save the PNG/JPG
    img = requests.get(data["image"], timeout=120)
    with open("output.png", "wb") as f:
        f.write(img.content)
    print("Saved output.png")
else:
    # SVG markup, save directly
    with open("output.svg", "w", encoding="utf-8") as f:
        f.write(data["image"])
    print("Saved output.svg")

A video request returns one of two shapes depending on how long the render takes: an inline 200 with the video URL, or an HTTP 202 job to poll (common for premium / Kling). The examples below handle both, branch on the HTTP status.

cURL, video (handles inline 200 and async 202)

# 1. Submit the render. -w captures the HTTP status after the body.
HTTP=$(curl -s -w '\n%{http_code}' --max-time 120 https://pixelfireman.com/v1/videos \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"a red fox trotting through autumn leaves","tier":"premium"}')
BODY=$(printf '%s' "$HTTP" | sed '$d')
CODE=$(printf '%s' "$HTTP" | tail -n1)

if [ "$CODE" = "202" ]; then
  # Async: grab the poll URL and loop until the job completes.
  POLL=$(printf '%s' "$BODY" | grep -o '"poll":"[^"]*"' | sed 's/"poll":"//;s/"//')
  echo "Rendering, polling $POLL"
  while true; do
    JOB=$(curl -s "$POLL" -H "Authorization: Bearer YOUR_API_KEY")
    STATUS=$(printf '%s' "$JOB" | grep -o '"status":"[^"]*"' | sed 's/"status":"//;s/"//')
    if [ "$STATUS" = "completed" ]; then BODY="$JOB"; break; fi
    if [ "$STATUS" = "failed" ]; then echo "Render failed: $JOB"; exit 1; fi
    sleep 5
  done
fi

# 2. Pull the MP4 URL out of the completed body and download it.
VID_URL=$(printf '%s' "$BODY" | grep -o '"video":"[^"]*"' | sed 's/"video":"//;s/"//')
curl -s "$VID_URL" -o output.mp4
echo "Saved output.mp4"

Node.js (fetch, Node 18+), video (handles inline 200 and async 202)

import fs from "node:fs";

const API_KEY = "YOUR_API_KEY";
const AUTH = { "Authorization": `Bearer ${API_KEY}` };

const res = await fetch("https://pixelfireman.com/v1/videos", {
  method: "POST",
  headers: { ...AUTH, "Content-Type": "application/json" },
  body: JSON.stringify({
    prompt: "a red fox trotting through autumn leaves",
    tier: "premium",
  }),
});

let data = await res.json().catch(() => ({}));

// 202 = still rendering. Poll the job URL until it completes.
if (res.status === 202) {
  console.log("Rendering, job:", data.jobId);
  while (true) {
    await new Promise((r) => setTimeout(r, 5000));
    const poll = await fetch(data.poll, { headers: AUTH });
    const job = await poll.json();
    if (job.status === "completed") { data = job; break; }
    if (job.status === "failed") throw new Error(`render failed: ${job.error}`);
    // else status === "processing", keep polling
  }
} else if (!res.ok) {
  throw new Error(`HTTP ${res.status}: ${data.error || "request failed"}`);
}

console.log("minutes used:", data.minutesUsed, "minutes left:", data.usage.minutesLeft);

// Now data.video is the finished MP4, download and save it.
const vid = await fetch(data.video);
const buf = Buffer.from(await vid.arrayBuffer());
fs.writeFileSync("output.mp4", buf);
console.log("Saved output.mp4");

Handling the response

Always branch on format:

mode: "draw" typically returns svg; mode: "generate" typically returns url. With mode: "auto" always check format before using image.

Check your usage

GET/v1/usage

Send the same Authorization (or x-api-key) header. No body.

curl -s https://pixelfireman.com/v1/usage \
  -H "Authorization: Bearer YOUR_API_KEY"

Response:

{
  "name": "My App Key",
  "images": 142,
  "lastUsed": "2026-10-01T18:22:04.000Z",
  "daily": { "2026-10-01": 12, "2026-09-30": 30 },
  "credits": 1358,
  "minutes": 470,
  "providerCost": 0.43
}
FieldTypeDescription
creditsintegerImage credits remaining in the account wallet. null if the key has no owner. This is your balance.
minutesintegerVideo minutes remaining in the account wallet. null if the key has no owner.
namestringLabel for this key.
imagesintegerLifetime images generated with this key (a counter, not a charge).
lastUsedstringISO timestamp of the last request.
dailyobjectImages generated per day (date → count).
costnumberInternal, lifetime raw provider cost in USD. Not your price. Safe to ignore.

Service details

The specifics a production integration needs, stated plainly.

TopicDetail
Image model + versionPhoto generation (mode: "generate") runs on FLUX.1 [schnell] served over fal.ai. Vector art (mode: "draw") is produced from a Claude model and returned as SVG. The engine field reports which path served your call. The underlying model can be upgraded; the request/response contract above stays stable.
Supported sizesAny WIDTHxHEIGHT up to 2048×2048. Larger is rejected with 400 and a maxSize field. draw mode ignores size (flat SVG). Common values: 1024x1024, 1024x1536, 1536x1024.
Credit roundingCredits for an image ≈ its megapixels, rounded up, minimum 1. 1024×1024 (1.05 MP) = 1 credit; 1536×1536 (2.36 MP) = 3 credits. Image edits (/v1/images/edit) cost more per megapixel. Always read creditsUsed from the response.
Rate & concurrency limits120 requests per minute and 6 concurrent in-flight requests per key (defaults). Over the per-minute limit returns 429 with a Retry-After: 60 header; over the concurrency cap returns 429 asking you to retry shortly. Need higher limits? Contact us.
Latency (typical, not guaranteed)Images usually return in 3–10 seconds. Video economy/standard often return inline within the request; premium (Kling) commonly takes ~4–5 minutes and is returned as an async 202 job to poll. Latency varies with size, prompt, and upstream load.
Retry & timeoutTreat 500 and 429 as retryable with exponential backoff (start ~2s). For video, set a client timeout of at least 120s on the POST, then switch to polling if you get a 202. Do not retry 400/401/451 without changing the request.
URL retentionImage and video url results are hosted links. Treat them as temporary, download and store the bytes if you need a permanent copy. Do not assume a returned URL lives forever.
Billing on failed / blocked requestsYou are not charged when a request fails. 401 (bad key), 400 (bad input), 451 (moderation block), and 500 (server error) spend no credits or minutes. Video minutes are billed only when a render completes, never on a failed or still-processing job. A custom-model training fee is refunded if training fails.
Key rotationCreate, name, and revoke keys in the account dashboard. To rotate: create a new key, deploy it, then revoke the old one, a revoked key immediately returns 401. Multiple live keys per account are supported (e.g. one per service); all draw from the same credit wallet.
Status & incidentsFor outages or degraded performance, and to report an incident, email support@pixelfireman.com. Prepaid credits and minutes never expire, so a transient outage does not cost you balance.

Errors

Errors return the matching HTTP status plus a JSON body { "error": "..." }.

StatusMeaningWhat to do
400Bad requestA required field is missing (usually prompt) or tier is not one of economy/standard/premium. Fix the body.
401Invalid or missing API keyCheck the Authorization / x-api-key header and your key.
402Out of credits / minutesImage calls: out of credits. Video calls: not enough video minutes. Buy more in the dashboard.
451Blocked by content moderationThe prompt was rejected. No real public figures, no sexual or illegal content. Reword and retry.
500Server errorTransient, generation failed. Retry with backoff; contact support if it persists. Video minutes are not charged on a failed generation.

Example error body:

{ "error": "out of credits" }

Credits & pricing

PackImagesPer 1,000
$101,400~$7.14
$253,750~$6.67
$508,000~$6.25

Buy packs with PayPal in the account dashboard. That is about $0.0071 per image at the Pro pack.

Video minutes & pricing

Video is billed from a separate “video minutes” wallet, it does not touch your image credits. 1 minute = $0.01. “Minutes” are wallet credits, not the length of the clip, every clip is about 5 seconds long.

Each ~5-second clip costs:

TierMinutesCost per clip
economy30$0.30
standard60$0.60
premium150$1.50

Buy video minutes with PayPal in the account dashboard. Minutes never expire.

PackMinutes
$5500
$202,000
$505,500
$10012,000

Integrating into your app / AI agent

This is a standard REST API with Bearer authentication, so it drops into any stack: