# Pixly Developer Reference

Pixly turns real-estate listing photos into staged photos and cinematic videos.
This is the complete machine-readable reference for the MCP endpoint, the
OAuth flow, and the CLI. Human version: https://pixly.app/docs

## Authentication

Two credential types, both sent as `Authorization: Bearer <token>`:

1. **API key** (`pixly_sk_…`) — created at https://pixly.app/app/settings → API keys.
   Shown once at creation; revocable anytime. For Claude Code, Cursor, scripts, CI.
2. **OAuth 2.1 access token** (`pixly_at_…`) — for clients with a sign-in flow
   (claude.ai custom connectors). Public clients + PKCE S256 only; refresh
   rotation. Discovery starts at the 401 WWW-Authenticate header or the
   .well-known endpoints below.

Every generation spends the account's normal Pixly credit balance. An active
subscription is required. Failed jobs enter an automatic refund review —
credits come back within 24 hours.

### OAuth endpoints

- `GET /.well-known/oauth-authorization-server` — RFC 8414 authorization-server metadata (endpoints, PKCE methods, grant types)
- `GET /.well-known/oauth-protected-resource/api/mcp` — RFC 9728 protected-resource metadata — advertised in the 401 WWW-Authenticate header
- `POST /api/oauth/register` — RFC 7591 dynamic client registration (open; public clients; body: client_name, redirect_uris[])
- `GET /oauth/authorize` — User consent page — query: response_type=code, client_id, redirect_uri, state, code_challenge, code_challenge_method=S256
- `POST /api/oauth/token` — 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, no sessions). Protocol versions
2025-03-26 and 2025-06-18. Methods: `initialize`, `tools/list`,
`tools/call`, `ping`; notifications are accepted with 202.

Client setup:
- claude.ai / Claude desktop: Settings → Connectors → Add custom connector → paste the endpoint URL → sign in.
- Claude Code: `claude mcp add -s user --transport http pixly https://pixly.app/api/mcp --header "Authorization: Bearer pixly_sk_..."`
- Cursor (`.cursor/mcp.json`): `{"mcpServers":{"pixly":{"url":"https://pixly.app/api/mcp","headers":{"Authorization":"Bearer pixly_sk_..."}}}}`
- ChatGPT: Settings → Connectors → Advanced → Developer mode → add connector with the URL.

### Example tools/call

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "virtual_staging",
    "arguments": {
      "r2Path": "users/<your-user-id>/staging/1754820000000-living-room.jpg",
      "style": "scandinavian",
      "numImages": 1
    }
  }
}
```

Response (`result.content[0].text` is JSON — a job snapshot; `result.isError: true` marks tool errors, with a human/agent-readable message in the text):

```json
{
  "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}"
      }
    ]
  }
}
```

### Job snapshot (returned by every generation tool and get_job)

| 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 (failed jobs enter the 24h credit-refund review automatically) |
| `note` | string | no | Hint for agents, e.g. poll again in a few seconds |

Image tools usually return `completed` with resultUrls in the same call
(they run inline, up to ~2 min). Video tools return `processing` — poll
`get_job` every few seconds (typical 1–5 min).

### Typical workflow

1. `create_upload_ticket` (local file → PUT bytes to uploadUrl) or `upload_image_from_url` → `r2Path`
2. A generation tool with that `r2Path` → job snapshot
3. `get_job` until `status: "completed"` → download `resultUrls`

## Tools

### virtual_staging

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 |
| --- | --- | --- | --- |
| `r2Path` | string | yes | R2 object path of the uploaded source photo (from an upload-url ticket) |
| `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 |
| `numImages` | integer (default: 1) | yes | Variations to generate (1-4) |
| `stagingQuality` | "standard" \| "pro" | no | pro = higher-quality 2K tier at 2 credits/image |

### 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 |
| --- | --- | --- | --- |
| `r2Path` | string | yes | R2 path of the PREVIOUS staged result to edit |
| `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 |

### cinematic_motion

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 |
| --- | --- | --- | --- |
| `r2Path` | string | yes | R2 object path of the uploaded source photo (from an upload-url ticket) |
| `cameraMove` | "zoom" \| "fly-through" \| "orbit" \| "pan" \| "aerial" \| "pull-back" \| "crane-up" \| "tilt-up" \| "boom-down" \| "drone-orbit" \| "flyover" \| "pull-away" \| "handheld" | yes | Camera-move preset |
| `durationSeconds` | 5 \| 10 | yes | Clip length; the routed model validates its supported tiers |
| `format` | "9:16" \| "16:9" | yes | Aspect ratio |

### before_after_reel

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 |
| --- | --- | --- | --- |
| `beforeR2Path` | string | yes | Start frame (e.g. the empty room photo) |
| `afterR2Path` | string | yes | End frame (e.g. the staged result) |
| `videoIntent` | "staging_reveal" \| "staging_reveal_reverse" \| "construction_timelapse" | yes |  |
| `revealStyle` | "smooth" \| "slideIn" \| "dropLand" \| "glowBuild" \| "movers" | yes | How the transformation is revealed |
| `durationSeconds` | 5 \| 10 | yes |  |
| `format` | "9:16" \| "16:9" \| "1:1" | yes |  |

### enhance_photo

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 |
| --- | --- | --- | --- |
| `r2Path` | string | yes | R2 object path of the uploaded source photo (from an upload-url ticket) |

### declutter_photo

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 |
| --- | --- | --- | --- |
| `r2Path` | string | yes | R2 object path of the uploaded source photo (from an upload-url ticket) |

### day_to_night

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 |
| --- | --- | --- | --- |
| `r2Path` | string | yes | R2 object path of the uploaded source photo (from an upload-url ticket) |

### plot_sign

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 |
| --- | --- | --- | --- |
| `r2Path` | string | yes | R2 object path of the uploaded source photo (from an upload-url ticket) |
| `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 |

### upload_image_from_url

Import an image into Pixly from a URL. Returns the r2Path that every generation tool takes as its photo input. Use this first when the photo isn't already in Pixly.

| 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 (when you have bytes, not a URL). PUT the file to uploadUrl with the same Content-Type, then pass the returned r2Path to a generation tool. 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" | yes | MIME type the PUT request will send |

### 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 (newest first).

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `type` | "images" \| "videos" | no | Filter by media type |
| `limit` | integer (default: 20) | yes |  |

### get_credit_balance

The user's current Pixly credit balance and plan.

## Errors

- HTTP 401 — missing/invalid credential. The WWW-Authenticate header carries
  the OAuth resource-metadata URL for automatic discovery.
- JSON-RPC errors: -32700 parse error, -32600 invalid request (incl. batch —
  not supported), -32601 unknown method, -32602 invalid params, -32603 internal.
- Tool-level failures return `result.isError: true` with an actionable text
  message (e.g. insufficient credits with the amount needed, invalid input
  with the exact field, source image no longer available). Post-charge
  failures state that credits return automatically within 24 hours.

## CLI

`npx pixly-cli help` · `npm install -g pixly-cli` · Node ≥ 18, zero deps.
Env: `PIXLY_API_KEY` (required), `PIXLY_MCP_URL` (override, for testing).
Local photo arguments upload automatically; results download to disk
(`--out` or a derived name; variations get -1, -2, … suffixes).

### `pixly stage <photo> --style <id>`

Virtually stage a room photo. Local files upload 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
- --variations <1-4> — number of variations (default 1)
- --pro — 2K quality tier (2 credits/image)
- --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 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.

## Agent setup prompt

Paste this into an agent to teach it Pixly in one message:

```
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: create_upload_ticket (local file) or upload_image_from_url → returns r2Path → call a generation tool (virtual_staging, enhance_photo, declutter_photo, day_to_night, plot_sign, cinematic_motion, before_after_reel, edit_staged_photo) → poll get_job until status "completed" → download resultUrls.
- Generations spend the user's Pixly credit balance (check with get_credit_balance). Failed jobs auto-refund within 24h.
- Full reference: https://pixly.app/docs.md
```
