# 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

```text
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](/docs/reference/catalogue) lists every field.

## 1. Load brands

**curl**

```bash
curl https://data-services.autouncle.com/v1/catalogue/brands \
  -H "Authorization: Bearer $AUTOUNCLE_API_KEY"
```

**Ruby**

```ruby
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))
```

**Python**

```python
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))
```

**JavaScript**

```javascript
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));
```

```json
{
  "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**

```bash
curl https://data-services.autouncle.com/v1/catalogue/brands/b-0957579dc5bca6de229d2f32/models \
  -H "Authorization: Bearer $AUTOUNCLE_API_KEY"
```

**Ruby**

```ruby
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))
```

**Python**

```python
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))
```

**JavaScript**

```javascript
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**

```bash
curl 'https://data-services.autouncle.com/v1/catalogue/models/cm-6bb52590daf6d1b19108ec93/generations?year=2021' \
  -H "Authorization: Bearer $AUTOUNCLE_API_KEY"
```

**Ruby**

```ruby
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))
```

**Python**

```python
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))
```

**JavaScript**

```javascript
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));
```

```json
{
  "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**

```bash
curl 'https://data-services.autouncle.com/v1/catalogue/generations/mg-40c5d8de40bb48ae60fc0a6c/drivetrains?fuel=petrol&transmission=manual' \
  -H "Authorization: Bearer $AUTOUNCLE_API_KEY"
```

**Ruby**

```ruby
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))
```

**Python**

```python
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))
```

**JavaScript**

```javascript
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:

```json
{ "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:

- `drive` is the catalogue's traction label, such as `2WD` or `AWD`. It is not the same vocabulary as the request field `vehicle.drive` (`fwd`, `rwd`, `awd`, `4wd`). You do not need to send it when you send the drivetrain ID.
- `fuel` is usually one of the public values from [/v1/catalogue/enums](/docs/reference/catalogue#get-v1-catalogue-enums), but a few catalogue values pass through unchanged (for example `CNG_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**

```bash
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
```

**Ruby**

```ruby
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))
```

**Python**

```python
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))
```

**JavaScript**

```javascript
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));
```

[Open this request in the console playground](https://data-services.autouncle.com/console/playground?example=valuation_catalogue)

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"`:

```json
{
  "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`](/docs/errors#catalogue_id_conflict). An ID that does not exist returns `404` [`unknown_catalogue_id`](/docs/errors#unknown_catalogue_id).

## Start from text with search

For a single search box, use `GET /v1/catalogue/search`:

**curl**

```bash
curl 'https://data-services.autouncle.com/v1/catalogue/search?q=golf' \
  -H "Authorization: Bearer $AUTOUNCLE_API_KEY"
```

**Ruby**

```ruby
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))
```

**Python**

```python
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))
```

**JavaScript**

```javascript
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`:

```text
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](/docs/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 a `make` or `model` text 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.

## Next steps

- [Improve precision with hints](/docs/guides/improve-precision).
- [Vehicle catalogue concepts](/docs/catalogue).
- [Catalogue reference](/docs/reference/catalogue).
