openapi: 3.1.0
info:
  title: Fahrzeugverwaltung.at Public API
  version: 1.1.0
  description: >
    Öffentliche REST-API für den veröffentlichten Fahrzeugbestand eines Händlers.
    Die Authentifizierung erfolgt entweder serverseitig über einen Company Token
    (Authorization: Bearer) oder – für tokenlose Website-Einbindungen – über die
    Seller-ID als Query-Parameter `?seller=`. Beide Wege liefern dieselben Daten.
    Der `view`-Parameter bestimmt, welcher Preis als `price` ausgeliefert wird.
  contact:
    name: Fahrzeugverwaltung.at Support
    email: support@fahrzeugverwaltung.at
    url: https://www.fahrzeugverwaltung.at/entwickler
servers:
  - url: https://www.fahrzeugverwaltung.at/api/public
    description: Produktion
tags:
  - name: Fahrzeuge
    description: Öffentlich verfügbare, veröffentlichte Fahrzeuge eines Händlers.
security:
  - CompanyToken: []
  - SellerId: []
paths:
  /vehicles:
    get:
      tags: [Fahrzeuge]
      summary: Veröffentlichte Fahrzeuge auflisten
      description: >
        Liefert alle veröffentlichten Fahrzeuge des Händlers. `total` ist immer die
        Gesamtanzahl passender Fahrzeuge, unabhängig von `limit`/`offset`. Ein
        `offset`, der die Gesamtanzahl erreicht oder übersteigt, liefert HTTP 200
        mit einer leeren `vehicles`-Liste (und weiterhin korrektem `total`).
      operationId: listVehicles
      parameters:
        - $ref: '#/components/parameters/Seller'
        - $ref: '#/components/parameters/View'
        - name: limit
          in: query
          required: false
          description: Maximale Anzahl zurückgegebener Fahrzeuge (1–500, Standard 500).
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 500
        - name: offset
          in: query
          required: false
          description: Überspringt n Fahrzeuge (Standard 0). `total` bleibt die Gesamtanzahl.
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        '200':
          description: Erfolgreiche Anfrage.
          headers:
            Access-Control-Allow-Origin:
              schema: { type: string }
              description: CORS – für Widget-Einbindungen auf "*" gesetzt.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VehicleListResponse'
              examples:
                beispiel:
                  value:
                    success: true
                    total: 42
                    seller:
                      id: 82801b90-c892-4055-97b0-76e596c4e024
                      company_name: Muster Automobile GmbH
                      city: Wien
                      phone: "+43 1 234 5678"
                      email: office@muster-automobile.at
                    vehicles:
                      - id: 82801b90-c892-4055-97b0-76e596c4e024
                        title: Audi RS6 Avant 4.0 quattro
                        make: Audi
                        model: RS6
                        model_variant: Avant 4.0 quattro
                        year: 2022
                        mileage: 85200
                        fuel_type: Benzin
                        power_hp: 600
                        price: 109999
                        price_label: Brutto inkl. NoVA
                        price_net: 78500
                        price_gross: 94200
                        price_gross_nova: 109999
                        vat_deductible: true
                        differential_taxation: false
                        image: https://cdn.example.com/1.jpg
                        equipment: []
                        url: https://www.fahrzeugverwaltung.at/embed/vehicles/82801b90-c892-4055-97b0-76e596c4e024?seller=…&view=consumer
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
        '503':
          $ref: '#/components/responses/Unavailable'
  /vehicles/{id}:
    get:
      tags: [Fahrzeuge]
      summary: Einzelnes Fahrzeug abrufen
      description: Liefert ein veröffentlichtes Fahrzeug mit Details, Bildern, Ausstattung und Händlerkontakt.
      operationId: getVehicle
      parameters:
        - name: id
          in: path
          required: true
          description: UUID des Fahrzeugs.
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/Seller'
        - $ref: '#/components/parameters/View'
      responses:
        '200':
          description: Erfolgreiche Anfrage.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VehicleDetailResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
components:
  securitySchemes:
    CompanyToken:
      type: http
      scheme: bearer
      bearerFormat: fv_live_…
      description: >
        Company Token aus dem Dashboard („API & Integrationen"). Nur serverseitig
        verwenden – nicht in Frontend-Code einbetten.
    SellerId:
      type: apiKey
      in: query
      name: seller
      description: Seller-ID des Händlers – für tokenlose Website-/Widget-Einbindungen.
  parameters:
    Seller:
      name: seller
      in: query
      required: false
      description: >
        Seller-ID (UUID). Erforderlich, wenn kein Company Token gesendet wird.
        Werden Token und seller gemeinsam gesendet, muss die Seller-ID zum Token
        gehören, sonst antwortet die API mit 403.
      schema:
        type: string
        format: uuid
    View:
      name: view
      in: query
      required: false
      description: >
        Bestimmt den ausgelieferten Hauptpreis (`price`).
        consumer = Brutto inkl. NoVA (Privatkunden AT),
        export = Brutto ohne NoVA (Export/Ausland),
        net = Netto (B2B/Händler),
        default = Händler-Ansicht (zusätzlich price_net, price_gross, price_gross_nova).
      schema:
        type: string
        enum: [consumer, export, net, default]
        default: default
  responses:
    BadRequest:
      description: Ungültiger Parameter oder weder Token noch seller angegeben.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    Unauthorized:
      description: Token fehlt, ist ungültig oder wurde rotiert.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    Forbidden:
      description: seller gehört nicht zum Token.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    NotFound:
      description: Händler oder Fahrzeug nicht gefunden.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    RateLimited:
      description: Rate-Limit überschritten – später erneut versuchen.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    ServerError:
      description: Temporärer Serverfehler.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    Unavailable:
      description: Öffentlicher Bestand vorübergehend nicht verfügbar.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
          description: Menschlich lesbare Fehlermeldung.
      required: [error]
    Seller:
      type: object
      properties:
        id: { type: string, format: uuid }
        company_name: { type: string }
        contact_name: { type: [string, 'null'] }
        email: { type: [string, 'null'] }
        phone: { type: [string, 'null'] }
        street: { type: [string, 'null'] }
        postal_code: { type: [string, 'null'] }
        city: { type: [string, 'null'] }
        website: { type: [string, 'null'] }
        description: { type: [string, 'null'] }
        logo_url: { type: [string, 'null'] }
    Equipment:
      type: object
      properties:
        code: { type: string }
        name: { type: string }
        category: { type: string }
        confidence: { type: number }
    Vehicle:
      type: object
      properties:
        id: { type: string, format: uuid }
        title: { type: string, description: 'Marke, Modell und Variante kombiniert.' }
        make: { type: string }
        model: { type: string }
        model_variant: { type: [string, 'null'] }
        year:
          type: [integer, 'null']
          description: Baujahr; ersatzweise aus der Erstzulassung abgeleitet, falls nicht explizit erfasst.
        mileage: { type: [integer, 'null'] }
        fuel_type: { type: [string, 'null'] }
        power_hp: { type: [integer, 'null'], description: 'PS; aus kW abgeleitet, falls nur kW erfasst.' }
        price:
          type: [number, 'null']
          description: Der zur gewählten `view` passende Hauptpreis. Kann bei `default` null sein.
        price_label: { type: string, description: 'Beschriftung des gelieferten Preises.' }
        price_net: { type: [number, 'null'] }
        price_gross: { type: [number, 'null'] }
        price_gross_nova: { type: [number, 'null'] }
        vat_deductible: { type: [boolean, 'null'] }
        differential_taxation: { type: [boolean, 'null'] }
        image: { type: [string, 'null'], description: 'URL des Hauptbilds.' }
        equipment:
          type: array
          items: { $ref: '#/components/schemas/Equipment' }
        url: { type: string, description: 'Direktlink zur Einzelfahrzeug-Ansicht.' }
      required: [id, title, make, model, price_label]
    VehicleListResponse:
      type: object
      properties:
        success: { type: boolean }
        total: { type: integer, description: 'Gesamtanzahl passender Fahrzeuge (unabhängig von limit/offset).' }
        seller: { $ref: '#/components/schemas/Seller' }
        vehicles:
          type: array
          items: { $ref: '#/components/schemas/Vehicle' }
      required: [success, total, vehicles]
    VehicleDetailResponse:
      type: object
      properties:
        success: { type: boolean }
        vehicle: { $ref: '#/components/schemas/Vehicle' }
        seller: { $ref: '#/components/schemas/Seller' }
      required: [success, vehicle]
