openapi: 3.1.0
info:
  title: VAIGO Partner API
  version: 1.0.0
  description: API B2B para cálculo de rotas VAIGO.
servers:
  - url: https://api.vaigo.online
security:
  - BearerAuth: []
paths:
  /v1/status:
    get:
      summary: Status da integração
      operationId: getStatus
      responses:
        '200':
          description: Estado atual do motor de rotas
          content:
            application/json:
              schema:
                type: object
                properties:
                  request_id: {type: string}
                  api_version: {type: string}
                  status: {type: string}
                  routing_ready: {type: boolean}
                  generated_at: {type: string, format: date-time}
        '401': {$ref: '#/components/responses/AuthError'}
        '429': {$ref: '#/components/responses/RateLimited'}
  /v1/routes:
    post:
      summary: Calcula uma rota
      operationId: calculateRoute
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RouteRequest'
      responses:
        '200':
          description: Rotas calculadas
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RouteResponse'
        '400': {$ref: '#/components/responses/BadRequest'}
        '401': {$ref: '#/components/responses/AuthError'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '415': {$ref: '#/components/responses/UnsupportedMedia'}
        '422': {$ref: '#/components/responses/ValidationError'}
        '429': {$ref: '#/components/responses/RateLimited'}
        '503': {$ref: '#/components/responses/Unavailable'}
        '504': {$ref: '#/components/responses/Timeout'}
  /v1/geocode:
    get:
      summary: Busca endereço (recurso opcional)
      operationId: geocode
      parameters:
        - in: query
          name: q
          required: true
          schema: {type: string, minLength: 3, maxLength: 240}
        - in: query
          name: proximity_lat
          schema: {type: number, minimum: -90, maximum: 90}
        - in: query
          name: proximity_lon
          schema: {type: number, minimum: -180, maximum: 180}
      responses:
        '200': {description: Resultados encontrados}
        '503': {$ref: '#/components/responses/Unavailable'}
  /v1/reverse-geocode:
    get:
      summary: Converte coordenadas em rótulo de endereço (recurso opcional)
      operationId: reverseGeocode
      parameters:
        - in: query
          name: lat
          required: true
          schema: {type: number, minimum: -90, maximum: 90}
        - in: query
          name: lon
          required: true
          schema: {type: number, minimum: -180, maximum: 180}
      responses:
        '200': {description: Endereço encontrado}
        '503': {$ref: '#/components/responses/Unavailable'}
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: VAIGO_API_KEY
  schemas:
    Coordinate:
      type: object
      required: [lat, lon]
      properties:
        lat: {type: number, minimum: -90, maximum: 90}
        lon: {type: number, minimum: -180, maximum: 180}
    RoutePreferences:
      type: object
      properties:
        avoid_ferries: {type: boolean, default: false}
        avoid_tolls: {type: boolean, default: false}
        avoid_unpaved: {type: boolean, default: false}
    RouteRequest:
      type: object
      required: [origin, destination]
      properties:
        origin: {$ref: '#/components/schemas/Coordinate'}
        destination: {$ref: '#/components/schemas/Coordinate'}
        profile:
          type: string
          enum: [driving, motorcycle]
          default: driving
        mode:
          type: string
          enum: [fastest, safest, smart, quietest]
          default: fastest
        depart_at: {type: string, default: now}
        heading: {type: number, minimum: 0, maximum: 360}
        speed_mps: {type: number, minimum: 0}
        reroute: {type: boolean, default: false}
        adaptive: {type: boolean, default: true}
        professional_driver: {type: boolean, default: true}
        local_hour: {type: integer, minimum: 0, maximum: 23}
        safety_bias: {type: number, minimum: 0, maximum: 100, default: 68}
        traffic_bias: {type: number, minimum: 0, maximum: 100, default: 62}
        preferences: {$ref: '#/components/schemas/RoutePreferences'}
    Route:
      type: object
      properties:
        id: {type: integer}
        distance_m: {type: number}
        duration_s: {type: number}
        duration_min: {type: number}
        geometry:
          type: object
          description: GeoJSON LineString
        steps:
          type: array
          items: {type: object}
        badges:
          type: array
          items: {type: string}
        micro_route: {type: boolean}
        traffic_score: {type: number}
        traffic_level: {type: string}
        safety_score: {type: number}
        safety_level: {type: integer}
        safety_level_label: {type: string}
    RouteResponse:
      type: object
      required: [request_id, api_version, selected_id, routes]
      properties:
        request_id: {type: string}
        api_version: {type: string}
        mode: {type: string}
        profile: {type: string}
        selected_id: {type: integer}
        routes:
          type: array
          items: {$ref: '#/components/schemas/Route'}
        meta:
          type: object
          properties:
            provider: {type: string, const: vaigo}
            generated_at: {type: string, format: date-time}
            route_count: {type: integer}
            degraded: {type: boolean}
    ApiError:
      type: object
      properties:
        error:
          type: object
          required: [code, message, request_id, retryable]
          properties:
            code: {type: string}
            message: {type: string}
            request_id: {type: string}
            retryable: {type: boolean}
            details: {type: object, additionalProperties: true}
  responses:
    BadRequest:
      description: Requisição inválida
      content: {application/json: {schema: {$ref: '#/components/schemas/ApiError'}}}
    AuthError:
      description: Credencial ausente, inválida, revogada ou expirada
      content: {application/json: {schema: {$ref: '#/components/schemas/ApiError'}}}
    Forbidden:
      description: Aplicação, escopo ou IP não autorizado
      content: {application/json: {schema: {$ref: '#/components/schemas/ApiError'}}}
    UnsupportedMedia:
      description: Content-Type incorreto
      content: {application/json: {schema: {$ref: '#/components/schemas/ApiError'}}}
    ValidationError:
      description: Dados semanticamente inválidos
      content: {application/json: {schema: {$ref: '#/components/schemas/ApiError'}}}
    RateLimited:
      description: Limite técnico temporário excedido
      headers:
        Retry-After: {schema: {type: integer}}
      content: {application/json: {schema: {$ref: '#/components/schemas/ApiError'}}}
    Unavailable:
      description: Serviço temporariamente indisponível
      content: {application/json: {schema: {$ref: '#/components/schemas/ApiError'}}}
    Timeout:
      description: Tempo de processamento excedido
      content: {application/json: {schema: {$ref: '#/components/schemas/ApiError'}}}
