Developers

Listwyse API

Send the photos you shoot. Get finished listing photos back.

https://listwyse.com/api/v1/studioOpenAPI file

Overview

The Listwyse API turns your photographers' raw vehicle photos into finished listing photos. You send each photo with the car's stock number. We retouch it where it was taken, or place the car in a showroom, check it against your original, and send it back.

You send

A photo (a link or a file), the car's stock number, and which dealer it's for.

You get back

The finished photo as a JPEG, PNG or WebP, usually a few minutes later.

You pay

Credits for each finished photo. Anything that fails is free.

A retouch fixes what a quick shoot on the lot gets wrong, and the car stays exactly the same car: same paint, wheels and badges. Each dealer's settings decide how far it goes: whether dirt and water spots come off, the lot behind the car is tidied, or a crooked photo is straightened. It's built for companies that shoot for many dealers: each dealer keeps its own settings, and you name dealers, cars and photos by your own ids.

Quickstart

Four requests, from your key to a finished photo.

  1. 1

    Check your key

    Your Listwyse contact gives you an API key; it starts sk_live_. Keep it on your server: anyone who has it can spend your credits. This call shows your account and your credits.

    curl "https://listwyse.com/api/v1/studio/account" \
      -H "Authorization: Bearer $LISTWYSE_KEY"
  2. 2

    Add a dealer

    Add each dealer you shoot for, with your own code for it (code, say DEALER-01). From then on you name the dealer by that code, as dealerCode. We also give each dealer our own id (dealerId); you can use that instead if you prefer. (A dealership's own account is one dealer, so it skips this step and never names one.)

    curl -X POST "https://listwyse.com/api/v1/studio/dealers" \
      -H "Authorization: Bearer $LISTWYSE_KEY" \
      -H "Content-Type: application/json" \
      -d '{
      "name": "Example Motors",
      "code": "DEALER-01"
    }'
  3. 3

    Send a photo

    Send the photo's link with the car's stock number and the dealer's code. Give it your own photoId: that's how you find it later and retry safely. The answer comes back at once, with the photo queued.

    curl -X POST "https://listwyse.com/api/v1/studio/photos" \
      -H "Authorization: Bearer $LISTWYSE_KEY" \
      -H "Content-Type: application/json" \
      -d '{
      "photoId": "PHOTO-1001",
      "url": "https://cdn.example.com/shoots/88231.jpg",
      "stockNumber": "A1234",
      "dealerCode": "DEALER-01"
    }'
  4. 4

    Get the finished photo

    A few minutes later the photo is completed and url is the finished file. Ask for it by your photoId, or set a webhook and we'll tell you the moment it's ready.

    curl "https://listwyse.com/api/v1/studio/photos/PHOTO-1001" \
      -H "Authorization: Bearer $LISTWYSE_KEY"
That's the whole loop. The rest of these docs is detail on those four steps.

Keys and requests

  • Every call goes to https://listwyse.com/api/v1/studio followed by the path, such as /photos.
  • Every request carries your key in a header: Authorization: Bearer sk_live_…. A key only sees its own account. Use it from your server, never from a browser or a phone app.
  • Requests and answers are JSON. Times are in UTC, written like 2026-10-24T15:05:40.000Z.
  • Every answer carries a requestId (also the X-Request-Id header). Quote it if you ask us about a call.
  • Your own ids work in paths: GET /photos/PHOTO-1001 finds the photo you sent as PHOTO-1001. Encode any characters a URL can't carry (encodeURIComponent in JavaScript).

How it works

How the API works

What the API is made of, how a request turns into a finished photo, and what it costs.

Structure

Account

Your company

Holds your credits and your API key, with its webhook URL.

your API key

Dealer

Example Motors

A dealership you shoot for, with its own settings.

dealerCode: DEALER-01

Vehicle

Stock A1234 / VIN 1HGCV1F34PA012345

One car at that dealer, named by its stock number or its VIN. Its first photo makes it.

stockNumber or vin

Photo

photoId: PHOTO-1001

frontAnglecompleted

Photo

photoId: PHOTO-1002

sidequality_assurance

Photo

photoId: PHOTO-1003

dashboardprocessing
+ the dealer's other vehicles
+ your other dealers
Each thing lives inside the one before. The label on the right of each is how a call names it.

There's no call to make a vehicle. Every photo names its car by stock number (stockNumber) or VIN (vin); send either, or both. The first photo with a new one makes the car, and every later photo with the same stock number or VIN, at the same dealer, joins it.

You can name everything by your own ids: dealers by your code (dealerCode), cars by stock number, photos by your photoId. Each also has our id (id) if you'd rather keep that.

Nothing waits

Making a photo takes a few minutes, so no request waits for it.

When you send a photo, the answer comes straight back (202 Accepted) with the photo queued. The work then happens on our side, and you hear it's done in one of two ways:

  • We tell you. A webhook: the moment a photo finishes, we POST it to your server. Use this in production.
  • You ask. GET /photos/:id until its status is completed or failed. Handy while you're building.

A photo's life

A photo has one status at a time and only moves forward. completed and failed are the two ends.

queuedcredits heldprocessingquality_assurancecan add a few minutescompletedurl is readyfailedcredits given backcan't startcan't be madedoesn't passpassescredits kept
A photo's statuses. Its credits are held while it's on its way, kept when it's completed, and given back if it fails.
statusWhat's happeningIts credits
queuedReceived. We download it and work out what it shows.Held
processingBeing made.Held
quality_assuranceBeing checked against your original. This can add a few minutes.Held
completedDone: url is the finished file.Kept
failedCouldn't be done, or didn't pass quality assurance: error says why.Given back

Every photo also has statusText: a short sentence for its status, ready to show in your own app (“Going through quality assurance”).

Quality assurance

Every photo goes through quality assurance before it reaches you.

As part of quality control we analyse each photo and make improvements to keep result to a high standard.

  • While this happens, the photo's status is quality_assurance. It can add a few minutes to a photo.
  • It passes: the photo is completed and sent to you.
  • It doesn't pass: the photo is failed, with error.code QUALITY_ASSURANCE_FAILED (“Did not pass quality assurance”). You aren't charged, and sending it again (the same photoId) tries it again.

Credits

You pay for photos you get back finished, and for the assets you have us make. Nothing else.

Your account has a balance of credits and a price per photo; GET /account shows both. A photo's price is held from your balance the moment you send it.

If your balance can't cover a call, nothing starts: you get 402 OUT_OF_CREDITS with what was needed. Every send answers with credits.held and credits.balance, so you always know where you stand. Credits count to one decimal place. Your Listwyse contact tops up your balance.

Making a dealer's assets costs 1 credit for each branding design or custom background build, held the same way and given back if it fails.

GET /usage lists every charge, newest first, with totals: narrow it to one dealer and a month to bill that dealer.

Guides

Guides

Each part of an integration in turn: sending, hearing back, settings, a dealer's assets, and what to do when something goes wrong.

Sending photos

Send a public https link as url: a JPEG, PNG, WebP or GIF under 12MB. We download it within moments and keep our own copy, so the link only needs to work until the photo is processing.

Or upload the file itself: send multipart/form-data with a file field (under 4MB) and the other fields beside it as form fields, branding as JSON text.

Upload a file
curl -X POST "https://listwyse.com/api/v1/studio/photos" \
  -H "Authorization: Bearer $LISTWYSE_KEY" \
  -F "file=@88231.jpg" \
  -F "photoId=PHOTO-1001" \
  -F "stockNumber=A1234" \
  -F "dealerCode=DEALER-01" \
  -F "angle=frontAngle" \
  -F "outputFormat=png"

Your id for the photo

photoId is your own id for the photo, any text up to 200 characters. Send it every time; it does two jobs.

  • Retrying is safe. Send the same photoId twice and you get the first photo back, not a second one, and you aren't charged again. So if a request times out, send it again. If that photo had failed, sending it again tries it again.
  • You can look it up by it: GET /photos/PHOTO-1001.

A photoId belongs to one car. Sending it again with a different stock number is refused (409 PHOTO_ID_TAKEN).

The car and the dealer

stockNumber or vin says which car the photo is of, and dealerCode says which dealer. If you shoot for dealers, every photo must name one (400 DEALER_REQUIRED otherwise), so a car never lands at the wrong dealer.

What the photo shows

You don't need to tell us. We look at each photo and work out what it shows (the front, a side, the dashboard, a badge…), and make it to suit.

If your app already knows each shot for certain, you can send it as angle (the names are in the reference). Only send one you're sure of: we use it as given, so a wrong one makes a worse photo than leaving it out. A photo that isn't of a car (a window sticker, a banner) comes back as it was, free, with a note.

How it should come out

Photos follow their dealer's saved settings. Any send can change them for its own photos: the controls (cleanup, outputFormat and the rest), branding (the dealer's plate, mat or screen) and background (no background, or a background). Settings has them all.

Limits

  • A car holds up to 60 photos, any mix of them: ten badges or thirty cabin photos are fine.
  • Uploaded files must be under 4MB; links, under 12MB.
  • A car shot again? Remove its old photos (DELETE /photos/:id) to make room.
  • Up to 300 of your account's photos are made at once. Send as many as you like: the rest wait, queued, and start as others finish. Nothing is turned down.
  • A key can make 600 requests a minute. Past that you get 429 RATE_LIMITED with a Retry-After header saying how many seconds to wait. Use webhooks rather than asking again and again.

Sending a whole car

POST /vehicles sends up to 60 photos of one car in one request.

Underneath, each photo is exactly a photo call: the same statuses, files and price. Four things are different:

  • All or nothing on credits. The whole call is held at once. If your balance can't cover every new photo, none start.
  • One signal at the end. Each photo still sends its own webhook, and vehicle.completed arrives once every photo from the call is done, with all of them in it.
  • Links only. A vehicle call takes urls, not file uploads.
  • Safe to repeat. Photos whose photoId you've sent before come back as they are (their ids are listed in reused), so sending the call again after a timeout costs nothing. Any that had failed are tried again.
curl -X POST "https://listwyse.com/api/v1/studio/vehicles" \
  -H "Authorization: Bearer $LISTWYSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "stockNumber": "A1234",
  "vin": "1HGCV1F34PA012345",
  "dealerCode": "DEALER-01",
  "branding": [
    "plate",
    "floorMat",
    "screen"
  ],
  "photos": [
    {
      "photoId": "PHOTO-1001",
      "url": "https://cdn.example.com/shoots/88231.jpg"
    },
    {
      "photoId": "PHOTO-1002",
      "url": "https://cdn.example.com/shoots/88232.jpg"
    },
    {
      "photoId": "PHOTO-1003",
      "url": "https://cdn.example.com/shoots/88233.jpg"
    }
  ]
}'

Which should I use?

Send photos one at a time if your app uploads them as they're shot: each comes back as soon as it's ready. Use the vehicle call if you collect a car's photos first and want one signal for the whole car.

Webhooks

A webhook is a request we send to your server when a photo, a branding design or a custom background finishes.

The webhook belongs to your API key: events for the photos sent, and the assets started, with a key go to that key's URL.

Set it up

  1. Pick an https URL on your server and set it: PUT /webhook.
  2. Copy the secret from the answer (GET /webhook shows it again any time). It signs every event, so you can tell they're from us.
  3. Send yourself a test: POST /webhook/test. GET /webhook/deliveries then shows what your server answered.

What we send

A POST with a JSON body, and these headers:

HeaderWhat it holds
X-Listwyse-EventThe event's name.
X-Listwyse-DeliveryThe delivery's id (the same as id in the body).
X-Listwyse-SignatureThe signature: t=…,v1=… (below).
EventWhenIn data
photo.completedA photo is done.photo
photo.failedA photo couldn't be done.photo, with its error
vehicle.completedEvery photo from one vehicle call is done.vehicle, with those photos, and batchId
branding.completedA branding design is saved and in use.branding, the item, and dealerId
branding.failedA branding design couldn't be made (its credit was given back).branding, the item, dealerId and error
background.completedA custom background is built: pick one of exampleUrls.template
background.failedA custom background couldn't be built (its credit was given back).template, with its error
webhook.testYou asked for a test.a message
photo.completed
{
  "id": "e2b1c0de-0a8f-4c55-9b1e-6f3a2d7c9e10",
  "event": "photo.completed",
  "createdAt": "2026-10-24T15:05:40.000Z",
  "data": {
    "photo": {
      "id": "5b0c1e8a-7f0e-4b7a-9a43-0a1c3f2d9e11",
      "photoId": "PHOTO-1001",
      "stockNumber": "A1234",
      "status": "completed",
      "url": "https://…/p29031.jpg",
      "format": "jpeg",
      "edited": true,
      "cost": 0.1,
      "…": "the rest of the photo"
    }
  }
}

Check it's from us

X-Listwyse-Signature looks like t=1729782340,v1=5f2b…. t is when it was sent, in seconds. v1 is an HMAC-SHA256, made with your secret, of t, a full stop and the raw body. Work it out yourself and compare. Check t is recent too, so an old event can't be replayed at you.

Verify
import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody: the request body exactly as it arrived, before any JSON parsing.
// header: the X-Listwyse-Signature header. secret: from GET /webhook.
export function isFromListwyse(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((part) => part.split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // older than 5 minutes
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const given = Buffer.from(parts.v1 ?? "", "hex");
  const wanted = Buffer.from(expected, "hex");
  return given.length === wanted.length && timingSafeEqual(given, wanted);
}
Use the body exactly as it arrived. Parsing the JSON and turning it back into text changes its bytes, and the signature won't match.

Answer fast, handle each event once

  • Reply with any 2xx within 8 seconds, then do the work. Anything else (an error, a time-out, a redirect) counts as a failed delivery.
  • A failed delivery is tried again after 1, 5, 15 and 30 minutes, then 1, 2, 4, 8, 12, 24 and 24 hours apart: about three days in all. GET /webhook/deliveries shows every try, and POST /webhook/deliveries/:id/resend sends one now.
  • A retry has the same id. Keep the ids you've handled and skip repeats.

Or ask

Without a webhook, ask GET /photos/:id (or GET /vehicles/:id for a vehicle call) every 10 to 30 seconds until it's done.

Wait for a photo
async function waitForPhoto(photoId) {
  for (;;) {
    const res = await fetch(`https://listwyse.com/api/v1/studio/photos/${encodeURIComponent(photoId)}`, {
      headers: { Authorization: `Bearer ${process.env.LISTWYSE_KEY}` },
    });
    const { photo } = await res.json();
    if (photo.status === "completed" || photo.status === "failed") return photo;
    await new Promise((resolve) => setTimeout(resolve, 15_000)); // 15 seconds
  }
}

Settings

Settings decide how a photo comes out. There are three kinds, and the photo, vehicle and redo calls take all three.

KindOn a callWhat it decidesLeft out of a call
Controlscleanup, outputFormat…How clean the car and the lot get, the framing, and the file you get back.The dealer's saved settings
BrandingbrandingThe dealer's plate, floor mat and screen.None
BackgroundsbackgroundNo background (the photo's own surroundings), or a background: one of our presets or a custom one.The dealer's saved settings

Controls

Controls are the sliders (how far the retouch goes) and the file you get back. Choose them on each call, or save them once for a dealer and leave them out. They have the same names either way, and a call's own choice always wins.

Manual: on the call

Send any of the controls with the photos. They apply to that call's photos only and aren't saved. Send just the ones you want to change: the rest come from the dealer's saved settings.

POST /photos
{
  "url": "https://cdn.example.com/shoots/88231.jpg",
  "stockNumber": "A1234",
  "dealerCode": "DEALER-01",
  "cleanup": 1,
  "reflections": 0,
  "outputFormat": "png",
  "aspectRatio": "4:3"
}
ControlChoicesDefaultWhat it does
cleanup0 As shot · 1 Detailed0 As shotHow clean the car looks: dirt, dust and water spots. Dents and scratches always stay.
backgroundTidy0 As shot · 1 Tidied · 2 Staged0 As shotHow much of the lot behind the car is tidied, when the background is kept.
reflections0 Minimal · 1 Natural0 MinimalHow much of the surroundings shows in the paint and glass.
framing0 Reframed · 1 As shot1 As shotWhether a crooked photo may be straightened and re-cropped, or keeps its framing.
outputFormatjpeg · png · webpjpegThe file type photos come back as.
aspectRatiomatch · 4:3 · 3:2 · 16:9matchThe shape photos come back in. match keeps each photo's own shape; the others crop the finished photo to that shape, keeping the car in frame.

Sliders are step numbers. GET /dealers/:id/settings lists every step and its name under options. The Default column is Listwyse's default.

Dealer settings: saved once

Save controls for a dealer and every call for that dealer uses them without saying. There's no separate settings id: the dealerId (or dealerCode) a call already sends picks the dealer's saved settings.

Each control comes from the first place that has it:

  1. The call itself (manual).
  2. The dealer's own settings: PATCH /dealers/:id/settings. The first change gives the dealer its own copy (what it followed, plus your change), so later changes to your account's settings don't reach it.
  3. Your account's settings: PATCH /dealers/default/settings. Every dealer follows them until it has its own.
  4. Listwyse's defaults.

Saved settings use the same names as a call, so what GET /dealers/:id/settings returns can be sent on a call as it is. They also hold a background. Branding isn't saved: ask for it on each call. The same settings show in the Listwyse app.

curl -X PATCH "https://listwyse.com/api/v1/studio/dealers/DEALER-01/settings" \
  -H "Authorization: Bearer $LISTWYSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "reflections": 1,
  "outputFormat": "png"
}'

Branding

Puts the dealer's own branding on its photos. It's off unless a call asks: send branding with the items you want.

On any photo, vehicle or redo call
"branding": ["plate", "floorMat", "screen"]
ItemWhat it adds
plateThe dealer's plate on the car, front or back, when the photo shows that plate spot.
floorMatThe dealer's mat in the driver's footwell, when the photo shows the footwell.
screenThe dealer's picture on the centre screen, when the photo shows the screen.

Only where it fits

We look at each photo and add an item only where the photo shows its spot: the plate on a photo that shows a plate spot, the mat where the driver's footwell shows, the screen on a dashboard photo. A photo with no spot gets none, at the same price. So it's safe to ask for every item on a whole car.
  • Set up once per dealer. The plate, mat and screen come from the dealer's logo, or from photos of its own: Asset creation shows how. Asking for branding before the dealer has a logo is turned down with 400 BRANDING_NOT_SET_UP.
  • On a redo, send branding to change it ([] turns it off), or leave it out to keep what the photo had.

Backgrounds

A photo comes back with no background or with a background. Send background on a call, or save one in the dealer's settings. Listwyse's default is no background.

OptionSendWhat you get
No background"keep"The photo keeps its own surroundings, retouched where it was taken, as far as Clean-up and Background tidy say.
A backgroundA background's idThe car is placed on a premade background, in the same view. You still get one finished photo for each photo you send.

A background is one of our presets, or a custom one made for the dealer.

Presets

Background idNameWhat it is
grey-studio-catalogueInfinitySeamless grey cyclorama.
panel-showroom-catalogueLumenGloss floor, light-panel ceiling.
showroom-set-v2SignatureLight-grey walls, a lit ceiling, a tiled floor.
outdoor-catalogueHorizonEmpty field, open sky.

Custom backgrounds

The best background is the dealer's own showroom, and we encourage it: make one in Asset creation, from photos of the building or from an empty photo of it you already have. Once it's ready, its id (starting custom:) works just like a preset, and GET /templates lists it beside ours.

A custom background is one dealer's own or universal, chosen when it's made. A dealer's photos can use its own and the universal ones, never another dealer's showroom; your account's default settings, only universal ones. GET /templates?dealerCode=… lists exactly what a dealer can use.

A close-up a background has no shot for (a badge, say) comes back with no background instead, and its note says so.

Asset creation

A dealer's branding and its own custom background are made once, then used on every photo that asks for them.

AssetMade fromCostsUsed when
Plate, screen, floor matThe dealer's logo (we design each one), or a photo of the dealer's own1 credit per design; a photo of their own is freeA call sends branding
A custom background (the dealer's showroom)Photos of the building (we build an empty showroom), or an empty photo you already have1 credit per build; a photo of your own is freeA call's background is its id

Name the dealer as on the photo calls, with dealerId or dealerCode; a dealership's own account leaves it out. A design or build that fails gives its credit back, and asking for one that's already being made isn't charged again.

Branding

  1. Add the logo: POST /branding/logo, as a link or a file.
  2. Make the items: POST /branding/designs designs a plate, a screen picture and a floor mat from the logo, 1 credit each, in a minute or two. Or, if the dealer has a real one, send a photo of it with POST /branding/photos, free.
  3. Check them: a branding.completed webhook arrives as each item lands (or ask GET /branding until each item's generating is false). chosenUrl is what photos get; PATCH /branding switches to another saved design.
  4. Use them: send branding on a photo or vehicle call.
curl -X POST "https://listwyse.com/api/v1/studio/branding/designs" \
  -H "Authorization: Bearer $LISTWYSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "kinds": [
    "displayPlate",
    "monitor",
    "floorMat"
  ],
  "dealerCode": "DEALER-01"
}'

A custom background

Made from the dealer's own showroom, so its cars are shown where they're sold.

  1. Start it: POST /templates/custom with up to 8 photos of the building: the showroom floor, the walls, the signage. A note says what to keep (“the red wall logo behind the car”). Name the dealer it's for (only its photos can use it), or send universal: true so every dealer on your account can.
  2. Build it: POST /templates/custom/:id/build, 1 credit, a few minutes. We turn the photos into an empty, tidied-up showroom with room for a car.
  3. Pick the photo: a background.completed webhook arrives when it's built (or ask GET /templates/custom/:id until status is examples), then PATCH /templates/custom/:id with one of exampleUrls as selectedUrl. The background is ready.
  4. Use it: send its templateId (custom:…) as background, or save it as the dealer's background in its settings.

Already have a clean photo of the empty showroom? Skip steps 2 and 3: POST /templates/custom/:id/plate makes it the background, ready at once and free.

statusWhat it means
draftAdd photos, then build it.
generatingBeing built.
examplesBuilt: pick one of exampleUrls.
readyUsable as a background.
errorThe build failed (its credit was given back): error says why. Build it again.
curl -X POST "https://listwyse.com/api/v1/studio/templates/custom/LOOK_ID/build" \
  -H "Authorization: Bearer $LISTWYSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

Errors and retrying

Every error has the same shape: a stable code to act on, and a plain message.

402
{
  "error": {
    "code": "OUT_OF_CREDITS",
    "message": "Not enough credits: this needs 0.3 and the account has 0.1."
  },
  "credits": { "balance": 0.1, "needed": 0.3, "pricePerPhoto": 0.1 },
  "requestId": "req_7c1e…"
}
codeStatusWhat it meansWhat to do
INVALID_REQUEST400A field is missing or wrong.message names the field: fix it and send again.
INVALID_JSON400The body isn't valid JSON.Check how the body is built.
INVALID_BACKGROUND400That background isn't a preset or a custom background this dealer can use.Use "keep" or an id from GET /templates?dealerCode=….
DEALER_REQUIRED400You shoot for dealers and the call didn't name one.Send dealerCode.
BRANDING_NOT_SET_UP400branding asked for, and the dealer has no logo yet.Add it with POST /branding/logo, then send again.
DUPLICATE_PHOTO_ID400One vehicle call has the same photoId twice.Give each photo its own photoId.
UNSUPPORTED_IMAGE400An uploaded file isn't a JPEG, PNG, WebP or GIF.Convert it, or send another file.
INVALID_WEBHOOK_URL400The webhook URL isn't https on the public internet.Use a public https URL.
ONE_DEALER_ACCOUNT400Adding a dealer to a dealership's own account.Ask us for an account that shoots for dealers.
UNAUTHORIZED401No key, or not a live one.Check the Authorization header.
OUT_OF_CREDITS402Your balance can't cover it. Nothing started.Top up, then send again.
NOT_FOUND, DEALER_NOT_FOUND404Not on your account.Check the id or code.
PHOTO_IN_PROGRESS409A redo or removal of a photo that's still being made.Wait until it's completed or failed.
PHOTO_ID_TAKEN409That photoId was already sent for another car.Use a new photoId for this car.
PHOTO_NOT_STORED409A redo of a photo we never received (its download failed).Send it again with POST /photos and the same photoId.
DEALER_CODE_TAKEN409Another of your dealers has that code.Pick another code.
NO_WEBHOOK_URL409A test event with no webhook URL set.Set one with PUT /webhook first.
DELIVERING409A resend of a delivery that's being sent right now.Wait a moment and look again.
TOO_LARGE413An uploaded file is over 4MB.Send it as a link instead.
RATE_LIMITED429More than 600 requests in a minute from one key.Wait the seconds in the Retry-After header, then send it again.
INTERNAL_ERROR500Something went wrong on our side.Send it again; quote requestId if it keeps happening.
UNAVAILABLE503We couldn't check your key just then.Send it again in a moment.

When a photo fails

A failed photo isn't an error answer. It's a photo with status failed and an error saying why, and you're never charged for one. To try again, send it again with the same photoId.

error.codeWhy
QUALITY_ASSURANCE_FAILEDDid not pass quality assurance, even after being made again on our side.
DOWNLOAD_FAILEDWe couldn't download the link: it isn't a public https link, isn't an image, or is over 12MB.
ANGLE_UNKNOWNWe couldn't tell what the photo shows. Send it again.
VEHICLE_FULLThe car already has 60 photos. Remove old ones to make room.
LOOK_UNAVAILABLEThe background can't be used any more (it was deleted, or isn't ready).
START_FAILEDIt couldn't be started; message says why.
GENERATION_FAILEDEvery image service we use failed on this photo.
START_TIMEOUTIt never got going. Send it again.
VEHICLE_GONEThe car was deleted before the photo started.

Retrying safely

  • A network error, 500 or 503? Send exactly the same request again. With a photoId, you get the first photo back if the first request did get through.
  • 409 PHOTO_IN_PROGRESS? Wait for the photo to finish, then try again.
  • Any other 4xx? The request won't work as it is: message says what to change.

Postman and AI tools

Postman, Insomnia and code generators

Every call is in one OpenAPI file: https://listwyse.com/api/v1/studio/openapi.json. In Postman, choose Import and paste that link, then set the collection's Authorization to Bearer Token with your key. Every call is then ready to try, with its fields and examples.

AI assistants

The same calls are tools for AI assistants that speak MCP (Claude Code, Cursor and others), at https://listwyse.com/api/mcp, with your key in the Authorization header. Ask in plain words (“send these photos for stock A1234 at Example Motors”). Tools that use credits tell the assistant to check with you first.

Claude Code
claude mcp add --transport http listwyse https://listwyse.com/api/mcp \
  --header "Authorization: Bearer sk_live_YOUR_KEY"
Cursor · mcp.json
{
  "mcpServers": {
    "listwyse": {
      "url": "https://listwyse.com/api/mcp",
      "headers": { "Authorization": "Bearer sk_live_YOUR_KEY" }
    }
  }
}

Reference

Every call

Each call with its fields, what happens when you make it, the errors it can answer with, a request in curl, JavaScript or Python, and an example answer.

Objects

What the calls answer with, field by field. In a photo, a field with nothing in it yet is null rather than missing.

The photo

One photo you sent, and what became of it. Every photo call answers with one.

id

string

Our id for the photo.

photoId

string or null

Your id for it, as you sent it.

vehicleId

string

Our id for its car.

stockNumber, vin

string or null

Its car's stock number and VIN.

dealerId

string or null

Our id for its dealer.

status

queued, processing, quality_assurance, completed or failed

Where it is. Only completed and failed are final.

statusText

string

A plain sentence for the status, ready to show people.

url

string or null

The finished file, once completed.

format

jpeg, png or webp

The file type of url.

aspectRatio

match, 4:3, 3:2 or 16:9

The shape of url: match is the photo's own shape; the others are cropped to that shape.

edited

boolean or null

true: our finished photo. false: it isn't a photo of a car (a window sticker, say), so it came back as it was, free. null until completed.

angle

string or null

What the photo shows (frontAngle, interior…): as you sent it, or as we worked it out.

angleSource

given, detected or null

Whether you sent the angle or we worked it out.

background

string

"keep" (no background), or the background it was put on.

branding

list

The dealer branding asked for (plate, floorMat, screen). Each is only added where the photo shows its spot.

version

number

1, then 2 after a redo, and so on.

cost

number

Credits this version cost: the price while held or kept, 0 once given back.

note

string or null

Why something out of the ordinary happened, such as your original being sent back.

error

object or null

On a failed photo: code and message say why.

inputUrl

string or null

The link you sent (null for an uploaded file).

createdAt, updatedAt, completedAt

string

When it was sent, last changed, and finished (UTC, ISO 8601).

Example
The photo
{
  "id": "5b0c1e8a-7f0e-4b7a-9a43-0a1c3f2d9e11",
  "photoId": "PHOTO-1001",
  "vehicleId": "0f3e6d2c-41a7-4a0b-8f1e-3b9d2c7a6e55",
  "stockNumber": "A1234",
  "vin": "1HGCV1F34PA012345",
  "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
  "status": "completed",
  "statusText": "Done",
  "url": "https://qzemyvwjmbhjilubfpzh.supabase.co/storage/v1/object/public/photos/…/p29031.jpg",
  "format": "jpeg",
  "aspectRatio": "match",
  "edited": true,
  "angle": "frontAngle",
  "angleSource": "given",
  "background": "keep",
  "branding": [],
  "version": 1,
  "cost": 0.1,
  "note": null,
  "error": null,
  "inputUrl": "https://cdn.example.com/shoots/88231.jpg",
  "createdAt": "2026-10-24T15:02:11.000Z",
  "updatedAt": "2026-10-24T15:05:40.000Z",
  "completedAt": "2026-10-24T15:05:40.000Z"
}

The vehicle

One car at one dealer. It's made the first time a photo arrives with its stock number (or VIN).

id

string

Our id for the car.

stockNumber, vin

string or null

How you named it. A VIN sent later is added to a car first sent by stock number.

dealerId

string or null

Our id for its dealer.

status

empty, processing or completed

processing while any of its photos is still going.

photoCount

number

How many photos it has.

counts

object

How many of its photos are at each status.

photos

list of photos

On GET /vehicles/:id (all of them) and POST /vehicles (that call's).

createdAt

string

When its first photo arrived.

Example
The vehicle
{
  "id": "0f3e6d2c-41a7-4a0b-8f1e-3b9d2c7a6e55",
  "stockNumber": "A1234",
  "vin": "1HGCV1F34PA012345",
  "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
  "status": "processing",
  "photoCount": 12,
  "counts": {
    "queued": 0,
    "processing": 3,
    "quality_assurance": 1,
    "completed": 8,
    "failed": 0
  },
  "createdAt": "2026-10-24T15:02:11.000Z",
  "photos": [
    {
      "id": "5b0c1e8a-7f0e-4b7a-9a43-0a1c3f2d9e11",
      "photoId": "PHOTO-1001",
      "vehicleId": "0f3e6d2c-41a7-4a0b-8f1e-3b9d2c7a6e55",
      "stockNumber": "A1234",
      "vin": "1HGCV1F34PA012345",
      "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
      "status": "completed",
      "statusText": "Done",
      "url": "https://qzemyvwjmbhjilubfpzh.supabase.co/storage/v1/object/public/photos/…/p29031.jpg",
      "format": "jpeg",
      "aspectRatio": "match",
      "edited": true,
      "angle": "frontAngle",
      "angleSource": "given",
      "background": "keep",
      "branding": [],
      "version": 1,
      "cost": 0.1,
      "note": null,
      "error": null,
      "inputUrl": "https://cdn.example.com/shoots/88231.jpg",
      "createdAt": "2026-10-24T15:02:11.000Z",
      "updatedAt": "2026-10-24T15:05:40.000Z",
      "completedAt": "2026-10-24T15:05:40.000Z"
    },
    "…"
  ]
}

The dealer

A dealership you shoot for.

id

string

Our id for the dealer: send it as dealerId.

name

string

Its name.

code

string or null

Your code for it: send it as dealerCode.

createdAt

string

When it was added.

Example
The dealer
{
  "id": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
  "name": "Example Motors",
  "code": "DEALER-01",
  "createdAt": "2026-10-24T14:40:00.000Z"
}

The webhook event

What we POST to your webhook URL. data holds the whole object, as GET would return it.

id

string

The delivery's id. A retry has the same id, so skip ids you've handled.

event

string

photo.completed, photo.failed, vehicle.completed, branding.completed, branding.failed, background.completed, background.failed or webhook.test.

createdAt

string

When the event happened.

data.photo

photo

On photo.completed and photo.failed.

data.vehicle, data.batchId

vehicle, string

On vehicle.completed: the car, with every photo from that vehicle call.

data.branding, data.item, data.dealerId

branding, string, string or null

On branding.completed and branding.failed: the dealer's branding (as GET /branding), the item that finished, and its dealer. A failed one also has data.error.

data.template

custom background

On background.completed (built: pick one of exampleUrls) and background.failed (its error says why).

Example
The webhook event
{
  "id": "e2b1c0de-…",
  "event": "photo.completed",
  "createdAt": "2026-10-24T15:05:40.000Z",
  "data": {
    "photo": {
      "id": "5b0c1e8a-7f0e-4b7a-9a43-0a1c3f2d9e11",
      "photoId": "PHOTO-1001",
      "vehicleId": "0f3e6d2c-41a7-4a0b-8f1e-3b9d2c7a6e55",
      "stockNumber": "A1234",
      "vin": "1HGCV1F34PA012345",
      "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
      "status": "completed",
      "statusText": "Done",
      "url": "https://qzemyvwjmbhjilubfpzh.supabase.co/storage/v1/object/public/photos/…/p29031.jpg",
      "format": "jpeg",
      "aspectRatio": "match",
      "edited": true,
      "angle": "frontAngle",
      "angleSource": "given",
      "background": "keep",
      "branding": [],
      "version": 1,
      "cost": 0.1,
      "note": null,
      "error": null,
      "inputUrl": "https://cdn.example.com/shoots/88231.jpg",
      "createdAt": "2026-10-24T15:02:11.000Z",
      "updatedAt": "2026-10-24T15:05:40.000Z",
      "completedAt": "2026-10-24T15:05:40.000Z"
    }
  }
}

The error

Every error has the same shape. Branch on error.code; error.message is plain English for logs and people.

error.code

string

A stable code, such as OUT_OF_CREDITS.

error.message

string

What went wrong and, where it helps, what to do.

requestId

string

Quote it if you ask us about the call.

credits

object

On OUT_OF_CREDITS: balance, needed and pricePerPhoto.

Example
The error
{
  "error": {
    "code": "OUT_OF_CREDITS",
    "message": "Not enough credits: this needs 0.3 and the account has 0.1."
  },
  "credits": {
    "balance": 0.1,
    "needed": 0.3,
    "pricePerPhoto": 0.1
  },
  "requestId": "req_7c1e…"
}

Angle names

Optional. Leave angle out and we work out what each photo shows. Send one of these only when your app knows the shot for certain: we use it as given.

frontFront
rearRear
sideSide
frontAngleFront angle
rearAngleRear angle
wheelWheel close-up
engineEngine
trunkTrunk
signBadge or emblem
doorDoor close-up
headlightHeadlight close-up
taillightRear light close-up
interiorFrontFront row - head on
interiorSideBack row - side on
seatFront row - side on
dashboardDashboard
monitorScreen close-up
gearStickGear stick
steeringWheelSteering wheel
odometerOdometer / gauges
pedalsPedals
startButtonStart button
mirrorRearview mirror
columnControlsColumn controls
doorCardDoor card
seatControlsSeat controls
windowControlsWindow controls
keysKeys
bedTruck bed
sunroofRoof
thirdRowThird row
chargePortCharge port
hitchTow hitch
topDownTop down
frunkFront trunk
runningBoardsRunning boards
tailgateTailgate
rearScreensRear screens
damageDamage
exhaustExhaust
fuelDoorFuel door
spareSpare and tools
sideMirrorSide mirror
tireTreadTire tread
interiorAny other cabin photo
bedCoverTruck bed - roof on
bedTailgateOpenTruck bed - tailgate open
detailsAny other close-up
roofAboveRoof from above

Photos

One photo in, one finished photo out. These are the calls most integrations use: send each photo as it's shot, then read it (or let the webhook tell you) when it's done.

POST/photosUses credits

Send a photo

Starts one photo and answers straight away with it queued. Send JSON with a link to the photo (url), or multipart/form-data with the photo as a file field (under 4MB) and the other fields beside it, branding as JSON text.

When to use it: You have one photo and want it back finished. This is the call most integrations make, once per photo, as photos are shot.

Body (JSON, or the same fields as a form with a file)

photoId
string

Your own id for the photo. Sending the same photoId again returns that photo instead of making (and charging for) it twice; if it had failed, it's tried again.

url
https linkrequired

A public https link to the photo.

angle
angle

Optional. Leave it out and we work out what the photo shows. Send it only if your app already knows the shot (front, frontAngle, side, interior…); the docs list every name.

stockNumber
string

The dealer's stock number. Photos with the same stock number (and dealer) are one vehicle.

vin
string

The 17-character VIN. Needed when there's no stockNumber.

dealerId
string

Our id for the dealer (GET /dealers). Needed if you shoot for dealers; a dealership's own account leaves it out.

dealerCode
string

Or your own code for the dealer, set on POST /dealers.

background
string

"keep" for no background (the photo keeps its own surroundings, retouched where it was taken), or a background's id (GET /templates): one of our presets or a custom one. Defaults to the dealer's setting.

cleanup
whole number

Clean-up: how clean the car looks (dirt, dust, water spots). Dents and scratches always stay. A step number (GET /dealers/:id/settings lists them).

backgroundTidy
whole number

Background tidy: how much of the lot behind the car is tidied, when the background is kept. A step number.

reflections
whole number

Reflections: how much of the surroundings shows in the paint and glass. A step number.

framing
whole number

Framing: whether a crooked photo may be straightened and re-cropped, or keeps its framing. A step number.

outputFormat
jpeg | png | webp

The file type photos come back as: jpeg, png or webp.

aspectRatio
match | 4:3 | 3:2 | 16:9

The shape photos come back in: "match" keeps each photo's own shape; 4:3, 3:2 or 16:9 crop the finished photo to that shape, keeping the car in frame.

branding
list of strings, up to 3

Dealer branding to add: any of "plate", "floorMat", "screen". Each is only added where the photo shows its spot (a plate where a plate goes, a mat where the driver's footwell shows). Leave it out for none. Needs the dealer's logo or designs (the branding calls).

What happens

  1. We check the request and hold the photo's price from your credits. The answer comes back at once: the photo, queued.
  2. We find the car by its stock number (or VIN) at that dealer, or make it if this is its first photo.
  3. In the background: we download the photo, work out what it shows, and make it.
  4. It goes through quality assurance: it's compared with your original, and made again automatically if it needs to be. This can add a few minutes.
  5. It ends completed (url is the finished file) or failed. A photo.completed or photo.failed webhook goes to your URL.

The answer: photo is the photo as it stands (queued). vehicle is its car, with how many photos it has. credits says what was held and what's left. A photoId you've sent before answers 200 with that photo and reused: true, and holds nothing; if that photo had failed, it's tried again instead (202, its version one higher).

Errors

DEALER_REQUIRED 400Your account shoots for dealers and the call didn't name one.
DEALER_NOT_FOUND 404No dealer with that dealerId or dealerCode on your account.
INVALID_REQUEST 400A field is missing or wrong (no stockNumber or vin, a bad VIN); message names it.
INVALID_BACKGROUND 400background isn't "keep", a preset, or a custom background this dealer can use.
BRANDING_NOT_SET_UP 400branding was asked for and the dealer has no logo yet (POST /branding/logo).
OUT_OF_CREDITS 402Your balance can't cover the photo. Nothing was started.
PHOTO_ID_TAKEN 409That photoId was already sent for a different car.
TOO_LARGE 413An uploaded file is over 4MB. Send it as a link instead.
UNSUPPORTED_IMAGE 400An uploaded file isn't a JPEG, PNG, WebP or GIF.
Request
curl -X POST "https://listwyse.com/api/v1/studio/photos" \
  -H "Authorization: Bearer $LISTWYSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "photoId": "PHOTO-1001",
  "url": "https://cdn.example.com/shoots/88231.jpg",
  "stockNumber": "A1234",
  "dealerCode": "DEALER-01"
}'
Answer · 202
{
  "photo": {
    "id": "5b0c1e8a-7f0e-4b7a-9a43-0a1c3f2d9e11",
    "photoId": "PHOTO-1001",
    "vehicleId": "0f3e6d2c-41a7-4a0b-8f1e-3b9d2c7a6e55",
    "stockNumber": "A1234",
    "vin": "1HGCV1F34PA012345",
    "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "status": "queued",
    "statusText": "Waiting to start",
    "url": null,
    "format": "jpeg",
    "aspectRatio": "match",
    "edited": null,
    "angle": "frontAngle",
    "angleSource": "given",
    "background": "keep",
    "branding": [],
    "version": 1,
    "cost": 0.1,
    "note": null,
    "error": null,
    "inputUrl": "https://cdn.example.com/shoots/88231.jpg",
    "createdAt": "2026-10-24T15:02:11.000Z",
    "updatedAt": "2026-10-24T15:02:11.000Z",
    "completedAt": null
  },
  "vehicle": {
    "id": "0f3e6d2c-41a7-4a0b-8f1e-3b9d2c7a6e55",
    "stockNumber": "A1234",
    "vin": "1HGCV1F34PA012345",
    "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "status": "processing",
    "photoCount": 12,
    "counts": {
      "queued": 0,
      "processing": 3,
      "quality_assurance": 1,
      "completed": 8,
      "failed": 0
    },
    "createdAt": "2026-10-24T15:02:11.000Z"
  },
  "credits": {
    "pricePerPhoto": 0.1,
    "held": 0.1,
    "balance": 41.9
  }
}
GET/photos/:id

Check a photo

The photo as it stands now. Use our id or your own photoId in the path.

When to use it: You want a photo's latest status, or you don't use webhooks and are waiting for one to finish.

Path

:id
in the pathrequired

Our photo id, or your photoId.

The answer: Once status is completed, url is the finished file. While it's still going, statusText is a sentence you can show. A failed photo carries error, with code QUALITY_ASSURANCE_FAILED when it didn't pass quality assurance.

Errors

NOT_FOUND 404No photo with that id or photoId on your account.
Request
curl "https://listwyse.com/api/v1/studio/photos/PHOTO-1001" \
  -H "Authorization: Bearer $LISTWYSE_KEY"
Answer · 200
{
  "photo": {
    "id": "5b0c1e8a-7f0e-4b7a-9a43-0a1c3f2d9e11",
    "photoId": "PHOTO-1001",
    "vehicleId": "0f3e6d2c-41a7-4a0b-8f1e-3b9d2c7a6e55",
    "stockNumber": "A1234",
    "vin": "1HGCV1F34PA012345",
    "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "status": "completed",
    "statusText": "Done",
    "url": "https://qzemyvwjmbhjilubfpzh.supabase.co/storage/v1/object/public/photos/…/p29031.jpg",
    "format": "jpeg",
    "aspectRatio": "match",
    "edited": true,
    "angle": "frontAngle",
    "angleSource": "given",
    "background": "keep",
    "branding": [],
    "version": 1,
    "cost": 0.1,
    "note": null,
    "error": null,
    "inputUrl": "https://cdn.example.com/shoots/88231.jpg",
    "createdAt": "2026-10-24T15:02:11.000Z",
    "updatedAt": "2026-10-24T15:05:40.000Z",
    "completedAt": "2026-10-24T15:05:40.000Z"
  }
}
GET/photos

List photos

Photos on your account, newest first, 50 at a time. Narrow them by vehicle, dealer or status.

When to use it: Catching up: say, every photo still processing, or a dealer's photos from today.

Query

vehicleId
string

Only this vehicle's photos.

dealerId
string

Only this dealer's photos.

status
queued | processing | quality_assurance | completed | failed

Only photos with this status.

limit
whole number

How many (1–100, default 50).

before
string

Paging: the createdAt of the last item you have; you get older ones.

The answer: next is set when there are more: send it back as before for the page after.

Request
curl "https://listwyse.com/api/v1/studio/photos?status=processing&limit=20" \
  -H "Authorization: Bearer $LISTWYSE_KEY"
Answer · 200
{
  "photos": [
    {
      "id": "5b0c1e8a-7f0e-4b7a-9a43-0a1c3f2d9e11",
      "photoId": "PHOTO-1001",
      "vehicleId": "0f3e6d2c-41a7-4a0b-8f1e-3b9d2c7a6e55",
      "stockNumber": "A1234",
      "vin": "1HGCV1F34PA012345",
      "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
      "status": "completed",
      "statusText": "Done",
      "url": "https://qzemyvwjmbhjilubfpzh.supabase.co/storage/v1/object/public/photos/…/p29031.jpg",
      "format": "jpeg",
      "aspectRatio": "match",
      "edited": true,
      "angle": "frontAngle",
      "angleSource": "given",
      "background": "keep",
      "branding": [],
      "version": 1,
      "cost": 0.1,
      "note": null,
      "error": null,
      "inputUrl": "https://cdn.example.com/shoots/88231.jpg",
      "createdAt": "2026-10-24T15:02:11.000Z",
      "updatedAt": "2026-10-24T15:05:40.000Z",
      "completedAt": "2026-10-24T15:05:40.000Z"
    }
  ],
  "next": "2026-10-24T15:02:11.000Z"
}
POST/photos/:id/redoUses credits

Make a photo again

Makes a completed or failed photo again as its next version. Change the background, the slider settings, the branding or the file type if you like; anything you leave out stays as it was.

When to use it: A finished photo isn't what the dealer wanted, or a failed one is worth another try.

Path

:id
in the pathrequired

Our photo id, or your photoId.

Body

background
string

"keep" for no background (the photo keeps its own surroundings, retouched where it was taken), or a background's id (GET /templates): one of our presets or a custom one. Defaults to the dealer's setting.

cleanup
whole number

Clean-up: how clean the car looks (dirt, dust, water spots). Dents and scratches always stay. A step number (GET /dealers/:id/settings lists them).

backgroundTidy
whole number

Background tidy: how much of the lot behind the car is tidied, when the background is kept. A step number.

reflections
whole number

Reflections: how much of the surroundings shows in the paint and glass. A step number.

framing
whole number

Framing: whether a crooked photo may be straightened and re-cropped, or keeps its framing. A step number.

outputFormat
jpeg | png | webp

The file type photos come back as: jpeg, png or webp.

aspectRatio
match | 4:3 | 3:2 | 16:9

The shape photos come back in: "match" keeps each photo's own shape; 4:3, 3:2 or 16:9 crop the finished photo to that shape, keeping the car in frame.

branding
list of strings, up to 3

Dealer branding to add: any of "plate", "floorMat", "screen". Each is only added where the photo shows its spot (a plate where a plate goes, a mat where the driver's footwell shows). Leave it out for none. Needs the dealer's logo or designs (the branding calls).

What happens

  1. The price is held again: a redo is a new photo.
  2. The photo goes back to queued with version one higher, and runs like a new photo, its checks asked afresh.
  3. The earlier version's file keeps working at its link.

Errors

PHOTO_IN_PROGRESS 409The photo is still being made. Wait for it to finish.
PHOTO_NOT_STORED 409We never received the photo (its download failed). Send it again with POST /photos and the same photoId.
OUT_OF_CREDITS 402Your balance can't cover it.
INVALID_BACKGROUND 400background isn't "keep", a preset, or a custom background this dealer can use.
BRANDING_NOT_SET_UP 400branding was asked for and the dealer has no logo yet (POST /branding/logo).
NOT_FOUND 404No photo with that id or photoId on your account.
Request
curl -X POST "https://listwyse.com/api/v1/studio/photos/PHOTO-1001/redo" \
  -H "Authorization: Bearer $LISTWYSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "cleanup": 1,
  "outputFormat": "png"
}'
Answer · 202
{
  "photo": {
    "id": "5b0c1e8a-7f0e-4b7a-9a43-0a1c3f2d9e11",
    "photoId": "PHOTO-1001",
    "vehicleId": "0f3e6d2c-41a7-4a0b-8f1e-3b9d2c7a6e55",
    "stockNumber": "A1234",
    "vin": "1HGCV1F34PA012345",
    "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "status": "queued",
    "statusText": "Waiting to start",
    "url": null,
    "format": "jpeg",
    "aspectRatio": "match",
    "edited": null,
    "angle": "frontAngle",
    "angleSource": "given",
    "background": "keep",
    "branding": [],
    "version": 2,
    "cost": 0.1,
    "note": null,
    "error": null,
    "inputUrl": "https://cdn.example.com/shoots/88231.jpg",
    "createdAt": "2026-10-24T15:02:11.000Z",
    "updatedAt": "2026-10-24T15:02:11.000Z",
    "completedAt": null
  },
  "credits": {
    "pricePerPhoto": 0.1,
    "held": 0.1,
    "balance": 41.9
  }
}
DELETE/photos/:id

Remove a photo

Takes a completed or failed photo off its car and forgets it. Its finished file keeps working at its link.

When to use it: A car is shot again and the old photos shouldn't count towards its 60.

Path

:id
in the pathrequired

Our photo id, or your photoId.

Errors

PHOTO_IN_PROGRESS 409The photo is still being made.
NOT_FOUND 404No photo with that id or photoId on your account.
Request
curl -X DELETE "https://listwyse.com/api/v1/studio/photos/PHOTO-1001" \
  -H "Authorization: Bearer $LISTWYSE_KEY"
Answer · 200
{
  "deleted": true,
  "id": "5b0c1e8a-7f0e-4b7a-9a43-0a1c3f2d9e11"
}

Vehicles

A whole car in one request. Every photo in it becomes a photo like the ones above, with the same statuses, files and price, plus one vehicle.completed webhook when the last one is done.

POST/vehiclesUses credits

Send a vehicle's photos

Up to 60 photos of one car in one request, as links. Each becomes a photo exactly as if it had been sent to POST /photos.

When to use it: You collect a car's photos first and want one signal when the whole car is done.

Body

stockNumber
string

The dealer's stock number. Photos with the same stock number (and dealer) are one vehicle.

vin
string

The 17-character VIN. Needed when there's no stockNumber.

dealerId
string

Our id for the dealer (GET /dealers). Needed if you shoot for dealers; a dealership's own account leaves it out.

dealerCode
string

Or your own code for the dealer, set on POST /dealers.

photos
list of objects, up to 60required

The car's photos, up to 60.

photos[].photoId
string

Your own id for the photo. Sending the same photoId again returns that photo instead of making (and charging for) it twice; if it had failed, it's tried again.

photos[].url
https linkrequired

A public https link to the photo.

photos[].angle
angle

Optional. Leave it out and we work out what the photo shows. Send it only if your app already knows the shot (front, frontAngle, side, interior…); the docs list every name.

background
string

"keep" for no background (the photo keeps its own surroundings, retouched where it was taken), or a background's id (GET /templates): one of our presets or a custom one. Defaults to the dealer's setting.

cleanup
whole number

Clean-up: how clean the car looks (dirt, dust, water spots). Dents and scratches always stay. A step number (GET /dealers/:id/settings lists them).

backgroundTidy
whole number

Background tidy: how much of the lot behind the car is tidied, when the background is kept. A step number.

reflections
whole number

Reflections: how much of the surroundings shows in the paint and glass. A step number.

framing
whole number

Framing: whether a crooked photo may be straightened and re-cropped, or keeps its framing. A step number.

outputFormat
jpeg | png | webp

The file type photos come back as: jpeg, png or webp.

aspectRatio
match | 4:3 | 3:2 | 16:9

The shape photos come back in: "match" keeps each photo's own shape; 4:3, 3:2 or 16:9 crop the finished photo to that shape, keeping the car in frame.

branding
list of strings, up to 3

Dealer branding to add: any of "plate", "floorMat", "screen". Each is only added where the photo shows its spot (a plate where a plate goes, a mat where the driver's footwell shows). Leave it out for none. Needs the dealer's logo or designs (the branding calls).

What happens

  1. Every new photo's price is held together. If your balance can't cover them all, none start.
  2. The answer comes back at once: the vehicle, with this call's photos, queued.
  3. Each photo runs on its own and sends its own photo.completed or photo.failed webhook.
  4. When the last photo from this call is done, a vehicle.completed webhook carries all of them.

The answer: batchId names this call's photos; the vehicle.completed webhook carries the same one. reused lists photos whose photoId you'd sent before: they come back as they are, uncharged (a failed one is tried again instead).

Errors

DEALER_REQUIRED 400Your account shoots for dealers and the call didn't name one.
DEALER_NOT_FOUND 404No dealer with that dealerId or dealerCode on your account.
INVALID_REQUEST 400A field is missing or wrong; message names it.
DUPLICATE_PHOTO_ID 400The same photoId appears twice in the call.
INVALID_BACKGROUND 400background isn't "keep", a preset, or a custom background this dealer can use.
BRANDING_NOT_SET_UP 400branding was asked for and the dealer has no logo yet (POST /branding/logo).
OUT_OF_CREDITS 402Your balance can't cover every new photo. Nothing was started.
PHOTO_ID_TAKEN 409One of the photoIds was already sent for a different car.
Request
curl -X POST "https://listwyse.com/api/v1/studio/vehicles" \
  -H "Authorization: Bearer $LISTWYSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "stockNumber": "A1234",
  "vin": "1HGCV1F34PA012345",
  "dealerCode": "DEALER-01",
  "branding": [
    "plate",
    "floorMat",
    "screen"
  ],
  "photos": [
    {
      "photoId": "PHOTO-1001",
      "url": "https://cdn.example.com/shoots/88231.jpg"
    },
    {
      "photoId": "PHOTO-1002",
      "url": "https://cdn.example.com/shoots/88232.jpg"
    },
    {
      "photoId": "PHOTO-1003",
      "url": "https://cdn.example.com/shoots/88233.jpg"
    }
  ]
}'
Answer · 202
{
  "vehicle": {
    "id": "0f3e6d2c-41a7-4a0b-8f1e-3b9d2c7a6e55",
    "stockNumber": "A1234",
    "vin": "1HGCV1F34PA012345",
    "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "status": "processing",
    "photoCount": 3,
    "counts": {
      "queued": 0,
      "processing": 3,
      "quality_assurance": 1,
      "completed": 8,
      "failed": 0
    },
    "createdAt": "2026-10-24T15:02:11.000Z",
    "photos": [
      {
        "id": "5b0c1e8a-7f0e-4b7a-9a43-0a1c3f2d9e11",
        "photoId": "PHOTO-1001",
        "vehicleId": "0f3e6d2c-41a7-4a0b-8f1e-3b9d2c7a6e55",
        "stockNumber": "A1234",
        "vin": "1HGCV1F34PA012345",
        "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
        "status": "queued",
        "statusText": "Waiting to start",
        "url": null,
        "format": "jpeg",
        "aspectRatio": "match",
        "edited": null,
        "angle": "frontAngle",
        "angleSource": "given",
        "background": "keep",
        "branding": [],
        "version": 1,
        "cost": 0.1,
        "note": null,
        "error": null,
        "inputUrl": "https://cdn.example.com/shoots/88231.jpg",
        "createdAt": "2026-10-24T15:02:11.000Z",
        "updatedAt": "2026-10-24T15:02:11.000Z",
        "completedAt": null
      },
      "…"
    ]
  },
  "batchId": "7d1f2c4e-…",
  "credits": {
    "pricePerPhoto": 0.1,
    "held": 0.3,
    "balance": 41.7
  }
}
GET/vehicles/:id

Check a vehicle

A car and every photo sent for it, oldest first.

When to use it: Waiting on a vehicle call without webhooks, or showing a car's progress.

Path

:id
in the pathrequired

The vehicle's id (vehicleId on any of its photos).

The answer: status is processing while any photo is still going, then completed (empty if it has no photos). counts says how many photos are at each status.

Errors

NOT_FOUND 404No vehicle with that id on your account.
Request
curl "https://listwyse.com/api/v1/studio/vehicles/VEHICLE_ID" \
  -H "Authorization: Bearer $LISTWYSE_KEY"
Answer · 200
{
  "vehicle": {
    "id": "0f3e6d2c-41a7-4a0b-8f1e-3b9d2c7a6e55",
    "stockNumber": "A1234",
    "vin": "1HGCV1F34PA012345",
    "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "status": "processing",
    "photoCount": 12,
    "counts": {
      "queued": 0,
      "processing": 3,
      "quality_assurance": 1,
      "completed": 8,
      "failed": 0
    },
    "createdAt": "2026-10-24T15:02:11.000Z",
    "photos": [
      {
        "id": "5b0c1e8a-7f0e-4b7a-9a43-0a1c3f2d9e11",
        "photoId": "PHOTO-1001",
        "vehicleId": "0f3e6d2c-41a7-4a0b-8f1e-3b9d2c7a6e55",
        "stockNumber": "A1234",
        "vin": "1HGCV1F34PA012345",
        "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
        "status": "completed",
        "statusText": "Done",
        "url": "https://qzemyvwjmbhjilubfpzh.supabase.co/storage/v1/object/public/photos/…/p29031.jpg",
        "format": "jpeg",
        "aspectRatio": "match",
        "edited": true,
        "angle": "frontAngle",
        "angleSource": "given",
        "background": "keep",
        "branding": [],
        "version": 1,
        "cost": 0.1,
        "note": null,
        "error": null,
        "inputUrl": "https://cdn.example.com/shoots/88231.jpg",
        "createdAt": "2026-10-24T15:02:11.000Z",
        "updatedAt": "2026-10-24T15:05:40.000Z",
        "completedAt": "2026-10-24T15:05:40.000Z"
      },
      "…"
    ]
  }
}
GET/vehicles

List or find vehicles

Cars on your account, newest first, without their photos. With stockNumber (or vin) it finds that one car; name its dealer too if you shoot for dealers.

When to use it: Looking a car up by its stock number, or listing recent cars.

Query

stockNumber
string

Find one vehicle by its stock number (with dealerId or dealerCode).

vin
string

Or by its VIN.

dealerId
string

—

dealerCode
string

—

limit
whole number

How many (1–100, default 50).

before
string

Paging: the createdAt of the last item you have; you get older ones.

Errors

DEALER_NOT_FOUND 404No dealer with that dealerId or dealerCode on your account.
Request
curl "https://listwyse.com/api/v1/studio/vehicles?stockNumber=A1234&dealerCode=DEALER-01" \
  -H "Authorization: Bearer $LISTWYSE_KEY"
Answer · 200
{
  "vehicles": [
    {
      "id": "0f3e6d2c-41a7-4a0b-8f1e-3b9d2c7a6e55",
      "stockNumber": "A1234",
      "vin": "1HGCV1F34PA012345",
      "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
      "status": "processing",
      "photoCount": 12,
      "counts": {
        "queued": 0,
        "processing": 3,
        "quality_assurance": 1,
        "completed": 8,
        "failed": 0
      },
      "createdAt": "2026-10-24T15:02:11.000Z"
    }
  ],
  "next": null
}

Dealers and settings

The dealers you shoot for, each with its own settings. Settings decide how a dealer's photos come out when a call doesn't say otherwise; the id default is your account's own settings, which every dealer follows until it has its own.

GET/dealers

List dealers

Every dealer on your account, by name. An account that is itself one dealer lists none.

Request
curl "https://listwyse.com/api/v1/studio/dealers" \
  -H "Authorization: Bearer $LISTWYSE_KEY"
Answer · 200
{
  "dealers": [
    {
      "id": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
      "name": "Example Motors",
      "code": "DEALER-01",
      "createdAt": "2026-10-24T14:40:00.000Z"
    }
  ],
  "accountKind": "provider"
}
POST/dealers

Add a dealer

Adds a dealer you shoot for. Give it your own code and you can name it by that code (dealerCode) on every call.

When to use it: You start shooting for a new dealership.

Body

name
stringrequired

The dealer's name.

code
string

Your own id for the dealer, to use as dealerCode.

The answer: A new dealer follows your account's settings (the id default) until you change one of its own.

Errors

ONE_DEALER_ACCOUNT 400Your account is a single dealer, so it has no dealers to add.
DEALER_CODE_TAKEN 409Another of your dealers already has that code.
Request
curl -X POST "https://listwyse.com/api/v1/studio/dealers" \
  -H "Authorization: Bearer $LISTWYSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Example Motors",
  "code": "DEALER-01"
}'
Answer · 201
{
  "dealer": {
    "id": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "name": "Example Motors",
    "code": "DEALER-01",
    "createdAt": "2026-10-24T14:40:00.000Z"
  }
}
GET/dealers/:id

Get a dealer

Path

:id
in the pathrequired

Our dealer id, or your code.

Errors

DEALER_NOT_FOUND 404No dealer with that id or code on your account.
Request
curl "https://listwyse.com/api/v1/studio/dealers/DEALER-01" \
  -H "Authorization: Bearer $LISTWYSE_KEY"
Answer · 200
{
  "dealer": {
    "id": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "name": "Example Motors",
    "code": "DEALER-01",
    "createdAt": "2026-10-24T14:40:00.000Z"
  }
}
PATCH/dealers/:id

Rename a dealer or change its code

Send only what changes. A code of null removes it.

Path

:id
in the pathrequired

Our dealer id, or your code.

Body

name
string

—

code
string or null

Your own id for the dealer; null removes it.

Errors

DEALER_CODE_TAKEN 409Another of your dealers already has that code.
DEALER_NOT_FOUND 404No dealer with that id or code on your account.
Request
curl -X PATCH "https://listwyse.com/api/v1/studio/dealers/DEALER-01" \
  -H "Authorization: Bearer $LISTWYSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Example Motors Downtown"
}'
Answer · 200
{
  "dealer": {
    "id": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "name": "Example Motors",
    "code": "DEALER-01",
    "createdAt": "2026-10-24T14:40:00.000Z"
  }
}
DELETE/dealers/:id

Delete a dealer

Takes a dealer off your account: it leaves GET /dealers and calls can't name it any more. Its cars and finished photos stay, and their files keep working. Its code is freed for a new dealer.

When to use it: You stop shooting for a dealership.

Path

:id
in the pathrequired

Our dealer id, or your code.

Errors

DEALER_NOT_FOUND 404No dealer with that id or code on your account.
Request
curl -X DELETE "https://listwyse.com/api/v1/studio/dealers/DEALER-01" \
  -H "Authorization: Bearer $LISTWYSE_KEY"
Answer · 200
{
  "deleted": true,
  "id": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77"
}
GET/dealers/:id/settings

Get a dealer's settings

How a dealer's photos come out when a call doesn't say otherwise, and every choice each setting has. Use the id default for your account's own settings.

Path

:id
in the pathrequired

Our dealer id, your code, or default for your account's own settings.

The answer: inherited is true while the dealer follows your account's settings (or Listwyse's, when your account has none of its own). options lists every background, file type and slider step you can choose, with the steps' names.

Errors

DEALER_NOT_FOUND 404No dealer with that id or code on your account.
Request
curl "https://listwyse.com/api/v1/studio/dealers/DEALER-01/settings" \
  -H "Authorization: Bearer $LISTWYSE_KEY"
Answer · 200
{
  "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
  "settings": {
    "background": "keep",
    "outputFormat": "jpeg",
    "aspectRatio": "match",
    "cleanup": 1,
    "backgroundTidy": 1,
    "reflections": 1,
    "framing": 1
  },
  "inherited": false,
  "options": {
    "background": [
      "keep",
      "grey-studio-catalogue",
      "panel-showroom-catalogue",
      "…"
    ],
    "outputFormat": [
      "jpeg",
      "png",
      "webp"
    ],
    "aspectRatio": [
      "match",
      "4:3",
      "3:2",
      "16:9"
    ],
    "sliders": {
      "cleanup": {
        "name": "Clean-up",
        "steps": [
          {
            "step": 0,
            "label": "As shot",
            "description": null
          },
          {
            "step": 1,
            "label": "Detailed",
            "description": null
          }
        ]
      },
      "backgroundTidy": {
        "name": "Background tidy",
        "steps": [
          "…"
        ]
      },
      "reflections": {
        "name": "Reflections",
        "steps": [
          "…"
        ]
      },
      "framing": {
        "name": "Framing",
        "steps": [
          "…"
        ]
      }
    }
  }
}
PATCH/dealers/:id/settings

Change a dealer's settings

Send only the settings that change. From then on the dealer keeps settings of its own instead of following your account's.

When to use it: Setting a new dealer up, or changing how its photos come out from now on.

Path

:id
in the pathrequired

Our dealer id, your code, or default for your account's own settings.

Body

background
string

The background photos get when a call doesn't say: "keep" (no background) or a background's id.

cleanup
whole number

Clean-up: how clean the car looks (dirt, dust, water spots). Dents and scratches always stay. A step number (GET /dealers/:id/settings lists them).

backgroundTidy
whole number

Background tidy: how much of the lot behind the car is tidied, when the background is kept. A step number.

reflections
whole number

Reflections: how much of the surroundings shows in the paint and glass. A step number.

framing
whole number

Framing: whether a crooked photo may be straightened and re-cropped, or keeps its framing. A step number.

outputFormat
jpeg | png | webp

The file type photos come back as: jpeg, png or webp.

aspectRatio
match | 4:3 | 3:2 | 16:9

The shape photos come back in: "match" keeps each photo's own shape; 4:3, 3:2 or 16:9 crop the finished photo to that shape, keeping the car in frame.

The answer: The settings as they now stand, in the same shape as GET. The same settings show in the Listwyse app.

Errors

INVALID_REQUEST 400A slider step that doesn't exist, or a field we don't know.
INVALID_BACKGROUND 400background isn't "keep", a preset, or a custom background this dealer can use.
DEALER_NOT_FOUND 404No dealer with that id or code on your account.
Request
curl -X PATCH "https://listwyse.com/api/v1/studio/dealers/DEALER-01/settings" \
  -H "Authorization: Bearer $LISTWYSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "reflections": 1,
  "outputFormat": "png"
}'
Answer · 200
{
  "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
  "settings": {
    "background": "keep",
    "outputFormat": "jpeg",
    "aspectRatio": "match",
    "cleanup": 1,
    "backgroundTidy": 1,
    "reflections": 1,
    "framing": 1
  },
  "inherited": false,
  "options": {
    "background": [
      "keep",
      "grey-studio-catalogue",
      "panel-showroom-catalogue",
      "…"
    ],
    "outputFormat": [
      "jpeg",
      "png",
      "webp"
    ],
    "aspectRatio": [
      "match",
      "4:3",
      "3:2",
      "16:9"
    ],
    "sliders": {
      "cleanup": {
        "name": "Clean-up",
        "steps": [
          {
            "step": 0,
            "label": "As shot",
            "description": null
          },
          {
            "step": 1,
            "label": "Detailed",
            "description": null
          }
        ]
      },
      "backgroundTidy": {
        "name": "Background tidy",
        "steps": [
          "…"
        ]
      },
      "reflections": {
        "name": "Reflections",
        "steps": [
          "…"
        ]
      },
      "framing": {
        "name": "Framing",
        "steps": [
          "…"
        ]
      }
    }
  }
}

Asset creation

A dealer's own branding and custom backgrounds, made once and used on every photo that asks. Making one costs 1 credit, given back if it fails; using a photo of your own is free.

GET/branding

See a dealer's branding

The dealer's logo and, for each branding item (plate, screen, mat), its saved designs and which one is used.

When to use it: Checking what a dealer's photos will get, or waiting for a design to finish.

Query

dealerId
string

Our id for the dealer (GET /dealers). Needed if you shoot for dealers; a dealership's own account leaves it out.

dealerCode
string

Or your own code for the dealer.

The answer: Each item's chosenUrl is the design photos get. generating is true while a design is being made; error says why one failed. uploaded marks a photo of the dealer's own item.

Errors

INVALID_REQUEST 400Your account shoots for dealers and the call didn't name one, or a field is wrong.
NOT_FOUND 404No dealer with that dealerCode on your account.
Request
curl "https://listwyse.com/api/v1/studio/branding?dealerCode=DEALER-01" \
  -H "Authorization: Bearer $LISTWYSE_KEY"
Answer · 200
{
  "branding": {
    "dealership": {
      "name": "Example Motors",
      "providerDealershipId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77"
    },
    "logoUrl": "https://qzemyvwjmbhjilubfpzh.supabase.co/storage/v1/object/public/photos/…/logo.png",
    "assets": {
      "displayPlate": {
        "chosenUrl": "https://…/plate.png",
        "designs": [
          {
            "url": "https://…/plate.png",
            "uploaded": false
          }
        ],
        "generating": false,
        "runId": null,
        "error": null
      },
      "monitor": {
        "chosenUrl": null,
        "designs": [],
        "generating": true,
        "runId": "run_…",
        "error": null
      },
      "floorMat": {
        "chosenUrl": "https://…/our-mat.jpg",
        "designs": [
          {
            "url": "https://…/our-mat.jpg",
            "uploaded": true
          }
        ],
        "generating": false,
        "runId": null,
        "error": null
      }
    }
  }
}
POST/branding/designsUses credits

Design branding items

Makes a plate, a screen picture and a floor mat from the logo with AI: one design each, used as soon as it lands. Each design costs 1 credit.

When to use it: The dealer has a logo but no plate, screen or mat of its own to photograph.

Body

kinds
list of strings

Which items to design. All three if left out.

dealerId
string

Our id for the dealer (GET /dealers). Needed if you shoot for dealers; a dealership's own account leaves it out.

dealerCode
string

Or your own code for the dealer.

What happens

  1. We hold 1 credit for each item and answer at once, with those items generating.
  2. Each design takes a minute or two. It's saved, and used, as soon as it lands.
  3. A design that can't be made gives its credit back, and its error says why.

The answer: A branding.completed (or branding.failed) webhook arrives as each item finishes. Without webhooks, ask GET /branding until each item's generating is false.

Errors

INVALID_REQUEST 400Your account shoots for dealers and the call didn't name one, or a field is wrong.
NOT_FOUND 404No dealer with that dealerCode on your account.
INVALID_REQUEST 400The dealer has no logo yet (POST /branding/logo).
OUT_OF_CREDITS 402Your balance can't cover it. Nothing was started.
CONFLICT 409Every item asked for is already being designed.
Request
curl -X POST "https://listwyse.com/api/v1/studio/branding/designs" \
  -H "Authorization: Bearer $LISTWYSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "kinds": [
    "displayPlate",
    "monitor",
    "floorMat"
  ],
  "dealerCode": "DEALER-01"
}'
Answer · 202
{
  "branding": {
    "dealership": {
      "name": "Example Motors",
      "providerDealershipId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77"
    },
    "logoUrl": "https://qzemyvwjmbhjilubfpzh.supabase.co/storage/v1/object/public/photos/…/logo.png",
    "assets": {
      "displayPlate": {
        "generating": true,
        "runId": "run_…"
      },
      "…": "…"
    }
  },
  "credits": {
    "pricePerDesign": 1,
    "held": 3,
    "balance": 38.9
  }
}
POST/branding/photos

Use the dealer's own item

A photo of a branding item the dealer really has, say its own floor mat, used instead of a design. Free. Send JSON with a link, or multipart/form-data with kind and a file field.

When to use it: The dealer has a real plate, mat or screen picture and wants exactly that on its photos.

Body (JSON, or the same fields as a form with a file)

kind
displayPlate | monitor | floorMatrequired

"displayPlate" (the plate), "monitor" (the screen) or "floorMat".

url
https linkrequired

A public https link to a photo of the dealer's own item. Or a multipart file field.

dealerId
string

Our id for the dealer (GET /dealers). Needed if you shoot for dealers; a dealership's own account leaves it out.

dealerCode
string

Or your own code for the dealer.

The answer: It's saved and used at once.

Errors

INVALID_REQUEST 400Your account shoots for dealers and the call didn't name one, or a field is wrong.
NOT_FOUND 404No dealer with that dealerCode on your account.
Request
curl -X POST "https://listwyse.com/api/v1/studio/branding/photos" \
  -H "Authorization: Bearer $LISTWYSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "kind": "floorMat",
  "url": "https://cdn.example.com/dealer/our-mat.jpg",
  "dealerCode": "DEALER-01"
}'
Answer · 200
{
  "kind": "floorMat",
  "url": "https://…/our-mat.jpg",
  "branding": "…"
}
PATCH/branding

Choose which design is used

Switches an item to another of its saved designs.

When to use it: A dealer has more than one design of an item and prefers an earlier one.

Body

chosen
objectrequired

For each item to switch, the url of one of its saved designs (GET /branding lists them).

chosen.displayPlate
https link

—

chosen.monitor
https link

—

chosen.floorMat
https link

—

dealerId
string

Our id for the dealer (GET /dealers). Needed if you shoot for dealers; a dealership's own account leaves it out.

dealerCode
string

Or your own code for the dealer.

Errors

INVALID_REQUEST 400Your account shoots for dealers and the call didn't name one, or a field is wrong.
NOT_FOUND 404No dealer with that dealerCode on your account.
Request
curl -X PATCH "https://listwyse.com/api/v1/studio/branding" \
  -H "Authorization: Bearer $LISTWYSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "chosen": {
    "floorMat": "https://…/our-mat.jpg"
  },
  "dealerCode": "DEALER-01"
}'
Answer · 200
{
  "branding": {
    "dealership": {
      "name": "Example Motors",
      "providerDealershipId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77"
    },
    "logoUrl": "https://qzemyvwjmbhjilubfpzh.supabase.co/storage/v1/object/public/photos/…/logo.png",
    "assets": {
      "displayPlate": {
        "chosenUrl": "https://…/plate.png",
        "designs": [
          {
            "url": "https://…/plate.png",
            "uploaded": false
          }
        ],
        "generating": false,
        "runId": null,
        "error": null
      },
      "monitor": {
        "chosenUrl": null,
        "designs": [],
        "generating": true,
        "runId": "run_…",
        "error": null
      },
      "floorMat": {
        "chosenUrl": "https://…/our-mat.jpg",
        "designs": [
          {
            "url": "https://…/our-mat.jpg",
            "uploaded": true
          }
        ],
        "generating": false,
        "runId": null,
        "error": null
      }
    }
  }
}
DELETE/branding/designs

Delete a saved design

Removes one saved design. If it was the one in use, the next saved one is used instead.

Body

kind
displayPlate | monitor | floorMatrequired

"displayPlate" (the plate), "monitor" (the screen) or "floorMat".

url
https linkrequired

The saved design's url.

dealerId
string

Our id for the dealer (GET /dealers). Needed if you shoot for dealers; a dealership's own account leaves it out.

dealerCode
string

Or your own code for the dealer.

Errors

INVALID_REQUEST 400Your account shoots for dealers and the call didn't name one, or a field is wrong.
NOT_FOUND 404No dealer with that dealerCode on your account.
NOT_FOUND 404No saved design with that url.
Request
curl -X DELETE "https://listwyse.com/api/v1/studio/branding/designs" \
  -H "Authorization: Bearer $LISTWYSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "kind": "displayPlate",
  "url": "https://…/plate.png",
  "dealerCode": "DEALER-01"
}'
Answer · 200
{
  "branding": {
    "dealership": {
      "name": "Example Motors",
      "providerDealershipId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77"
    },
    "logoUrl": "https://qzemyvwjmbhjilubfpzh.supabase.co/storage/v1/object/public/photos/…/logo.png",
    "assets": {
      "displayPlate": {
        "chosenUrl": "https://…/plate.png",
        "designs": [
          {
            "url": "https://…/plate.png",
            "uploaded": false
          }
        ],
        "generating": false,
        "runId": null,
        "error": null
      },
      "monitor": {
        "chosenUrl": null,
        "designs": [],
        "generating": true,
        "runId": "run_…",
        "error": null
      },
      "floorMat": {
        "chosenUrl": "https://…/our-mat.jpg",
        "designs": [
          {
            "url": "https://…/our-mat.jpg",
            "uploaded": true
          }
        ],
        "generating": false,
        "runId": null,
        "error": null
      }
    }
  }
}
POST/templates/custom

Start a custom background

A background of the dealer's own showroom, made from photos of the building. Name the dealer it's for, and only that dealer's photos can use it; or send universal: true and every dealer on your account can. It starts as a draft: add photos, then build it (or use an empty photo of the place you already have).

When to use it: A dealer wants its cars shown in its own showroom rather than on one of our presets.

Body

name
string

A name for the background.

note
string

Anything the build should keep, e.g. "show the red wall logo behind the car".

photoUrls
list of strings, up to 8

Photos of the building: the showroom floor, the storefront, a branded wall. Up to 8.

dealerId
string

The dealer it's for: only that dealer's photos can use it. A dealership's own account leaves it out.

dealerCode
string

Or your own code for that dealer.

universal
boolean

Instead of a dealer: true lets every dealer on your account use it. An account that shoots for dealers sends a dealer or this.

Errors

INVALID_REQUEST 400Your account shoots for dealers and the call named no dealer and didn't send universal: true, or named both.
NOT_FOUND 404No dealer with that dealerCode on your account.
Request
curl -X POST "https://listwyse.com/api/v1/studio/templates/custom" \
  -H "Authorization: Bearer $LISTWYSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Example Motors showroom",
  "note": "Show the red wall logo behind the car",
  "photoUrls": [
    "https://cdn.example.com/dealer/floor.jpg",
    "https://cdn.example.com/dealer/wall.jpg"
  ],
  "dealerCode": "DEALER-01"
}'
Answer · 201
{
  "template": {
    "id": "3b5f0a1e-9c2d-4e7f-8a61-2d4c9b0e7f13",
    "templateId": "custom:3b5f0a1e-9c2d-4e7f-8a61-2d4c9b0e7f13",
    "name": "Example Motors showroom",
    "note": "Show the red wall logo behind the car",
    "status": "draft",
    "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "universal": false,
    "providerDealershipId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "photoUrls": [
      "https://…/floor.jpg",
      "https://…/wall.jpg"
    ],
    "exampleUrls": [],
    "selectedUrl": null,
    "error": null,
    "createdAt": "2026-10-26T09:12:00.000Z",
    "updatedAt": "2026-10-26T09:16:40.000Z"
  }
}
GET/templates/custom

List your custom backgrounds

Every custom background made for your account, in any status. GET /templates lists only the ready ones, beside our presets.

Query

dealerId
string

Our id for the dealer (GET /dealers). Needed if you shoot for dealers; a dealership's own account leaves it out.

dealerCode
string

Or your own code for the dealer.

The answer: status is draft (add photos, then build), generating, examples (pick one), ready (usable as a background) or error. With a dealer named, you get the ones it can use: its own and the universal ones (universal: true).

Errors

INVALID_REQUEST 400Your account shoots for dealers and the call didn't name one, or a field is wrong.
NOT_FOUND 404No dealer with that dealerCode on your account.
Request
curl "https://listwyse.com/api/v1/studio/templates/custom?dealerCode=DEALER-01" \
  -H "Authorization: Bearer $LISTWYSE_KEY"
Answer · 200
{
  "templates": [
    {
      "id": "3b5f0a1e-9c2d-4e7f-8a61-2d4c9b0e7f13",
      "templateId": "custom:3b5f0a1e-9c2d-4e7f-8a61-2d4c9b0e7f13",
      "name": "Example Motors showroom",
      "note": "Show the red wall logo behind the car",
      "status": "examples",
      "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
      "universal": false,
      "providerDealershipId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
      "photoUrls": [
        "https://…/floor.jpg",
        "https://…/wall.jpg"
      ],
      "exampleUrls": [
        "https://…/built-1.jpg"
      ],
      "selectedUrl": null,
      "error": null,
      "createdAt": "2026-10-26T09:12:00.000Z",
      "updatedAt": "2026-10-26T09:16:40.000Z"
    }
  ]
}
GET/templates/custom/:id

Check a custom background

One custom background, by its id or its templateId.

When to use it: Waiting for a build to finish.

Path

:id
in the pathrequired

The background's id, or its templateId (custom:…).

The answer: Once status is examples, exampleUrls holds the built photos to pick from.

Errors

NOT_FOUND 404No custom background with that id on your account.
Request
curl "https://listwyse.com/api/v1/studio/templates/custom/LOOK_ID" \
  -H "Authorization: Bearer $LISTWYSE_KEY"
Answer · 200
{
  "template": {
    "id": "3b5f0a1e-9c2d-4e7f-8a61-2d4c9b0e7f13",
    "templateId": "custom:3b5f0a1e-9c2d-4e7f-8a61-2d4c9b0e7f13",
    "name": "Example Motors showroom",
    "note": "Show the red wall logo behind the car",
    "status": "examples",
    "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "universal": false,
    "providerDealershipId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "photoUrls": [
      "https://…/floor.jpg",
      "https://…/wall.jpg"
    ],
    "exampleUrls": [
      "https://…/built-1.jpg"
    ],
    "selectedUrl": null,
    "error": null,
    "createdAt": "2026-10-26T09:12:00.000Z",
    "updatedAt": "2026-10-26T09:16:40.000Z"
  }
}
POST/templates/custom/:id/photos

Add photos of the building

More photos for a custom background, up to 8 in all. Send JSON with links, or multipart/form-data with a file field.

Path

:id
in the pathrequired

The background's id, or its templateId.

Body (JSON, or the same fields as a form with a file)

urls
list of strings, up to 8required

More photos of the building (up to 8 in all). Or a multipart file field.

Errors

NOT_FOUND 404No custom background with that id on your account.
INVALID_REQUEST 400More than 8 photos in all.
CONFLICT 409The background is being built. Wait for it to finish.
Request
curl -X POST "https://listwyse.com/api/v1/studio/templates/custom/LOOK_ID/photos" \
  -H "Authorization: Bearer $LISTWYSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "urls": [
    "https://cdn.example.com/dealer/entrance.jpg"
  ]
}'
Answer · 200
{
  "template": {
    "id": "3b5f0a1e-9c2d-4e7f-8a61-2d4c9b0e7f13",
    "templateId": "custom:3b5f0a1e-9c2d-4e7f-8a61-2d4c9b0e7f13",
    "name": "Example Motors showroom",
    "note": "Show the red wall logo behind the car",
    "status": "draft",
    "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "universal": false,
    "providerDealershipId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "photoUrls": [
      "https://…/floor.jpg",
      "https://…/wall.jpg"
    ],
    "exampleUrls": [
      "https://…/built-1.jpg"
    ],
    "selectedUrl": null,
    "error": null,
    "createdAt": "2026-10-26T09:12:00.000Z",
    "updatedAt": "2026-10-26T09:16:40.000Z"
  }
}
DELETE/templates/custom/:id/photos

Remove a photo of the building

Takes one photo off a custom background. It goes back to draft until it's built again.

Path

:id
in the pathrequired

The background's id, or its templateId.

Body

url
https linkrequired

One of the background's photoUrls.

Errors

NOT_FOUND 404No custom background with that id on your account.
NOT_FOUND 404That photo isn't on the background.
Request
curl -X DELETE "https://listwyse.com/api/v1/studio/templates/custom/LOOK_ID/photos" \
  -H "Authorization: Bearer $LISTWYSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://…/wall.jpg"
}'
Answer · 200
{
  "template": {
    "id": "3b5f0a1e-9c2d-4e7f-8a61-2d4c9b0e7f13",
    "templateId": "custom:3b5f0a1e-9c2d-4e7f-8a61-2d4c9b0e7f13",
    "name": "Example Motors showroom",
    "note": "Show the red wall logo behind the car",
    "status": "draft",
    "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "universal": false,
    "providerDealershipId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "photoUrls": [
      "https://…/floor.jpg",
      "https://…/wall.jpg"
    ],
    "exampleUrls": [
      "https://…/built-1.jpg"
    ],
    "selectedUrl": null,
    "error": null,
    "createdAt": "2026-10-26T09:12:00.000Z",
    "updatedAt": "2026-10-26T09:16:40.000Z"
  }
}
POST/templates/custom/:id/buildUses credits

Build a custom background

Turns the photos of the building into an empty, tidied-up photo of the place with room for a car. Costs 1 credit.

When to use it: The background has its photos and is ready to be made.

Path

:id
in the pathrequired

The background's id, or its templateId.

Body

regenerate
boolean

Build a ready background again from scratch. It can't be used until a new photo is picked.

What happens

  1. We hold 1 credit and answer at once, with the background generating.
  2. The build takes a few minutes. A background already building is handed back, not started again (and not charged).
  3. It ends at examples, and a background.completed webhook carries it: pick one of exampleUrls with PATCH /templates/custom/:id, and the background is ready. A build that fails gives its credit back and sends background.failed.

Errors

NOT_FOUND 404No custom background with that id on your account.
INVALID_REQUEST 400The background has no photos yet.
OUT_OF_CREDITS 402Your balance can't cover it. Nothing was started.
CONFLICT 409The background is already ready. Send regenerate: true to build it again.
Request
curl -X POST "https://listwyse.com/api/v1/studio/templates/custom/LOOK_ID/build" \
  -H "Authorization: Bearer $LISTWYSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
Answer · 202
{
  "template": {
    "id": "3b5f0a1e-9c2d-4e7f-8a61-2d4c9b0e7f13",
    "templateId": "custom:3b5f0a1e-9c2d-4e7f-8a61-2d4c9b0e7f13",
    "name": "Example Motors showroom",
    "note": "Show the red wall logo behind the car",
    "status": "generating",
    "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "universal": false,
    "providerDealershipId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "photoUrls": [
      "https://…/floor.jpg",
      "https://…/wall.jpg"
    ],
    "exampleUrls": [],
    "selectedUrl": null,
    "error": null,
    "createdAt": "2026-10-26T09:12:00.000Z",
    "updatedAt": "2026-10-26T09:16:40.000Z"
  },
  "runId": "run_…",
  "operationId": "…",
  "started": true,
  "credits": {
    "pricePerBuild": 1,
    "held": 1,
    "balance": 40.9
  }
}
PATCH/templates/custom/:id

Pick the built photo

Makes the background ready with one of its built photos (selectedUrl). Also renames it or changes its note.

Path

:id
in the pathrequired

The background's id, or its templateId.

Body

selectedUrl
https link

One of exampleUrls: the background is ready to use.

name
string

—

note
string

—

The answer: Once status is ready, its templateId works as background on any photo or vehicle call.

Errors

NOT_FOUND 404No custom background with that id on your account.
INVALID_REQUEST 400selectedUrl isn't one of exampleUrls.
CONFLICT 409The background is still being built.
Request
curl -X PATCH "https://listwyse.com/api/v1/studio/templates/custom/LOOK_ID" \
  -H "Authorization: Bearer $LISTWYSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "selectedUrl": "https://…/built-1.jpg"
}'
Answer · 200
{
  "template": {
    "id": "3b5f0a1e-9c2d-4e7f-8a61-2d4c9b0e7f13",
    "templateId": "custom:3b5f0a1e-9c2d-4e7f-8a61-2d4c9b0e7f13",
    "name": "Example Motors showroom",
    "note": "Show the red wall logo behind the car",
    "status": "ready",
    "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "universal": false,
    "providerDealershipId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "photoUrls": [
      "https://…/floor.jpg",
      "https://…/wall.jpg"
    ],
    "exampleUrls": [
      "https://…/built-1.jpg"
    ],
    "selectedUrl": "https://…/built-1.jpg",
    "error": null,
    "createdAt": "2026-10-26T09:12:00.000Z",
    "updatedAt": "2026-10-26T09:16:40.000Z"
  }
}
POST/templates/custom/:id/plate

Use your own empty photo

Skips the build: a finished, empty photo of the place you already have becomes the background, ready at once. Free. Send JSON with a link, or multipart/form-data with a file field.

When to use it: You already have a clean photo of the empty showroom.

Path

:id
in the pathrequired

The background's id, or its templateId.

Body (JSON, or the same fields as a form with a file)

url
https linkrequired

A finished, empty photo of the place, with room for a car. Or a multipart file field.

name
string

—

Errors

NOT_FOUND 404No custom background with that id on your account.
CONFLICT 409The background is being built. Wait for it to finish.
Request
curl -X POST "https://listwyse.com/api/v1/studio/templates/custom/LOOK_ID/plate" \
  -H "Authorization: Bearer $LISTWYSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://cdn.example.com/dealer/empty-showroom.jpg"
}'
Answer · 200
{
  "template": {
    "id": "3b5f0a1e-9c2d-4e7f-8a61-2d4c9b0e7f13",
    "templateId": "custom:3b5f0a1e-9c2d-4e7f-8a61-2d4c9b0e7f13",
    "name": "Example Motors showroom",
    "note": "Show the red wall logo behind the car",
    "status": "ready",
    "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "universal": false,
    "providerDealershipId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
    "photoUrls": [
      "https://…/floor.jpg",
      "https://…/wall.jpg"
    ],
    "exampleUrls": [
      "https://…/built-1.jpg"
    ],
    "selectedUrl": "https://…/empty-showroom.jpg",
    "error": null,
    "createdAt": "2026-10-26T09:12:00.000Z",
    "updatedAt": "2026-10-26T09:16:40.000Z"
  }
}
DELETE/templates/custom/:id

Delete a custom background

Removes a custom background. Photos already made on it keep their files.

Path

:id
in the pathrequired

The background's id, or its templateId.

Errors

NOT_FOUND 404No custom background with that id on your account.
Request
curl -X DELETE "https://listwyse.com/api/v1/studio/templates/custom/LOOK_ID" \
  -H "Authorization: Bearer $LISTWYSE_KEY"
Answer · 200
{
  "deleted": true,
  "id": "3b5f0a1e-9c2d-4e7f-8a61-2d4c9b0e7f13"
}

Webhooks

Where we tell you about finished photos, branding designs and custom backgrounds. Set a URL, try it with a test event, and see every delivery and what your server answered.

GET/webhook

Your webhook

The URL we send events to, the secret that signs them, and the events there are.

Request
curl "https://listwyse.com/api/v1/studio/webhook" \
  -H "Authorization: Bearer $LISTWYSE_KEY"
Answer · 200
{
  "webhook": {
    "url": "https://api.example.com/listwyse/events",
    "secret": "9f86d081884c7d65…",
    "events": [
      "photo.completed",
      "photo.failed",
      "vehicle.completed",
      "branding.completed",
      "branding.failed",
      "background.completed",
      "background.failed",
      "webhook.test"
    ]
  }
}
PUT/webhook

Set your webhook URL

An https URL on the public internet, or null to stop sending. The secret stays the same.

Body

url
https linkrequired

Your https endpoint, or null to stop webhooks.

Errors

INVALID_WEBHOOK_URL 400Not an https link on the public internet.
Request
curl -X PUT "https://listwyse.com/api/v1/studio/webhook" \
  -H "Authorization: Bearer $LISTWYSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://api.example.com/listwyse/events"
}'
Answer · 200
{
  "webhook": {
    "url": "https://api.example.com/listwyse/events",
    "secret": "9f86d081884c7d65…"
  }
}
POST/webhook/test

Send a test event

Sends a webhook.test event to your URL now, signed like the real ones.

When to use it: Checking your endpoint receives and verifies events before any photo is sent.

The answer: The delivery. A moment later GET /webhook/deliveries shows what your server answered.

Errors

NO_WEBHOOK_URL 409Set a URL first (PUT /webhook).
Request
curl -X POST "https://listwyse.com/api/v1/studio/webhook/test" \
  -H "Authorization: Bearer $LISTWYSE_KEY"
Answer · 202
{
  "delivery": {
    "id": "e2b1…",
    "event": "webhook.test",
    "status": "queued"
  }
}
GET/webhook/deliveries

Recent deliveries

Every event we've sent (or are about to), newest first: what your server answered, how many tries, and when the next one is.

When to use it: Finding out why your server didn't get an event.

Query

limit
whole number

How many (1–100, default 50).

before
string

Paging: the createdAt of the last item you have; you get older ones.

The answer: status is queued, delivering, delivered or failed. A failed one with nextAttemptAt will be tried again then; one without has run out of tries (resend it).

Request
curl "https://listwyse.com/api/v1/studio/webhook/deliveries?limit=20" \
  -H "Authorization: Bearer $LISTWYSE_KEY"
Answer · 200
{
  "deliveries": [
    {
      "id": "e2b1…",
      "event": "photo.completed",
      "status": "failed",
      "attempts": 2,
      "responseStatus": 500,
      "error": "Webhook endpoint returned HTTP 500.",
      "nextAttemptAt": "2026-10-24T15:12:00.000Z",
      "createdAt": "2026-10-24T15:05:40.000Z",
      "deliveredAt": null,
      "payload": {
        "id": "e2b1…",
        "event": "photo.completed",
        "data": {
          "photo": {
            "id": "…"
          }
        }
      }
    }
  ],
  "next": null
}
POST/webhook/deliveries/:id/resend

Send a delivery again

Sends one delivery again now, with the same id and body.

Path

:id
in the pathrequired

The delivery's id.

Errors

NOT_FOUND 404No delivery with that id for your key.
DELIVERING 409It's being sent right now.
Request
curl -X POST "https://listwyse.com/api/v1/studio/webhook/deliveries/DELIVERY_ID/resend" \
  -H "Authorization: Bearer $LISTWYSE_KEY"
Answer · 202
{
  "delivery": {
    "id": "e2b1…",
    "status": "queued"
  }
}

Account and backgrounds

Your key, your credits and what you've spent them on, and the backgrounds a photo can be put on.

GET/account

Check your key and credits

Who the key belongs to, its credit balance and price per photo, and the limits.

When to use it: Your first call, to check the key works; and before a big send, to check the balance.

The answer: credits.balance is what's left to spend; credits.pricePerPhoto is what one finished photo costs. organization.kind is provider for an account that shoots for dealers (dealerships lists them) and dealership for one dealer's own account.

Request
curl "https://listwyse.com/api/v1/studio/account" \
  -H "Authorization: Bearer $LISTWYSE_KEY"
Answer · 200
{
  "key": {
    "id": "…",
    "name": "Example Photo Co",
    "prefix": "sk_live_1d8d34c6…",
    "webhookUrl": "https://api.example.com/listwyse/events"
  },
  "organization": {
    "id": "…",
    "name": "Example Photo Co",
    "kind": "provider"
  },
  "dealerships": [
    {
      "id": "d89df6f9-…",
      "name": "Example Motors"
    }
  ],
  "credits": {
    "balance": 41.9,
    "pricePerPhoto": 0.1
  },
  "limits": {
    "photosPerVehicle": 60,
    "photosPerVehicleCall": 60,
    "uploadBytes": 4194304
  }
}
GET/usage

See what you've spent

Every charge on your credits, newest first, 50 at a time: photos, branding designs and custom background builds made through the API, and Vehicle Tokens spent in the Listwyse app (the same balance). Narrow it by dealer and dates.

When to use it: Billing your dealers for a month, or checking what a balance went on.

Query

dealerId
string

Only this dealer's charges (photos, designs and builds made through the API).

dealerCode
string

Or the dealer by your code.

from
string

From this date or time (2026-10-01, UTC).

to
string

Up to, not including, this date or time.

limit
whole number

How many (1–100, default 50).

before
string

Paging: the createdAt of the last item you have; you get older ones.

The answer: Each charge has its credits and status: held (still being made), charged, or refunded (refunded says how much came back). totals adds up every charge the filters match, not just this page. next is set when there are more: send it back as before.

Errors

INVALID_REQUEST 400from or to isn't a date or time.
DEALER_NOT_FOUND 404No dealer with that dealerId or dealerCode on your account.
Request
curl "https://listwyse.com/api/v1/studio/usage?dealerCode=DEALER-01&from=2026-10-01&to=2026-11-01" \
  -H "Authorization: Bearer $LISTWYSE_KEY"
Answer · 200
{
  "usage": [
    {
      "id": "8c0f2a51-…",
      "createdAt": "2026-10-24T15:02:11.000Z",
      "type": "photo",
      "description": "Partner API photo",
      "credits": 0.1,
      "refunded": 0,
      "status": "charged",
      "dealerId": "d89df6f9-0c4e-4a43-b3d4-6a2f0c9e1b77",
      "vehicleId": "0f3e6d2c-41a7-4a0b-8f1e-3b9d2c7a6e55",
      "photoId": "5b0c1e8a-7f0e-4b7a-9a43-0a1c3f2d9e11",
      "stockNumber": "A1234"
    }
  ],
  "totals": {
    "charged": 41.3,
    "held": 0.4,
    "refunded": 1.2
  },
  "next": "2026-10-24T15:02:11.000Z"
}
GET/templates

List the backgrounds

Every background a photo can be put on: our presets, and the ready custom ones made for your account. Name a dealer to get only what its photos can use: the presets, its own custom backgrounds and the universal ones.

Query

dealerId
string

Our id for the dealer (GET /dealers). Needed if you shoot for dealers; a dealership's own account leaves it out.

dealerCode
string

Or your own code for the dealer.

The answer: Send one's id as background. A custom one's dealerId says whose it is (null with universal: true). Skip ones with videoOnly: true; they only make films.

Request
curl "https://listwyse.com/api/v1/studio/templates" \
  -H "Authorization: Bearer $LISTWYSE_KEY"
Answer · 200
{
  "templates": [
    {
      "id": "panel-showroom-catalogue",
      "name": "Lumen",
      "description": "Gloss floor, light-panel ceiling.",
      "builtIn": true,
      "videoOnly": false
    },
    {
      "id": "custom:3b5f…",
      "name": "Example Motors showroom",
      "builtIn": false,
      "videoOnly": false
    }
  ]
}

Questions about the API? Write to your Listwyse contact with the call's requestId.