AutoUncle Data Services is in beta. The API, MCP tools and agent skill may change often, including breaking changes, until version 1.0. Read the changelog before you upgrade an integration.
Build a catalogue picker
Let users pin the exact vehicle by choosing brand, model, generation and drivetrain from the AutoUncle catalogue. A pinned vehicle removes identification guesswork, which is the single biggest lever on precision.
The console playground's Form tab contains a working version of this picker. Use it to explore the data before you build your own.
How the cascade works
brand ──► model ──► generation ──► drivetrain
│
└──► equipment variant (trim line, optional)
| Step | Endpoint | Returns |
|---|---|---|
| 1. Brand | GET /v1/catalogue/brands |
Every brand, for example VW, BMW, Skoda |
| 2. Model | GET /v1/catalogue/brands/{brand_id}/models |
Models of that brand |
| 3. Generation | GET /v1/catalogue/models/{model_id}/generations?year=2021 |
Generations, optionally only those produced in that year |
| 4. Drivetrain | GET /v1/catalogue/generations/{generation_id}/drivetrains |
Engine and gearbox variants, optionally filtered |
| Optional | GET /v1/catalogue/brands/{brand_id}/equipment-variants |
Trim or equipment lines of the brand |
All catalogue calls are free, need the catalogue:read scope and share a rate limit of 120 requests per minute per workspace by default. The catalogue reference lists every field.
1. Load brands
curl https://data-services.autouncle.com/v1/catalogue/brands \
-H "Authorization: Bearer $AUTOUNCLE_API_KEY"
require "json"
require "net/http"
uri = URI("https://data-services.autouncle.com/v1/catalogue/brands")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("AUTOUNCLE_API_KEY")}"
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
puts JSON.pretty_generate(JSON.parse(response.body))
import json
import os
import requests
response = requests.get(
"https://data-services.autouncle.com/v1/catalogue/brands",
headers={
"Authorization": f"Bearer {os.environ['AUTOUNCLE_API_KEY']}",
},
timeout=30,
)
print(json.dumps(response.json(), indent=2))
const response = await fetch("https://data-services.autouncle.com/v1/catalogue/brands", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.AUTOUNCLE_API_KEY}`,
},
});
console.log(JSON.stringify(await response.json(), null, 2));
{
"object": "list",
"version": "5.6.7:NEW",
"data": [
{ "id": "b-3008744b5957dcdba36baeec", "name": "AC", "object": "brand" },
{ "id": "b-0957579dc5bca6de229d2f32", "name": "VW", "object": "brand" }
],
"notes": [],
"attributions": [{ "source_id": "autouncle_vehicle_catalogue", "role": "vehicle_catalogue", "pointers": ["/data"] }]
}
Lists are sorted by name and are not paginated. The brand list is long (over 300 entries), so make the first field a type-ahead rather than a plain dropdown.
Note: Brand names are the catalogue's own spelling. Volkswagen is
VW. Match your users' input against the list, or use search (below), rather than hard-coding names.
2. Load models for the brand
curl https://data-services.autouncle.com/v1/catalogue/brands/b-0957579dc5bca6de229d2f32/models \
-H "Authorization: Bearer $AUTOUNCLE_API_KEY"
require "json"
require "net/http"
uri = URI("https://data-services.autouncle.com/v1/catalogue/brands/b-0957579dc5bca6de229d2f32/models")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("AUTOUNCLE_API_KEY")}"
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
puts JSON.pretty_generate(JSON.parse(response.body))
import json
import os
import requests
response = requests.get(
"https://data-services.autouncle.com/v1/catalogue/brands/b-0957579dc5bca6de229d2f32/models",
headers={
"Authorization": f"Bearer {os.environ['AUTOUNCLE_API_KEY']}",
},
timeout=30,
)
print(json.dumps(response.json(), indent=2))
const response = await fetch("https://data-services.autouncle.com/v1/catalogue/brands/b-0957579dc5bca6de229d2f32/models", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.AUTOUNCLE_API_KEY}`,
},
});
console.log(JSON.stringify(await response.json(), null, 2));
Each model has an id, a name and its brand_id.
3. Load generations, narrowed by year
Ask for the registration year first and pass it as year. Only generations whose production years include it come back, which usually leaves a short list:
curl 'https://data-services.autouncle.com/v1/catalogue/models/cm-6bb52590daf6d1b19108ec93/generations?year=2021' \
-H "Authorization: Bearer $AUTOUNCLE_API_KEY"
require "json"
require "net/http"
uri = URI("https://data-services.autouncle.com/v1/catalogue/models/cm-6bb52590daf6d1b19108ec93/generations?year=2021")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("AUTOUNCLE_API_KEY")}"
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
puts JSON.pretty_generate(JSON.parse(response.body))
import json
import os
import requests
response = requests.get(
"https://data-services.autouncle.com/v1/catalogue/models/cm-6bb52590daf6d1b19108ec93/generations?year=2021",
headers={
"Authorization": f"Bearer {os.environ['AUTOUNCLE_API_KEY']}",
},
timeout=30,
)
print(json.dumps(response.json(), indent=2))
const response = await fetch("https://data-services.autouncle.com/v1/catalogue/models/cm-6bb52590daf6d1b19108ec93/generations?year=2021", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.AUTOUNCLE_API_KEY}`,
},
});
console.log(JSON.stringify(await response.json(), null, 2));
{
"object": "list",
"data": [
{ "id": "mg-40c5d8de40bb48ae60fc0a6c", "name": "Golf VIII", "object": "generation", "model_id": "cm-6bb52590daf6d1b19108ec93", "from_year": 2020, "to_year": 2024 },
{ "id": "mg-0b2cb501f1fb299cd93ea4f6", "name": "Golf VIII Variant", "object": "generation", "model_id": "cm-6bb52590daf6d1b19108ec93", "from_year": 2020, "to_year": 2024 }
]
}
Generation names include body variants (Variant is VW's estate), so this step also settles the body style. year must be an integer from 1900 to next year; anything else returns 400 invalid_request.
4. Load drivetrains
curl 'https://data-services.autouncle.com/v1/catalogue/generations/mg-40c5d8de40bb48ae60fc0a6c/drivetrains?fuel=petrol&transmission=manual' \
-H "Authorization: Bearer $AUTOUNCLE_API_KEY"
require "json"
require "net/http"
uri = URI("https://data-services.autouncle.com/v1/catalogue/generations/mg-40c5d8de40bb48ae60fc0a6c/drivetrains?fuel=petrol&transmission=manual")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("AUTOUNCLE_API_KEY")}"
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
puts JSON.pretty_generate(JSON.parse(response.body))
import json
import os
import requests
response = requests.get(
"https://data-services.autouncle.com/v1/catalogue/generations/mg-40c5d8de40bb48ae60fc0a6c/drivetrains?fuel=petrol&transmission=manual",
headers={
"Authorization": f"Bearer {os.environ['AUTOUNCLE_API_KEY']}",
},
timeout=30,
)
print(json.dumps(response.json(), indent=2))
const response = await fetch("https://data-services.autouncle.com/v1/catalogue/generations/mg-40c5d8de40bb48ae60fc0a6c/drivetrains?fuel=petrol&transmission=manual", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.AUTOUNCLE_API_KEY}`,
},
});
console.log(JSON.stringify(await response.json(), null, 2));
Each drivetrain describes one engine and gearbox combination:
{ "id": "dv-947a3199394a5a4b41cb2869", "name": "1.5 TSI (130 Hp)", "object": "drivetrain",
"generation_id": "mg-40c5d8de40bb48ae60fc0a6c", "fuel": "petrol", "power_hp": 130,
"transmission": "manual", "engine_size_l": 1.5, "drive": "2WD" }
Use the optional filters to shorten the list when you already know some attributes: fuel (for example petrol), transmission (manual or automatic) and power_hp (exact match). Show name as the label: it is what users recognise from their registration documents.
Two details to handle:
driveis the catalogue's traction label, such as2WDorAWD. It is not the same vocabulary as the request fieldvehicle.drive(fwd,rwd,awd,4wd). You do not need to send it when you send the drivetrain ID.fuelis usually one of the public values from /v1/catalogue/enums, but a few catalogue values pass through unchanged (for exampleCNG_Hybrid). Display them; do not reject them.
5. Value the pinned vehicle
Send the lowest level the user chose. Higher levels are derived from it:
curl https://data-services.autouncle.com/v1/valuations \
-H "Authorization: Bearer $AUTOUNCLE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: valuation-catalogue-001' \
-d @- <<'JSON'
{
"country": "DK",
"vehicle": {
"catalogue": {
"drivetrain_id": "dv-947a3199394a5a4b41cb2869"
},
"model_year": 2021,
"mileage": {
"value": 45000,
"unit": "km"
}
},
"pricing": {
"price_basis": "dealer_retail"
}
}
JSON
require "json"
require "net/http"
uri = URI("https://data-services.autouncle.com/v1/valuations")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("AUTOUNCLE_API_KEY")}"
request["Content-Type"] = "application/json"
request["Idempotency-Key"] = "valuation-catalogue-001"
request.body = JSON.generate({
"country" => "DK",
"vehicle" => {
"catalogue" => {
"drivetrain_id" => "dv-947a3199394a5a4b41cb2869"
},
"model_year" => 2021,
"mileage" => {
"value" => 45000,
"unit" => "km"
}
},
"pricing" => {
"price_basis" => "dealer_retail"
}
})
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
puts JSON.pretty_generate(JSON.parse(response.body))
import json
import os
import requests
response = requests.post(
"https://data-services.autouncle.com/v1/valuations",
headers={
"Authorization": f"Bearer {os.environ['AUTOUNCLE_API_KEY']}",
"Idempotency-Key": "valuation-catalogue-001",
},
json={
"country": "DK",
"vehicle": {
"catalogue": {
"drivetrain_id": "dv-947a3199394a5a4b41cb2869"
},
"model_year": 2021,
"mileage": {
"value": 45000,
"unit": "km"
}
},
"pricing": {
"price_basis": "dealer_retail"
}
},
timeout=30,
)
print(json.dumps(response.json(), indent=2))
const response = await fetch("https://data-services.autouncle.com/v1/valuations", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AUTOUNCLE_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "valuation-catalogue-001",
},
body: JSON.stringify({
country: "DK",
vehicle: {
catalogue: {
drivetrain_id: "dv-947a3199394a5a4b41cb2869"
},
model_year: 2021,
mileage: {
value: 45000,
unit: "km"
}
},
pricing: {
price_basis: "dealer_retail"
}
}),
});
console.log(JSON.stringify(await response.json(), null, 2));
The returned vehicle then shows every level, and its specs are the drivetrain's. In identification.fields, the level you sent has source: "catalogue_id" and the levels above it source: "catalogue_derived":
{
"vehicle": {
"avs": { "version": "0.3", "kind": "vehicle" },
"identity": {
"brand": { "name": "VW", "ids": [{ "scheme": "autouncle", "value": "b-0957579dc5bca6de229d2f32" }] },
"model": { "name": "Golf", "ids": [{ "scheme": "autouncle", "value": "cm-6bb52590daf6d1b19108ec93" }] },
"body": { "name": "Golf VIII", "ids": [{ "scheme": "autouncle", "value": "mg-40c5d8de40bb48ae60fc0a6c" }], "fromYear": 2019 },
"drivetrain": { "name": "1.5 TSI (130 Hp)", "ids": [{ "scheme": "autouncle", "value": "dv-947a3199394a5a4b41cb2869" }] }
},
"specification": { "powertrain": { "energy": ["petrol"], "powerKw": 96, "powerHp": 130, "transmission": { "type": "manual" } } },
"vehicle": { "registration": { "country": "DK", "modelYear": 2021 }, "odometer": { "km": 45000 } }
},
"identification": {
"status": "identified",
"level": "drivetrain",
"fields": {
"/identity/brand": { "basis": "catalogue", "source": "catalogue_derived", "status": "derived" },
"/identity/drivetrain": { "basis": "observed", "source": "catalogue_id", "status": "supplied" }
}
}
}
The example is shortened. In an AVS document, the same ID goes in identity.drivetrain.ids with scheme autouncle.
If the user stops at generation, send generation_id and let the service resolve the drivetrain from other attributes you have (fuel, gearbox, power). You will get an improve_precision hint for the drivetrain.
If you send several levels, they must describe one consistent path. A drivetrain from a different generation returns 422 catalogue_id_conflict. An ID that does not exist returns 404 unknown_catalogue_id.
Start from text with search
For a single search box, use GET /v1/catalogue/search:
curl 'https://data-services.autouncle.com/v1/catalogue/search?q=golf' \
-H "Authorization: Bearer $AUTOUNCLE_API_KEY"
require "json"
require "net/http"
uri = URI("https://data-services.autouncle.com/v1/catalogue/search?q=golf")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("AUTOUNCLE_API_KEY")}"
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
puts JSON.pretty_generate(JSON.parse(response.body))
import json
import os
import requests
response = requests.get(
"https://data-services.autouncle.com/v1/catalogue/search?q=golf",
headers={
"Authorization": f"Bearer {os.environ['AUTOUNCLE_API_KEY']}",
},
timeout=30,
)
print(json.dumps(response.json(), indent=2))
const response = await fetch("https://data-services.autouncle.com/v1/catalogue/search?q=golf", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.AUTOUNCLE_API_KEY}`,
},
});
console.log(JSON.stringify(await response.json(), null, 2));
Search is a case-insensitive substring match on brand names and on "brand model" as the catalogue spells them. It returns up to 20 brands and 80 models. It does not know synonyms: golf finds VW's Golf models, but Volkswagen Golf finds nothing because the brand is VW. Search for the model name, or for the catalogue brand name, and let the user pick.
A search result is a brand or a model. Continue the cascade from there.
Model aliases
Some catalogue models are aliases that stand for generations of another model. For example, a valuation of a 2020 "VW Golf" can resolve to a model named Golf VII with its own ID. When you list generations for an alias, you get the generations it stands for. To list drivetrains for an alias model's generation, pass the alias as model_id:
GET /v1/catalogue/generations/{generation_id}/drivetrains?model_id={alias_model_id}
Always use the IDs the catalogue returns, in the order it returns them. Do not construct, parse or reuse IDs across levels.
Cache the catalogue
The catalogue changes rarely. Cache lists in your application and refresh them when GET /v1/catalogue/version returns a different value. Every list response also carries version. If you keep IDs in your own database, an unknown_catalogue_id refusal is the signal to look the vehicle up again. See vehicle catalogue.
Pitfalls
- Skipping the year. Without
year, a popular model can have a dozen generations. Ask for the year first. - Sending names and IDs that disagree. If you send
drivetrain_id, do not also send amakeormodeltext for a different vehicle. - Asking users for what the catalogue knows. Once a drivetrain is pinned, fuel, power and engine size come from the catalogue. Do not ask for them again.