Public REST API for AI property video generation — upload property photos, get back cinematic videos and reels.
The Property Edits API v1 lets you turn property photos into cinematic videos programmatically, then merge them into branded reels. All endpoints are served from a single base URL.
https://app.photoaivideo.comAllowed durations (seconds)
456
Camera effects
dolly_zoompull_outpush_inaerial_dronelow_anglehigh_angleaerialzoom_inzoom_outtruck_lefttruck_rightpedestal_downpedestal_upcrane_downcrane_uppan_leftpan_rightorbit_leftorbit_right
All input image URLs must live on our storage (from /api/v1/presigned-urls) — external URLs are rejected.
Every request needs an API key, sent either way. Create keys from the API dashboard.
Authorization: Bearer <API_KEY>
# or
x-api-key: <API_KEY>401403402{ "error": "Invalid or missing API key" }402GET /api/v1/credits{ "error": "Not enough credits", "required": 1, "remaining": 0 }/api/v1/presigned-urlsGet signed upload URLs for your input images. Free — no credits.
| Field | Required | Default | Notes |
|---|---|---|---|
count | optional | 1 | number of URLs, 1–20 (query parameter) |
{
"bucket": "api-uploads",
"uploads": [
{
"path": "<keyId>/<uuid>",
"token": "...",
"presignedUrl": "https://....supabase.co/storage/...",
"publicUrl": "https://....supabase.co/storage/..."
}
]
}Then upload each file with PUT <presignedUrl> and the raw image bytes as the body (Content-Type: image/jpeg or image/png). Use the matching publicUrl in the endpoints below.
/api/v1/videosTurn one property photo into a cinematic video clip. Asynchronous — returns a jobId immediately; poll for the result below. Costs 1 credit.
| Field | Required | Default | Notes |
|---|---|---|---|
prompt | required | — | describes the scene/motion |
imageUrl | required | — | first frame; our storage only |
endImageUrl | optional | — | optional last frame; our storage only |
effects | optional | [] | camera-movement effect codes (see Getting started) |
duration | optional | 5 | 4, 5 or 6 seconds |
{
"prompt": "smooth cinematic reveal of a modern living room",
"imageUrl": "https://....supabase.co/storage/v1/object/public/api-uploads/...",
"effects": ["push_in", "pan_left"],
"duration": 5
}{ "jobId": "uuid", "status": "pending", "creditsCharged": 1 }/api/v1/videos/:jobId{
"job": {
"id": "uuid",
"model": "seedance-1-5-pro-251215",
"prompt": "smooth cinematic reveal of a modern living room",
"image_url": "https://...",
"end_image_url": null,
"effects": ["push_in", "pan_left"],
"duration": 5,
"status": "pending | processing | completed | failed",
"video_url": "https://...-or-null",
"download_url": "https://...?download",
"error_message": null,
"credit_cost": 1,
"created_at": "...",
"processed_at": "..."
}
}video_url plays inline in a browser; download_url is the same file with a forced-download flag. Poll every few seconds until status is completed or failed. Failed jobs refund the credit automatically.
/api/v1/videos?limit=20&offset=0List this key's video jobs, newest first. Returns { jobs: [...], total, limit, offset }.
/api/v1/reelsMerge 2–20 completed video jobs into one reel, in the order given, with optional background music, text overlay, ending screen and brand logo. Asynchronous — poll for the result below. Costs 1 credit.
| Field | Required | Default | Notes |
|---|---|---|---|
videoJobIds | required | — | 2–20 completed job ids; order = clip order; duplicates allowed |
orientation | optional | landscape | landscape (1920x1080) | portrait (1080x1920) |
musicType | optional | no_music | no_music | tropical | lofi | jazz |
isFadingEnable | optional | true | fade in/out per clip |
textOverlay | optional | — | text on top of the video (see below) |
ending | optional | — | closing card appended after the last clip (see below) |
logo | optional | — | watermark logo overlaid on the whole video (see below) |
textOverlay
| Field | Required | Default | Notes |
|---|---|---|---|
text | required | — | overlay text, line breaks allowed |
fontFamily | optional | Roboto | Arial | Helvetica | Times New Roman | Roboto | Open Sans | Montserrat |
fontSize | optional | 48 | 12–200 px |
color | optional | #ffffff | text color, hex |
backgroundColor | optional | #000000 | text box color, hex |
backgroundOpacity | optional | 40 | 0–100 |
bold / italic | optional | false | |
position | optional | {x: 50, y: 80} | percent of frame — {x:0,y:0} top-left, {x:100,y:100} bottom-right |
duration | optional | whole video | seconds the text stays visible from the start |
ending
| Field | Required | Default | Notes |
|---|---|---|---|
text | required | — | ending message |
fontSize | optional | 72 | 12–200 px |
color | optional | #ffffff | text color, hex |
backgroundColor | optional | #000000 | card background, hex (ignored when backgroundImageUrl set) |
backgroundImageUrl | optional | — | card background image; our storage only |
duration | optional | 3 | 1–10 seconds |
logo
| Field | Required | Default | Notes |
|---|---|---|---|
url | required | — | PNG recommended; our storage only |
position | optional | {x: 95, y: 5} | percent of frame (default = top-right) |
scale | optional | 15 | logo width as % of video width, 5–40 |
opacity | optional | 100 | 0–100 |
{
"videoJobIds": ["uuid-1", "uuid-2"],
"musicType": "lofi",
"orientation": "portrait",
"textOverlay": {
"text": "123 Main Street, Miami",
"fontFamily": "Montserrat",
"fontSize": 56,
"bold": true,
"position": { "x": 50, "y": 85 },
"duration": 5
},
"ending": {
"text": "Contact us today!",
"backgroundColor": "#1a1a2e",
"duration": 3
},
"logo": {
"url": "https://....supabase.co/storage/v1/object/public/api-uploads/.../logo.png",
"scale": 15,
"opacity": 90
}
}{ "jobId": "uuid", "status": "pending", "creditsCharged": 1 }/api/v1/reels/:jobId{
"job": {
"id": "uuid",
"video_job_ids": ["uuid-1", "uuid-2"],
"orientation": "portrait",
"music_type": "lofi",
"is_fading_enable": true,
"status": "pending | processing | completed | failed",
"output_video_url": "https://...-or-null",
"download_url": "https://...?download",
"error_message": null,
"credit_cost": 1,
"created_at": "...",
"processed_at": "..."
}
}/api/v1/reels?limit=20&offset=0List this key's reel jobs, newest first. Returns { jobs: [...], total, limit, offset }.
/api/v1/credits{
"totalCredits": 100,
"usedCredits": 12,
"remainingCredits": 88
}KEY="your-api-key"
BASE="https://app.photoaivideo.com"
# 1. get an upload URL
curl -s "$BASE/api/v1/presigned-urls?count=1" -H "Authorization: Bearer $KEY"
# 2. upload the property photo
curl -s -X PUT "<presignedUrl>" -H "Content-Type: image/jpeg" --data-binary @photo.jpg
# 3. create the video job
curl -s -X POST "$BASE/api/v1/videos" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{
"prompt": "cinematic reveal of a modern kitchen",
"imageUrl": "<publicUrl>",
"effects": ["push_in"],
"duration": 5
}'
# 4. poll until completed
curl -s "$BASE/api/v1/videos/<jobId>" -H "Authorization: Bearer $KEY"
# 5. merge completed clips into a reel
curl -s -X POST "$BASE/api/v1/reels" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{ "videoJobIds": ["<jobId1>", "<jobId2>"], "musicType": "lofi" }'
# 6. poll the reel
curl -s "$BASE/api/v1/reels/<reelJobId>" -H "Authorization: Bearer $KEY"| Status | Meaning |
|---|---|
| 400 | Validation failed — message lists the allowed values |
| 401 | Invalid or missing API key |
| 402 | Not enough credits (required / remaining included) |
| 403 | API access not enabled for this account — contact support |
| 404 | Resource not found (job not owned by this key) |
| 500 | Internal server error |