Register

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):

FieldRequiredSupported valuesDescription
nameYestextAgent name, shown on overlay cards
headshot_url / logo_urlNoimage 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_numberNotextContact details shown per display_options
fontNoany 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_colorNohex colour, e.g. #1A6E54Accent, card background, and text colours on overlays
display_optionsNoobject with boolean values for: name, licenseNumber, phone, email, website, instagram, jobTitle, brokerage, headshot, logoWhich 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:

FieldRequiredDescription & supported values
addressYesFull property address
priceNoAsking price, digits only (e.g. "650000")
price_currencyNoOne of: $ £ ¥ R د.إ
price_typeNoFor rentals: month, week, year or night
bedrooms / bathrooms / parkingNoProperty basics (numbers)
sqftNoInterior size in square feet
property_typeNoe.g. house, condo, townhouse, vacant
transaction_typeNosale or rent
highlightsNoFree text features for the AI to feature
languageNoOutput language, e.g. EN-US (default)
external_idNoYour own reference id; filterable on the list endpoint
branding_idNoWhich 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:

FieldApplies toRequiredSupported valuesDescription
typeallYesvideo, descriptionWhat to generate
aspect_ratiovideoNo16:9 (default), 9:16, 4:5, 1:1Output dimensions: landscape, vertical (Reels), portrait feed, square
modevideoNofull (default), liteDefault scene mode: full = AI camera movement at 1 video credit per scene, lite = free pan & zoom
musicvideoNoa music_id from GET /music, or none (default)Soundtrack, looped with a fade-out at the end
transitionvideoNofade (default), instant, fadeblack, fadewhite, left, right, top, bottom, slideleft, slideright, slideup, slidedown, blur, pixelize, zoomin, radialDefault transition between scenes (effect details below)
transition_durationvideoNo0 to 1.0 seconds, e.g. 0.5 (default 1.0)Default transition length; 0 behaves like an instant cut
caption_fontvideoNoInter (default) — full list of 24 fonts belowFont for all scene captions
start_scenevideoNotrue (= highlight), or { "template": ... }: highlight, fullscreenBranded opening card (address, price, basics) — the same templates as the web app's video editor
end_scenevideoNotrue (= modern), or { "template": ... }: modern, traditional, brokerage-focusBranded contact card (headshot, name, phone, logo) over a freeze-frame of the last scene — same templates as the web app
scenesvideoNoarray of scene objects — see the scene fields table belowPer-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:

StatusMeaning
queuedAccepted, waiting for a worker
processingGenerating (videos include a progress_stage)
completedresult.url (videos) or result.text (descriptions) is ready
failederror.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 fieldRequiredSupported valuesDescription
image_idYesan image id belonging to this listingFrom the upload response or GET .../images
orderNointeger, e.g. 1, 2, 3Scene position. When omitted, scenes play in array order.
modeNofull, litefull = AI camera movement (1 credit), lite = free pan & zoom. Defaults to the top-level mode.
zoomNofor 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_down
for lite: zoom-in, zoom-out, static
Motion effect (described in the tables below). When omitted we pick a pleasing mix automatically.
transitionNosame values as the top-level transitionPlays at the end of this scene, into the next one. Defaults to the top-level value.
transition_durationNo0 to 1.0 secondsLength of this scene's transition. Defaults to the top-level value (default 1.0); 0 = instant cut.
captionNo{ "text": "...", "style": "..." }text required, max 100 characters; style: clean_shadow (default), typewriter, dark_box, fade_up, lower_thirdText 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:

ValueEffect
regularZoom center in (default)
zoom_center_outZoom center out
sweep_in_right / sweep_in_left / sweep_in_up / sweep_in_downCamera sweeps into the scene from that direction
sweep_out_right / sweep_out_left / sweep_out_up / sweep_out_downCamera sweeps out of the scene toward that direction

zoom — when the scene's mode is lite

Pan & zoom effect applied to the photo:

ValueEffect
zoom-inSlow zoom in (scenes alternate in/out by default)
zoom-outSlow zoom out
staticNo motion

transition — request level or per scene

How a scene hands off to the next one (a scene's transition plays at its end):

ValueEffect
fadeCross-fade (default)
instantInstant cut
fadeblack / fadewhiteFade to black / white between scenes
left / right / top / bottomWipe in that direction
slideleft / slideright / slideup / slidedownNext scene slides in
blurHorizontal blur blend
pixelizePixelate blend
zoominZoom into the next scene
radialRadial wipe

caption.style — per scene

The look of a scene's caption overlay:

ValueLook
clean_shadowWhite text with a subtle shadow (default)
typewriterText types out with a blinking cursor
dark_boxWhite text on a dark background box
fade_upText slides up while fading in (luxury feel)
lower_thirdProfessional 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 }
}
KeyRequiredSupported valuesDescription
templateNostart: highlight (default), fullscreen
end: modern (default), traditional, brokerage-focus
Which card design to render
textNostart: headline, address, price, summary
end: 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.
showNostart: headline, address, price, summary
end: all the text keys plus headshot, logo
(booleans)
Show or hide individual fields. Defaults come from the branding's display_options.
styleNobg_color / text_color: hex colour; bg_opacity: 0100; font: any of the 24 caption fontsCard 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": "..." } }

HTTPCodeMeaning
401invalid_api_keyMissing, malformed or revoked key
402insufficient_creditsNot enough credits; includes credits_required and credits_available
403ip_not_allowedRequest IP is outside the key's allowlist
403subscription_requiredThe account no longer has a paid plan
404not_foundListing, image or asset doesn't exist (or isn't yours)
409asset_in_progressAn asset of that type is already generating; includes asset_id
422invalid_request / no_images / too_many_images / image_too_large / image_download_failedThe request can't be processed as sent
429rate_limitedOver 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.