openapi: 3.1.0
info:
  title: Clara Cars Public API
  version: "1.0.0"
  summary: Portuguese car import-tax (ISV) & road-tax (IUC) estimates, market valuation, and used-car stock.
  description: |
    Open, key-free JSON API for the Portuguese used- and imported-car market, published by
    Clara Cars (claracars.pt).

    **What it does**
    - `GET /isv` — estimate **ISV** (Imposto Sobre Veículos, one-off import tax) **and IUC**
      (Imposto Único de Circulação, annual road tax) for a vehicle, in a single call, from
      engine cc + CO₂ + fuel + first-registration year. Covers Tabela A/B/C/D + motorhomes and
      motorcycles.
    - `GET /valuation` — market price range for a car from live comparable listings.
    - `GET /car_refs` — canonical make / model vocabulary the other endpoints expect.
    - `GET /cars`, `GET /cars/{slug}` — current published stock.

    **Honesty / accuracy.** The ISV/IUC figures are a **maintained estimate** of the Portuguese
    tax, cross-checked periodically against the official AT (Autoridade Tributária) simulator and
    public tax tables. They are **not the official AT figure** and do not replace the AT simulator
    or a licensed despachante — regional specifics, NEDC edge cases, light-commercial subtleties,
    exact month rounding and IVA on the diesel surcharge are approximated. Use for planning.

    **Auth.** None. The calculation tier is public and key-free by design. Fair-use per-IP rate
    limits apply (see `x-rate-limit`); responses may be cached at the CDN. A higher-limit soft key
    for widgets/partners is available on request but is **not required**.

    **No PII, no state.** Calculation endpoints take only vehicle parameters and return numbers.
  termsOfService: https://claracars.pt/en/terms
  contact:
    name: Clara Cars
    url: https://claracars.pt/en/api
    email: claracars.pt@gmail.com
  license:
    name: CC BY 4.0
    url: https://creativecommons.org/licenses/by/4.0/
  x-logo:
    url: https://claracars.pt/logo-blue.png
    altText: Clara Cars
    backgroundColor: "#FEFEFC"

servers:
  - url: https://claracars.pt/api/public
    description: Production

externalDocs:
  description: API docs, quickstart & MCP server
  url: https://claracars.pt/en/api

tags:
  - name: Tax calculators
    description: ISV (import tax) + IUC (annual road tax) estimates for Portugal.
  - name: Valuation
    description: Market price of a car from comparable Portuguese listings.
  - name: Inventory
    description: Cars currently for sale.

x-rate-limit:
  policy: Per-IP fair-use. Calculators are generous; DB-backed endpoints are tighter.
  isv: 60 requests / minute / IP (burst 20). 429 with Retry-After on exceed.
  valuation: 20 requests / minute / IP.
  headers: 429 responses include `Retry-After` (seconds).

paths:
  /isv:
    get:
      tags: [Tax calculators]
      operationId: estimateIsvIuc
      summary: Estimate ISV (import tax) and IUC (annual road tax)
      description: |
        Returns both the one-off import tax (ISV) and the annual road tax (IUC) for a vehicle.
        Deterministic: the same parameters always return the same numbers. Electric vehicles are
        exempt from both. Result is a maintained estimate, not the official AT figure.
      parameters:
        - name: cc
          in: query
          description: Engine displacement in cm³ (0–10000). 0 for pure-electric.
          schema: { type: integer, minimum: 0, maximum: 10000, default: 0, example: 1950 }
        - name: co2
          in: query
          description: CO₂ emissions g/km, WLTP for 2020+ regs (0–1000).
          schema: { type: integer, minimum: 0, maximum: 1000, default: 0, example: 130 }
        - name: fuel
          in: query
          description: Fuel type.
          schema:
            type: string
            enum: [petrol, diesel, hybrid, phev, electric, lpg]
            default: petrol
        - name: year
          in: query
          description: First-registration year (1950–current). Drives age reduction, WLTP/NEDC and coefficients.
          schema: { type: integer, minimum: 1950, maximum: 2100, example: 2016 }
        - name: month
          in: query
          description: First-registration month 1–12 (refines the age reduction).
          schema: { type: integer, minimum: 1, maximum: 12, default: 6 }
        - name: used
          in: query
          description: Used import (true) → age reduction applies; new (false) → no reduction.
          schema: { type: boolean, default: true }
        - name: from_eu
          in: query
          description: EU import (true) qualifies for the Tabela D age reduction; non-EU does not.
          schema: { type: boolean, default: true }
        - name: vehicle_type
          in: query
          description: Vehicle category (selects the ISV table).
          schema:
            type: string
            enum: [passenger, van_10, commercial_15, commercial_50, commercial_100, motorcycle, autocaravana]
            default: passenger
        - name: range_km
          in: query
          description: Electric range (km). A PHEV needs ≥50 km (and low CO₂) for the 75% reduction.
          schema: { type: integer, minimum: 0, maximum: 2000, default: 0 }
        - name: particulas
          in: query
          description: Diesel particulate surcharge applies (≥0.001 g/km on the COC).
          schema: { type: boolean, default: true }
      responses:
        "200":
          description: ISV + IUC estimate.
          content:
            application/json:
              schema:
                type: object
                properties:
                  isv: { $ref: "#/components/schemas/IsvResult" }
                  iuc: { $ref: "#/components/schemas/IucResult" }
              examples:
                petrol_2016:
                  summary: 2016 petrol, 1950 cc, 130 g/km, used EU import
                  value:
                    isv:
                      isv: 1893.36
                      exempt: false
                      table: "A"
                      comp_cilindrada: 4744.47
                      comp_ambiental: 66.36
                      diesel_surcharge: 0.0
                      base: 4810.83
                      rate_pct: 100
                      age_years: 9.6
                      age_reduction_pct: 70
                    iuc:
                      iuc: 214.23
                      exempt: false
                      category: "B"
                      taxa_cilindrada: 127.35
                      taxa_co2: 65.15
                      coef: 1.15
                      diesel_adic: 0.0
                      co2_adic: 0.0
                electric:
                  summary: Pure electric — exempt from both taxes
                  value:
                    isv: { isv: 0.0, exempt: true, note: "100% elétrico — isento de ISV" }
                    iuc: { iuc: 0.0, exempt: true, category: "B", note: "100% elétrico — isento de IUC" }
        "429":
          $ref: "#/components/responses/RateLimited"

  /valuation:
    get:
      tags: [Valuation]
      operationId: valuation
      summary: Market price range for a car
      description: |
        Estimated market value from comparable live Portuguese listings (asking-price band).
        Call `/car_refs` first to get the exact `make`/`model` strings this endpoint expects.
      parameters:
        - { name: make, in: query, required: true, schema: { type: string, example: "BMW" } }
        - { name: model, in: query, required: true, schema: { type: string, example: "320" } }
        - { name: year, in: query, required: true, schema: { type: integer, example: 2018 } }
        - { name: km, in: query, required: true, schema: { type: integer, minimum: 0, maximum: 1000000, example: 90000 } }
        - { name: fuel, in: query, schema: { type: string, example: "diesel" } }
        - { name: gearbox, in: query, schema: { type: string, example: "automatic" } }
        - { name: hp, in: query, schema: { type: integer, example: 190 } }
      responses:
        "200":
          description: Valuation result (or `found:false` when there aren't enough comparables).
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ValuationResult" }
              examples:
                found:
                  value: { found: true, comps: 34, match_level: "make_model_year", days_sell: 41, rough: false, low: 21500, median: 24900, high: 27800 }
                notfound:
                  value: { found: false, comps: 2 }
        "429":
          $ref: "#/components/responses/RateLimited"

  /car_refs:
    get:
      tags: [Valuation]
      operationId: carRefs
      summary: Canonical make / model vocabulary
      description: No `make` → the list of brands. With `make` → that brand's canonical models. Pass the returned values verbatim to `/valuation`.
      parameters:
        - { name: make, in: query, schema: { type: string, example: "BMW" } }
      responses:
        "200":
          description: Brand or model list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  makes: { type: array, items: { type: string } }
                  make: { type: string }
                  models: { type: array, items: { type: string } }
              examples:
                brands: { value: { makes: ["BMW", "Mercedes-Benz", "Audi", "Volkswagen"] } }
                models: { value: { make: "BMW", models: ["320", "X1", "118", "530"] } }

  /cars:
    get:
      tags: [Inventory]
      operationId: listCars
      summary: List cars currently for sale
      parameters:
        - { name: site, in: query, required: true, schema: { type: string, default: "claracars", example: "claracars" } }
        - { name: lang, in: query, schema: { type: string, enum: [pt, en, ru, ua], default: "pt" } }
        - { name: make, in: query, schema: { type: string } }
        - { name: fuel, in: query, schema: { type: string } }
        - { name: gearbox, in: query, schema: { type: string } }
        - { name: max_price, in: query, schema: { type: integer } }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 96, default: 24 } }
        - { name: offset, in: query, schema: { type: integer, minimum: 0, default: 0 } }
      responses:
        "200":
          description: Paged list of published cars.
          content:
            application/json:
              schema:
                type: object
                properties:
                  site: { type: string }
                  lang: { type: string }
                  total: { type: integer }
                  cars: { type: array, items: { $ref: "#/components/schemas/CarSummary" } }
        "404":
          description: Unknown site.

  /cars/{slug}:
    get:
      tags: [Inventory]
      operationId: getCar
      summary: Get one car by slug
      parameters:
        - { name: slug, in: path, required: true, schema: { type: string } }
        - { name: lang, in: query, schema: { type: string, enum: [pt, en, ru, ua], default: "pt" } }
      responses:
        "200":
          description: Car detail.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CarDetail" }
        "404":
          description: Not found.

components:
  responses:
    RateLimited:
      description: Per-IP rate limit exceeded.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema: { type: integer, example: 30 }
      content:
        application/json:
          schema:
            type: object
            properties:
              detail: { type: string, example: "rate limited" }
  schemas:
    IsvResult:
      type: object
      description: ISV estimate. `isv` is the euro amount. Electric → `exempt:true`, `isv:0`.
      properties:
        isv: { type: number, description: "Estimated ISV in EUR", example: 1893.36 }
        exempt: { type: boolean }
        note: { type: [string, "null"], description: "Advisory (PHEV conditions, classic-car, pre-1970, etc.)" }
        table: { type: string, enum: [A, B, C, "B/autocaravana"], description: "ISV table applied" }
        comp_cilindrada: { type: number, description: "Engine (cc) component, EUR" }
        comp_ambiental: { type: number, description: "Environmental (CO₂) component, EUR (may be netted negative)" }
        diesel_surcharge: { type: number, description: "Diesel particulate surcharge, EUR" }
        base: { type: number }
        rate_pct: { type: integer, description: "Rate applied (e.g. 25 for a qualifying PHEV)" }
        age_years: { type: number }
        age_reduction_pct: { type: integer, description: "Tabela D reduction applied" }
        min_isv_applied: { type: boolean, description: "€100 statutory minimum was applied" }
        breakdown: { type: object, description: "Line-by-line AT-style demonstração (comp_cc, comp_amb, taxa_tabela, red_cc, red_amb, agravamento, red_agrav, total)" }
    IucResult:
      type: object
      description: IUC (annual road tax) estimate. `iuc` is the yearly euro amount.
      properties:
        iuc: { type: number, example: 214.23 }
        exempt: { type: boolean }
        note: { type: [string, "null"] }
        category: { type: string, enum: [A, B, E], description: "IUC category (B = passenger ≥2007, A = <2007, E = motorcycle)" }
        taxa_cilindrada: { type: number }
        taxa_co2: { type: number }
        coef: { type: number, description: "Year coefficient" }
        diesel_adic: { type: number }
        co2_adic: { type: number, description: "CO₂ additional (registrations ≥2017)" }
    ValuationResult:
      type: object
      properties:
        found: { type: boolean }
        comps: { type: integer, description: "Number of comparable listings" }
        match_level: { type: string }
        days_sell: { type: [integer, "null"], description: "Typical days-to-sell for comparables" }
        rough: { type: boolean, description: "true = shaky sample (broadened match or few comps)" }
        low: { type: integer, description: "P25 asking price, EUR" }
        median: { type: integer, description: "Median asking price, EUR" }
        high: { type: integer, description: "P75 asking price, EUR" }
    CarSummary:
      type: object
      properties:
        slug: { type: string }
        make: { type: string }
        model: { type: string }
        year: { type: integer }
        km: { type: integer }
        price_eur: { type: integer }
    CarDetail:
      type: object
      properties:
        id: { type: integer }
        slug: { type: string }
        make: { type: string }
        model: { type: string }
        year: { type: integer }
        km: { type: integer }
        price_eur: { type: integer }
        fuel: { type: string }
        gearbox: { type: string }
        body: { type: string }
        colour: { type: string }
        hp: { type: [integer, "null"] }
        cc: { type: [integer, "null"] }
        co2: { type: [integer, "null"] }
        description: { type: string }
        photos: { type: array, items: { type: string, format: uri } }
        updated_at: { type: string, format: date-time }
