Stage a real-estate photo from your own code. One HTTP call returns one staged image. Prepaid credits at $0.15 an image, with no subscription, no minimum and no bill that can run away from you.
- Base URL
- https://stagify.ai/api/v1
- Authentication
- Bearer stg_live_…
- Content type
- multipart/form-data
Introduction
The API exposes one thing: the staging pipeline behind the web app. You post a photo of a room, name a room type and a furniture style, and get a staged image back in the same response. There is no job queue and nothing to poll on the happy path.
Credits are whole images. You buy them up front and each successful render spends one. A render that fails is refunded automatically, so you only ever pay for a photo you received. And because the balance is prepaid, a stolen key costs you at most what is left on it. There is no invoice at the end of the month.
Keys are meant for server-to-server use. The API sends no CORS headers, so a browser cannot call it directly; that is deliberate, and a key reachable from browser JavaScript is a leaked key. We show a key once, store only a hash, and revoke immediately.
Quickstart
Create a key on your API keys page, buy a credit pack, then post a photo. The response carries the staged image as a data URL.
curl https://stagify.ai/api/v1/renders \
-H "Authorization: Bearer $STAGIFY_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-F "image=@living-room.jpg" \
-F "roomType=Living room" \
-F "furnitureStyle=modern" \
-F "labelVirtuallyStaged=true"
A render takes up to a couple of minutes. Set your client’s read timeout to
300 seconds and retry with the same
Idempotency-Key. That is what guarantees you are never charged twice
for one photo.
Credit packs
Loading pricing…
Request parameters
POST /api/v1/renders, sent as multipart/form-data. Every accepted value below is also served as JSON from GET /api/v1/options, which needs no API key, so a client can populate its own dropdowns instead of hard-coding this table.
| Field | Type | Notes |
|---|---|---|
image | file | Required. The room photo. JPEG, PNG or WebP, up to 25 MB. The MIME type is what is checked, not the file extension. |
roomType | enum |
Bedroom, Living room, Dining room,
Kitchen, Office, Bathroom,
Outdoors, Dorm.
Default Living room.
Case- and space-sensitive, and an unrecognised value is
not rejected. It is written into the prompt as
free text, so Living Room is not Living room.
|
furnitureStyle | enum |
standard, modern, midcentury,
scandinavian, luxury, coastal,
farmhouse, custom.
Default standard, which is also what an unrecognised value
silently becomes. custom uses your
additionalPrompt in place of the built-in style text.
|
additionalPrompt | string | Free-text direction for this render. |
removeFurniture | boolean | Empty the room before staging it. Send true or on; anything else, including 1 and yes, reads as false. |
keepFurniture | string | Free text naming what to leave in place, e.g. keep the dining table and the built-in shelves. Read only when removeFurniture is set. 500 characters. |
furnitureImage | file[] | Up to 5 reference photos of furniture to match. Same formats and size limit as image. |
labelVirtuallyStaged | boolean | Burn the “Virtually staged” disclosure onto the output. |
stampStyle | enum | dark, light, minimal,
banner. Default dark. Case-insensitive;
an unrecognised value falls back to the default. |
stampScale | number | 0.7 to 1.6, default 1.
Out-of-range values are clamped rather than refused. |
stampLang | enum | The badge language: english, spanish,
french, german, chinese,
korean, portuguese, russian,
italian, japanese, dutch.
Default english. |
variations | integer | Only 1 is accepted. Issue concurrent requests for more. |
Idempotency
A render holds the connection for minutes, which means the failure that will
actually bite you is a dead socket after the work completed: charged, holding
nothing. Send an Idempotency-Key header on every request and retry
with the same value.
| What we have on file | What a retry does |
|---|---|
| A succeeded request, same parameters | Returns the stored result with X-Stagify-Replayed: true. No second charge. |
| A request still running | 409 REQUEST_IN_FLIGHT. Wait, then retry. |
| A request abandoned mid-render | Re-runs on the existing charge, up to three attempts. |
| The same key with different parameters | 422 IDEMPOTENCY_KEY_REUSED. Use a fresh key. |
Omitting the header is allowed and means no replay protection, which is the honest default. We do not derive a key from your photo: two genuinely separate renders of the same room would collide and the second caller would be handed the first one’s image.
Disclosure labels
Many MLS and NAR rules require a staged photo to say that it is staged.
labelVirtuallyStaged burns that label into the pixels rather than
attaching it as metadata that a downstream tool can strip.
If the label cannot be applied, the image is withheld rather than
delivered unlabelled, the call answers 500
DISCLOSURE_STAMP_FAILED, and your credit is refunded. There is no way to
switch that behaviour off, and no per-key default that quietly disables it.
Errors
Every failure answers { "error": "…", "code": "…" }. Branch on
code, never on the message. Messages change, codes do not.
| Status | Code | What to do |
|---|---|---|
| 401 | API_KEY_MISSING / API_KEY_INVALID / API_KEY_REVOKED | Check the Authorization header, or mint a new key. |
| 402 | INSUFFICIENT_CREDITS | Top up. The body carries credits_remaining. |
| 403 | ACCOUNT_SUSPENDED | Contact support. |
| 400 | VARIATIONS_UNSUPPORTED | This API renders one image per request. Issue concurrent requests instead. |
| 409 | REQUEST_IN_FLIGHT | That idempotency key is still running. Wait, then retry. |
| 409 | CONCURRENCY_LIMIT | Too many renders at once. Retry in a few seconds. |
| 422 | IDEMPOTENCY_KEY_REUSED | You reused a key with different parameters. Use a fresh one. |
| 422 | NO_IMAGE_GENERATED | The model returned nothing. Refunded; retry or try another photo. |
| 429 | RATE_LIMITED | Back off and retry. |
| 500 | DISCLOSURE_STAMP_FAILED | The label could not be applied so the image was withheld. Refunded. |
| 500 | RENDER_FAILED | Refunded. Quote the ref if you contact support. |
Other endpoints
GET /api/v1/options: the accepted values for every enum above. No API key required.GET /api/v1/me: is my key working? Returns the account and balance.GET /api/v1/credits: balance and lifetime totals.GET /api/v1/renders/{id}: the status of one render.
Every response carries X-Stagify-Credits-Remaining, so you can watch the
balance fall without polling for it.
Questions, higher limits, or an invoice instead of a card: team@stagify.ai.