ListingAI API Reference
Create listings, upload photos, and generate AI videos and listing descriptions from your own systems. Every endpoint below has a copy-pasteable example.
Base URL
https://customer-api.listingai.co/api/v3
Authentication
Create an API key in Settings → API Keys (included with the Expert plan). Keys can optionally be restricted to specific IP addresses or ranges. Include your key as a Bearer token in the Authorization header on every request. The examples below assume:
export API_KEY="lai_live_your_key_here"
Requests are limited to 120 per minute per key. A 429 response includes a Retry-After header.
How it fits together
A listing is a property. You upload images to it, then request assets (a video or a description) and poll until they're ready. Only one asset of each type can be generating per listing at a time. The first asset on a listing uses 1 listing credit; full-AI video scenes use 1 video credit each.
Check your account & credits
curl https://customer-api.listingai.co/api/v3/account \
-H "Authorization: Bearer $API_KEY"
# => {
# "email": "you@example.com",
# "credits": { "video": 12, "image": 30, "listing": 8 }
# }Brandings
A branding is an agent's identity — name, contact details, headshot, logo, colours and font — applied to everything generated for listings that reference it. Create one per agent, store its id, and pass branding_id when creating listings. Updates apply automatically from the next generation: brandings are resolved fresh at render time, never copied.
# Create
curl -X POST https://customer-api.listingai.co/api/v3/brandings \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Jane Agent",
"brokerage": "Lakeside Realty",
"job_title": "Broker of Record",
"phone": "555-0142",
"email": "jane@lakesiderealty.com",
"headshot_url": "https://example.com/jane.jpg",
"logo_url": "https://example.com/lakeside-logo.png",
"brand_color": "#1A6E54",
"font": "Playfair Display",
"display_options": { "email": false }
}'
# => { "branding": { "id": 412, ... } }
# headshot_url / logo_url are downloaded and re-hosted on our CDN
# List / fetch / update
curl https://customer-api.listingai.co/api/v3/brandings -H "Authorization: Bearer $API_KEY"
curl https://customer-api.listingai.co/api/v3/brandings/412 -H "Authorization: Bearer $API_KEY"
curl -X PATCH https://customer-api.listingai.co/api/v3/brandings/412 \
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
-d '{ "phone": "555-0199" }'
# Logo & headshot can also be uploaded as files (or replaced later; 10MB max)
curl -X POST https://customer-api.listingai.co/api/v3/brandings/412/logo \
-H "Authorization: Bearer $API_KEY" \
-F "image=@logo.png"
curl -X POST https://customer-api.listingai.co/api/v3/brandings/412/headshot \
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
-d '{ "url": "https://example.com/jane.jpg" }'Branding fields (all optional except name; PATCH only touches what you send):
| Field | Required | Supported values | Description |
|---|---|---|---|
name | Yes | text | Agent name, shown on overlay cards |
headshot_url / logo_url | No | image URL (jpg/png/webp, 10MB max) | Agent headshot and brokerage logo — imported and re-hosted. Or upload files via POST /brandings/:id/headshot and /logo. |
brokerage / job_title / phone / email / website / instagram / license_number | No | text | Contact details shown per display_options |
font | No | any of the 24 caption fonts listed in the video section (default Inter) | Font used on overlay cards |
brand_color / overlay_background_color / text_color / button_text_color | No | hex colour, e.g. #1A6E54 | Accent, card background, and text colours on overlays |
display_options | No | object with boolean values for: name, licenseNumber, phone, email, website, instagram, jobTitle, brokerage, headshot, logo | Which fields appear on video scenes |
Create a listing
curl -X POST https://customer-api.listingai.co/api/v3/listings \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"address": "123 Main St, Toronto, ON",
"price": "650000",
"price_currency": "$",
"bedrooms": 3,
"bathrooms": 2,
"sqft": 1850,
"property_type": "house",
"transaction_type": "sale",
"highlights": "renovated kitchen, lake view",
"external_id": "crm-12345"
}'
# => { "id": "lst_ab12cd34...", "address": "123 Main St, Toronto, ON", ... }Only address is required. All fields:
| Field | Required | Description & supported values |
|---|---|---|
address | Yes | Full property address |
price | No | Asking price, digits only (e.g. "650000") |
price_currency | No | One of: $ £ € ¥ ₹ R د.إ |
price_type | No | For rentals: month, week, year or night |
bedrooms / bathrooms / parking | No | Property basics (numbers) |
sqft | No | Interior size in square feet |
property_type | No | e.g. house, condo, townhouse, vacant |
transaction_type | No | sale or rent |
highlights | No | Free text features for the AI to feature |
language | No | Output language, e.g. EN-US (default) |
external_id | No | Your own reference id; filterable on the list endpoint |
branding_id | No | Which of your branding profiles to use on generated assets. Defaults to your primary branding. |
List, fetch & update listings
# List (newest first; ?page, ?per_page up to 50, ?external_id filter)
curl "https://customer-api.listingai.co/api/v3/listings?per_page=10&external_id=crm-12345" \
-H "Authorization: Bearer $API_KEY"
# Fetch one listing with its images and assets
curl https://customer-api.listingai.co/api/v3/listings/lst_ab12cd34... \
-H "Authorization: Bearer $API_KEY"
# Update details or reassign the branding (applies from the next generation)
curl -X PATCH https://customer-api.listingai.co/api/v3/listings/lst_ab12cd34... \
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
-d '{ "price": "675000", "branding_id": 412 }'Upload images
Two ways: multipart file upload, or give us URLs to import. Limits: 20 images per request, 50 per listing, 10MB per image (jpg, png or webp).
# Multipart upload
curl -X POST https://customer-api.listingai.co/api/v3/listings/lst_ab12cd34.../images \
-H "Authorization: Bearer $API_KEY" \
-F "images[]=@front.jpg" \
-F "images[]=@kitchen.jpg"
# ...or import from URLs
curl -X POST https://customer-api.listingai.co/api/v3/listings/lst_ab12cd34.../images \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "urls": ["https://example.com/front.jpg", "https://example.com/kitchen.jpg"] }'
# => { "images": [ { "id": 101, "url": "https://...", "order": 0 },
# { "id": 102, "url": "https://...", "order": 1 } ] }List, reorder & delete images
# List
curl https://customer-api.listingai.co/api/v3/listings/lst_ab12cd34.../images \
-H "Authorization: Bearer $API_KEY"
# Reorder (lower order = earlier in the video; 0 is the opening shot)
curl -X PATCH https://customer-api.listingai.co/api/v3/listings/lst_ab12cd34.../images/101 \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "order": 0 }'
# Delete
curl -X DELETE https://customer-api.listingai.co/api/v3/listings/lst_ab12cd34.../images/102 \
-H "Authorization: Bearer $API_KEY"Browse music for videos
Lists the stock music library plus your own uploaded tracks (manage uploads in the web app's video editor). Use a track's music_id as the music field when generating a video — or skip music entirely (the default).
curl https://customer-api.listingai.co/api/v3/music \
-H "Authorization: Bearer $API_KEY"
# => { "tracks": [
# { "music_id": "custom_7", "type": "custom", "title": "my-anthem.mp3",
# "preview_url": "https://..." },
# { "music_id": "01_luxury_piano.mp3", "type": "stock", "title": "Luxury Piano",
# "category": "Luxury", "preview_url": "https://..." },
# ...
# ] }Generating assets
POST /listings/:id/assets starts a generation and returns 202 immediately; you then poll GET /listings/:id/assets/:asset_id until it finishes. Which fields you send depends on the asset type:
| Field | Applies to | Required | Supported values | Description |
|---|---|---|---|---|
type | all | Yes | video, description | What to generate |
aspect_ratio | video | No | 16:9 (default), 9:16, 4:5, 1:1 | Output dimensions: landscape, vertical (Reels), portrait feed, square |
mode | video | No | full (default), lite | Default scene mode: full = AI camera movement at 1 video credit per scene, lite = free pan & zoom |
music | video | No | a music_id from GET /music, or none (default) | Soundtrack, looped with a fade-out at the end |
transition | video | No | fade (default), instant, fadeblack, fadewhite, left, right, top, bottom, slideleft, slideright, slideup, slidedown, blur, pixelize, zoomin, radial | Default transition between scenes (effect details below) |
transition_duration | video | No | 0 to 1.0 seconds, e.g. 0.5 (default 1.0) | Default transition length; 0 behaves like an instant cut |
caption_font | video | No | Inter (default) — full list of 24 fonts below | Font for all scene captions |
start_scene | video | No | true (= highlight), or { "template": ... }: highlight, fullscreen | Branded opening card (address, price, basics) — the same templates as the web app's video editor |
end_scene | video | No | true (= modern), or { "template": ... }: modern, traditional, brokerage-focus | Branded contact card (headshot, name, phone, logo) over a freeze-frame of the last scene — same templates as the web app |
scenes | video | No | array of scene objects — see the scene fields table below | Per-scene control: which images, order, motion, mode, captions, transitions. When omitted, every listing image is used in image order |
A description takes only type — everything else is video-specific. Asset lifecycle, returned by the polling endpoint:
| Status | Meaning |
|---|---|
queued | Accepted, waiting for a worker |
processing | Generating (videos include a progress_stage) |
completed | result.url (videos) or result.text (descriptions) is ready |
failed | error.message explains; video credits are automatically refunded |
Only one asset of each type can be generating per listing at a time — a second request returns 409 with the in-flight asset's id so you can poll it instead.
Generating a description
Descriptions are written from the listing details you provided (price, beds, baths, highlights, etc.) — no images needed. They generate in under a minute; poll every 5 seconds.
# 1. Request the description
curl -X POST https://customer-api.listingai.co/api/v3/listings/lst_ab12cd34.../assets \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "type": "description" }'
# => 202 { "asset": { "id": 41, "type": "description", "status": "queued", ... } }
# 2. Poll until completed
curl https://customer-api.listingai.co/api/v3/listings/lst_ab12cd34.../assets/41 \
-H "Authorization: Bearer $API_KEY"
# => { "asset": {
# "id": 41, "type": "description", "status": "completed",
# "result": { "text": "Welcome to this beautifully renovated 3-bedroom..." },
# "completed_at": "2026-06-11T18:30:00Z"
# } }Generating a video
Each image becomes one scene. Full-AI scenes (mode: "full") get cinematic camera movement and cost 1 video credit each; lite scenes (mode: "lite") are free pan & zoom. Videos render at 720p and take 2–6 minutes; poll every 10 seconds.
The simplest call uses every uploaded image in order, full AI, no music:
# 1. Request the video (defaults: 16:9, full AI, image order, no music)
curl -X POST https://customer-api.listingai.co/api/v3/listings/lst_ab12cd34.../assets \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "type": "video" }'
# => 202 { "asset": { "id": 42, "type": "video", "status": "queued",
# "credits": { "video_cost": 2 } } }
# 2. Poll until completed
curl https://customer-api.listingai.co/api/v3/listings/lst_ab12cd34.../assets/42 \
-H "Authorization: Bearer $API_KEY"
# while generating:
# => { "asset": { "status": "processing", "progress_stage": "generate_videos" } }
# when done:
# => { "asset": {
# "id": 42, "type": "video", "status": "completed",
# "result": { "url": "https://.../video.mp4",
# "thumbnail_url": "https://.../video.jpg" },
# "completed_at": "2026-06-11T18:35:00Z"
# } }The full-control version — vertical video, music, custom scene order, mixed modes, per-scene motion, captions and transitions (this one costs 2 video credits: two full scenes, one lite):
curl -X POST https://customer-api.listingai.co/api/v3/listings/lst_ab12cd34.../assets \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "video",
"aspect_ratio": "9:16",
"music": "01_luxury_piano.mp3",
"caption_font": "Playfair Display",
"transition": "fade",
"transition_duration": 0.5,
"scenes": [
{ "image_id": 101, "order": 1, "mode": "full", "zoom": "sweep_in_right",
"transition": "fadeblack",
"caption": { "text": "Welcome to 123 Main St", "style": "fade_up" } },
{ "image_id": 104, "order": 2, "mode": "full", "zoom": "regular",
"caption": { "text": "Chef'"'"'s kitchen", "style": "fade_up" } },
{ "image_id": 102, "order": 3, "mode": "lite", "zoom": "zoom-out" }
]
}'Scene fields
| Scene field | Required | Supported values | Description |
|---|---|---|---|
image_id | Yes | an image id belonging to this listing | From the upload response or GET .../images |
order | No | integer, e.g. 1, 2, 3 | Scene position. When omitted, scenes play in array order. |
mode | No | full, lite | full = AI camera movement (1 credit), lite = free pan & zoom. Defaults to the top-level mode. |
zoom | No | for full: regular, zoom_center_out, sweep_in_right, sweep_in_left, sweep_in_up, sweep_in_down, sweep_out_right, sweep_out_left, sweep_out_up, sweep_out_downfor lite: zoom-in, zoom-out, static | Motion effect (described in the tables below). When omitted we pick a pleasing mix automatically. |
transition | No | same values as the top-level transition | Plays at the end of this scene, into the next one. Defaults to the top-level value. |
transition_duration | No | 0 to 1.0 seconds | Length of this scene's transition. Defaults to the top-level value (default 1.0); 0 = instant cut. |
caption | No | { "text": "...", "style": "..." } — text required, max 100 characters; style: clean_shadow (default), typewriter, dark_box, fade_up, lower_third | Text overlay for this scene. Omit for no caption (the default). |
Video option values
Reference for every value the video fields above accept. Each table is named after the field it belongs to.
zoom — when the scene's mode is full
AI camera movement applied to the scene:
| Value | Effect |
|---|---|
regular | Zoom center in (default) |
zoom_center_out | Zoom center out |
sweep_in_right / sweep_in_left / sweep_in_up / sweep_in_down | Camera sweeps into the scene from that direction |
sweep_out_right / sweep_out_left / sweep_out_up / sweep_out_down | Camera sweeps out of the scene toward that direction |
zoom — when the scene's mode is lite
Pan & zoom effect applied to the photo:
| Value | Effect |
|---|---|
zoom-in | Slow zoom in (scenes alternate in/out by default) |
zoom-out | Slow zoom out |
static | No motion |
transition — request level or per scene
How a scene hands off to the next one (a scene's transition plays at its end):
| Value | Effect |
|---|---|
fade | Cross-fade (default) |
instant | Instant cut |
fadeblack / fadewhite | Fade to black / white between scenes |
left / right / top / bottom | Wipe in that direction |
slideleft / slideright / slideup / slidedown | Next scene slides in |
blur | Horizontal blur blend |
pixelize | Pixelate blend |
zoomin | Zoom into the next scene |
radial | Radial wipe |
caption.style — per scene
The look of a scene's caption overlay:
| Value | Look |
|---|---|
clean_shadow | White text with a subtle shadow (default) |
typewriter | Text types out with a blinking cursor |
dark_box | White text on a dark background box |
fade_up | Text slides up while fading in (luxury feel) |
lower_third | Professional broadcast-style lower bar |
caption_font — request level
Font for all captions and used by scene cards (default Inter): Inter, Helvetica, Arial, Times New Roman, Georgia, Playfair Display, Merriweather, Crimson Text, Roboto, Open Sans, Lato, Work Sans, Noto Sans, PT Sans, Raleway, Montserrat, Poppins, Quicksand, Nunito, Ubuntu, Oswald, Roboto Condensed, Roboto Slab, Dancing Script.
music — request level
Any music_id from GET /music (stock tracks like 01_luxury_piano.mp3 or your own uploads like custom_7), or none for no music — which is also the default when the field is omitted.
Branded start & end scenes
start_scene opens the video with a card showing the address, price and property basics over your first scene; end_scene closes it with your contact card (headshot, name, phone, email, logo) over a freeze-frame of the last scene. Both are rendered server-side from the listing's branding profile — set up your branding (colors, logo, headshot, fonts) in the web app, and pass branding_id at listing creation if you have more than one.
curl -X POST https://customer-api.listingai.co/api/v3/listings/lst_ab12cd34.../assets \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "video",
"start_scene": true,
"end_scene": { "template": "traditional" }
}'Templates are identical to the web app's video editor: start scenes support highlight (default) and fullscreen; end scenes support modern (default), traditional and brokerage-focus. Videos with start/end scenes spend a few extra seconds in queued while the cards render.
Every text on a card is editable, every field's visibility is toggleable, and the card styling can be overridden per video — anything you don't override falls back to the listing and branding:
"start_scene": {
"template": "highlight",
"text": { "headline": "Coming Soon", // texts default to listing values
"price": "Offers over $650k" },
"show": { "summary": false }, // hide/show any field
"style": { "bg_color": "#0B3D2E", "bg_opacity": 75,
"text_color": "#FFFFFF", "font": "Oswald" }
},
"end_scene": {
"template": "traditional",
"text": { "name": "The McGrath Team", "phone": "555-0100" }, // default: branding
"show": { "headshot": false, "email": true }
}| Key | Required | Supported values | Description |
|---|---|---|---|
template | No | start: highlight (default), fullscreenend: modern (default), traditional, brokerage-focus | Which card design to render |
text | No | start: headline, address, price, summaryend: name, phone, email, website, instagram, job_title, brokerage, license_number(strings, max 200 characters each) | Override any text the template renders. Omitted keys use the listing (start) or branding (end) values. |
show | No | start: headline, address, price, summaryend: all the text keys plus headshot, logo(booleans) | Show or hide individual fields. Defaults come from the branding's display_options. |
style | No | bg_color / text_color: hex colour; bg_opacity: 0–100; font: any of the 24 caption fonts | Card styling for this video only. Defaults come from the branding. |
Listing all assets
curl https://customer-api.listingai.co/api/v3/listings/lst_ab12cd34.../assets \
-H "Authorization: Bearer $API_KEY"
# => { "assets": [ { "id": 42, "type": "video", "status": "completed", ... },
# { "id": 41, "type": "description", "status": "completed", ... } ] }Credits
The API draws from the same credit balances as the web app. The first asset generated for a listing uses 1 listing credit. Full-mode video scenes use 1 video credit each; lite scenes and descriptions use no extra credits. If a request can't be covered, the API responds 402 with your balance before anything generates — check ahead with GET /account.
Errors
Errors share one envelope: { "error": { "code": "...", "message": "..." } }
| HTTP | Code | Meaning |
|---|---|---|
| 401 | invalid_api_key | Missing, malformed or revoked key |
| 402 | insufficient_credits | Not enough credits; includes credits_required and credits_available |
| 403 | ip_not_allowed | Request IP is outside the key's allowlist |
| 403 | subscription_required | The account no longer has a paid plan |
| 404 | not_found | Listing, image or asset doesn't exist (or isn't yours) |
| 409 | asset_in_progress | An asset of that type is already generating; includes asset_id |
| 422 | invalid_request / no_images / too_many_images / image_too_large / image_download_failed | The request can't be processed as sent |
| 429 | rate_limited | Over 120 requests/minute; retry after Retry-After seconds |
Support
Common questions about access, credits, and limits are answered in the API FAQ. Need something the API doesn't do yet? Contact support — we're happy to help with integrations.