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.
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" }
}
}
}
| Tool | What it does |
|---|---|
generate_image | prompt → image URL (or SVG). Args: prompt, size, mode. |
generate_video | prompt → short clip URL. Args: prompt, tier. |
check_balance | returns 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.
Paste it into your AI assistant and it will know how to call PixelFireman.
https://pixelfireman.comgenerate) and clean flat SVG vector art (draw), or let the service pick (auto).POST /v1/videos (billed from a separate video-minutes wallet, see Generate a video).Coming from OpenAI’s image API? See the migration guide for a side-by-side request/response mapping.
pf_live_xxxxx….Keep your key secret. Treat it like a password, anyone with it can spend your credits.
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.
Send a JSON body with a Content-Type: application/json header.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
prompt | string | Yes | , | What to generate. |
mode | string | No | "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. |
size | string | No | "1024x1024" | Output dimensions. Examples: "1024x1024" (square), "1024x1536" (portrait), "1536x1024" (landscape). |
model | string | No | , | 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. |
Lead on creditsUsed (what this call cost you) and usage.creditsLeft (your remaining balance). Those two fields are your source of truth for billing.
| Field | Type | Description |
|---|---|---|
creditsUsed | integer | What 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.creditsLeft | integer | Your image-credit balance after this call. null if the key is not attached to an account wallet. |
format | string | "url" or "svg". Tells you how to read the image field. |
image | string | If format is "url": an https URL to the PNG/JPG, fetch/download it. If format is "svg": the raw SVG markup string. |
engine | string | Which engine served the request. Informational only, not needed to use the result. |
model | string | Present only when you generated with one of your custom style models, echoes the model id used. |
usage.images | integer | Lifetime image count on this key (not a charge). Use creditsUsed for per-call spend. |
providerCost | number | Internal: 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 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.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
prompt | string | Yes | , | What to generate (a short ~5-second clip). |
tier | string | No | "economy" | One of "economy", "standard", "premium". Higher tiers cost more minutes and look better. |
| Field | Type | Description |
|---|---|---|
tier | string | The tier that served the request. |
format | string | Always "url" for video. |
video | string | An https URL to the generated MP4, fetch/download it. |
minutesUsed | number | Video minutes spent on this clip. |
usage | object | { 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.
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.
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.
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).
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes | , | A label for the model, shown in your account. |
images | string[] | Yes | , | 10 to 30 training images as base64 data (or https URLs). Fewer than 10 or more than 30 is rejected with 400. |
isStyle | boolean | No | true | true for a brand or style look, false for a specific subject, product or face. |
triggerWord | string | No | auto | The 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"
}
Lists all models on your account with their status. Send the same Authorization header, no body.
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
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.
Replace YOUR_API_KEY with your real key (pf_live_…) in each example.
# 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"
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");
}
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.
# 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"
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");
Always branch on format:
format: "url", image is an https link to a PNG/JPG. Fetch it to download the bytes, or store the URL and serve it directly. Download it if you need a permanent copy; do not assume the URL lives forever.format: "svg", image is the raw SVG markup. Write it to a .svg file, inline it into HTML, or render it however you like. No second request needed.mode: "draw" typically returns svg; mode: "generate" typically returns url. With mode: "auto" always check format before using image.
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
}
| Field | Type | Description |
|---|---|---|
credits | integer | Image credits remaining in the account wallet. null if the key has no owner. This is your balance. |
minutes | integer | Video minutes remaining in the account wallet. null if the key has no owner. |
name | string | Label for this key. |
images | integer | Lifetime images generated with this key (a counter, not a charge). |
lastUsed | string | ISO timestamp of the last request. |
daily | object | Images generated per day (date → count). |
cost | number | Internal, lifetime raw provider cost in USD. Not your price. Safe to ignore. |
The specifics a production integration needs, stated plainly.
| Topic | Detail |
|---|---|
| Image model + version | Photo 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 sizes | Any 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 rounding | Credits 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 limits | 120 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 & timeout | Treat 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 retention | Image 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 requests | You 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 rotation | Create, 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 & incidents | For 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 return the matching HTTP status plus a JSON body { "error": "..." }.
| Status | Meaning | What to do |
|---|---|---|
400 | Bad request | A required field is missing (usually prompt) or tier is not one of economy/standard/premium. Fix the body. |
401 | Invalid or missing API key | Check the Authorization / x-api-key header and your key. |
402 | Out of credits / minutes | Image calls: out of credits. Video calls: not enough video minutes. Buy more in the dashboard. |
451 | Blocked by content moderation | The prompt was rejected. No real public figures, no sexual or illegal content. Reword and retry. |
500 | Server error | Transient, 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" }
creditsUsed.| Pack | Images | Per 1,000 |
|---|---|---|
| $10 | 1,400 | ~$7.14 |
| $25 | 3,750 | ~$6.67 |
| $50 | 8,000 | ~$6.25 |
Buy packs with PayPal in the account dashboard. That is about $0.0071 per image at the Pro pack.
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:
| Tier | Minutes | Cost per clip |
|---|---|---|
economy | 30 | $0.30 |
standard | 60 | $0.60 |
premium | 150 | $1.50 |
Buy video minutes with PayPal in the account dashboard. Minutes never expire.
| Pack | Minutes |
|---|---|
| $5 | 500 |
| $20 | 2,000 |
| $50 | 5,500 |
| $100 | 12,000 |
This is a standard REST API with Bearer authentication, so it drops into any stack:
fetch, axios, requests, curl, Go net/http, PHP curl, and so on. One POST /v1/images with a JSON body and an Authorization: Bearer header is all you need.generate_image(prompt, mode, size), that performs the POST /v1/images call and returns format + image. The small, flat request/response shape maps cleanly to a function schema. Have the agent branch on format (url vs svg) when handling the result.GET /v1/usage to track spend, and handle 402 by prompting the user to top up. Handle 451 by surfacing the moderation message so the agent can reword the prompt and retry.