Batch stock and image upload
If your stock lives in another system (a DMS, a spreadsheet, a supplier feed), batch upload is the way to push it into Vehiso. One request creates or updates up to 500 vehicles, with their photos, and is processed in the background.
All endpoints on this page need the vehicles:write scope (listing images needs only vehicles:read) and a secret key or OAuth token; publishable keys cannot write stock.
How matching works
POST /v1/vehicles/batch is an upsert. Each item is matched to an existing vehicle by the first of these it carries, in this order:
id- the Vehiso vehicle idexternal_id- your own stock number for the vehicleregistration- the registration plate
A matched item updates that vehicle. An item that matches nothing creates a new one.
Always send external_id if your system has its own stock number. It is stored against the vehicle, so the next upload matches on it even if the registration changes (a cherished plate, for example), and you never need to store Vehiso's ids yourself.
Send a batch
Each item takes the same vehicle fields as POST /v1/vehicles, plus the matching fields above and an optional images list. An item that creates a vehicle must include make and model, or it fails with validation_failed. Prices in a request are integers in minor units of the dealer's currency.
curl https://api.vehiso.com/v1/vehicles/batch \
-H "Authorization: Bearer $VEHISO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3d6f0a52-8c1e-4b7a-9f2d-6e5c4b3a2f10" \
-d @batch.json
{
"vehicles": [
{
"external_id": "STK-1042",
"registration": "AB12CDE",
"make": "Volkswagen",
"model": "Golf",
"mileage": 32150,
"price": 1499500,
"images": [
"https://cdn.example-motors.co.uk/stock/STK-1042/1.jpg",
"https://cdn.example-motors.co.uk/stock/STK-1042/2.jpg"
]
}
]
}
The example shows a few fields; the vehicle fields include derivative, colour, fuel type, transmission, registration date, VAT scheme, descriptions and more.
The API checks each item's shape and answers 202 Accepted at once with the batch, whose id you use to follow progress. Items that fail that check are already failed in this response; the rest are written in the background.
A vehicle_batch.completed event fires when the whole batch has been processed, so you can wait for a webhook instead of polling.
Limits
- Up to 500 vehicles per request. Split larger stock lists into several batches.
- Up to 100 image URLs per vehicle.
- Send an
Idempotency-Keyso a retried upload is not processed twice. See Idempotency.
Check the result
GET /v1/vehicle-batches/{id} reports the batch's progress (total, processed, created, updated and failed counts) and, in items, a result for every item:
{
"index": 0,
"external_id": "STK-1042",
"status": "created",
"vehicle_id": "5f0c6a8e-2d41-4f7b-9a53-1f0e8c2b7d19",
"errors": []
}
An item's status is pending, created, updated or failed. index is its position in your request, from 0. When an item fails, errors is an object with a code, a message and, for validation failures, fields. Codes include validation_failed, duplicate_external_id, external_id_taken and plan_stock_cap_reached. A created or updated item can also carry images_not_queued when its photos could not be queued.
A batch's status moves from queued to processing, and ends as completed or failed. Poll until it reaches one of the last two.
- Node.js
- Python
const headers = {Authorization: `Bearer ${process.env.VEHISO_API_KEY}`};
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
export async function waitForBatch(batchId) {
for (;;) {
const res = await fetch(`https://api.vehiso.com/v1/vehicle-batches/${batchId}`, {headers});
const {data} = await res.json();
if (data.status === 'completed' || data.status === 'failed') return data;
await sleep(5000);
}
}
import os
import time
import requests
HEADERS = {"Authorization": f"Bearer {os.environ['VEHISO_API_KEY']}"}
def wait_for_batch(batch_id):
while True:
res = requests.get(
f"https://api.vehiso.com/v1/vehicle-batches/{batch_id}", headers=HEADERS, timeout=30
)
data = res.json()["data"]
if data["status"] in ("completed", "failed"):
return data
time.sleep(5)
Poll every few seconds at most; a batch of 500 with photos takes a while. The vehicle_batch.completed event carries the batch without items, so fetch the batch for per-item results.
A failed item does not stop the rest of the batch. Fix the failed items and send them again in a new batch: matching makes that safe, because items that did succeed are simply updated again.
Plan stock cap
On the Free plan, which caps how many vehicles a dealer can have in stock, the batch applies that cap exactly as Vehiso's own stock import does:
- Updates always go through, whatever the stock count.
- New vehicles beyond the cap fail with the item error
plan_stock_cap_reached. The rest of the batch is still processed.
Images
Images in a batch
An item's images list replaces that vehicle's photos, in the order given. The new photos are downloaded and staged first, and only swapped in once they are ready, so the vehicle never appears on the website with no photos part way through. This is the same import the stock feeds use.
- Leave
imagesout of an item to keep the vehicle's current photos. - Image URLs must be publicly reachable: Vehiso downloads them.
- To change photos, send the full new list, not just the additions.
Importing images for one vehicle
POST /v1/vehicles/{id}/images/import imports photos from up to 100 URLs for a single vehicle. Like images in a batch, it replaces the vehicle's photos, in the order given, through the same staged import, so the vehicle is never left without photos part way through. It answers 202 Accepted and the import runs in the background.
curl https://api.vehiso.com/v1/vehicles/5f0c6a8e-2d41-4f7b-9a53-1f0e8c2b7d19/images/import \
-H "Authorization: Bearer $VEHISO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"urls": ["https://cdn.example-motors.co.uk/stock/STK-1042/1.jpg", "https://cdn.example-motors.co.uk/stock/STK-1042/2.jpg"]}'
{"data": {"vehicle_id": "5f0c6a8e-2d41-4f7b-9a53-1f0e8c2b7d19", "status": "queued", "images": 2}}
Uploading image files
If your photos are not online, upload the files directly with POST /v1/vehicles/{id}/images as multipart/form-data, one files[] part per image. Uploaded images are appended after the vehicle's existing photos.
- Up to 20 files per request, each up to 20 MB.
- JPEG (
.jpg,.jpeg), PNG, WebP or HEIC.
curl https://api.vehiso.com/v1/vehicles/5f0c6a8e-2d41-4f7b-9a53-1f0e8c2b7d19/images \
-H "Authorization: Bearer $VEHISO_API_KEY" \
-F "files[]=@front.jpg" \
-F "files[]=@interior.jpg"
It answers 201 with the images added. A request body larger than the server accepts is refused with 413 payload_too_large: send fewer or smaller files per request.
Ordering and removing images
| Endpoint | Does |
|---|---|
GET /v1/vehicles/{id}/images | Lists the vehicle's images in display order. Needs vehicles:read. |
PUT /v1/vehicles/{id}/images/order | Sets the display order. |
DELETE /v1/vehicles/{id}/images/{image_id} | Removes one image. |
The request bodies are in the API reference.
Removing vehicles
DELETE /v1/vehicles/{id} deletes a vehicle as the DMS does. It is a soft delete, so the vehicle appears as a tombstone in lists with include_deleted=true. A vehicle on a deal (409 vehicle_in_deal) or with a purchase order (409 vehicle_has_purchase_order) cannot be deleted: set its status to SOLD or ARCHIVED instead.
To show a vehicle as sold or reserved, change its status with POST /v1/vehicles/{id}/status; valid statuses come from GET /v1/reference/vehicle-statuses.