Pixly Developer Docs
Everything Pixly's app can generate — virtual staging, photo enhancement, cinematic video — is callable by AI agents and scripts through one MCP endpoint, with an OAuth sign-in flow and a CLI on top. This page is the complete reference; the machine-readable mirror lives at /docs.md.
Getting started
The fastest path, end to end:
- Create an API key in Settings → API keys (shown once — copy it immediately). An active Pixly subscription is required.
- Connect a client (one-step setup per client) or install the CLI:
npx pixly-cli help - Ask for a staged room — or run
pixly stage photo.jpg --style scandinavian
Generations spend the account's normal credit balance — same pipeline, quality, and pricing as the app, and every result also appears in the Pixly Library.
Authentication
Two credential types, both sent as Authorization: Bearer <token>:
- API keys (pixly_sk_…) — for Claude Code, Cursor, scripts, and CI. Created in Settings, stored hashed, revocable instantly. Keys can never mint other keys.
- OAuth 2.1 (pixly_at_… access tokens) — for clients with a sign-in flow, like claude.ai custom connectors. Public clients with PKCE S256 only; single-use authorization codes; refresh-token rotation.
OAuth surface (discovery starts from the 401 WWW-Authenticate header):
| Parameter | Type | Required | Description |
|---|---|---|---|
| GET /.well-known/oauth-authorization-server | — | no | RFC 8414 authorization-server metadata (endpoints, PKCE methods, grant types) |
| GET /.well-known/oauth-protected-resource/api/mcp | — | no | RFC 9728 protected-resource metadata — advertised in the 401 WWW-Authenticate header |
| POST /api/oauth/register | — | no | RFC 7591 dynamic client registration (open; public clients; body: client_name, redirect_uris[]) |
| GET /oauth/authorize | — | no | User consent page — query: response_type=code, client_id, redirect_uri, state, code_challenge, code_challenge_method=S256 |
| POST /api/oauth/token | — | no | Token endpoint — grant_type authorization_code (code, code_verifier, client_id, redirect_uri) or refresh_token (rotates the pair). Form-encoded or JSON. |
MCP endpoint
POST https://pixly.app/api/mcp
Model Context Protocol over streamable HTTP, stateless: single JSON responses, no SSE streams, no sessions. Protocol versions 2025-03-26 and 2025-06-18. Methods: initialize, tools/list, tools/call, ping; notifications return 202.
Example request:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "virtual_staging",
"arguments": {
"imageUrl": "https://example.com/living-room.jpg",
"style": "scandinavian"
}
}
}Example response — result.content[0].text is JSON (a job snapshot); tool failures set result.isError: true with an actionable message:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "{\n \"jobId\": \"4bf4bb95-f5af-44d8-9508-65807f68636d\",\n \"type\": \"image_staging\",\n \"status\": \"processing\",\n \"creditsCharged\": 1,\n \"createdAt\": \"2026-08-10T20:07:08Z\",\n \"note\": \"Still running — call get_job again in a few seconds.\"\n}"
}
]
}
}REST API
Prefer plain HTTP to JSON-RPC? Every tool is also a REST endpoint — same pipeline, same credits, results in the same Library. Identical bearer auth. /v1 is a stable contract: fields and tools get added, never removed or reshaped.
| Parameter | Type | Required | Description |
|---|---|---|---|
| GET /api/v1/tools | — | no | Tool catalog with JSON Schema for every input — self-documenting |
| POST /api/v1/tools/{slug} | — | no | Run a tool. 200 = finished inline (photo tools), 202 = accepted, poll the job (video tools) |
| POST /api/v1/uploads | — | no | Only for local files: { "filename", "contentType" } returns a presigned PUT ticket, an imageUrl to pass to any tool, and a non-expiring r2Path. (A photo that already has a URL goes straight to a tool as imageUrl.) |
| GET /api/v1/jobs/{id} | — | no | Job status + result URLs (valid 7 days) |
| GET /api/v1/jobs | — | no | Recent generations — ?limit=1-50&type=images|videos |
| GET /api/v1/account | — | no | Credit balance and plan |
End to end:
# 1. run a tool — pass the photo's URL, we fetch and store it
curl -X POST https://pixly.app/api/v1/tools/virtual_staging \
-H "Authorization: Bearer $PIXLY_API_KEY" -H "Content-Type: application/json" \
-d '{"imageUrl":"https://example.com/living-room.jpg","style":"scandinavian"}'
# → { "jobId": "...", "status": "completed", "creditsCharged": 1, "resultUrls": ["https://..."] }
# 2. for video tools (202), poll until completed
curl https://pixly.app/api/v1/jobs/JOB_ID -H "Authorization: Bearer $PIXLY_API_KEY"
# Local file with no URL? Get a presigned PUT ticket, upload, pass r2Path.
curl -X POST https://pixly.app/api/v1/uploads \
-H "Authorization: Bearer $PIXLY_API_KEY" -H "Content-Type: application/json" \
-d '{"filename":"living-room.jpg","contentType":"image/jpeg"}'
# → { "uploadUrl": "https://...", "r2Path": "users/.../living-room.jpg" }Status codes:
| Parameter | Type | Required | Description |
|---|---|---|---|
| 200 | — | no | Done — resultUrls present (photo tools finish inline) |
| 202 | — | no | Accepted and running — poll GET /api/v1/jobs/{id} (video tools) |
| 401 | — | no | Missing/invalid credential |
| 402 | — | no | Out of credits, or no active subscription (error.required = credits needed) |
| 404 | — | no | Unknown tool or job |
| 410 | — | no | Source photo no longer available — re-upload |
| 422 | — | no | Invalid input — error.issues lists the offending fields |
| 429 | — | no | Rate limited — see Retry-After (120 req/min, 30/min for generations) |
| 502 / 503 | — | no | Provider trouble; credits from a charged job auto-refund within 24h |
Errors come back as { "error": { "code", "message", "issues"?, "required"?, "jobId"? } }.
Typical workflow
- Call a generation tool with imageUrl — any public https URL, or a data:image/…;base64, URI. Pixly fetches and stores it for you → job snapshot.
- Poll get_job until status: "completed" → download resultUrls. Image tools usually complete inline (up to ~2 min); videos take 1–5 minutes.
Results come back as URLs, so chaining needs no upload step — feed a resultUrlsentry straight back in as the next tool's imageUrl to animate a photo you just staged. Only a local file with no URL needs create_upload_ticket first: PUT the bytes to the returned uploadUrl, then send r2Path instead.
Tools reference
Generated from the live tool registry. Every tool is callable three ways — MCP, REST, and the CLI — with identical parameters.
virtual_staging
Try it in the app →Furnish and style an empty (or badly furnished) room photo in a chosen interior style. The flagship tool. HD, watermark-free results. Costs credits from the user's Pixly balance.
| Parameter | Type | Required | Description |
|---|---|---|---|
| imageUrl | string | no | Public https URL of the source photo, or a data: URI. Either imageUrl or r2Path is required. |
| r2Pathadvanced | string | no | R2 object path from an upload ticket (POST /api/v1/uploads) — the alternative to imageUrl when the photo is a local file. |
| style | string | yes | Style id from the Pixly styles catalog (lib/real-estate/styles.ts) |
| roomType | string | no | Room type hint; omit to let vision infer it from the photo |
| customInstructions | string | no | Optional extra instructions blended into the staging prompt |
| numImagesdeprecated | integer (default: 1) | no | Ignored — staging always returns one image. Call again for another take. |
| stagingQuality | "standard" | "pro" | no | pro = Nano Banana Pro instead of Nano Banana 2, at 2 credits instead of 1. Same prompt and same 2K output resolution either way — the model is the only difference. |
curl -X POST https://pixly.app/api/v1/tools/virtual_staging \
-H "Authorization: Bearer $PIXLY_API_KEY" -H "Content-Type: application/json" \
-d '{"imageUrl":"https://example.com/room.jpg","style":"..."}'edit_staged_photo
Apply a specific change to a previously staged photo (swap the sofa, add a rug) while preserving everything else. Costs credits from the user's Pixly balance.
| Parameter | Type | Required | Description |
|---|---|---|---|
| imageUrl | string | no | URL of the PREVIOUS staged result to edit (the resultUrls entry from its job). Either imageUrl or r2Path is required. |
| r2Pathadvanced | string | no | R2 path of the previous staged result — the alternative to imageUrl. |
| style | string | yes | Style id, used as context to match new elements |
| customInstructions | string | yes | The concrete edit to apply, e.g. "change the sofa to grey" |
| sourceJobId | string | no | Job the edited result came from (lineage) |
| sourceResultIndex | integer | no | Which variation was edited |
curl -X POST https://pixly.app/api/v1/tools/edit_staged_photo \
-H "Authorization: Bearer $PIXLY_API_KEY" -H "Content-Type: application/json" \
-d '{"imageUrl":"https://example.com/room.jpg","style":"...","customInstructions":"..."}'cinematic_motion
Try it in the app →Turn a single listing photo into a short cinematic clip with a professional camera move (zoom, orbit, fly-through, crane up…). No second frame needed. Costs credits from the user's Pixly balance.
| Parameter | Type | Required | Description |
|---|---|---|---|
| imageUrl | string | no | Public https URL of the source photo, or a data: URI. Either imageUrl or r2Path is required. |
| r2Pathadvanced | string | no | R2 object path from an upload ticket (POST /api/v1/uploads) — the alternative to imageUrl when the photo is a local file. |
| cameraMove | "zoom" | "fly-through" | "orbit" | "pan" | "aerial" | "pull-back" | "crane-up" | "tilt-up" | "boom-down" | "drone-orbit" | "flyover" | "pull-away" | "handheld" | no | Camera-move preset |
| direction | "left-to-right" | "right-to-left" | no | The direction the camera travels, for the moves that have one (orbit, pan, drone-orbit). 'left-to-right' sends the camera rightward, 'right-to-left' leftward. Ignored by every other move; omit to use the move's own default. |
| durationSeconds | 5 | 10 | no | Clip length; the routed model validates its supported tiers |
| format | "9:16" | "16:9" | no | Aspect ratio |
curl -X POST https://pixly.app/api/v1/tools/cinematic_motion \
-H "Authorization: Bearer $PIXLY_API_KEY" -H "Content-Type: application/json" \
-d '{"imageUrl":"https://example.com/room.jpg"}'before_after_reel
Try it in the app →Animate a before→after transformation (e.g. empty room → staged) into a social-ready reveal video from two frames. Costs credits from the user's Pixly balance.
| Parameter | Type | Required | Description |
|---|---|---|---|
| beforeImageUrl | string | no | Start frame (e.g. the empty room photo) — https URL or data: URI |
| afterImageUrl | string | no | End frame (e.g. the staged result) — https URL or data: URI |
| beforeR2Pathadvanced | string | no | R2 path of the start frame — the alternative to beforeImageUrl. |
| afterR2Pathadvanced | string | no | R2 path of the end frame — the alternative to afterImageUrl. |
| videoIntent | "staging_reveal" | "staging_reveal_reverse" | "construction_timelapse" | no | |
| revealStyle | "smooth" | "slideIn" | "dropLand" | "glowBuild" | "movers" | no | How the transformation is revealed |
| durationSeconds | 5 | 10 | no | |
| format | "9:16" | "16:9" | "1:1" | no |
curl -X POST https://pixly.app/api/v1/tools/before_after_reel \
-H "Authorization: Bearer $PIXLY_API_KEY" -H "Content-Type: application/json" \
-d '{"beforeImageUrl":"https://example.com/room.jpg","afterImageUrl":"https://example.com/room.jpg"}'enhance_photo
Try it in the app →Turn an amateur listing photo into a finished, professional real-estate photo (exposure, color, clarity). Returns 3 variants to pick from. Costs credits from the user's Pixly balance.
| Parameter | Type | Required | Description |
|---|---|---|---|
| imageUrl | string | no | Public https URL of the source photo, or a data: URI. Either imageUrl or r2Path is required. |
| r2Pathadvanced | string | no | R2 object path from an upload ticket (POST /api/v1/uploads) — the alternative to imageUrl when the photo is a local file. |
curl -X POST https://pixly.app/api/v1/tools/enhance_photo \
-H "Authorization: Bearer $PIXLY_API_KEY" -H "Content-Type: application/json" \
-d '{"imageUrl":"https://example.com/room.jpg"}'declutter_photo
Try it in the app →Remove clutter, mess, and personal items from a listing photo while keeping the room, furniture, and architecture intact. Costs credits from the user's Pixly balance.
| Parameter | Type | Required | Description |
|---|---|---|---|
| imageUrl | string | no | Public https URL of the source photo, or a data: URI. Either imageUrl or r2Path is required. |
| r2Pathadvanced | string | no | R2 object path from an upload ticket (POST /api/v1/uploads) — the alternative to imageUrl when the photo is a local file. |
curl -X POST https://pixly.app/api/v1/tools/declutter_photo \
-H "Authorization: Bearer $PIXLY_API_KEY" -H "Content-Type: application/json" \
-d '{"imageUrl":"https://example.com/room.jpg"}'remove_furniture
Try it in the app →Empty a room completely — remove all furniture, rugs, art, plants and decor, and rebuild the floor and walls behind them, keeping the architecture and camera angle identical. The blank canvas for restaging. (Declutter is the opposite: it keeps the furniture.) Costs credits from the user's Pixly balance.
| Parameter | Type | Required | Description |
|---|---|---|---|
| imageUrl | string | no | Public https URL of the source photo, or a data: URI. Either imageUrl or r2Path is required. |
| r2Pathadvanced | string | no | R2 object path from an upload ticket (POST /api/v1/uploads) — the alternative to imageUrl when the photo is a local file. |
curl -X POST https://pixly.app/api/v1/tools/remove_furniture \
-H "Authorization: Bearer $PIXLY_API_KEY" -H "Content-Type: application/json" \
-d '{"imageUrl":"https://example.com/room.jpg"}'day_to_night
Try it in the app →Turn a daytime listing photo into a magazine-style night scene — lights on, warm glow, dusk sky — with the building and camera angle unchanged. Costs credits from the user's Pixly balance.
| Parameter | Type | Required | Description |
|---|---|---|---|
| imageUrl | string | no | Public https URL of the source photo, or a data: URI. Either imageUrl or r2Path is required. |
| r2Pathadvanced | string | no | R2 object path from an upload ticket (POST /api/v1/uploads) — the alternative to imageUrl when the photo is a local file. |
curl -X POST https://pixly.app/api/v1/tools/day_to_night \
-H "Authorization: Bearer $PIXLY_API_KEY" -H "Content-Type: application/json" \
-d '{"imageUrl":"https://example.com/room.jpg"}'plot_sign
Try it in the app →Place a photorealistic 3D monument sign with your exact text (price, area, SOLD) onto a photo of an empty plot, matched to perspective and lighting. Costs credits from the user's Pixly balance.
| Parameter | Type | Required | Description |
|---|---|---|---|
| imageUrl | string | no | Public https URL of the source photo, or a data: URI. Either imageUrl or r2Path is required. |
| r2Pathadvanced | string | no | R2 object path from an upload ticket (POST /api/v1/uploads) — the alternative to imageUrl when the photo is a local file. |
| text | string | yes | Sign label, verbatim — a price ("$1,200,000"), area ("800 m²"), or short word ("SOLD") |
| look | "stone" | "metal" | "grass" | "sign" | no | Sign material/style; defaults server-side |
| orientation | "standing" | "lying" | no | Letter orientation (letter looks only); defaults server-side |
curl -X POST https://pixly.app/api/v1/tools/plot_sign \
-H "Authorization: Bearer $PIXLY_API_KEY" -H "Content-Type: application/json" \
-d '{"imageUrl":"https://example.com/room.jpg","text":"..."}'upload_image_from_url
Rarely needed: generation tools take an imageUrl directly. Use this only to import a photo into Pixly ahead of time, e.g. to reuse one URL across several tools without refetching it each time.
| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string | yes | Publicly reachable image URL (JPG/PNG/WebP, max 10 MB) to import into Pixly |
create_upload_ticket
Get a presigned upload URL for a LOCAL image file — the one case a URL can't cover. PUT the file to uploadUrl with the same Content-Type, then pass the returned r2Path to a generation tool instead of imageUrl. Ticket expires in 1 hour.
| Parameter | Type | Required | Description |
|---|---|---|---|
| filename | string | yes | Original filename, used for the extension |
| contentType | "image/jpeg" | "image/png" | "image/webp" | "image/heic" | "image/heif" | "image/tiff" | yes | MIME type the PUT request will send |
| sizeBytes | integer | no | Exact byte size of the file. Recommended: when given it is signed into the URL, so the upload is bounded server-side. |
get_job
Check a generation job's status and fetch result URLs when it's done. Video jobs take 1-5 minutes — poll this with a few seconds between calls.
| Parameter | Type | Required | Description |
|---|---|---|---|
| jobId | string | yes | Job id returned by a generation tool |
list_library
List the user's recent generations, or with type "uploads" their own uploaded source photos, newest first. Uploads are the way in when the photo is a local file: connectors cannot send bytes, so the user adds it at https://pixly.app/app and you pick up the r2Path here.
| Parameter | Type | Required | Description |
|---|---|---|---|
| type | "images" | "videos" | "uploads" | no | Filter by media type. "uploads" lists the user's own source photos instead of generations — use it when they refer to a photo they added but have not run a tool on yet. |
| limit | integer (default: 20) | no |
get_credit_balance
The user's current Pixly credit balance and plan.
Jobs & results
Every generation tool and get_job return the same snapshot shape:
| Parameter | Type | Required | Description |
|---|---|---|---|
| jobId | string | yes | Job id — pass to get_job to poll |
| type | "image_staging" | "video_pair" | "video_tour" | yes | Job family |
| status | "pending" | "processing" | "completed" | "failed" | yes | Anything not completed/failed is still running |
| creditsCharged | number | yes | Credits debited for this job |
| createdAt | string (ISO 8601) | yes | Submission time |
| completedAt | string (ISO 8601) | no | Present once finished |
| resultUrls | string[] | no | Presigned download URLs (present when completed; valid 7 days) |
| error | string | no | Failure reason; the message says whether the credit came back immediately or is in 24h refund review |
| note | string | no | Hint for agents, e.g. poll again in a few seconds |
Result URLs are presigned and valid for 7 days; the generation itself stays in the user's Pixly Library permanently.
Errors & refunds
- HTTP 401 — missing or invalid credential. The WWW-Authenticate header carries the OAuth resource-metadata URL so OAuth-capable clients discover the sign-in flow automatically.
- JSON-RPC errors — -32700 parse, -32600 invalid request (batching is not supported), -32601 unknown method, -32602 invalid params, -32603 internal.
- Tool errors — result.isError: true with a message agents can act on: insufficient credits (with the amount needed), invalid input (with the exact field), or a source photo that's no longer available.
- Refunds — a failed job always returns the credit. If it died before any model ran, the refund is immediate; otherwise it enters refund review and credits return within 24 hours. No action needed either way.
CLI
npx pixly-cli help # or: npm install -g pixly-cli export PIXLY_API_KEY=pixly_sk_...
Node ≥ 18, zero dependencies. Local photo arguments upload automatically; results download to the current directory (--out to choose a path; tools that return several results — enhance gives you 3 — get -1, -2, … suffixes).
pixly stage <photo> --style <id>
Virtually stage a room photo. Takes a URL or a local file (uploaded automatically).
- --style <id> (required) — style id, e.g. scandinavian, modern-luxury
- --room <type> — room-type hint; omitted = inferred from the photo
- --instructions <text> — extra instructions blended into the prompt
- --pro — Nano Banana Pro instead of Nano Banana 2 (2 credits instead of 1; same 2K output)
- --out <file.jpg> — output path (default: <name>-staged.jpg)
pixly enhance <photo>
Professional photo enhancement — returns 3 variants.
- --out <file.jpg>
pixly declutter <photo>
Remove clutter and personal items.
- --out <file.jpg>
pixly remove-furniture <photo>
Empty the room — all furniture, rugs, art and decor out, surfaces rebuilt.
- --out <file.jpg>
pixly day-to-night <photo>
Turn a daytime photo into a night scene.
- --out <file.jpg>
pixly plot-sign <photo> --text "SOLD"
Photorealistic 3D monument sign on an empty-plot photo.
- --text <label> (required, max 24 chars)
- --look stone|metal|grass|sign
- --orientation standing|lying
- --out <file.jpg>
pixly motion <photo>
Cinematic camera-move clip from a single photo.
- --move zoom|fly-through|orbit|pan|aerial|pull-back|crane-up|tilt-up|boom-down|drone-orbit|flyover|pull-away|handheld
- --duration 5|10 (default 5)
- --format 9:16|16:9 (default 9:16)
- --out <file.mp4>
pixly reel --before <a.jpg> --after <b.jpg>
Before→after reveal video from two frames.
- --intent staging_reveal|staging_reveal_reverse|construction_timelapse
- --reveal smooth|slideIn|dropLand|glowBuild|movers (default smooth)
- --duration 5|10 · --format 9:16|16:9|1:1 · --out <file.mp4>
pixly job <jobId>
Job status + result URLs (JSON).
pixly jobs
Recent generations.
- --limit <n> (default 20)
- --type images|videos
pixly balance
Credits remaining and plan.
pixly tools
List every available tool.
For AI agents
This entire reference is available as pure markdown at https://pixly.app/docs.md (also linked from /llms.txt). To teach an agent Pixly in one message, paste this:
Connect to Pixly (real-estate photo & video AI) via MCP: - Endpoint: https://pixly.app/api/mcp (streamable HTTP, stateless JSON responses) - Auth: header "Authorization: Bearer pixly_sk_..." (user creates the key at https://pixly.app/app/settings) or OAuth sign-in - Workflow: call a generation tool (virtual_staging, enhance_photo, declutter_photo, remove_furniture, day_to_night, plot_sign, cinematic_motion, before_after_reel, edit_staged_photo) with imageUrl = the photo's https URL (or a data: URI) → poll get_job until status "completed" → download resultUrls. Only a local file with no URL needs create_upload_ticket first — and if you are a connector that cannot PUT bytes, have the user add the photo at https://pixly.app/app and call list_library with type "uploads" to get its r2Path. - Generations spend the user's Pixly credit balance (check with get_credit_balance). Failed jobs always refund — instantly if nothing ran, otherwise within 24h. - Full reference: https://pixly.app/docs.md