Skip to content

Car Hierarchy — Frontend Developer Guide

Full 4-level car reference data is now imported into the dev database and available through the API. This guide covers the data model, endpoints, response shapes, enum values, and implementation patterns for building the YMMM (Year/Make/Model/Modification) cascade filter and related UI.

Data Summary

TableRecordsDescription
car_makes124Brands: BMW, Toyota, Audi, etc.
car_models4,795Models grouped per make: 3 Series, Camry, A4
car_generations7,207Year-based generations: "2018-Present", "2007-2014"
car_modifications30,066Engine/transmission variants with full specs

Entity Relationship

car_makes (124)
  └── car_models (4,795)        — FK: make_id
        └── car_generations (7,207)   — FK: model_id
              └── car_modifications (30,066) — FK: generation_id

Each level depends on its parent. The frontend loads them in cascade: user picks a make, then models load, then generations, then modifications.


Public API Endpoints (no auth required)

Base: /api/store/cars/...

All responses are cached server-side for 1 hour.

1. List Makes

GET /api/store/cars/makes
GET /api/store/cars/makes?popular=true

Response:

json
{
  "data": [
    {
      "id": 12,
      "name": "BMW",
      "slug": "bmw",
      "logo_url": null,
      "is_popular": false
    },
    {
      "id": 100,
      "name": "Toyota",
      "slug": "toyota",
      "logo_url": null,
      "is_popular": false
    }
  ]
}
FieldTypeNotes
idnumberSERIAL PK, use for next cascade call
namestringDisplay name, title-cased
slugstringURL-friendly, unique
logo_urlstring | nullNot populated yet
is_popularbooleanUse ?popular=true to filter

2. List Models by Make

GET /api/store/cars/makes/{makeId}/models

Example: GET /api/store/cars/makes/12/models (BMW)

Response:

json
{
  "data": [
    {
      "id": 418,
      "make_id": 12,
      "name": "1 Series",
      "slug": "1-series"
    },
    {
      "id": 420,
      "make_id": 12,
      "name": "3 Series",
      "slug": "3-series"
    }
  ]
}
FieldTypeNotes
idnumberUse for next cascade call
make_idnumberParent make FK
namestringDisplay name
slugstringURL-friendly

3. List Generations by Model

GET /api/store/cars/models/{modelId}/generations

Example: GET /api/store/cars/models/420/generations (BMW 3 Series)

Response:

json
{
  "data": [
    {
      "id": 663,
      "model_id": 420,
      "name": "2019-Present",
      "code": null,
      "year_from": 2019,
      "year_to": null,
      "steering": null
    },
    {
      "id": 664,
      "model_id": 420,
      "name": "2012-2018",
      "code": null,
      "year_from": 2012,
      "year_to": 2018,
      "steering": null
    }
  ]
}
FieldTypeNotes
idnumberUse for next cascade call
model_idnumberParent model FK
namestringTypically year range: "2019-Present", "2012-2018"
codestring | nullGeneration code (E46, F30, etc.) — currently null for imported data
year_fromnumber | nullStart year
year_tonumber | nullEnd year; null = still in production
steeringstring | null"left", "right", "both", "universal", or null

4. List Modifications by Generation

GET /api/store/cars/generations/{generationId}/modifications

Example: GET /api/store/cars/generations/663/modifications

Response:

json
{
  "data": [
    {
      "id": 27111,
      "generation_id": 663,
      "name": "1.8L 5MT FWD (90 HP)",
      "engine_volume": "1.8",
      "fuel_type": "petrol",
      "power": 90,
      "transmission": "mt",
      "drivetrain": "fwd"
    },
    {
      "id": 27112,
      "generation_id": 663,
      "name": "2.0L 5MT FWD (107 HP)",
      "engine_volume": "2.0",
      "fuel_type": "petrol",
      "power": 107,
      "transmission": "mt",
      "drivetrain": "fwd"
    },
    {
      "id": 27113,
      "generation_id": 663,
      "name": "1.8L TD 5MT FWD (73 HP)",
      "engine_volume": "1.8",
      "fuel_type": "diesel",
      "power": 73,
      "transmission": "mt",
      "drivetrain": "fwd"
    }
  ]
}
FieldTypeNotes
idnumberModification PK
generation_idnumberParent generation FK
namestringHuman-readable label (max 150 chars), e.g. "2.0L 4cyl 6AT (150 HP)"
engine_volumestring | nullLiters as decimal string: "1.6", "2.0", "3.5"
fuel_typestring | nullEnum — see below
powernumber | nullHorsepower (HP)
transmissionstring | nullEnum — see below
drivetrainstring | nullEnum — see below

Enum Values

fuel_type

ValueLabel (RU)Count
petrolБензин20,324
dieselДизель8,454
hybridГибрид664
electricЭлектро445
gasГаз149

transmission

ValueLabel (RU)Count
mtМеханика16,260
atАвтомат11,237
robotРобот (DSG/S-Tronic)995
cvtВариатор779
amtAMT1

drivetrain

ValueLabel (RU)Count
fwdПередний16,637
rwdЗадний7,079
awdПолный6,184
4wd4WD0 (exists in enum, not in current dataset)

steering (on generations)

ValueLabel (RU)
leftЛевый
rightПравый
bothОба
universalУниверсальный

Zod Schemas

ts
import { z } from 'zod'

// Enums
export const FuelType = z.enum(['petrol', 'diesel', 'hybrid', 'electric', 'gas'])
export const Transmission = z.enum(['at', 'mt', 'cvt', 'amt', 'robot'])
export const Drivetrain = z.enum(['fwd', 'rwd', 'awd', '4wd'])
export const Steering = z.enum(['left', 'right', 'both', 'universal'])

// Entities
export const CarMakeSchema = z.object({
  id: z.number(),
  name: z.string(),
  slug: z.string(),
  logo_url: z.string().nullable(),
  is_popular: z.boolean(),
})

export const CarModelSchema = z.object({
  id: z.number(),
  make_id: z.number(),
  name: z.string(),
  slug: z.string(),
})

export const CarGenerationSchema = z.object({
  id: z.number(),
  model_id: z.number(),
  name: z.string(),
  code: z.string().nullable(),
  year_from: z.number().nullable(),
  year_to: z.number().nullable(),
  steering: Steering.nullable(),
})

export const CarModificationSchema = z.object({
  id: z.number(),
  generation_id: z.number(),
  name: z.string(),
  engine_volume: z.string().nullable(),
  fuel_type: FuelType.nullable(),
  power: z.number().nullable(),
  transmission: Transmission.nullable(),
  drivetrain: Drivetrain.nullable(),
})

// Type exports
export type CarMake = z.infer<typeof CarMakeSchema>
export type CarModel = z.infer<typeof CarModelSchema>
export type CarGeneration = z.infer<typeof CarGenerationSchema>
export type CarModification = z.infer<typeof CarModificationSchema>

API Functions (FSD: entities/car/api/)

ts
// entities/car/api/index.ts
import type { CarMake, CarModel, CarGeneration, CarModification } from '../model'

export async function fetchCarMakes(popular?: boolean): Promise<CarMake[]> {
  const params = popular ? '?popular=true' : ''
  const { data } = await $fetch<{ data: CarMake[] }>(`/api/store/cars/makes${params}`)
  return data
}

export async function fetchCarModels(makeId: number): Promise<CarModel[]> {
  const { data } = await $fetch<{ data: CarModel[] }>(
    `/api/store/cars/makes/${makeId}/models`,
  )
  return data
}

export async function fetchCarGenerations(modelId: number): Promise<CarGeneration[]> {
  const { data } = await $fetch<{ data: CarGeneration[] }>(
    `/api/store/cars/models/${modelId}/generations`,
  )
  return data
}

export async function fetchCarModifications(generationId: number): Promise<CarModification[]> {
  const { data } = await $fetch<{ data: CarModification[] }>(
    `/api/store/cars/generations/${generationId}/modifications`,
  )
  return data
}

Cascade Filter Composable

ts
// widgets/ymm-filter/model/useCarCascade.ts
export function useCarCascade() {
  const selectedMakeId = ref<number | null>(null)
  const selectedModelId = ref<number | null>(null)
  const selectedGenerationId = ref<number | null>(null)
  const selectedModificationId = ref<number | null>(null)

  const makes = ref<CarMake[]>([])
  const models = ref<CarModel[]>([])
  const generations = ref<CarGeneration[]>([])
  const modifications = ref<CarModification[]>([])

  // Load makes on mount
  onMounted(async () => {
    makes.value = await fetchCarMakes()
  })

  // When make changes → load models, reset downstream
  watch(selectedMakeId, async (makeId) => {
    models.value = []
    generations.value = []
    modifications.value = []
    selectedModelId.value = null
    selectedGenerationId.value = null
    selectedModificationId.value = null

    if (makeId) {
      models.value = await fetchCarModels(makeId)
    }
  })

  // When model changes → load generations, reset downstream
  watch(selectedModelId, async (modelId) => {
    generations.value = []
    modifications.value = []
    selectedGenerationId.value = null
    selectedModificationId.value = null

    if (modelId) {
      generations.value = await fetchCarGenerations(modelId)
    }
  })

  // When generation changes → load modifications
  watch(selectedGenerationId, async (genId) => {
    modifications.value = []
    selectedModificationId.value = null

    if (genId) {
      modifications.value = await fetchCarModifications(genId)
    }
  })

  return {
    makes, models, generations, modifications,
    selectedMakeId, selectedModelId, selectedGenerationId, selectedModificationId,
  }
}

UI Implementation Notes

Generation display

Generation name is typically a year range string ("2019-Present", "2012-2018"). For display, combine with year_from/year_to fields for formatting flexibility:

ts
function formatGeneration(gen: CarGeneration): string {
  if (gen.year_from && gen.year_to) return `${gen.year_from}–${gen.year_to}`
  if (gen.year_from) return `${gen.year_from}–н.в.`  // "н.в." = настоящее время
  return gen.name
}

Modification display

The name field is already human-readable ("2.0L 4cyl 6AT (150 HP)"). For richer UI, use the parsed fields:

ts
function formatModification(mod: CarModification): string {
  const parts: string[] = []
  if (mod.engine_volume) parts.push(`${mod.engine_volume}L`)
  if (mod.fuel_type) parts.push(fuelTypeLabel(mod.fuel_type))
  if (mod.power) parts.push(`${mod.power} л.с.`)
  if (mod.transmission) parts.push(transmissionLabel(mod.transmission))
  if (mod.drivetrain) parts.push(drivetrainLabel(mod.drivetrain))
  return parts.join(' / ') || mod.name
}

i18n label maps

ts
export const fuelTypeLabels: Record<string, string> = {
  petrol: 'Бензин',
  diesel: 'Дизель',
  hybrid: 'Гибрид',
  electric: 'Электро',
  gas: 'Газ',
}

export const transmissionLabels: Record<string, string> = {
  mt: 'Механика',
  at: 'Автомат',
  cvt: 'Вариатор',
  amt: 'АМТ',
  robot: 'Робот',
}

export const drivetrainLabels: Record<string, string> = {
  fwd: 'Передний',
  rwd: 'Задний',
  awd: 'Полный',
  '4wd': '4WD',
}

Where This Data Is Used

Page / FeatureHow car data is used
Catalog filtersYMMM cascade dropdowns to narrow product search
Add listing (seller)Seller selects make → model → generation → modification for part compatibility
Product cardShows compatible cars: "BMW 3 Series 2019–Present, 2.0L Petrol AT FWD"
Product searchFilter by make/model/generation/modification via query params
SEO URLsUse slug fields for routes: /cars/bmw/3-series

Error Handling

StatusCodeWhen
404not_foundInvalid make/model/generation ID in URL

Example:

json
{
  "error": {
    "code": "not_found",
    "message": "Car make not found"
  }
}

Dev Environment

Test the live endpoints (requires HTTP Basic Auth on dev):

bash
# List all makes
curl -u $PARTIZAP_BASIC_AUTH_USER:$PARTIZAP_BASIC_AUTH_PASS https://dev.partizap.ru/api/store/cars/makes

# Models for BMW (id=12)
curl -u $PARTIZAP_BASIC_AUTH_USER:$PARTIZAP_BASIC_AUTH_PASS https://dev.partizap.ru/api/store/cars/makes/12/models

# Generations for a model
curl -u $PARTIZAP_BASIC_AUTH_USER:$PARTIZAP_BASIC_AUTH_PASS https://dev.partizap.ru/api/store/cars/models/420/generations

# Modifications for a generation
curl -u $PARTIZAP_BASIC_AUTH_USER:$PARTIZAP_BASIC_AUTH_PASS https://dev.partizap.ru/api/store/cars/generations/663/modifications