openapi: 3.0.3
info:
  title: 'gf-cars API'
  description: 'Multi-brand car-rental aggregator API with per-tenant credentials and rate pools.'
  version: 1.0.0
servers:
  -
    url: 'http://localhost:8100'
tags:
  -
    name: Auth
    description: ''
  -
    name: Bookings
    description: ''
  -
    name: Coupons
    description: ''
  -
    name: Endpoints
    description: ''
  -
    name: Fleet
    description: ''
  -
    name: Places
    description: ''
components:
  securitySchemes:
    default:
      type: http
      scheme: bearer
      description: 'Tenants authenticate with an API key formatted `gfc_<prefix>_<secret>` — the prefix is indexed, the full value is stored as a sha256 hash. Pass it in `Authorization: Bearer ...` or `X-API-Key: ...`.'
security:
  -
    default: []
paths:
  /api/v1/ping:
    get:
      summary: 'Health check'
      operationId: healthCheck
      description: "Lightweight liveness probe. Returns the current server time in ISO-8601 so\nmonitoring tooling can also assert clock drift. No authentication required."
      parameters: []
      responses:
        200:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  pong: '2026-04-18T11:22:45+00:00'
                properties:
                  pong:
                    type: string
                    example: '2026-04-18T11:22:45+00:00'
      tags:
        - Auth
      security: []
  /api/v1/me:
    get:
      summary: 'Who am I'
      operationId: whoAmI
      description: "Returns the tenant (`client`) the current API key resolves to. Use this as\na smoke test before driving the booking flow — a 401 here means the key\nis wrong or has been rotated."
      parameters: []
      responses:
        200:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  client:
                    id: 1
                    name: 'Movix (demo)'
                    slug: movix
                properties:
                  client:
                    type: object
                    properties:
                      id:
                        type: integer
                        example: 1
                      name:
                        type: string
                        example: 'Movix (demo)'
                      slug:
                        type: string
                        example: movix
        401:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'Invalid API key'
                properties:
                  message:
                    type: string
                    example: 'Invalid API key'
      tags:
        - Auth
  /api/v1/bookings:
    post:
      summary: 'Create booking'
      operationId: createBooking
      description: "Step 3 of the booking flow. Consumes a `tokenc` minted by `/fleet/show`,\nsubmits the reservation to the underlying GDS, persists a `Booking` + `Sale`\n(and optional `DiscountCouponUsage`), and queues voucher emission + a\nconfirmation email. Voucher PDF is available at\n`/bookings/{code}/voucher` once the queue worker has processed the job.\n\nWave 3 — discount coupons: when `coupon_code` is supplied the controller\nruns a pre-flight `/coupons/validate`-equivalent check (same service,\nsame error codes) BEFORE calling the GDS so a bad coupon never burns a\nprovider booking. On success, the discount amount is subtracted from\nthe persisted `Sale.estimated_total_amount`, a `DiscountCouponUsage`\nrow is written, and the booking row is hydrated with\n`discount_coupon_id` / `discount_coupon_code` / `discount_amount` /\n`discount_currency`. Cancelling the booking via DELETE rolls all of\nthat back via `DiscountCouponService::revertOnCancellation()`."
      parameters: []
      responses:
        201:
          description: ''
          content:
            application/json:
              schema:
                oneOf:
                  -
                    description: 'booking confirmed (LOCALIZA)'
                    type: object
                    example:
                      data:
                        uuid: 01935f1e-9b0a-7c88-b9e3-3f7c8e9d1a02
                        booking_code: BR-1234567
                        local_number: XCL2026000042
                        brand: LOCALIZA
                        name: Joao
                        last_name: Silva
                        voucher:
                          status: queued
                    properties:
                      data:
                        type: object
                        properties:
                          uuid:
                            type: string
                            example: 01935f1e-9b0a-7c88-b9e3-3f7c8e9d1a02
                          booking_code:
                            type: string
                            example: BR-1234567
                          local_number:
                            type: string
                            example: XCL2026000042
                          brand:
                            type: string
                            example: LOCALIZA
                          name:
                            type: string
                            example: Joao
                          last_name:
                            type: string
                            example: Silva
                          voucher:
                            type: object
                            properties:
                              status:
                                type: string
                                example: queued
                  -
                    description: 'booking confirmed with discount coupon applied'
                    type: object
                    example:
                      data:
                        uuid: 01935f1e-9b0a-7c88-b9e3-3f7c8e9d1a02
                        booking_code: BR-1234568
                        local_number: XCL2026000043
                        brand: LOCALIZA
                        name: Joao
                        last_name: Silva
                        voucher:
                          status: queued
                        discount_amount: 68.09
                        discount_currency: BRL
                        discount_coupon_code: SUMMER25
                    properties:
                      data:
                        type: object
                        properties:
                          uuid:
                            type: string
                            example: 01935f1e-9b0a-7c88-b9e3-3f7c8e9d1a02
                          booking_code:
                            type: string
                            example: BR-1234568
                          local_number:
                            type: string
                            example: XCL2026000043
                          brand:
                            type: string
                            example: LOCALIZA
                          name:
                            type: string
                            example: Joao
                          last_name:
                            type: string
                            example: Silva
                          voucher:
                            type: object
                            properties:
                              status:
                                type: string
                                example: queued
                          discount_amount:
                            type: number
                            example: 68.09
                          discount_currency:
                            type: string
                            example: BRL
                          discount_coupon_code:
                            type: string
                            example: SUMMER25
        401:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'Invalid API key'
                properties:
                  message:
                    type: string
                    example: 'Invalid API key'
        422:
          description: ''
          content:
            application/json:
              schema:
                oneOf:
                  -
                    description: 'GDS rejected the rate'
                    type: object
                    example:
                      error:
                        message: 'RATE NOT AVAILABLE FOR THIS CAR CLASS'
                    properties:
                      error:
                        type: object
                        properties:
                          message:
                            type: string
                            example: 'RATE NOT AVAILABLE FOR THIS CAR CLASS'
                  -
                    description: 'coupon validation failed (pre-flight)'
                    type: object
                    example:
                      error: coupon_validation_failed
                      reason: coupon_expired
                    properties:
                      error:
                        type: string
                        example: coupon_validation_failed
                      reason:
                        type: string
                        example: coupon_expired
                  -
                    description: 'missing required fields'
                    type: object
                    example:
                      message: 'The brand field is required. (and 11 more errors)'
                      errors:
                        brand:
                          - 'The brand field is required.'
                        tokenc:
                          - 'The tokenc field is required.'
                        customer.email:
                          - 'The customer.email field is required.'
                        customer.address.country_code:
                          - 'The customer.address.country_code field is required.'
                    properties:
                      message:
                        type: string
                        example: 'The brand field is required. (and 11 more errors)'
                      errors:
                        type: object
                        properties:
                          brand:
                            type: array
                            example:
                              - 'The brand field is required.'
                            items:
                              type: string
                          tokenc:
                            type: array
                            example:
                              - 'The tokenc field is required.'
                            items:
                              type: string
                          customer.email:
                            type: array
                            example:
                              - 'The customer.email field is required.'
                            items:
                              type: string
                          customer.address.country_code:
                            type: array
                            example:
                              - 'The customer.address.country_code field is required.'
                            items:
                              type: string
      tags:
        - Bookings
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                brand:
                  type: integer
                  description: 'Brand id.'
                  example: 11
                pickup_location:
                  type: string
                  description: ''
                  example: AACGH
                dropoff_location:
                  type: string
                  description: ''
                  example: AACGH
                pickup_date:
                  type: string
                  description: 'Full datetime (Y-m-d\TH:i:s).'
                  example: '2026-05-15T10:00:00'
                dropoff_date:
                  type: string
                  description: 'Full datetime.'
                  example: '2026-05-18T10:00:00'
                rate:
                  type: string
                  description: 'Rate code.'
                  example: '011880'
                vehicle_code:
                  type: string
                  description: 'SIPP / brand car code.'
                  example: B
                tokenc:
                  type: string
                  description: 'Token returned by `/fleet/show`.'
                  example: 836c7fb31f624ca2a8b5a519f26825d2
                coupon_code:
                  type: string
                  description: 'Optional discount coupon. Pre-validated server-side via the same path as `/coupons/validate`; a 422 with `error: coupon_validation_failed` (and `reason: <error_code>`) is returned before the GDS is called when validation fails. On success the discount is applied to the Sale and the persisted booking row is hydrated with `discount_amount` + `discount_currency` (visible via the booking row, e.g. for invoicing).'
                  example: SUMMER25
                  nullable: true
                customer:
                  type: object
                  description: ''
                  example: []
                  properties:
                    name:
                      type: string
                      description: ''
                      example: Joao
                    lastname:
                      type: string
                      description: ''
                      example: Silva
                    email:
                      type: string
                      description: ''
                      example: joao@example.com
                    age:
                      type: integer
                      description: 18+.
                      example: 30
                    cpf:
                      type: string
                      description: 'Brazilian CPF if BR tenant.'
                      example: '12345678901'
                      nullable: true
                    document_number:
                      type: string
                      description: 'Passport / ID number.'
                      example: AB123456
                      nullable: true
                    address:
                      type: object
                      description: ''
                      example: []
                      properties:
                        country_code:
                          type: string
                          description: 'ISO alpha-2.'
                          example: BR
                      required:
                        - country_code
                    telephone:
                      type: object
                      description: ''
                      example: []
                      properties:
                        phone_number:
                          type: string
                          description: ''
                          example: '11987654321'
                        area_code:
                          type: string
                          description: 'Optional dial area.'
                          example: '11'
                          nullable: true
                      required:
                        - phone_number
                  required:
                    - name
                    - lastname
                    - email
                    - age
                airline:
                  type: object
                  description: ''
                  example: []
                  properties:
                    iata:
                      type: string
                      description: "Optional flight info — propagated to MarketingCompany / FlightNumber\nin the OTA envelope for ABG / ANE adapters when both are present. Must not be greater than 3 characters."
                      example: 'n'
                      nullable: true
                    flight_number:
                      type: string
                      description: 'Must not be greater than 10 characters.'
                      example: gzmiyv
                      nullable: true
                frequent_flyer:
                  type: object
                  description: ''
                  example: []
                  properties:
                    iata:
                      type: string
                      description: 'Optional frequent-flyer info — same propagation path. Must not be greater than 3 characters.'
                      example: d
                      nullable: true
                    number:
                      type: string
                      description: 'Must not be greater than 30 characters.'
                      example: l
                      nullable: true
                mail_notification:
                  type: boolean
                  description: 'Whether to send the confirmation email (default true).'
                  example: true
                sms_notification:
                  type: boolean
                  description: 'Whether to send an SMS confirmation (default false).'
                  example: false
              required:
                - brand
                - pickup_location
                - dropoff_location
                - pickup_date
                - dropoff_date
                - rate
                - vehicle_code
                - tokenc
  '/api/v1/bookings/{code}':
    get:
      summary: 'View booking'
      operationId: viewBooking
      description: "Retrieve a booking by `booking_code` or `local_number`. A matching `lastname`\nquery param is required as a lightweight integrity check. The upstream GDS\nis queried live and returned under `data.gds`."
      parameters:
        -
          in: query
          name: lastname
          description: "Customer's last name (must match the booking)."
          example: Silva
          required: true
          schema:
            type: string
            description: "Customer's last name (must match the booking)."
            example: Silva
      responses:
        200:
          description: 'booking found'
          content:
            application/json:
              schema:
                type: object
                example:
                  data:
                    uuid: 01935f1e-9b0a-7c88-b9e3-3f7c8e9d1a02
                    booking_code: BR-1234567
                    local_number: XCL2026000042
                    status: RESERVED
                    pickup_date: '2026-05-15 10:00:00'
                    dropoff_date: '2026-05-18 10:00:00'
                    gds:
                      booking_code: BR-1234567
                      status: RESERVED
                      ...: 'vendor-specific shape'
                properties:
                  data:
                    type: object
                    properties:
                      uuid:
                        type: string
                        example: 01935f1e-9b0a-7c88-b9e3-3f7c8e9d1a02
                      booking_code:
                        type: string
                        example: BR-1234567
                      local_number:
                        type: string
                        example: XCL2026000042
                      status:
                        type: string
                        example: RESERVED
                      pickup_date:
                        type: string
                        example: '2026-05-15 10:00:00'
                      dropoff_date:
                        type: string
                        example: '2026-05-18 10:00:00'
                      gds:
                        type: object
                        properties:
                          booking_code:
                            type: string
                            example: BR-1234567
                          status:
                            type: string
                            example: RESERVED
                          ...:
                            type: string
                            example: 'vendor-specific shape'
        401:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'Invalid API key'
                properties:
                  message:
                    type: string
                    example: 'Invalid API key'
        404:
          description: 'wrong lastname / code'
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'No query results for model [App\Models\Booking].'
                properties:
                  message:
                    type: string
                    example: 'No query results for model [App\Models\Booking].'
        422:
          description: 'missing lastname'
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The lastname field is required.'
                  errors:
                    lastname:
                      - 'The lastname field is required.'
                properties:
                  message:
                    type: string
                    example: 'The lastname field is required.'
                  errors:
                    type: object
                    properties:
                      lastname:
                        type: array
                        example:
                          - 'The lastname field is required.'
                        items:
                          type: string
      tags:
        - Bookings
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                lastname:
                  type: string
                  description: ''
                  example: architecto
              required:
                - lastname
    delete:
      summary: 'Cancel booking'
      operationId: cancelBooking
      description: "Cancels the booking on the upstream GDS and marks the local record +\nassociated Sale as canceled. A cancellation email is dispatched to the\ncustomer; mail failures are non-fatal."
      parameters: []
      responses:
        200:
          description: 'cancellation confirmed'
          content:
            application/json:
              schema:
                type: object
                example:
                  data:
                    success: true
                    booking_code: BR-1234567
                    local_number: XCL2026000042
                properties:
                  data:
                    type: object
                    properties:
                      success:
                        type: boolean
                        example: true
                      booking_code:
                        type: string
                        example: BR-1234567
                      local_number:
                        type: string
                        example: XCL2026000042
        401:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'Invalid API key'
                properties:
                  message:
                    type: string
                    example: 'Invalid API key'
        404:
          description: 'unknown booking for this tenant'
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'No query results for model [App\Models\Booking].'
                properties:
                  message:
                    type: string
                    example: 'No query results for model [App\Models\Booking].'
        422:
          description: 'GDS rejected the cancel'
          content:
            application/json:
              schema:
                type: object
                example:
                  error:
                    message: 'DRIVER LAST NAME DOES NOT MATCH'
                properties:
                  error:
                    type: object
                    properties:
                      message:
                        type: string
                        example: 'DRIVER LAST NAME DOES NOT MATCH'
      tags:
        - Bookings
    parameters:
      -
        in: path
        name: code
        description: 'Booking code or local number.'
        example: BR-1234567
        required: true
        schema:
          type: string
  '/api/v1/bookings/{code}/voucher':
    get:
      summary: 'Download voucher PDF'
      operationId: downloadVoucherPDF
      description: "Streams the latest active voucher PDF for the given booking. `{code}` can be\neither the `booking_code` (from the GDS) or the tenant-local `local_number`."
      parameters: []
      responses:
        200:
          description: 'voucher found (binary PDF stream)'
          content:
            application/json:
              schema:
                type: object
                example:
                  _: 'Content-Type: application/pdf — body is the raw PDF bytes, named voucher-{number}.pdf via Content-Disposition.'
                properties:
                  _:
                    type: string
                    example: 'Content-Type: application/pdf — body is the raw PDF bytes, named voucher-{number}.pdf via Content-Disposition.'
        401:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'Invalid API key'
                properties:
                  message:
                    type: string
                    example: 'Invalid API key'
        404:
          description: ''
          content:
            application/json:
              schema:
                oneOf:
                  -
                    description: 'booking not found for tenant'
                    type: object
                    example:
                      message: 'No query results for model [App\Models\Booking].'
                    properties:
                      message:
                        type: string
                        example: 'No query results for model [App\Models\Booking].'
                  -
                    description: 'no active voucher yet (queue still pending)'
                    type: object
                    example:
                      message: 'No query results for model [App\Models\Voucher].'
                    properties:
                      message:
                        type: string
                        example: 'No query results for model [App\Models\Voucher].'
      tags:
        - Bookings
    parameters:
      -
        in: path
        name: code
        description: 'Booking code or local number.'
        example: BR-1234567
        required: true
        schema:
          type: string
  '/api/v1/bookings/{code}/voucher/resend':
    post:
      summary: 'Resend voucher email'
      operationId: resendVoucherEmail
      description: "Re-sends the latest voucher email for a booking. If no voucher exists yet\n(e.g. the initial emission failed), a fresh one is generated synchronously."
      parameters: []
      responses:
        200:
          description: 'voucher (re-)sent'
          content:
            application/json:
              schema:
                type: object
                example:
                  data:
                    voucher_number: XCL2026000042
                    emailed_at: '2026-05-15T10:12:03+00:00'
                properties:
                  data:
                    type: object
                    properties:
                      voucher_number:
                        type: string
                        example: XCL2026000042
                      emailed_at:
                        type: string
                        example: '2026-05-15T10:12:03+00:00'
        401:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'Invalid API key'
                properties:
                  message:
                    type: string
                    example: 'Invalid API key'
        404:
          description: 'unknown booking'
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'No query results for model [App\Models\Booking].'
                properties:
                  message:
                    type: string
                    example: 'No query results for model [App\Models\Booking].'
      tags:
        - Bookings
    parameters:
      -
        in: path
        name: code
        description: 'Booking code or local number.'
        example: BR-1234567
        required: true
        schema:
          type: string
  /api/v1/coupons/validate:
    post:
      summary: 'Validate coupon'
      operationId: validateCoupon
      description: "Check whether a coupon code is currently usable for the given brand (and\noptionally rate + country). Returns discount metadata so the frontend can\npreview the discount before the booking step.\n\nResponse shape (Wave 3):\n  `code`            — the matched coupon code (echo of the request).\n  `discount_type`   — `percentage` | `fixed`.\n  `discount_value`  — number; percent (0-100) or fixed-amount in `currency`.\n  `currency`        — ISO-4217 (only for `fixed`); null for percentage.\n  `discount_code`   — per-brand GDS upstream code (when the coupon's\n                      `discount_coupon_brands` row carries one). Forwarded\n                      to the GDS as `discount_number` on `/fleet/show`.\n  `stackable`       — whether this coupon can stack with brand-level promo\n                      pricing (UI hint; the backend always honours this)."
      parameters: []
      responses:
        200:
          description: ''
          content:
            application/json:
              schema:
                oneOf:
                  -
                    description: 'valid percentage coupon'
                    type: object
                    example:
                      data:
                        code: SUMMER25
                        discount_type: percentage
                        discount_value: 25.0
                        currency: null
                        discount_code: S25BR
                        stackable: false
                    properties:
                      data:
                        type: object
                        properties:
                          code:
                            type: string
                            example: SUMMER25
                          discount_type:
                            type: string
                            example: percentage
                          discount_value:
                            type: number
                            example: 25.0
                          currency:
                            type: string
                            example: null
                            nullable: true
                          discount_code:
                            type: string
                            example: S25BR
                          stackable:
                            type: boolean
                            example: false
                  -
                    description: 'valid fixed-amount coupon'
                    type: object
                    example:
                      data:
                        code: WELCOME10
                        discount_type: fixed
                        discount_value: 10.0
                        currency: BRL
                        discount_code: null
                        stackable: true
                    properties:
                      data:
                        type: object
                        properties:
                          code:
                            type: string
                            example: WELCOME10
                          discount_type:
                            type: string
                            example: fixed
                          discount_value:
                            type: number
                            example: 10.0
                          currency:
                            type: string
                            example: BRL
                          discount_code:
                            type: string
                            example: null
                            nullable: true
                          stackable:
                            type: boolean
                            example: true
        401:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'Invalid API key'
                properties:
                  message:
                    type: string
                    example: 'Invalid API key'
        404:
          description: 'unknown code'
          content:
            application/json:
              schema:
                type: object
                example:
                  error: coupon_not_found
                properties:
                  error:
                    type: string
                    example: coupon_not_found
        422:
          description: ''
          content:
            application/json:
              schema:
                oneOf:
                  -
                    description: 'expired or out-of-window'
                    type: object
                    example:
                      error: coupon_not_valid
                    properties:
                      error:
                        type: string
                        example: coupon_not_valid
                  -
                    description: 'brand or rate mismatch'
                    type: object
                    example:
                      error: coupon_brand_or_rate_mismatch
                    properties:
                      error:
                        type: string
                        example: coupon_brand_or_rate_mismatch
                  -
                    description: 'country mismatch'
                    type: object
                    example:
                      error: coupon_country_mismatch
                    properties:
                      error:
                        type: string
                        example: coupon_country_mismatch
                  -
                    description: "rental days outside the coupon's min/max window"
                    type: object
                    example:
                      error: coupon_days_out_of_range
                    properties:
                      error:
                        type: string
                        example: coupon_days_out_of_range
                  -
                    description: 'missing required fields'
                    type: object
                    example:
                      message: 'The code field is required. (and 1 more error)'
                      errors:
                        code:
                          - 'The code field is required.'
                        brand:
                          - 'The brand field is required.'
                    properties:
                      message:
                        type: string
                        example: 'The code field is required. (and 1 more error)'
                      errors:
                        type: object
                        properties:
                          code:
                            type: array
                            example:
                              - 'The code field is required.'
                            items:
                              type: string
                          brand:
                            type: array
                            example:
                              - 'The brand field is required.'
                            items:
                              type: string
      tags:
        - Coupons
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                code:
                  type: string
                  description: 'Coupon code (case-sensitive).'
                  example: SUMMER25
                brand:
                  type: integer
                  description: 'Brand id.'
                  example: 11
                rate:
                  type: string
                  description: 'Optional rate code; if given, coupon must either be unrestricted or cover this rate.'
                  example: '011880'
                  nullable: true
                country_id:
                  type: integer
                  description: 'Optional country scope check.'
                  example: 31
                  nullable: true
                days:
                  type: integer
                  description: "Optional rental length in days; checked against the coupon's `min_days`/`max_days` window."
                  example: 3
                  nullable: true
              required:
                - code
                - brand
  /api/v1/tenancy/brands:
    get:
      summary: 'GET /api/v1/tenancy/brands'
      operationId: gETapiv1tenancybrands
      description: "Returns the full brand catalog plus a boolean `active` flag per brand\nindicating whether the current client has it enabled."
      parameters: []
      responses:
        401:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'API key missing'
                properties:
                  message:
                    type: string
                    example: 'API key missing'
      tags:
        - Endpoints
      security: []
  '/api/v1/tenancy/brands/{brandId}':
    put:
      summary: 'PUT /api/v1/tenancy/brands/{brandId}'
      operationId: pUTapiv1tenancybrandsbrandId
      description: "Upsert a single ClientBrand row — toggles whether this client can use\nthe given brand. Creates the row on first call."
      parameters: []
      responses: {  }
      tags:
        - Endpoints
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                active:
                  type: boolean
                  description: ''
                  example: true
              required:
                - active
      security: []
    parameters:
      -
        in: path
        name: brandId
        description: ''
        example: '564'
        required: true
        schema:
          type: string
  '/api/v1/tenancy/brands/{brandId}/countries':
    get:
      summary: 'GET /api/v1/tenancy/brands/{brandId}/countries'
      operationId: gETapiv1tenancybrandsbrandIdcountries
      description: "Returns all selectable countries with a per-client `active` flag for\nthe given brand. Used to populate the country drill-down."
      parameters: []
      responses:
        401:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'API key missing'
                properties:
                  message:
                    type: string
                    example: 'API key missing'
      tags:
        - Endpoints
      security: []
    parameters:
      -
        in: path
        name: brandId
        description: ''
        example: '564'
        required: true
        schema:
          type: string
  '/api/v1/tenancy/brands/{brandId}/countries/{countryId}':
    put:
      summary: 'PUT /api/v1/tenancy/brands/{brandId}/countries/{countryId}'
      operationId: pUTapiv1tenancybrandsbrandIdcountriescountryId
      description: 'Toggle a (client, brand, country) triple. Creates the row on first call.'
      parameters: []
      responses: {  }
      tags:
        - Endpoints
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                active:
                  type: boolean
                  description: ''
                  example: false
              required:
                - active
      security: []
    parameters:
      -
        in: path
        name: brandId
        description: ''
        example: '564'
        required: true
        schema:
          type: string
      -
        in: path
        name: countryId
        description: ''
        example: '564'
        required: true
        schema:
          type: string
  '/api/v1/tenancy/brands/{brandId}/countries/{countryId}/rates':
    get:
      summary: 'GET /api/v1/tenancy/brands/{brandId}/countries/{countryId}/rates'
      operationId: gETapiv1tenancybrandsbrandIdcountriescountryIdrates
      description: "Returns the rates available for (brand, country) in the catalog plus the\nper-client `active` flag and `priority`. Clients without a row for a\ngiven rate come back as `active=false, priority=999`."
      parameters: []
      responses:
        401:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'API key missing'
                properties:
                  message:
                    type: string
                    example: 'API key missing'
      tags:
        - Endpoints
      security: []
    put:
      summary: 'PUT /api/v1/tenancy/brands/{brandId}/countries/{countryId}/rates'
      operationId: pUTapiv1tenancybrandsbrandIdcountriescountryIdrates
      description: "Bulk upsert the rate configuration for a (brand, country). Body is an\narray of `{rate_id, active, priority}` objects; any rate not in the\npayload is left untouched. Priority is stored as-is (lower = sooner)."
      parameters: []
      responses: {  }
      tags:
        - Endpoints
      security: []
    parameters:
      -
        in: path
        name: brandId
        description: ''
        example: '564'
        required: true
        schema:
          type: string
      -
        in: path
        name: countryId
        description: ''
        example: '564'
        required: true
        schema:
          type: string
  /api/v1/fleet/list:
    post:
      summary: 'Fleet list (multi-brand availability)'
      operationId: fleetListmultiBrandAvailability
      description: "Step 1 of the booking flow. Iterates the tenant's enabled brands (or just the\none in `brand`), fans out per-brand rate searches, and normalises each row into\na flat `cars[]` array so clients don't have to care which vendor produced it.\n\nCars are deduplicated by vehicle_code per brand: when an adapter returns N rows\n(one per rate) we collapse them into a single row whose `rates[]` array carries\nevery rate the upstream returned. The legacy `rate_code` field is kept for\nback-compat but is deprecated — clients should iterate `rates[]`.\n\nResponses are cached under tags `fleet` + `client:{id}` for 2 minutes. Use\n`php artisan cache:flush fleet` (or `cache:flush client:{id}`) to invalidate."
      parameters: []
      responses:
        200:
          description: ''
          content:
            application/json:
              schema:
                oneOf:
                  -
                    description: 'success (LOCALIZA, AACGH 3-day rental)'
                    type: object
                    example:
                      cars:
                        -
                          brand_id: 11
                          brand_code: LOCALIZA
                          brand_name: LOCALIZA
                          rate_code: '011880'
                          vehicle_code: B
                          vehicle_name: 'Fiat Mobi 1.0'
                          category: null
                          transmission: Manual
                          fuel: null
                          seats: 4
                          doors: 2
                          luggage: 4
                          price:
                            currency: BRL
                            total: 272.3775
                            daily: null
                          rates:
                            -
                              code: '011880'
                              currency: BRL
                              total_amount: 236.85
                              estimated_total_amount: 272.3775
                              type: null
                            -
                              code: 011880P
                              currency: BRL
                              total_amount: 236.85
                              estimated_total_amount: 272.3775
                              type: null
                          bookable: true
                          bookable_reason: null
                          pickup_office: null
                          dropoff_office: null
                          _raw:
                            ...: truncated
                      offices:
                        -
                          id: 27712
                          code: AACGH
                          name: 'AGENCIA AEROPORTO CONGONHAS'
                          brand_id: 11
                          country_id: 31
                          city: 'SAO PAULO'
                          state: SP
                          is_airport: true
                          latitude: -23.620612
                          longitude: -46.6653852
                      places:
                        pickup:
                          -
                            uuid: bdb35d57-fa14-4d04-b722-d2249c618225
                            name: Moema
                            place_type_id: 2
                            latitude: -23.61
                            longitude: -46.665
                            city: 'São Paulo'
                            state: SP
                        dropoff:
                          -
                            uuid: bdb35d57-fa14-4d04-b722-d2249c618225
                            name: Moema
                            place_type_id: 2
                            latitude: -23.61
                            longitude: -46.665
                            city: 'São Paulo'
                            state: SP
                      groups:
                        -
                          id: null
                          name: Uncategorised
                          description: null
                          icon: null
                          image: null
                          categories:
                            -
                              id: 11
                              name: Economy
                              image: null
                              display_order: null
                      days_calc: 3
                      brand_ratings:
                        -
                          brand_id: 11
                          country_id: null
                          score: 4.3
                          count: 15600
                          source: manual_seed
                      meta:
                        LOCALIZA:
                          count: 22
                          bookable_count: 22
                          unbookable_count: 0
                          ms: 1404
                          rate_codes:
                            - '011880'
                            - 011880P
                      errors: {  }
                      cached: false
                    properties:
                      cars:
                        type: array
                        example:
                          -
                            brand_id: 11
                            brand_code: LOCALIZA
                            brand_name: LOCALIZA
                            rate_code: '011880'
                            vehicle_code: B
                            vehicle_name: 'Fiat Mobi 1.0'
                            category: null
                            transmission: Manual
                            fuel: null
                            seats: 4
                            doors: 2
                            luggage: 4
                            price:
                              currency: BRL
                              total: 272.3775
                              daily: null
                            rates:
                              -
                                code: '011880'
                                currency: BRL
                                total_amount: 236.85
                                estimated_total_amount: 272.3775
                                type: null
                              -
                                code: 011880P
                                currency: BRL
                                total_amount: 236.85
                                estimated_total_amount: 272.3775
                                type: null
                            bookable: true
                            bookable_reason: null
                            pickup_office: null
                            dropoff_office: null
                            _raw:
                              ...: truncated
                        items:
                          type: object
                          properties:
                            brand_id:
                              type: integer
                              example: 11
                            brand_code:
                              type: string
                              example: LOCALIZA
                            brand_name:
                              type: string
                              example: LOCALIZA
                            rate_code:
                              type: string
                              example: '011880'
                            vehicle_code:
                              type: string
                              example: B
                            vehicle_name:
                              type: string
                              example: 'Fiat Mobi 1.0'
                            category:
                              type: string
                              example: null
                              nullable: true
                            transmission:
                              type: string
                              example: Manual
                            fuel:
                              type: string
                              example: null
                              nullable: true
                            seats:
                              type: integer
                              example: 4
                            doors:
                              type: integer
                              example: 2
                            luggage:
                              type: integer
                              example: 4
                            price:
                              type: object
                              properties:
                                currency:
                                  type: string
                                  example: BRL
                                total:
                                  type: number
                                  example: 272.3775
                                daily:
                                  type: string
                                  example: null
                                  nullable: true
                            rates:
                              type: array
                              example:
                                -
                                  code: '011880'
                                  currency: BRL
                                  total_amount: 236.85
                                  estimated_total_amount: 272.3775
                                  type: null
                                -
                                  code: 011880P
                                  currency: BRL
                                  total_amount: 236.85
                                  estimated_total_amount: 272.3775
                                  type: null
                              items:
                                type: object
                                properties:
                                  code:
                                    type: string
                                    example: '011880'
                                  currency:
                                    type: string
                                    example: BRL
                                  total_amount:
                                    type: number
                                    example: 236.85
                                  estimated_total_amount:
                                    type: number
                                    example: 272.3775
                                  type:
                                    type: string
                                    example: null
                                    nullable: true
                            bookable:
                              type: boolean
                              example: true
                            bookable_reason:
                              type: string
                              example: null
                              nullable: true
                            pickup_office:
                              type: string
                              example: null
                              nullable: true
                            dropoff_office:
                              type: string
                              example: null
                              nullable: true
                            _raw:
                              type: object
                              properties:
                                ...:
                                  type: string
                                  example: truncated
                      offices:
                        type: array
                        example:
                          -
                            id: 27712
                            code: AACGH
                            name: 'AGENCIA AEROPORTO CONGONHAS'
                            brand_id: 11
                            country_id: 31
                            city: 'SAO PAULO'
                            state: SP
                            is_airport: true
                            latitude: -23.620612
                            longitude: -46.6653852
                        items:
                          type: object
                          properties:
                            id:
                              type: integer
                              example: 27712
                            code:
                              type: string
                              example: AACGH
                            name:
                              type: string
                              example: 'AGENCIA AEROPORTO CONGONHAS'
                            brand_id:
                              type: integer
                              example: 11
                            country_id:
                              type: integer
                              example: 31
                            city:
                              type: string
                              example: 'SAO PAULO'
                            state:
                              type: string
                              example: SP
                            is_airport:
                              type: boolean
                              example: true
                            latitude:
                              type: number
                              example: -23.620612
                            longitude:
                              type: number
                              example: -46.6653852
                      places:
                        type: object
                        properties:
                          pickup:
                            type: array
                            example:
                              -
                                uuid: bdb35d57-fa14-4d04-b722-d2249c618225
                                name: Moema
                                place_type_id: 2
                                latitude: -23.61
                                longitude: -46.665
                                city: 'São Paulo'
                                state: SP
                            items:
                              type: object
                              properties:
                                uuid:
                                  type: string
                                  example: bdb35d57-fa14-4d04-b722-d2249c618225
                                name:
                                  type: string
                                  example: Moema
                                place_type_id:
                                  type: integer
                                  example: 2
                                latitude:
                                  type: number
                                  example: -23.61
                                longitude:
                                  type: number
                                  example: -46.665
                                city:
                                  type: string
                                  example: 'São Paulo'
                                state:
                                  type: string
                                  example: SP
                          dropoff:
                            type: array
                            example:
                              -
                                uuid: bdb35d57-fa14-4d04-b722-d2249c618225
                                name: Moema
                                place_type_id: 2
                                latitude: -23.61
                                longitude: -46.665
                                city: 'São Paulo'
                                state: SP
                            items:
                              type: object
                              properties:
                                uuid:
                                  type: string
                                  example: bdb35d57-fa14-4d04-b722-d2249c618225
                                name:
                                  type: string
                                  example: Moema
                                place_type_id:
                                  type: integer
                                  example: 2
                                latitude:
                                  type: number
                                  example: -23.61
                                longitude:
                                  type: number
                                  example: -46.665
                                city:
                                  type: string
                                  example: 'São Paulo'
                                state:
                                  type: string
                                  example: SP
                      groups:
                        type: array
                        example:
                          -
                            id: null
                            name: Uncategorised
                            description: null
                            icon: null
                            image: null
                            categories:
                              -
                                id: 11
                                name: Economy
                                image: null
                                display_order: null
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              example: null
                              nullable: true
                            name:
                              type: string
                              example: Uncategorised
                            description:
                              type: string
                              example: null
                              nullable: true
                            icon:
                              type: string
                              example: null
                              nullable: true
                            image:
                              type: string
                              example: null
                              nullable: true
                            categories:
                              type: array
                              example:
                                -
                                  id: 11
                                  name: Economy
                                  image: null
                                  display_order: null
                              items:
                                type: object
                                properties:
                                  id:
                                    type: integer
                                    example: 11
                                  name:
                                    type: string
                                    example: Economy
                                  image:
                                    type: string
                                    example: null
                                    nullable: true
                                  display_order:
                                    type: string
                                    example: null
                                    nullable: true
                      days_calc:
                        type: integer
                        example: 3
                      brand_ratings:
                        type: array
                        example:
                          -
                            brand_id: 11
                            country_id: null
                            score: 4.3
                            count: 15600
                            source: manual_seed
                        items:
                          type: object
                          properties:
                            brand_id:
                              type: integer
                              example: 11
                            country_id:
                              type: string
                              example: null
                              nullable: true
                            score:
                              type: number
                              example: 4.3
                            count:
                              type: integer
                              example: 15600
                            source:
                              type: string
                              example: manual_seed
                      meta:
                        type: object
                        properties:
                          LOCALIZA:
                            type: object
                            properties:
                              count:
                                type: integer
                                example: 22
                              bookable_count:
                                type: integer
                                example: 22
                              unbookable_count:
                                type: integer
                                example: 0
                              ms:
                                type: integer
                                example: 1404
                              rate_codes:
                                type: array
                                example:
                                  - '011880'
                                  - 011880P
                                items:
                                  type: string
                      errors:
                        type: object
                        properties: {  }
                      cached:
                        type: boolean
                        example: false
                  -
                    description: 'cache hit (note original_ms)'
                    type: object
                    example:
                      cars: []
                      offices: []
                      places:
                        pickup: []
                        dropoff: []
                      groups: []
                      days_calc: 3
                      brand_ratings: []
                      meta:
                        LOCALIZA:
                          count: 22
                          bookable_count: 22
                          unbookable_count: 0
                          ms: 4
                          original_ms: 1404
                          rate_codes:
                            - '011880'
                      errors: {  }
                      cached: true
                    properties:
                      cars:
                        type: array
                        example: []
                      offices:
                        type: array
                        example: []
                      places:
                        type: object
                        properties:
                          pickup:
                            type: array
                            example: []
                          dropoff:
                            type: array
                            example: []
                      groups:
                        type: array
                        example: []
                      days_calc:
                        type: integer
                        example: 3
                      brand_ratings:
                        type: array
                        example: []
                      meta:
                        type: object
                        properties:
                          LOCALIZA:
                            type: object
                            properties:
                              count:
                                type: integer
                                example: 22
                              bookable_count:
                                type: integer
                                example: 22
                              unbookable_count:
                                type: integer
                                example: 0
                              ms:
                                type: integer
                                example: 4
                              original_ms:
                                type: integer
                                example: 1404
                              rate_codes:
                                type: array
                                example:
                                  - '011880'
                                items:
                                  type: string
                      errors:
                        type: object
                        properties: {  }
                      cached:
                        type: boolean
                        example: true
        401:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'Invalid API key'
                properties:
                  message:
                    type: string
                    example: 'Invalid API key'
        422:
          description: 'missing required fields'
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The pickup location field is required. (and 7 more errors)'
                  errors:
                    pickup_location:
                      - 'The pickup location field is required.'
                    pickup_date:
                      - 'The pickup date field is required.'
                    passenger_country_code:
                      - 'The passenger country code field is required.'
                properties:
                  message:
                    type: string
                    example: 'The pickup location field is required. (and 7 more errors)'
                  errors:
                    type: object
                    properties:
                      pickup_location:
                        type: array
                        example:
                          - 'The pickup location field is required.'
                        items:
                          type: string
                      pickup_date:
                        type: array
                        example:
                          - 'The pickup date field is required.'
                        items:
                          type: string
                      passenger_country_code:
                        type: array
                        example:
                          - 'The passenger country code field is required.'
                        items:
                          type: string
      tags:
        - Fleet
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                pickup_location:
                  type: string
                  description: 'IATA/brand office code to pick up from.'
                  example: AACGH
                dropoff_location:
                  type: string
                  description: 'IATA/brand office code to return to.'
                  example: AACGH
                pickup_date:
                  type: string
                  description: 'Pickup date (Y-m-d).'
                  example: '2026-05-15'
                pickup_time:
                  type: string
                  description: 'Pickup time (HH:MM).'
                  example: '10:00'
                dropoff_date:
                  type: string
                  description: 'Drop-off date (Y-m-d).'
                  example: '2026-05-18'
                dropoff_time:
                  type: string
                  description: 'Drop-off time (HH:MM).'
                  example: '10:00'
                passenger_country_code:
                  type: string
                  description: 'ISO-3166-1 alpha-2 country of the driver.'
                  example: BR
                passenger_age:
                  type: integer
                  description: 'Driver age (18-99).'
                  example: 30
                brand:
                  type: integer
                  description: 'Single brand id to restrict the search to.'
                  example: 11
                brands:
                  type: array
                  description: 'Multiple brand ids (ignored if `brand` is set).'
                  example:
                    - 11
                    - 12
                  items:
                    type: integer
                include_unbookable:
                  type: boolean
                  description: 'When false, drops cars flagged `bookable:false` (e.g. HDT bare fallback rows) from `cars[]`. Per-brand `meta.{brand}.bookable_count` / `unbookable_count` always reflect the pre-filter counts so the client can tell what was hidden. Defaults to true (back-compat).'
                  example: false
              required:
                - pickup_location
                - dropoff_location
                - pickup_date
                - pickup_time
                - dropoff_date
                - dropoff_time
                - passenger_country_code
                - passenger_age
  /api/v1/fleet/show:
    post:
      summary: 'Fleet show (single vehicle detail + tokenc)'
      operationId: fleetShowsingleVehicleDetail+Tokenc
      description: "Step 2 of the booking flow. Replays a per-brand availability for the chosen\nvehicle_code + rate and returns a normalised `car` payload plus a `tokenc`\n(UUID) that the subsequent `POST /bookings` call must echo back.\n\nThe tokenc is cached for 3 hours alongside the detailed quote (prices,\nquote_token, ABG vehicle_type/class/group, HDT reference, etc.) so the\nbooking step can reconstruct the upstream OTA envelope exactly."
      parameters: []
      responses:
        200:
          description: ''
          content:
            application/json:
              schema:
                oneOf:
                  -
                    description: 'success (LOCALIZA Fiat Mobi, no coupon)'
                    type: object
                    example:
                      car:
                        brand_id: 11
                        brand_code: LOCALIZA
                        brand_name: LOCALIZA
                        rate_code: '011880'
                        vehicle_code: B
                        vehicle_name: 'Fiat Mobi 1.0'
                        category: null
                        transmission: Manual
                        fuel: null
                        seats: 4
                        doors: 2
                        luggage: 4
                        price:
                          currency: BRL
                          total: 272.3775
                          daily: null
                        rates:
                          -
                            code: '011880'
                            currency: BRL
                            total_amount: 236.85
                            estimated_total_amount: 272.3775
                            type: null
                        bookable: true
                        bookable_reason: null
                        pickup_office: null
                        dropoff_office: null
                        _raw:
                          ...: truncated
                      tokenc: 836c7fb31f624ca2a8b5a519f26825d2
                    properties:
                      car:
                        type: object
                        properties:
                          brand_id:
                            type: integer
                            example: 11
                          brand_code:
                            type: string
                            example: LOCALIZA
                          brand_name:
                            type: string
                            example: LOCALIZA
                          rate_code:
                            type: string
                            example: '011880'
                          vehicle_code:
                            type: string
                            example: B
                          vehicle_name:
                            type: string
                            example: 'Fiat Mobi 1.0'
                          category:
                            type: string
                            example: null
                            nullable: true
                          transmission:
                            type: string
                            example: Manual
                          fuel:
                            type: string
                            example: null
                            nullable: true
                          seats:
                            type: integer
                            example: 4
                          doors:
                            type: integer
                            example: 2
                          luggage:
                            type: integer
                            example: 4
                          price:
                            type: object
                            properties:
                              currency:
                                type: string
                                example: BRL
                              total:
                                type: number
                                example: 272.3775
                              daily:
                                type: string
                                example: null
                                nullable: true
                          rates:
                            type: array
                            example:
                              -
                                code: '011880'
                                currency: BRL
                                total_amount: 236.85
                                estimated_total_amount: 272.3775
                                type: null
                            items:
                              type: object
                              properties:
                                code:
                                  type: string
                                  example: '011880'
                                currency:
                                  type: string
                                  example: BRL
                                total_amount:
                                  type: number
                                  example: 236.85
                                estimated_total_amount:
                                  type: number
                                  example: 272.3775
                                type:
                                  type: string
                                  example: null
                                  nullable: true
                          bookable:
                            type: boolean
                            example: true
                          bookable_reason:
                            type: string
                            example: null
                            nullable: true
                          pickup_office:
                            type: string
                            example: null
                            nullable: true
                          dropoff_office:
                            type: string
                            example: null
                            nullable: true
                          _raw:
                            type: object
                            properties:
                              ...:
                                type: string
                                example: truncated
                      tokenc:
                        type: string
                        example: 836c7fb31f624ca2a8b5a519f26825d2
                  -
                    description: 'success with coupon applied (Wave 3 — SUMMER25, 25% off)'
                    type: object
                    example:
                      car:
                        brand_id: 11
                        brand_code: LOCALIZA
                        brand_name: LOCALIZA
                        rate_code: '011880'
                        vehicle_code: B
                        vehicle_name: 'Fiat Mobi 1.0'
                        price:
                          currency: BRL
                          total: 204.28
                          daily: null
                          with_discount: 204.28
                          original_total: 272.3775
                          discount_amount: 68.09
                        rates:
                          -
                            code: '011880'
                            currency: BRL
                            total_amount: 236.85
                            estimated_total_amount: 272.3775
                            type: null
                        bookable: true
                      tokenc: 836c7fb31f624ca2a8b5a519f26825d2
                      coupon_applied:
                        code: SUMMER25
                        discount_code: S25BR
                        amount: 68.09
                        currency: BRL
                    properties:
                      car:
                        type: object
                        properties:
                          brand_id:
                            type: integer
                            example: 11
                          brand_code:
                            type: string
                            example: LOCALIZA
                          brand_name:
                            type: string
                            example: LOCALIZA
                          rate_code:
                            type: string
                            example: '011880'
                          vehicle_code:
                            type: string
                            example: B
                          vehicle_name:
                            type: string
                            example: 'Fiat Mobi 1.0'
                          price:
                            type: object
                            properties:
                              currency:
                                type: string
                                example: BRL
                              total:
                                type: number
                                example: 204.28
                              daily:
                                type: string
                                example: null
                                nullable: true
                              with_discount:
                                type: number
                                example: 204.28
                              original_total:
                                type: number
                                example: 272.3775
                              discount_amount:
                                type: number
                                example: 68.09
                          rates:
                            type: array
                            example:
                              -
                                code: '011880'
                                currency: BRL
                                total_amount: 236.85
                                estimated_total_amount: 272.3775
                                type: null
                            items:
                              type: object
                              properties:
                                code:
                                  type: string
                                  example: '011880'
                                currency:
                                  type: string
                                  example: BRL
                                total_amount:
                                  type: number
                                  example: 236.85
                                estimated_total_amount:
                                  type: number
                                  example: 272.3775
                                type:
                                  type: string
                                  example: null
                                  nullable: true
                          bookable:
                            type: boolean
                            example: true
                      tokenc:
                        type: string
                        example: 836c7fb31f624ca2a8b5a519f26825d2
                      coupon_applied:
                        type: object
                        properties:
                          code:
                            type: string
                            example: SUMMER25
                          discount_code:
                            type: string
                            example: S25BR
                          amount:
                            type: number
                            example: 68.09
                          currency:
                            type: string
                            example: BRL
        401:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'Invalid API key'
                properties:
                  message:
                    type: string
                    example: 'Invalid API key'
        422:
          description: ''
          content:
            application/json:
              schema:
                oneOf:
                  -
                    description: 'coupon validation failed (pre-flight)'
                    type: object
                    example:
                      error: coupon_validation_failed
                      reason: coupon_not_found
                    properties:
                      error:
                        type: string
                        example: coupon_validation_failed
                      reason:
                        type: string
                        example: coupon_not_found
                  -
                    description: 'missing rate'
                    type: object
                    example:
                      message: 'The rate field is required.'
                      errors:
                        rate:
                          - 'The rate field is required.'
                    properties:
                      message:
                        type: string
                        example: 'The rate field is required.'
                      errors:
                        type: object
                        properties:
                          rate:
                            type: array
                            example:
                              - 'The rate field is required.'
                            items:
                              type: string
      tags:
        - Fleet
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                brand:
                  type: integer
                  description: 'Brand id (see `/me` for the enabled set).'
                  example: 11
                pickup_location:
                  type: string
                  description: ''
                  example: AACGH
                dropoff_location:
                  type: string
                  description: ''
                  example: AACGH
                pickup_date:
                  type: string
                  description: Y-m-d.
                  example: '2026-05-15'
                pickup_time:
                  type: string
                  description: 'HH:MM.'
                  example: '10:00'
                dropoff_date:
                  type: string
                  description: Y-m-d.
                  example: '2026-05-18'
                dropoff_time:
                  type: string
                  description: 'HH:MM.'
                  example: '10:00'
                rate:
                  type: string
                  description: 'Rate code returned by `/fleet/list`.'
                  example: '011880'
                vehicle_code:
                  type: string
                  description: 'SIPP or brand-specific car code.'
                  example: B
                passenger_country_code:
                  type: string
                  description: 'ISO alpha-2.'
                  example: BR
                passenger_age:
                  type: integer
                  description: 18-99.
                  example: 30
                coupon_code:
                  type: string
                  description: 'Optional discount coupon. When valid, the per-brand `discount_code` (if any) is forwarded to the GDS as `discount_number`, and a preview discount is applied on the response payload. Adapters that do not support `discount_number` (LOCALIZA, MOVIDA — their rate codes already encode promotional pricing) still get the post-quote price preview.'
                  example: SUMMER25
                  nullable: true
                tokenc:
                  type: string
                  description: 'Optional client-supplied token to reuse across replays. Defaults to a fresh UUID.'
                  example: 836c7fb31f624ca2a8b5a519f26825d2
              required:
                - brand
                - pickup_location
                - dropoff_location
                - pickup_date
                - pickup_time
                - dropoff_date
                - dropoff_time
                - rate
                - vehicle_code
                - passenger_country_code
                - passenger_age
  /api/v1/places/search:
    get:
      summary: 'Nearby places search'
      operationId: nearbyPlacesSearch
      description: "Radius search over `places` using PostGIS `ST_DWithin` on the pre-computed\n`location` geography column. Results are ordered by distance (km) and\ninclude the pivoted offices (with brand) so a caller can jump straight\ninto `/fleet/list`.\n\nResponses are cached under the `places` tag with a 30-minute TTL keyed on\nthe full request shape. Flush with `php artisan cache:flush places`."
      parameters:
        -
          in: query
          name: lat
          description: 'Latitude (-90..90).'
          example: -23.6273
          required: true
          schema:
            type: number
            description: 'Latitude (-90..90).'
            example: -23.6273
        -
          in: query
          name: lng
          description: 'Longitude (-180..180).'
          example: -46.6566
          required: true
          schema:
            type: number
            description: 'Longitude (-180..180).'
            example: -46.6566
        -
          in: query
          name: radius_km
          description: 'Search radius in km (max 500, default 50).'
          example: 25.0
          required: false
          schema:
            type: number
            description: 'Search radius in km (max 500, default 50).'
            example: 25.0
        -
          in: query
          name: country_id
          description: 'Restrict to a country.'
          example: 31
          required: false
          schema:
            type: integer
            description: 'Restrict to a country.'
            example: 31
        -
          in: query
          name: q
          description: 'Free-text filter on `places.name` (ilike).'
          example: Congonhas
          required: false
          schema:
            type: string
            description: 'Free-text filter on `places.name` (ilike).'
            example: Congonhas
      responses:
        200:
          description: 'success — Congonhas Airport (3 closest places)'
          content:
            application/json:
              schema:
                type: object
                example:
                  data:
                    -
                      uuid: c31ec1ee-3fdf-4cb2-8445-6d07e82dacd0
                      name: 'Congonhas Airport'
                      latitude: -23.62799975
                      longitude: -46.65703389
                      distance_km: 0.089
                      place_type: 1
                      offices:
                        -
                          code: AACGH
                          name: 'AGENCIA AEROPORTO CONGONHAS'
                          brand_id: 11
                          distance_km: 0.142
                    -
                      uuid: bdb35d57-fa14-4d04-b722-d2249c618225
                      name: Moema
                      latitude: -23.61
                      longitude: -46.665
                      distance_km: 2.099
                      place_type: 2
                      offices: []
                properties:
                  data:
                    type: array
                    example:
                      -
                        uuid: c31ec1ee-3fdf-4cb2-8445-6d07e82dacd0
                        name: 'Congonhas Airport'
                        latitude: -23.62799975
                        longitude: -46.65703389
                        distance_km: 0.089
                        place_type: 1
                        offices:
                          -
                            code: AACGH
                            name: 'AGENCIA AEROPORTO CONGONHAS'
                            brand_id: 11
                            distance_km: 0.142
                      -
                        uuid: bdb35d57-fa14-4d04-b722-d2249c618225
                        name: Moema
                        latitude: -23.61
                        longitude: -46.665
                        distance_km: 2.099
                        place_type: 2
                        offices: []
                    items:
                      type: object
                      properties:
                        uuid:
                          type: string
                          example: c31ec1ee-3fdf-4cb2-8445-6d07e82dacd0
                        name:
                          type: string
                          example: 'Congonhas Airport'
                        latitude:
                          type: number
                          example: -23.62799975
                        longitude:
                          type: number
                          example: -46.65703389
                        distance_km:
                          type: number
                          example: 0.089
                        place_type:
                          type: integer
                          example: 1
                        offices:
                          type: array
                          example:
                            -
                              code: AACGH
                              name: 'AGENCIA AEROPORTO CONGONHAS'
                              brand_id: 11
                              distance_km: 0.142
                          items:
                            type: object
                            properties:
                              code:
                                type: string
                                example: AACGH
                              name:
                                type: string
                                example: 'AGENCIA AEROPORTO CONGONHAS'
                              brand_id:
                                type: integer
                                example: 11
                              distance_km:
                                type: number
                                example: 0.142
        401:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'Invalid API key'
                properties:
                  message:
                    type: string
                    example: 'Invalid API key'
        422:
          description: 'missing or invalid coordinates'
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The lat field is required.'
                  errors:
                    lat:
                      - 'The lat field is required.'
                    lng:
                      - 'The lng field is required.'
                properties:
                  message:
                    type: string
                    example: 'The lat field is required.'
                  errors:
                    type: object
                    properties:
                      lat:
                        type: array
                        example:
                          - 'The lat field is required.'
                        items:
                          type: string
                      lng:
                        type: array
                        example:
                          - 'The lng field is required.'
                        items:
                          type: string
      tags:
        - Places
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                lat:
                  type: number
                  description: 'Must be between -90 and 90.'
                  example: -89
                lng:
                  type: number
                  description: 'Must be between -180 and 180.'
                  example: -179
                radius_km:
                  type: number
                  description: 'Must be at least 0. Must not be greater than 500.'
                  example: 7
                country_id:
                  type: integer
                  description: ''
                  example: 16
                q:
                  type: string
                  description: 'Must not be greater than 200 characters.'
                  example: 'n'
              required:
                - lat
                - lng
