Stager AI API · v1

Put your real estate photo editing on autopilot

Connect your own app, CRM or photography platform. Stager AI merges brackets, fixes lenses and verticals, stages rooms and builds listing videos, then hands the results back to you.

Start in 4 steps
Base URL
api.stagerai.com
Auth header
stager-api-key
Format
JSON over HTTPS
Price
$0.25 per photo
Quickstart

Your first edit in 4 steps

Every request goes to https://api.stagerai.com with your key in the stager-api-key header. That is all the setup there is.

  1. 01Get your keyAsk us in the chat. Send it as the stager-api-key header on every request.
  2. 02Create a projectOne project per listing keeps everything together.
  3. 03Upload your photosAsk for a signed URL per photo and upload straight to storage.
  4. 04Get the resultsPoll the status endpoint, or let a webhook tell you when each photo is ready.
Pricing

Simple, per-photo pricing

You need an active subscription to use the API. Pick any plan: we set your API quota to the standard rate of $0.25 per photo for every feature, so the API costs the same as the web app.

Unused monthly credits roll over to the next month, so you can save up for big projects or busy seasons. Credits are only deducted when processing finishes successfully.

HDR multi-bracket merge$0.25 per final photo

A bracket group of up to 7 exposures counts as 1 final photo. Upload 5 brackets, get 1 merged HDR image, pay $0.25.

Example: Processing 10 bracket groups (50 input photos) gives 10 final images = $2.50.

AI staging and retouching$0.25 per photo

Virtual staging, lawn replacement, sky replacement and retouching. Through the API, the first 3 re-renders of a photo are included (the web app includes 9). After that, every 3rd render costs 1 credit ($0.25): renders 4, 7, 10, 13 and so on.

Example: Stage a photo ($0.25), re-renders 2, 3, 5 and 6 are included, re-render 4 ($0.25) and re-render 7 ($0.25) = $0.75 for 7 renders.

Video generation6 credits per scene

Each scene is 10–15 seconds of animation made from 1 to 5 photos. The order of the photos sets the order of the animation.

Example: A video with 4 scenes (living room, kitchen, bedroom, exterior) = 4 × 6 = 24 credits, about 50 seconds long.

Authentication

Get your API key

API access is open to business accounts and platforms. Send us a message in the chat and we'll set up your production key. Every request needs it in the stager-api-key header.

Base URL https://api.stagerai.com

Check your photo quota

Shows how many photo credits you have left. Handy before you send a big batch.

FieldWhat it does
photosQuotaCredits you can spend right now. They roll over month to month, up to a cap.
GET/api/photos-quota
Header stager-api-key: your_api_key_here
Response
{
"success": true,
"data": {
"photosQuota": 128
}
}
HDR editing

Brackets in, finished photos out

Send single photos or bracketed exposures (1 to 7 per group). Stager AI aligns and blends them, then fixes the lens, the verticals and the windows for you.

HDR multi-bracket blendingCombines up to 7 exposures into one balanced image.
Lens and fish-eye correctionRemoves the distortion of wide-angle lenses.
Perspective straighteningMakes vertical lines truly vertical.
Window pullsRecovers blown-out windows so the view outside shows.

Supported files: All common raw formats: Canon (CR3, CR2), Nikon (NEF, NRW), Sony (ARW, SRF), Fujifilm (RAF), Panasonic (RW2), plus JPEG, PNG and TIFF.

What happens to every bracket group

  1. 1Upload 1 to 7 exposures
  2. 2Detect and align
  3. 3Merge and blend
  4. 4Lens, perspective, windows
  5. 5Final HDR photo

Create a project

Both HDR options start here. A project is a folder for one listing.

POST/api/projects
Header stager-api-key: your_api_key_here
Request body
{
"title": "123 Main Street Suite"
}
Response
{
"id": "project_uuid_here",
"title": "123 Main Street Suite"
}
Option 1: Batch uploadWe find the bracket groups for you.
1

Create a batch

Opens a batch for all the photos of one listing, and sets the edits to apply. We find the bracket groups for you.

clientReferenceId is optional. Use it to link this request to a record in your system: we send it back in this response and in every status check.

POST/api/hdr-batch
Header stager-api-key: your_api_key_here
Request body
{
"projectId": "project_uuid_here",
"lensCorrection": true,
"perspectiveCorrection": true,
"photographerRemoval": true,
"fireplaceEnhancement": false,
"tvScreenReplacement": false,
"autoPrivacy": false,
"blurLicensePlates": false,
"editingStyle": 11, // Stager Classic V2 Or leave empty to use latest model
"clientReferenceId": "your_internal_id" // optional
}
Response
{
"batchId": "batch_uuid_here",
"clientReferenceId": "your_internal_id"
}
2

Get an upload URL for each photo

Call this once per photo. You get a signed URL to upload that file to.

There is no limit on the number of photos in a batch. We group them into brackets using their EXIF data (capture time and exposure).

POST/api/hdr-batch/:batchId/url
Header stager-api-key: your_api_key_here
Request body
{
"filename": "living_room_1.jpg"
}
Response
{
"id": "image_uuid_here",
"url": "https://storage.googleapis.com/...temporary_gcloud_put_url..."
}
3

Process the batch

Once every photo is uploaded, start processing.

We read the EXIF data (time, exposure time, ISO, f-number) to find which photos belong together, then process each bracket group in the background.

POST/api/hdr-batch/:batchId/enhance
Header stager-api-key: your_api_key_here
Response
{}
4

Get the batch results

Check progress and collect the finished HDR photos, one result per bracket group.

FieldWhat it does
formatOptional query string parameter (not a body field: this is a GET request).
jpgpng
processingTrue while any group is still processing.
resultsOne result per detected bracket group.
groupIdUnique ID of the bracket group.
imageUrlThe final HDR photo, once it is ready.
status"error" if processing failed, otherwise undefined.
codeNumeric error code when status is "error". See the error codes below.
errorMessagePlain-English explanation of the error.
clientReferenceIdThe value you sent when creating the batch, if any.

Poll every 30–40 seconds. The batch is done when processing is false and every group has an imageUrl or an error status.

GET/api/hdr-batch/:batchId?format=jpg
Header stager-api-key: your_api_key_here
Response
{
"processing": true,
"clientReferenceId": "your_internal_id",
"results": [
{
"groupId": "group_uuid_1",
"imageUrl": "https://storage.googleapis.com/.../hdr_output_1.jpg",
"processing": false,
"status": undefined
},
{
"groupId": "group_uuid_2",
"imageUrl": undefined,
"processing": true,
"status": undefined
},
{
"groupId": "group_uuid_3",
"imageUrl": undefined,
"processing": false,
"status": "error",
"code": 1,
"message": "No credits left",
"errorMessage": "No credits left. Please purchase more credits or upgrade your plan."
}
]
}
Option 2: Single bracketYou decide which photos belong together.
1

Create an HDR group

A group holds one bracket set (up to 7 exposures). Create one per bracket set, with the edits to apply.

clientReferenceId is optional. Use it to link this request to a record in your system: we send it back in this response and in every status check.

POST/api/hdr-image-group
Header stager-api-key: your_api_key_here
Request body
{
"projectId": "project_uuid_here",
"lensCorrection": true,
"perspectiveCorrection": true,
"photographerRemoval": true,
"fireplaceEnhancement": false,
"tvScreenReplacement": false,
"autoPrivacy": false,
"blurLicensePlates": false,
"editingStyle": 11, // Stager Classic V2 Or leave empty to use latest model
"clientReferenceId": "your_internal_id" // optional
}
Response
{
"groupId": "group_uuid_here",
"clientReferenceId": "your_internal_id"
}
2

Get an upload URL for each exposure

Call this once per exposure in the group, then upload the file to the signed URL.

Up to 7 photos per group.

POST/api/hdr-upload-url
Header stager-api-key: your_api_key_here
Request body
{
"groupId": "group_uuid_here",
"filename": "living_room_exposure_1.jpg"
}
Response
{
"id": "image_uuid_here",
"uploadUrl": "https://storage.googleapis.com/...temporary_gcloud_put_url..."
}
3

Process the group

Merges the uploaded exposures into one HDR photo.

POST/api/hdr-enhance
Header stager-api-key: your_api_key_here
Request body
{
"groupId": "group_uuid_here"
}
Response
{}
4

Check the status and get the result

Check progress and collect the finished HDR photo.

Poll every 5–10 seconds. A bracket group usually takes about 1 to 5 minutes, depending on size and complexity.

clientReferenceId is the value you sent when creating the group, if any.

GET/api/hdr-image-group/:groupId?format=jpg
Header stager-api-key: your_api_key_here
Response
// If processing:
{
"status": "processing",
"clientReferenceId": "your_internal_id"
}
// On Complete:
{
"status": "completed",
"imageUrl": "https://storage.googleapis.com/stager-final-results/output.jpg",
"filename": "living_room_exposure_1.jpg",
"clientReferenceId": "your_internal_id"
}
// On Error:
{
"status": "error",
"code": 11,
"errorMessage": "Failed to align the bracketed exposures. Make sure the images were taken from the same position.",
"clientReferenceId": "your_internal_id"
}

Error codes

When a bracket group fails, the status endpoints return status: "error" with a numeric code and a readable errorMessage. The hdr.image.failed webhook sends the same code as errorCode.

CodeNameWhat it means
1noCreditsNo credits left. Please purchase more credits or upgrade your plan.
2enfuseErrorFailed to merge the bracketed exposures into an HDR image.
3editsErrorFailed to apply the requested edits to the image.
4styleMergeErrorFailed to apply the selected editing style to the image.
5timeoutErrorProcessing took too long and timed out.
6noImagesNo images were found for this group. Upload at least 1 image before enhancing.
7uploadPngConversionErrorFailed to convert the uploaded image. The file may be corrupted or in an unsupported format.
8maxRetriesProcessing failed after the maximum number of retries.
9generationErrorFailed to generate the enhanced image.
10tooManyImagesToo many images in this group. Maximum 7 brackets allowed.
11alignmentErrorFailed to align the bracketed exposures. Make sure the images were taken from the same position.
Webhooks (optional)

Get told when photos are ready

Skip the polling: we tell your server the moment each bracket group is done. Works for both the batch and the single-bracket options, with one event per bracket group.

Add a webhook URL on your account page, under API Access. Saving it creates a webhook secret, which we use to sign every request so you can check it really came from Stager AI. Open your account page

Delivery: if your endpoint doesn't answer with a 2xx, we retry with backoff, but not forever. Treat webhooks as a faster alternative to polling, not a replacement: if no webhook arrives after a few minutes, fall back to the GET endpoints above.

There's no separate "batch completed" event yet. For a batch, listen for one hdr.image.* event per bracket group.

Events

Every webhook is a POST with a JSON body like this:

POSThdr.image.completed
Body
{
"eventId": "evt_uuid_here",
"eventType": "hdr.image.completed",
"createdAt": "2026-09-13T10:15:30.000Z",
"data": {
"groupId": "group_uuid_here",
"clientReferenceId": "your_internal_id",
"imageUrl": "https://storage.googleapis.com/stager-final-results/output.jpg",
"filename": "living_room_exposure_1.jpg"
}
}
POSThdr.image.failed
Body
{
"eventId": "evt_uuid_here",
"eventType": "hdr.image.failed",
"createdAt": "2026-09-13T10:15:30.000Z",
"data": {
"groupId": "group_uuid_here",
"clientReferenceId": "your_internal_id",
"errorCode": "...",
"message": "..."
}
}

eventId is unique per event. Use it to ignore duplicates if the same event is ever delivered twice (standard practice for any webhook).

Verify signatures

Every request carries two headers, so you can check it came from Stager AI and wasn't changed:

X-Stager-Signature

sha256=<hex digest>

X-Stager-Timestamp

Unix time in milliseconds when the request was sent.

The signature is an HMAC-SHA256 of ${timestamp}.${rawBody}, using your webhook secret (shown next to your webhook URL under API Access). In Node.js:

Node.js
const crypto = require('crypto')
function isValidSignature(rawBody, timestamp, signatureHeader, webhookSecret) {
const expected = crypto
.createHmac('sha256', webhookSecret)
.update(`${timestamp}.${rawBody}`)
.digest('hex')
return signatureHeader === `sha256=${expected}`
}

Sign the raw request body, before parsing the JSON. Re-serializing the parsed object can produce a different string and fail the check.

AI edits

Staging, lawns, skies and decluttering

Stage empty rooms, green up lawns, swap skies and clear out clutter. Upload the photo once, then call any of these edits with its id.

1

Upload a photo

Start here. Upload the photo as multipart form data: the id you get back is the imageId for every edit below.

POST/api/image
Header stager-api-key: your_api_key_here
Form data
// Multi-part Form Data Form
File: binary_image_payload.jpg / .png
Response
{
"success": true,
"data": {
"id": "img_71283912739",
"imageUrl": "https://storage.googleapis.com/stager-resized/cache_file.jpg",
"width": 1920,
"height": 1280
}
}

Virtual staging

Furnishes an empty room in the style you choose.

FieldWhat it does
actionTypeStaging mode.
subtleStagingautoStaging
resolutionOutput resolution.
lowhighsuperHigh
roomTypeThe type of room in the photo.
LIVING_ROOMDINING_ROOMBEDROOMKITCHENKITCHEN_LIVING_AREAHOME_OFFICETERRACEBALCONYBATHROOMEXTERIORGAME_ROOMKIDS_ROOMGARAGE
roomStyleFurniture style.
modernminimalistscandinavianindustrialrusticfarmhousekidsRoomcoastalModernluxurymidCenturybohoChicjapandi
colorTypesColor palette for the furniture.
classicWhitecharcoalGraymatteBlackbeigeespressoBrownwalnutnaturalWoodnavyBlueforestGreenrusticOakivorysteelBlueashGraymahogany
POST/api/images/:imageId/stage
Header stager-api-key: your_api_key_here
Request body
{
"roomType": "LIVING_ROOM",
"roomStyle": "modern",
"actionType": "subtleStaging",
"resolution": "high",
"colorTypes": ["classicWhite", "naturalWood"],
"removeFurniture": false,
"extraInstructions": "Add a luxury linen sofa assembly"
}
Response
{
"success": true,
"data": [
{
"id": "res_9823192",
"imageUrl": "https://storage.googleapis.com/.../staged_output.png",
"width": 1920,
"height": 1280
}
]
}

Lawn replacement

Replaces patchy grass, weeds and bare soil with a fresh, freshly cut lawn.

FieldWhat it does
lawnTypeHow strong the change is.
lightmediumstrong
resolutionOutput resolution.
lowhighsuperHigh
POST/api/images/:imageId/lawn-replace
Header stager-api-key: your_api_key_here
Request body
{
"lawnType": "medium",
"resolution": "high",
"extraInstructions": "Keep the stone pathway intact"
}
Response
{
"success": true,
"data": [
{
"id": "res_8812301",
"imageUrl": "https://storage.googleapis.com/.../lawn_output.png",
"width": 1920,
"height": 1280
}
]
}

Sky replacement

Swaps a grey or blown-out sky for a blue sky or a twilight, and adjusts the light on the building to match.

FieldWhat it does
resolutionOutput resolution.
lowhighsuperHigh
skyTypeThe new sky.
bluetwilight
skySubTypeHow strong the effect is.
subtlemiddledramatic
POST/api/images/:imageId/sky-replace
Header stager-api-key: your_api_key_here
Request body
{
"skyType": "twilight",
"resolution": "high",
"skySubType": "dramatic"
}
Response
{
"success": true,
"data": [
{
"id": "res_1102931",
"imageUrl": "https://storage.googleapis.com/.../sky_output.png",
"width": 1920,
"height": 1280
}
]
}

Declutter

Removes the furniture and objects in the room, leaving it empty and ready to stage.

POST/api/images/:imageId/declutter
Header stager-api-key: your_api_key_here
Request body
{
"extraInstructions": "Keep the built-in shelving"
}
Response
{
"success": true,
"data": [
{
"id": "res_4471029",
"imageUrl": "https://storage.googleapis.com/.../declutter_output.png",
"width": 1920,
"height": 1280
}
]
}
Video

Listing videos from your photos

Turn listing photos into marketing videos: walkthroughs, before-and-after staging, renovations and more.

Floor plan rotationSmooth 360° rotations with zoom.
Listing slow motionElegant pans and zooms across the photos.
Before and after stagingThe original next to the virtually staged room.
Before and after renovationShow what a property could become.
MusicRoyalty-free tracks, or upload your own.

How a video is made

  1. 11 to 5 photos per scene
  2. 2Order the frames
  3. 3Apply a style
  4. 4Add music
  5. 5Render
  6. 6Final video (10–15 s per scene)
1

Upload photos

Start here. Upload each photo as multipart form data and keep the id: you need it to build clips.

You can reuse an uploaded photo in several videos, or several times in the same video.

POST/api/image
Header stager-api-key: your_api_key_here
Form data
// Multi-part Form Data Form
File: binary_image_payload.jpg / .png
Response
{
"success": true,
"data": {
"id": "img_71283912739",
"imageUrl": "https://storage.googleapis.com/stager-resized/cache_file.jpg",
"width": 1920,
"height": 1280
}
}
2

Get the music library

Lists the background tracks you can use: our royalty-free music and any audio you uploaded.

GET/api/audio
Header stager-api-key: your_api_key_here
Response
{
"data": {
"items": [
{
"id": "audio_001",
"name": "Corporate Inspirational",
"url": "https://storage.googleapis.com/.../audio.mp3",
"isCustom": false
},
{
"id": "custom_audio_123",
"name": "My Custom Soundtrack",
"url": "https://storage.googleapis.com/.../custom.mp3",
"isCustom": true
}
]
}
}
3

Create a video

Starts a new video with its orientation and, optionally, music. You choose the animation style per clip.

FieldWhat it does
orientationVideo format.
horizontalvertical
audioIdOptional. An id from the music library. Without it, the template default is used.
POST/api/videos
Header stager-api-key: your_api_key_here
Request body
{
"title": "Luxury Home Tour - 123 Main St",
"orientation": "horizontal",
"audioId": "audio_001"
}
Response
{
"data": {
"id": "video_abc123xyz",
"title": "Luxury Home Tour - 123 Main St",
"orientation": "horizontal",
"videoType": "listing",
"videoUrl": null,
"clips": []
}
}
4

List your videos

All the videos you created, a page at a time.

GET/api/videos?offset=0&limit=50
Header stager-api-key: your_api_key_here
Response
{
"data": {
"items": [
{
"id": "video_abc123xyz",
"title": "Luxury Home Tour - 123 Main St",
"orientation": "horizontal",
"videoType": "listingSlowMotion",
"videoUrl": "https://storage.googleapis.com/.../final_video.mp4",
"createdAt": "2024-01-15T10:30:00Z"
}
],
"total": 1
}
}
5

Get a video

Everything about one video: its clips, photos and render status.

GET/api/videos/:videoId
Header stager-api-key: your_api_key_here
Response
{
"data": {
"id": "video_abc123xyz",
"title": "Luxury Home Tour - 123 Main St",
"videoUrl": "https://storage.googleapis.com/.../final_video.mp4",
"clips": [
{
"id": "clip_001",
"order": 0,
"videoUrl": "https://storage.googleapis.com/.../clip_001.mp4",
"images": [
{
"id": "img_123",
"imageId": "img_uuid",
"url": "https://storage.googleapis.com/.../thumbnail.jpg",
"isGeneration": false
}
]
}
]
}
}
6

Add a clip

Adds a clip (a scene) to a video. Each photo is a frame, in the order you set.

FieldWhat it does
effectThe animation style of the clip.
default (0)SlowMotionWalkthrough (1)SlowMotionAerial (2)FastSpeedRamps (3)SplitScreen (4)ExteriorToInterior (5)StagingFurnitureAppears (6)DawnToNoon (7)DayToNight (8)StarryNight (9)SummerToWinter (10)HouseLandsOnTerrain (11)HouseBuildsUp (12)Renovation (13)
cameraMotionOptional. Only used when effect is SlowMotionWalkthrough (1); ignored for any other effect.
default (0)pushIn (1)pushOut (2)slowMoDescending (3)slowMoElevating (4)orbitRight (5)orbitLeft (6)

The photo IDs must be uploaded first. order sets the frame order in the animation.

POST/api/videos/:videoId/clips
Header stager-api-key: your_api_key_here
Request body
{
"images": [
{ "id": "img_123", "order": 0 },
{ "id": "img_124", "order": 1 },
{ "id": "img_125", "order": 2 }
],
"customInstructions": "Add smooth transitions between rooms",
"effect": 1,
"cameraMotion": 0
}
Response
{
"data": {
"id": "clip_001"
}
}
7

Render the video

Builds the final video from all its clips. It runs in the background: track it with the progress endpoint.

A render usually takes 5–30 minutes, depending on the length and complexity of the video.

POST/api/videos/:videoId/render
Header stager-api-key: your_api_key_here
Request body
{}
Response
{
"data": {}
}
8

Check the render progress

Shows how far the render is and, when it is done, the video URL and file size.

Poll every 5–10 seconds. You get one of three states: "progress" (with a percentage), "done" (with the URL and size) or "error" (with a message).

GET/api/videos/:videoId/progress
Header stager-api-key: your_api_key_here
Response
// While processing:
{
"status": "progress",
"progress": 45
}
// When complete:
{
"status": "done",
"url": "https://storage.googleapis.com/.../final_video.mp4",
"size": 125000000
}
// On error:
{
"status": "error",
"message": "Render failed: insufficient memory"
}
9

Edit a clip

Changes a clip's photos, order, effect, camera motion or instructions. The video then needs a new render.

effect and cameraMotion are optional here: leave one out to keep its current value. The values are listed under Add a clip.

PUT/api/videos/:videoId/clips/:clipId
Header stager-api-key: your_api_key_here
Request body
{
"images": [
{ "id": "img_123", "order": 0 },
{ "id": "img_126", "order": 1 }
],
"customInstructions": "Updated smooth transitions",
"effect": 1,
"cameraMotion": 4
}
Response
{
"data": {
"success": true
}
}
10

Delete a clip

Removes a clip from the video. Render the video again afterwards.

DELETE/api/videos/:videoId/clips/:clipId
Header stager-api-key: your_api_key_here
Response
{
"data": {
"success": true
}
}

Ready to connect?

Tell us what you're building and we'll set up your key. We can also tune an editing style to match your look.