Every image model behind one endpoint: your agent asks for an image; Pixfaro holds the provider accounts, keys, retries and format differences, and bills one prepaid balance.
https://api.pixfaro.comhttps://mcp.pixfaro.com/mcpllms.txtBase URL https://api.pixfaro.com. JSON everywhere. Errors use one envelope:
{ "error": { "code": "insufficient_balance", "message": "…", "request_id": "rq_a1c4f0" } }
Every response carries an x-request-id header — include it when you contact
support. Money is always a USD decimal string ("0.080", never a float).
Times are ISO 8601 UTC.
| Principal | How |
|---|---|
| API key | Authorization: Bearer pf_live_… |
| MCP | OAuth 2.1 at https://mcp.pixfaro.com (remote) or PIXFARO_KEY env (stdio) |
Keys are created on the dashboard. Scopes:
generate (default — image endpoints only) and full (adds balance, billing,
logos, and brand-kit management).
New accounts must verify their email before generating — unverified calls
return 403 email_unverified. The $1 welcome credit lands on verification
(instantly for Google/GitHub signups).
Generate an image. Sync — the response is the finished image.
{
"model": "nano-banana-2",
"prompt": "a lighthouse at night, minimal flat style",
"aspect_ratio": "16:9",
"resolution": "1K",
"overlay": "default"
}
| Field | Required | Notes |
|---|---|---|
model |
yes | id from GET /v1/models |
prompt |
yes | 1–4000 chars |
aspect_ratio |
no | "w:h", e.g. "1:1" (default), "16:9", "9:16" |
resolution |
no | per-model tiers from GET /v1/models prices (default 1K) |
overlay |
no | corner branding: "default" applies your saved brand kit, or an explicit object (below) |
n > 1 and response_format other than "url" are not supported yet — one
image per request, delivered as a hosted URL (your agent's context never
carries base64).
200:
{
"id": "img_8f2a…", "url": "https://api.pixfaro.com/i/…", "model": "nano-banana-2",
"resolution": "1K", "latency_ms": 10400, "cost": "0.080", "balance_after": "12.32",
"overlay_applied": false, "request_id": "rq_a1c4f0"
}
Errors: 402 insufficient_balance (body includes balance, needed,
topup_url), 400 invalid_model / invalid_prompt / invalid_request,
403 email_unverified, 409 model_disabled, 429 rate_limited,
502 provider_failed — no charge, and the body says so explicitly:
"charged": false.
Brands a corner of the image with your handle or logo. Exactly one of text
(≤ 64 chars) or logo_id (a logo_… from POST /v1/logos):
{ "overlay": { "text": "@yourbrand", "position": "bottom-right", "opacity": 0.9 } }
Optional: position (default bottom-right), opacity (0.2–1.0; text
defaults to 0.9, logos to 1.0), logo_style (sticker — default — | shadow
| outline | none), size,
margin, and for text font / weight / color ("auto" picks ink or paper
per corner brightness). Save your defaults once via PUT /v1/brand-kit, then
"overlay": "default" everywhere. A bad overlay fails the request before
generation — you are not charged.
Edit a previous generation with a natural-language instruction. Same response shape as generations.
{ "model": "nano-banana-2", "image": "img_8f2a…", "instruction": "make the sky darker" }
image takes an img_… id from a previous generation (URLs are not
accepted).instruction: 1–4000 chars — what to change; everything else stays put.aspect_ratio keeps the source image's shape (generations
default to 1:1).resolution inherits — and bills at — the source image's tier.Public, no auth. Cached ~5 min.
[{ "id": "nano-banana-2", "name": "Nano Banana 2",
"best_for": "general-purpose images: blog art, social posts, mockups",
"p50_ms": 10700, "p95_ms": 13600, "price": "0.080",
"prices": { "1K": "0.080", "2K": "0.121", "4K": "0.181" },
"mode": "sync", "enabled": true }]
price is the 1K figure; prices maps every supported resolution tier to its
retail price. enabled: false marks models that are coming soon (calling one
returns 409 model_disabled).
Typeset a card from a template instead of generating one — flat $0.02, ~2s, sharp type. Full reference: Card templates.
curl https://api.pixfaro.com/v1/renders -H "Authorization: Bearer pf_live_…" \
-H "Content-Type: application/json" \
-d '{"template":"quote-card","slots":{"quote":"Ship it.","handle":"@you"},"style":"auto"}'
A render is a generation: it returns an img_…, lands in your history, and is
debited and refunded by the same rules.
Public, no auth — the card catalog with slots, style axes and price. See Card templates.
Requires scope full (a default generate key gets 403 insufficient_scope).
{ "balance": "12.32" }
full)| Endpoint | What |
|---|---|
POST /v1/topups |
{ "amount_usd": 25 } → { "checkout_url": … } (Stripe; integer 5–1000, default 10) |
GET/PATCH /v1/autoreload |
auto top-up: { enabled, threshold, amount, monthly_cap } — explicit opt-in |
POST /v1/logos |
upload a transparent PNG (≤ 1 MB, ≤ 2048px/side, ≤ 10 live logos) — raw binary body, or JSON { "image": "<base64 or data URI>", "name"? } |
GET /v1/logos · DELETE /v1/logos/:id |
list / remove logos |
GET/PUT/DELETE /v1/brand-kit |
saved overlay defaults used by "overlay": "default" |
POST /v1/assets |
upload a PNG/JPEG (≤ 5 MB, ≤ 4096px/side, ≤ 100 live) for card slots — raw binary with ?kind=, or JSON { "kind", "data" } |
GET /v1/assets · DELETE /v1/assets/:id |
list / remove assets |
GET/PUT/DELETE /v1/brand-kit/identity |
name, handle, avatar and palette — what "default" resolves to in a card slot |
Top-ups, keys, and usage are also on the dashboard.
Public, no auth — report a generated image that violates our
acceptable use policy:
{ "image_url", "reason", "details"?, "reporter_email"? } — image_url must
be a Pixfaro-served image link (…/i/…); reason is one of csam,
nonconsensual, violence_hate, infringement, other.
Fair-use limits apply on auth and abuse-prone endpoints (429 rate_limited).
There is no fixed per-key request cap today; sustained high volume is welcome —
talk to us if you're planning a big batch.
Path-versioned (/v1/). Additive changes (new optional fields) don't bump the
version; breaking changes go to /v2/ with 6 months of /v1/ support. The
x-pixfaro-deprecation header announces sunsets ahead of time.
Not every image needs a model. A card is typeset, not generated: you send text, we render it in a real browser and hand back a hosted PNG. Flat $0.02, about two seconds, and the type comes out sharp — which is the one thing image models still get wrong.
Same key, same balance, same MCP as everything else. A render returns an
img_… at /i/…, shows up in your history, and can be downloaded, edited or
overlaid like any other image.
curl https://api.pixfaro.com/v1/renders \
-H "Authorization: Bearer pf_live_…" -H "Content-Type: application/json" \
-d '{"template":"quote-card",
"slots":{"quote":"Stop mistaking silence for nobody'\''s home.","handle":"@dashaworks"},
"style":"auto"}'
{ "id": "img_4e1807777fc895e5642a",
"url": "https://api.pixfaro.com/i/usr_…/img_….png",
"kind": "render", "template": "quote-card", "size": "16:9",
"style": { "palette": "charcoal", "font": "schibsted", "layout": "bold-left", "shadow": true },
"cost": "0.020", "balance_after": "12.30", "latency_ms": 1412 }
Public, no auth. Every template with its slots, style axes and exact price — enough to build a form from, which is what the dashboard does.
{ "templates": [
{ "id": "quote-card", "name": "Quote card", "tier": "simple", "price": "0.020",
"best_for": "one line worth quoting — hooks, testimonials, pull quotes",
"sizes": ["16:9", "1:1", "4:5", "og"],
"slots": [ { "name": "quote", "type": "text", "required": true, "max": 280, "hint": "the line itself" } ],
"styles": { "palettes": ["paper", "charcoal", …], "fonts": ["schibsted", …], "layouts": ["bold-left", …], "shadow": true } } ] }
| Field | What |
|---|---|
template |
id from GET /v1/templates |
slots |
the template's slot values (below) |
size |
16:9 1200×675 · 1:1 1080×1080 · 4:5 1080×1350 · og 1200×630 — default 16:9 |
style |
omit for the template default, "auto", "brand", or an object of axes |
scale |
device pixel ratio: 1, 2 (default) or 3 (+$0.01) |
overlay |
optional corner branding, exactly as on a generation |
Scope generate is enough — a render is a generation.
Text slots take a string and are rejected (never truncated) past their max,
so you never pay for a card that quietly lost its last sentence. Image slots
take only Pixfaro-hosted references:
| Value | Meaning |
|---|---|
"ast_…" or { "asset": "ast_…" } |
an asset you uploaded (POST /v1/assets) |
"https://api.pixfaro.com/i/…" |
one of your own generated images |
"data:image/png;base64,…" |
inline PNG or JPEG, ≤ 2 MB — validated as an image, not as a string |
"default" |
from your brand identity (PUT /v1/brand-kit/identity) |
Arbitrary URLs are refused: the renderer is a real browser, and a fetchable
slot would make every card a probe into whatever that browser can reach.
"default" also works on the name and handle text slots.
"auto" picks a random combination and avoids the one you used last on this
template — two cards in a row that differ only in wording read as a bug, not as
a series. "brand" paints your saved colours (identity.palette). An object
pins whichever axes you name and defaults the rest:
{ "style": { "palette": "charcoal", "font": "instrument", "layout": "block", "shadow": true } }
The chosen combination comes back in the response, so a card you like can be re-rendered exactly.
| Code | Meaning |
|---|---|
invalid_template |
unknown id — see GET /v1/templates |
invalid_slot |
with a slot field naming which one |
asset_not_found |
unknown, deleted, or not yours (404, never an existence oracle) |
render_busy |
429, renderer at capacity — retry; not charged |
render_failed |
502; refunded, and "charged": false says so |
Scope full. PNG or JPEG, ≤ 5 MB, ≤ 4096 px/side, 100 live per account. Raw
binary with ?kind=avatar, or JSON:
curl https://api.pixfaro.com/v1/assets \
-H "Authorization: Bearer pf_live_…" -H "Content-Type: application/json" \
-d '{"kind":"avatar","data":"data:image/png;base64,…"}'
→ { "id": "ast_…", "kind": "avatar", "width": 512, "height": 512 }
GET /v1/assets?kind=avatar lists yours; DELETE /v1/assets/:id removes one.
Assets are private — they are never served at a public URL; the renderer reads
them server-side and inlines them into the card.
Uploading a photo of a person means you have the right to use it. Personas are self-likeness only, and attributing words to someone who did not say them is impersonation whatever the styling — see the acceptable use policy.
PUT /v1/brand-kit/identity (scope full) is what "default" resolves to:
{ "name": "Dasha", "handle": "@dashaworks", "avatar_asset_id": "ast_…",
"palette": { "bg": "#101014", "ink": "#fdfdfb", "accent": "#b4e33d" } }
It sits beside the overlay brand kit (/v1/brand-kit) and is cleared
separately.
| id | Tier | Slots |
|---|---|---|
quote-card |
simple · $0.02 | quote, name?, handle?, avatar? |
post-card |
simple · $0.02 | text, name, handle?, platform?, date?, avatar? |
More arrive with composition — an image slot you can fill with a generation in the same request.
Cards are set in self-hosted SIL OFL 1.1 faces — no font CDN in the render path, so a card is deterministic and its typography does not depend on a third party being up. The licence text ships beside every family we serve.
| Face | Class | Cyrillic | Licence |
|---|---|---|---|
| Schibsted Grotesk | sans | — | OFL-1.1 |
| Space Grotesk | sans | — | OFL-1.1 |
| Manrope | sans | ✓ | OFL-1.1 |
| Inter | sans | ✓ | OFL-1.1 |
| Syne | sans | — | OFL-1.1 |
| Bricolage Grotesque | sans | — | OFL-1.1 |
| Fraunces | serif | — | OFL-1.1 |
| Playfair Display | serif | ✓ | OFL-1.1 |
| DM Serif Display | serif | — | OFL-1.1 |
| Instrument Serif | serif | — | OFL-1.1 |
| IBM Plex Mono | mono | ✓ | OFL-1.1 |
Cyrillic text set in a Latin-only face is switched to the closest Cyrillic-capable face of the same class (Manrope, Playfair Display or IBM Plex Mono), rather than falling through to a system font mid-headline.
One MCP connection gives your agent every major image model. Two ways in:
| Transport | For | How |
|---|---|---|
| Remote (streamable HTTP + OAuth 2.1) | claude.ai, Claude Desktop, any HTTP-capable client | add https://mcp.pixfaro.com/mcp — the client runs the OAuth flow, you sign in with your Pixfaro account |
| Local (stdio, npm) | Claude Code, Cursor, Windsurf, config-file setups | npx -y @pixfaro/mcp with PIXFARO_KEY in env |
Source: github.com/pixfaro/mcp — the client is intentionally small enough to read before you hand it a key.
The remote server is the easiest path on every desktop app: paste one URL, sign in with your Pixfaro account in the browser window that opens, done. No API key ends up in any config file.
Claude (claude.ai, Claude Desktop — any plan):
https://mcp.pixfaro.com/mcp → Add → sign in when prompted.
On Team/Enterprise an org Owner adds it once for everyone
(Organization settings → Connectors → Custom → Web).Cursor — one click:
or by hand: Cursor Settings → MCP → Add new MCP server, type URL,
address https://mcp.pixfaro.com/mcp — Cursor runs the OAuth flow itself.
ChatGPT (Pro / Plus / Business / Edu):
pixfaro, URL https://mcp.pixfaro.com/mcp,
transport streamable HTTP, authentication OAuth → sign in.Claude Code:
claude mcp add pixfaro -e PIXFARO_KEY=pf_live_… -- npx -y @pixfaro/mcp
Windsurf or any stdio client (config file; key from the dashboard):
{
"mcpServers": {
"pixfaro": {
"command": "npx",
"args": ["-y", "@pixfaro/mcp"],
"env": { "PIXFARO_KEY": "pf_live_…" }
}
}
}
Clients that speak streamable HTTP can also use the remote form in the same file — no key, OAuth on first use:
{ "mcpServers": { "pixfaro": { "url": "https://mcp.pixfaro.com/mcp" } } }
| Tool | What it does |
|---|---|
generate_image |
prompt (+ optional model, aspect_ratio, resolution, overlay — incl. "default" for your saved brand kit) → hosted image URL, with cost and remaining balance in the reply |
edit_image |
natural-language edit of a previous generation by its img_… id (keeps the source's shape unless you pass aspect_ratio) |
render_template |
typeset card from a template — slots in, hosted PNG out (flat $0.02, ~2s); sharper type than any model, and cheaper |
upload_asset |
store a PNG/JPEG (≤ 5 MB) and get an ast_… id to pass into a card slot |
list_models |
live models with price, latency, and what each is best for |
list_templates |
card templates with their slots, style axes and exact price |
get_balance |
current prepaid balance — needs scope full (stdio: a full-scope key; remote OAuth grants generate by default) |
Replies are agent-readable text carrying a hosted URL — never base64, so your agent's context stays small. When the balance runs low, replies append a top-up warning.
| Variable | Meaning |
|---|---|
PIXFARO_KEY |
API key (pf_live_…) from pixfaro.com |
PIXFARO_API_URL |
endpoint override (staging / self-hosted) |
The same repo ships the pixfaro CLI for scripts, CI, and pipelines — the
literal one-command time-to-first-image:
export PIXFARO_KEY=pf_live_…
npx pixfaro gen "a lighthouse at night, minimal flat style" -a 16:9 -o cover.png
npx pixfaro edit img_8f2a… "make the sky darker" -o v2.png
npx pixfaro models
npx pixfaro balance
-o downloads the image; without it the CLI prints the hosted URL. A failed
download never swallows the URL you already paid for.