Import API
Keep your Mi Taller inventory in step with your DMS or stock system. Create and update vehicles, push complete inventories, manage photos — through a versioned REST API scoped to your workshop.
The API is for professional sellers, dealer groups, DMS vendors and integration partners. Records you create appear on mitaller.co under your workshop, exactly as if they had been added by hand — and they can still be edited by hand afterwards.
Quickstart
1. Create an API key
Sign in to mitaller.co → Dashboard → API keys, pick the workshop, give the key a name and copy it. The key is shown once; if you lose it, revoke it and create another.
2. Test authentication
curl https://api.mitaller.co/v1/me \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json"
3. Create a vehicle
curl -X POST https://api.mitaller.co/v1/vehicles \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"external_id": "STOCK-4821",
"external_updated_at": "2026-09-05T08:30:00Z",
"make": "Volkswagen",
"model": "Golf",
"year": 2022,
"vin": "WVWZZZAUZNW123456",
"license_plate": "1-ABC-123",
"mileage": 41200,
"first_registration": "2022-03-15",
"fuel_type": "petrol",
"gearbox_type": "dct",
"body_type": "hatchback",
"power_hp": 150,
"num_doors": 5,
"num_seats": 5,
"color": "gray",
"interior_material": "fabric",
"vehicle_condition": "used",
"asking_price": 24950,
"is_tax_vehicle": true,
"short_description": "Golf 1.5 TSI Life, first owner, full history",
"seller_description": "Serviced at the dealership, two keys, winter tyres included.",
"features": [
"appleCarplay",
"androidAuto",
"adaptiveCruiseControl",
"ledHeadlights",
"alloyWheels"
],
"parking_assist": [
"sens_front",
"sens_rear",
"cam_rear"
],
"image_urls": [
"https://cdn.example-dealer.be/stock/4821/front.jpg",
"https://cdn.example-dealer.be/stock/4821/interior.jpg"
]
}'
The response is the stored vehicle including its uuid, public_url, currency and photos, plus a warnings array when something non-fatal happened (a photo that could not be fetched, a model that was created).
Base URL & versioning
https://api.mitaller.co/v1
The major version is part of the path. Backwards-incompatible changes get a new major version; /v1 keeps its behaviour. Additive changes (new optional fields, new enumeration values) can appear within v1 — read GET /v1/options rather than hard-coding lists.
Paths are shown without a trailing slash; one is accepted.
Authentication
Bearer-token authentication with a workshop API key. Keys start with wsk_.
Authorization: Bearer wsk_…
X-API-Key: wsk_… is accepted as an alternative header. A key is either read-write or read-only; a read-only key gets 403 read_only_key on any write.
Request headers
| Header | Value | Purpose |
|---|---|---|
Authorization | Bearer wsk_… | Authenticates the workshop. |
Content-Type | application/json | For JSON bodies. Photo file uploads use multipart/form-data. |
Accept | application/json | Responses are always JSON. |
Vehicle data model
Fields accepted on POST, PUT and PATCH. Responses return the same names, plus read-only uuid, status (published / unlisted), public_url, currency, images, for_sale_since and created_at.
| Field | Type | Create | Description |
|---|---|---|---|
external_id | string ≤120 | recommended | Your own stable id for the vehicle (stock number, DMS id). Lets you address the car by it and enables upsert and inventory sync. |
external_updated_at | datetime | no | When your system last changed the vehicle. An older timestamp than the one stored is rejected with 409 stale_update. |
make / make_id | string / integer | yes | Brand, by name (case-insensitive) or id. GET /v1/makes. |
model / model_id | string / integer | yes | Model within the make. Unknown names are rejected unless create_missing_model is true. |
create_missing_model | boolean | no | Add an unknown model name under the make instead of failing. Default false. |
vehicle_type / vehicle_type_id | string / integer | no | Car, Motorcycle, SUV, Truck, Van. Default Car. |
year | integer | yes | Model year, 1990–2027. |
license_plate | string ≤20 | no | Must be unique across the platform. |
vin | string (17) | no | Chassis number, unique across the platform. Stored upper-case. |
color | string ≤50 | no | Use a value from the color enumeration to get it translated; other words are shown as sent. |
mileage | integer km | no | Odometer. |
first_registration | date | no | YYYY-MM-DD. |
motor_type | string ≤100 | no | Engine designation, e.g. "2.0 TDI 150hp". |
fuel_type | enum | no | See enumerations. |
emission_standard | enum | no | Euro class. |
co2_gkm | integer | no | CO₂ in g/km. |
power_hp / power_kw | integer | no | Send either; the other is derived. |
gearbox_type | enum | no | |
body_type | enum | no | |
num_doors / num_seats | integer | no | |
drivetrain | enum | no | front, rear or 4wd. |
steering_position | enum | no | lhd or rhd. |
cylinder_capacity | integer cc | no | |
tyre_size / bolt_pattern / et_offset | string | no | e.g. "225/45R17", "5x112", "+35". |
weight | integer kg | no | |
airbags | enum | no | |
air_conditioning | enum | no | |
interior_color | string ≤50 | no | |
interior_material | enum | no | |
features | array[string] | no | Equipment slugs from the catalogue below. Unknown slugs are rejected. Sending [] clears the list. |
parking_assist | array[string] | no | Parking aid slugs from the catalogue below. |
is_for_sale | boolean | no | Default true on create. false takes the car off the marketplace but keeps it. |
asking_price | decimal | no | In the workshop's currency (returned as `currency`). Plain number, not cents. |
is_margin_vehicle / is_tax_vehicle | boolean | no | VAT treatment: margin scheme or VAT-deductible. |
vehicle_condition | enum | no | |
short_description | string ≤120 | no | One-liner on listing cards. |
seller_description | text | no | Full description. |
num_owners | integer | no | |
maintenance_history | enum | no | |
carpass_url | url | no | Car-Pass document link (Belgium). |
image_urls | array[url] ≤30 | no | Photos to download from your servers, in display order. When present it replaces the whole photo set; omit to leave photos alone. |
external_id — never a stock position or display order. It is what makes upsert, GET /v1/vehicles/{ref} by your id, and inventory sync work.Endpoints
Account
Reference (no key needed)
Vehicles
Photos
Full request and response schemas, with every enumeration inlined: Swagger UI · ReDoc · openapi.json.
List response
{
"count": 37, "limit": 50, "offset": 0, "next_offset": null,
"results": [ { "uuid": "…", "external_id": "STOCK-4821", "status": "published", … } ]
}
Upsert & stale updates
POST /v1/vehicles with an external_id that your workshop already uses updates that vehicle (HTTP 200) instead of creating a second one (HTTP 201). This makes a naive "push everything every night" integration idempotent.
When you also send external_updated_at, an update whose timestamp is older than the one stored is refused with 409 stale_update, so a delayed event can never overwrite a newer one. Inside a sync such a vehicle is reported as skipped_stale and the rest proceeds.
PATCH changes only the fields you send. PUT is a full replacement: optional fields you leave out are cleared. Both accept the same body as create.
Inventory sync
POST /v1/vehicles/sync upserts a whole inventory in one call (≤200 vehicles; call it repeatedly for more). Every vehicle needs an external_id.
{
"complete": true,
"vehicles": [
{
"external_id": "STOCK-4821",
"make": "Volkswagen",
"model": "Golf",
"year": 2022,
"asking_price": 24950,
"fuel_type": "petrol",
"mileage": 41200
},
{
"external_id": "STOCK-4835",
"make": "Audi",
"model": "A4",
"year": 2021,
"asking_price": 28900,
"fuel_type": "diesel",
"body_type": "estate"
}
]
}
- All-or-nothing validation. The whole payload is validated first. One invalid vehicle rejects the call with
422and a per-vehiclevehiclesmap; nothing is written. complete: truedeclares the payload to be your entire stock. API-managed vehicles (those carrying one of your external ids) that are missing from it are unlisted, never deleted — they stay in your dashboard and come back if you send them again.- Empty protection. A complete sync with zero vehicles needs
allow_empty: true, otherwise409 empty_snapshot. dry_run: truereturnswould_create,would_update,would_unlistand writes nothing.- Slots. Vehicles beyond your sale-slot allowance are saved unlisted with a
sale_limit_reachedwarning instead of failing the sync.
Vehicles added by hand on the website are never touched by a sync.
Sale slots
Each workshop account has an allowance of vehicles that may be for sale at the same time (free tier, subscription tiers, or an amount assigned by Mi Taller). GET /v1/me returns slots.used, slots.limit and slots.available.
A single create or update that would exceed the allowance fails with 403 sale_limit_reached. To free a slot, PATCH {"is_for_sale": false} a vehicle — it stays stored and can be re-listed later. More slots can be bought on mitaller.co.
Photos
Photos are ordered; order 0 is the main photo shown on listing cards. Large images are resized to a 2560 px long edge and re-encoded; anything above 15 MB after that is refused.
With the vehicle
Send image_urls in create/update. The photos are downloaded from your servers in the order given and replace the whole photo set. Omit the field to leave photos untouched. If none of the URLs can be fetched the existing photos are kept and a warning is returned.
One at a time
curl -X POST https://api.mitaller.co/v1/vehicles/STOCK-4821/images \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://cdn.example-dealer.be/stock/4821/rear.jpg", "order": 1}'
curl -X POST https://api.mitaller.co/v1/vehicles/STOCK-4821/images \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "image=@rear.jpg" -F "order=1"
Reorder
curl -X PUT https://api.mitaller.co/v1/vehicles/STOCK-4821/images/order \
-H "Authorization: Bearer YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"ids": [912, 910, 911]}'
http(s) addresses. Local, private and reserved network destinations are refused at every redirect hop. Content is checked to be a real image regardless of the Content-Type the host claims.Equipment (106 values)
Send equipment as slugs in features, and parking aids in parking_assist. Only the values below are accepted; unknown slugs return 422 naming them. Omitting the field on PATCH keeps the equipment; sending [] clears it. Labels in every language: GET /v1/options.
Parking assist
Electronics · exterior · 10
Lighting · exterior · 6
Headlights · exterior · 7
Driving Assistants · exterior · 12
Comfort & Other · exterior · 43
Infotainment · interior · 17
Seats · interior · 11
Enumerations
English and Dutch labels shown; French, Spanish and Portuguese are in GET /v1/options. Unknown values are rejected rather than guessed.
vehicle_type
year
1990 – 2027
fuel_type
| Value | English | Nederlands |
|---|---|---|
petrol | Petrol / Gasoline | Benzine |
diesel | Diesel | Diesel |
electric | Electric | Elektrisch |
hybrid_petrol | Hybrid (Petrol) | Hybride (Benzine) |
hybrid_diesel | Hybrid (Diesel) | Hybride (Diesel) |
phev_petrol | PHEV (Petrol) | PHEV (Benzine) |
phev_diesel | PHEV (Diesel) | PHEV (Diesel) |
lpg | LPG | LPG |
cng | CNG | CNG |
hydrogen | Hydrogen | Waterstof |
mild_hybrid | Mild Hybrid | Mild Hybride |
other | Other | Overig |
gearbox_type
| Value | English | Nederlands |
|---|---|---|
manual | Manual | Handgeschakeld |
automatic | Automatic | Automatisch |
cvt | CVT | CVT |
dct | Dual-Clutch (DCT) | Dubbelkoppeling (DCT) |
semi_auto | Semi-automatic | Semi-automatisch |
body_type
| Value | English | Nederlands |
|---|---|---|
sedan | Sedan | Sedan |
hatchback | Hatchback | Hatchback |
estate | Estate / Break | Stationwagen |
coupe | Coupé | Coupé |
convertible | Cabriolet / Convertible | Cabriolet |
suv | SUV | SUV |
crossover | Crossover | Crossover |
mpv | MPV / Minivan | MPV / Minivan |
pickup | Pickup | Pickup |
van | Van | Bestelwagen |
other | Other | Overig |
drivetrain
| Value | English | Nederlands |
|---|---|---|
4wd | 4 wheel drive | 4-wielaandrijving |
front | Front drive | Voorwielaandrijving |
rear | Rear drive | Achterwielaandrijving |
steering_position
| Value | English | Nederlands |
|---|---|---|
lhd | Left-hand drive | Linksgestuurd |
rhd | Right-hand drive | Rechtsgestuurd |
emission_standard
| Value | English | Nederlands |
|---|---|---|
euro0 | Euro 0 | Euro 0 |
euro1 | Euro 1 | Euro 1 |
euro2 | Euro 2 | Euro 2 |
euro3 | Euro 3 | Euro 3 |
euro4 | Euro 4 | Euro 4 |
euro5 | Euro 5 | Euro 5 |
euro6 | Euro 6 | Euro 6 |
euro6b | Euro 6b | Euro 6b |
euro6c | Euro 6c | Euro 6c |
euro6d | Euro 6d | Euro 6d |
euro6d_temp | Euro 6d-temp | Euro 6d-temp |
other | Other | Overig |
color
| Value | English | Nederlands |
|---|---|---|
white | White | Wit |
black | Black | Zwart |
silver | Silver | Zilver |
gray | Gray | Grijs |
blue | Blue | Blauw |
red | Red | Rood |
green | Green | Groen |
yellow | Yellow | Geel |
orange | Orange | Oranje |
brown | Brown | Bruin |
beige | Beige | Beige |
gold | Gold | Goud |
purple | Purple | Paars |
pink | Pink | Roze |
other | Other | Ander |
interior_material
| Value | English | Nederlands |
|---|---|---|
alcantara | Alcantara | Alcantara |
fabric | Fabric | Stof |
artificial_leather | Artificial leather | Kunstleer |
partial_leather | Partial leather | Deelleder |
full_leather | Full leather | Volleder |
velour | Velour | Velours |
vehicle_condition
| Value | English | Nederlands |
|---|---|---|
factory_new | Factory New | Fabriek nieuw |
new_condition | New Condition | Nieuwe staat |
new_with_damage | New Condition with Damage | Nieuwe staat met schade |
used | Used | Gebruikt |
used_with_damage | Used with Damage | Gebruikt met schade |
parts | Used for Parts | Gebruikt voor onderdelen |
maintenance_history
| Value | English | Nederlands |
|---|---|---|
none | No | Nee |
dealership | Yes, with dealership | Ja, bij dealer |
platform | Yes, with the platform | Ja, via the platform |
both | Yes, with dealership and the platform | Ja, bij dealer en via the platform |
airbags
| Value | English | Nederlands |
|---|---|---|
airbagDriver | Driver airbag | Bestuurdersairbag |
airbagFront | Front airbags | Front airbags |
airbagFrontSide | Front & side airbags | Front & zijairbags |
airbagFull | Front, side & rear airbags | Front, zij en achter airbags |
air_conditioning
| Value | English | Nederlands |
|---|---|---|
acNone | None | Geen |
acManual | Manual | Handmatig |
ac2Zone | 2-zone automatic | Automatisch 2-zones |
ac3Zone | 3-zone automatic | Automatisch 3-zones |
ac4Zone | 4-zone automatic | Automatisch 4-zones |
Errors
Errors are JSON with a stable error code and a human message; some carry extra keys.
{
"error": "validation_error",
"message": "One or more fields are invalid.",
"fields": {
"fuel_type": [
"\"gasoline\" is not a valid choice."
],
"features": [
"Unknown features: sunroof. GET /v1/options lists the accepted values."
]
}
}
| HTTP | error | Meaning |
|---|---|---|
| 401 | invalid_api_key | Missing, unknown or revoked key. |
| 403 | read_only_key | The key may only read. |
| 403 | workshop_blocked / workshop_inactive | The workshop may not publish at the moment. |
| 403 | sale_limit_reached | All for-sale slots are in use. Body carries used, limit, available. |
| 404 | vehicle_not_found / image_not_found / make_not_found | Not found within this workshop. |
| 409 | duplicate_vin / duplicate_license_plate | Another vehicle already has that identifier. |
| 409 | stale_update | The stored external_updated_at is newer than the one sent. |
| 409 | empty_snapshot | A complete sync with no vehicles needs allow_empty: true. |
| 422 | validation_error | Field errors under `fields` (or `vehicles` for a sync). Nothing was written. |
| 422 | image_url_* / image_too_large / image_unreadable | A photo could not be fetched. On create/update these are warnings, not failures. |
| 429 | — | Rate limit. Retry after the `Retry-After` header. |
Successful responses: 200 read or update, 201 created, 204 deleted.
Rate limits
Each key may make 3 000 requests per hour. Above that the API answers 429 with a Retry-After header. A full nightly sync of a few hundred cars uses a handful of calls; if you need more, contact Mi Taller.
Photo downloads count against the same budget through the calls that trigger them, not per image.