openapi: 3.1.2

# ---------------------------------------------------------------------------
# Rocket Car Rentals Vietnam — Partner API v1
#
# WHY 3.1.x: this contract uses nothing that OpenAPI 3.2.0 (released 2025-09-19) adds — its headline
# additions are around streaming media types and richer tagging, and this is a plain
# request/response JSON API. 3.1.x has had years of tooling adoption behind it, and its schema
# dialect is plain JSON Schema 2020-12, which is exactly what the contract test in
# tests/unit/partner-api/openapi-contract.test.ts validates every example against with Ajv.
# Choosing the older, more widely implemented minor version puts no integration risk on a partner
# whose toolchain we do not control, and costs this contract nothing.
#
# We have NOT benchmarked 3.2.0 support across specific tools and make no claim about which
# parsers do or do not accept it. If the partner tells us their toolchain reads 3.2.0, revisiting
# this is cheap — the document would need no structural change.
#
# WHY .2: within 3.1.x, 3.1.2 is the latest patch. The specification states that the patch version
# SHOULD NOT be considered by tooling and that anything supporting OAS 3.1 should accept all 3.1.*
# releases, so this is the current patch of the chosen minor version at zero compatibility cost.
#
# This file is NOT decorative. tests/unit/partner-api/openapi-contract.test.ts asserts that it
# parses, that every $ref resolves, that every documented path exists in the implementation
# (apps/public/worker/routes/partner-api.ts) and vice versa, and that every example here validates
# against the schema it is attached to.
# ---------------------------------------------------------------------------

info:
  title: Rocket Car Rentals Vietnam — Partner API
  version: 1.0.0
  summary: B2B distribution API for fleet, locations, availability, quotes and reservations.
  description: |
    Machine-to-machine API for distribution partners of Rocket Car Rentals Vietnam.

    **Authentication** is OAuth 2.0 client credentials (RFC 6749 §4.4). Exchange your `client_id`
    and `client_secret` at `/api/partner/v1/oauth/token` for a short-lived bearer access token, then
    send it as `Authorization: Bearer <token>` on every other call.

    **Money** is always an object — an integer amount in minor units plus its currency and exponent.
    There are no bare floating-point amounts anywhere in this contract.

    **Availability** at Rocket is model-level, not unit-level. See the `/availability` operation.

    **Standards**: this API uses ACRISS/SIPP vehicle codes and, where an airport genuinely applies,
    IATA airport identifiers, as interoperability references. Rocket is not a member of, nor
    certified by, ACRISS, OpenTravel or IATA and makes no such claim.
  contact:
    name: Rocket Car Rentals Vietnam — Partner Integrations
    email: partners@rocketcarrentalsvietnam.com
  license:
    name: Proprietary — use governed by the partner agreement
    identifier: LicenseRef-Rocket-Partner-Agreement

servers:
  - url: https://rocketcarrentalsvietnam.com
    description: Production. The partner API is disabled until Rocket provisions your credentials.

tags:
  - name: Authentication
    description: Token issuance.
  - name: Reference data
    description: Fleet, locations and your own commercial codes. Change rarely; cache them.
  - name: Shopping
    description: Availability and quotes.
  - name: Reservations
    description: Create, read and cancel reservations.

security:
  - partnerBearer: []

paths:
  /api/partner/v1/oauth/token:
    post:
      tags: [Authentication]
      operationId: issueToken
      summary: Exchange client credentials for an access token
      description: |
        RFC 6749 §4.4. Present credentials either with HTTP Basic (`client_secret_basic`,
        recommended) or in the form body (`client_secret_post`).

        No refresh token is issued: a client-credentials client can always re-authenticate with its
        own credentials, so a refresh token would only be a second long-lived secret to protect.

        Tokens are valid for 600 seconds. Request a new one when it expires, or shortly before —
        do not request one per API call.
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/TokenRequest'
            example:
              grant_type: client_credentials
              scope: fleet:read locations:read availability:read quotes:write
      responses:
        '200':
          description: Access token issued.
          headers:
            Cache-Control: { $ref: '#/components/headers/CacheControl' }
            X-Request-Id: { $ref: '#/components/headers/RequestId' }
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
              example:
                access_token: <example-jwt-access-token-placeholder>
                token_type: Bearer
                expires_in: 600
                scope: fleet:read locations:read availability:read quotes:write
        '400':
          description: Malformed request, unsupported grant type, or a scope beyond your grant.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/OAuthError' }
              example:
                error: invalid_scope
                error_description: The requested scope exceeds this client's grant.
        '401':
          description: Client authentication failed, or the client/partner is not active.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/OAuthError' }
              example:
                error: invalid_client
                error_description: Client authentication failed.
        '429':
          description: Too many token requests.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/OAuthError' }
              example:
                error: slow_down
                error_description: Too many token requests for this client.
        '503':
          description: Token issuance is temporarily unavailable.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/OAuthError' }
              example:
                error: temporarily_unavailable
                error_description: Token issuance is temporarily unavailable.

  /api/partner/v1/fleet:
    get:
      tags: [Reference data]
      operationId: listFleet
      summary: List every vehicle offered to partners
      description: |
        Returns the complete partner-offerable fleet. A vehicle appears only when Rocket has
        published it AND its partner specification is complete (SIPP, doors, air conditioning,
        seats, transmission, fuel). Cache this; it changes rarely.
      security:
        - partnerBearer: [fleet:read]
      responses:
        '200':
          description: The fleet catalogue.
          headers:
            X-Request-Id: { $ref: '#/components/headers/RequestId' }
            X-RateLimit-Remaining: { $ref: '#/components/headers/RateLimitRemaining' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/FleetList' }
              examples:
                fleet:
                  $ref: '#/components/examples/FleetListExample'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/Unavailable' }

  /api/partner/v1/fleet/{vehicleCode}:
    get:
      tags: [Reference data]
      operationId: getVehicle
      summary: Fetch one vehicle specification
      security:
        - partnerBearer: [fleet:read]
      parameters:
        - name: vehicleCode
          in: path
          required: true
          description: Rocket vehicle code, e.g. `RC0007`. Permanent — it never changes for a given car model.
          schema: { $ref: '#/components/schemas/VehicleCode' }
      responses:
        '200':
          description: The vehicle.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/FleetVehicle' }
              examples:
                vehicle:
                  $ref: '#/components/examples/FleetVehicleExample'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /api/partner/v1/locations:
    get:
      tags: [Reference data]
      operationId: listLocations
      summary: List every active rental location
      description: |
        Rocket location codes are their own namespace. A location code is NOT an IATA code and NOT
        your code: `iataCode` is a separate optional field, populated only for airport stations, and
        your own code is agreed separately during onboarding.
      security:
        - partnerBearer: [locations:read]
      responses:
        '200':
          description: The location catalogue.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/LocationList' }
              examples:
                locations:
                  $ref: '#/components/examples/LocationListExample'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /api/partner/v1/locations/{locationCode}:
    get:
      tags: [Reference data]
      operationId: getLocation
      summary: Fetch one location
      security:
        - partnerBearer: [locations:read]
      parameters:
        - name: locationCode
          in: path
          required: true
          schema: { $ref: '#/components/schemas/LocationCode' }
      responses:
        '200':
          description: The location.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Location' }
              examples:
                location:
                  $ref: '#/components/examples/LocationExample'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /api/partner/v1/rates:
    get:
      tags: [Reference data]
      operationId: listRates
      summary: List your own account and rate codes
      description: |
        Only codes belonging to the authenticated partner, and only those Rocket has approved and
        activated. An empty list means no commercial terms have been agreed yet — you can still shop
        and book at Rocket's public rate by omitting `accountCode`/`rateCode`.
      security:
        - partnerBearer: [rates:read]
      responses:
        '200':
          description: Your sellable rate plans.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/RateList' }
              examples:
                rates:
                  $ref: '#/components/examples/RateListExample'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /api/partner/v1/availability:
    post:
      tags: [Shopping]
      operationId: searchAvailability
      summary: Shop — which vehicle classes can be offered for a rental window
      description: |
        **What "available" means at Rocket.** Rocket's inventory is modelled per vehicle MODEL, not
        per physical car: one fleet record represents a model Rocket supplies in quantity, and there
        is deliberately no per-unit date-overlap check anywhere in the system. An offer here
        therefore means *"Rocket offers this vehicle class at this location for these dates at this
        price"*. It is **not** a guarantee that a specific physical car is held for you, and no hold
        is placed by shopping or by quoting.

        If your platform requires hard unit-level availability, say so during onboarding — it is a
        change to Rocket's inventory model, not an API option.

        Each offer carries a `quoteId` valid for 30 minutes, so you can book straight from a shop
        response without calling `/quotes` again.
      security:
        - partnerBearer: [availability:read]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AvailabilityRequest' }
            examples:
              search:
                $ref: '#/components/examples/AvailabilityRequestExample'
      responses:
        '200':
          description: Offers for the requested window. An empty `offers` array is a valid answer.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AvailabilityResponse' }
              examples:
                offers:
                  $ref: '#/components/examples/AvailabilityResponseExample'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '409': { $ref: '#/components/responses/Conflict' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /api/partner/v1/quotes:
    post:
      tags: [Shopping]
      operationId: createQuote
      summary: Price one vehicle for one rental window
      description: |
        Returns a server-authoritative price with a full charge breakdown and a `quoteId` you pass
        to `POST /reservations`. Rocket re-prices at booking time and rejects the reservation with
        `QUOTE_STALE` if the price has moved — the quote is evidence of what was asked for, never a
        price Rocket takes on trust from the caller.
      security:
        - partnerBearer: [quotes:write]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/QuoteRequest' }
            examples:
              quote:
                $ref: '#/components/examples/QuoteRequestExample'
      responses:
        '201':
          description: The quote.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Quote' }
              examples:
                quote:
                  $ref: '#/components/examples/QuoteExample'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '409': { $ref: '#/components/responses/Conflict' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /api/partner/v1/reservations:
    post:
      tags: [Reservations]
      operationId: createReservation
      summary: Create a reservation from a quote
      description: |
        **`Idempotency-Key` is mandatory.** Retrying with the same key and the same body replays the
        original response instead of creating a second reservation. The same key with a *different*
        body is a `409 IDEMPOTENCY_CONFLICT`. Keys are remembered for 24 hours.

        Reservations are created as confirmed with no online payment taken: settlement between
        Rocket and the partner happens outside the API, on the terms of the partner agreement. The
        refundable security deposit is collected from the driver at pickup and is shown separately
        from the rental charge.
      security:
        - partnerBearer: [reservations:write]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ReservationRequest' }
            examples:
              reservation:
                $ref: '#/components/examples/ReservationRequestExample'
      responses:
        '201':
          description: Reservation created.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Reservation' }
              examples:
                reservation:
                  $ref: '#/components/examples/ReservationExample'
        '200':
          description: |
            Idempotent replay — this exact request already succeeded. The body is the original
            response and carries the header `Idempotent-Replay: true`.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Reservation' }
              examples:
                reservation:
                  $ref: '#/components/examples/ReservationExample'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '409': { $ref: '#/components/responses/Conflict' }
        '422':
          description: Rocket declined the reservation.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
              examples:
                rejected:
                  $ref: '#/components/examples/ReservationRejectedExample'
        '429': { $ref: '#/components/responses/RateLimited' }

    get:
      tags: [Reservations]
      operationId: listReservations
      summary: List your recent reservations
      security:
        - partnerBearer: [reservations:read]
      parameters:
        - name: limit
          in: query
          required: false
          description: 1-100, default 25. Newest first.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
      responses:
        '200':
          description: Your reservations. Never another partner's.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ReservationList' }
              examples:
                reservations:
                  $ref: '#/components/examples/ReservationListExample'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /api/partner/v1/reservations/{reservationId}:
    get:
      tags: [Reservations]
      operationId: getReservation
      summary: Fetch one reservation
      security:
        - partnerBearer: [reservations:read]
      parameters:
        - $ref: '#/components/parameters/ReservationId'
      responses:
        '200':
          description: The reservation.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Reservation' }
              examples:
                reservation:
                  $ref: '#/components/examples/ReservationExample'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }

    patch:
      tags: [Reservations]
      operationId: modifyReservation
      summary: Not supported in v1
      description: |
        Always answers `501 MODIFICATION_NOT_SUPPORTED`. Cancel and re-book instead. Documented
        rather than hidden so you can see it is a deliberate boundary, not a missing route.
      security:
        - partnerBearer: [reservations:write]
      parameters:
        - $ref: '#/components/parameters/ReservationId'
      responses:
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '501':
          description: Modification is not implemented in v1.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
              examples:
                notSupported:
                  $ref: '#/components/examples/ModificationNotSupportedExample'

  /api/partner/v1/reservations/{reservationId}/cancel:
    post:
      tags: [Reservations]
      operationId: cancelReservation
      summary: Cancel a reservation
      description: |
        Cancels a reservation that carries no money. If any payment has already been taken against
        it, Rocket does **not** cancel automatically: the call returns `409
        CANCELLATION_NEEDS_STAFF`, Rocket staff are alerted immediately, and a human applies the
        published cancellation-fee and refund policy. The API never invents a refund.

        Cancelling an already-cancelled reservation is a success and returns its current state.
      security:
        - partnerBearer: [reservations:cancel]
      parameters:
        - $ref: '#/components/parameters/ReservationId'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CancelRequest' }
            example:
              reason: Traveller changed plans
      responses:
        '200':
          description: Cancelled. The body is the reservation in its new state.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Reservation' }
              examples:
                cancelled:
                  $ref: '#/components/examples/CancelledReservationExample'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409':
          description: |
            The reservation cannot be cancelled through the API — either it is not in a cancellable
            state, or money has been taken and a human must handle it.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
              examples:
                needsStaff:
                  $ref: '#/components/examples/CancellationNeedsStaffExample'
        '429': { $ref: '#/components/responses/RateLimited' }

components:
  securitySchemes:
    partnerBearer:
      type: oauth2
      description: |
        OAuth 2.0 client credentials. Tokens are bearer tokens over TLS and last 600 seconds.
        Send them as `Authorization: Bearer <access_token>`.
      flows:
        clientCredentials:
          tokenUrl: https://rocketcarrentalsvietnam.com/api/partner/v1/oauth/token
          scopes:
            fleet:read: Read the vehicle catalogue.
            locations:read: Read rental locations.
            rates:read: Read your own account and rate codes.
            availability:read: Shop for offerable vehicles.
            quotes:write: Request a priced quote.
            reservations:read: Read your own reservations.
            reservations:write: Create reservations.
            reservations:cancel: Cancel your own reservations.

  headers:
    RequestId:
      description: Correlation id for this request. Quote it in any support conversation.
      schema: { type: string }
    CacheControl:
      description: Always `no-store` — partner responses are tenant-specific.
      schema: { type: string }
    RateLimitRemaining:
      description: Requests left in the current one-minute window.
      schema: { type: integer }

  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: |
        8-200 characters, unique per reservation attempt. A UUID is ideal. Reuse the SAME key when
        retrying after a timeout — that is what makes the retry safe.
      schema:
        type: string
        minLength: 8
        maxLength: 200
    ReservationId:
      name: reservationId
      in: path
      required: true
      description: Rocket reservation id, returned when the reservation was created.
      schema: { type: string }

  schemas:
    Money:
      type: object
      description: |
        An exact amount. `amount` is an integer in the currency's MINOR units and `exponent` says how
        many decimal places that is — 32500 with exponent 2 is 325.00 USD. `display` is a
        convenience rendering and must never be parsed as the authoritative value.
      required: [currency, amount, exponent, display]
      additionalProperties: false
      properties:
        currency:
          type: string
          pattern: '^[A-Z]{3}$'
          description: ISO 4217.
        amount:
          type: integer
          description: Integer minor units. Never a decimal, never a float.
        exponent:
          type: integer
          minimum: 0
          maximum: 4
        display:
          type: string
      examples:
        - { currency: USD, amount: 32500, exponent: 2, display: '325.00' }

    VehicleCode:
      type: string
      pattern: '^[A-Z0-9-]{2,32}$'
      description: Permanent Rocket vehicle code, e.g. `RC0007`.

    LocationCode:
      type: string
      pattern: '^[A-Z0-9-]{2,32}$'
      description: Permanent Rocket location code, e.g. `HCM-APT`. Not an IATA code.

    CommercialCode:
      type: string
      pattern: '^[A-Z0-9_-]{1,32}$'

    SippCode:
      type: [string, 'null']
      pattern: '^[A-Z]{4}$'
      description: |
        ACRISS/SIPP four-character classification: category, type, transmission/drive,
        fuel/air-conditioning. `null` only ever appears on a vehicle Rocket has not published.

    Extras:
      type: object
      additionalProperties: false
      description: |
        Optional extras, priced by Rocket's own catalogue. `fullProtection` supersedes
        `cdwInsurance`; sending both is a validation error, not a double charge.
      properties:
        cdwInsurance: { type: boolean }
        fullProtection: { type: boolean }
        additionalDriver: { type: boolean }
        roadSideAssistance: { type: boolean }
        childSeats: { type: integer, minimum: 0, maximum: 3 }

    RentalWindow:
      type: object
      required: [pickupLocation, dropoffLocation, pickupDate, pickupTime, dropoffDate, dropoffTime]
      properties:
        pickupLocation: { $ref: '#/components/schemas/LocationCode' }
        dropoffLocation: { $ref: '#/components/schemas/LocationCode' }
        pickupDate:
          type: string
          format: date
          description: ISO-8601 calendar date, LOCAL to the pickup location.
        pickupTime:
          type: string
          pattern: '^([01][0-9]|2[0-3]):[0-5][0-9]$'
          description: 24-hour local time at the pickup location.
        dropoffDate: { type: string, format: date }
        dropoffTime:
          type: string
          pattern: '^([01][0-9]|2[0-3]):[0-5][0-9]$'
        accountCode: { $ref: '#/components/schemas/CommercialCode' }
        rateCode: { $ref: '#/components/schemas/CommercialCode' }

    TokenRequest:
      type: object
      required: [grant_type]
      additionalProperties: false
      properties:
        grant_type:
          type: string
          const: client_credentials
        client_id:
          type: string
          description: Only when using `client_secret_post`. Prefer HTTP Basic.
        client_secret:
          type: string
        scope:
          type: string
          description: Space-delimited subset of your grant. Omit to receive everything you hold.

    TokenResponse:
      type: object
      required: [access_token, token_type, expires_in, scope]
      additionalProperties: false
      properties:
        access_token: { type: string }
        token_type: { type: string, const: Bearer }
        expires_in: { type: integer }
        scope: { type: string }

    OAuthError:
      type: object
      required: [error, error_description]
      additionalProperties: false
      description: RFC 6749 §5.2 error shape. Used ONLY by the token endpoint.
      properties:
        error: { type: string }
        error_description: { type: string }

    Error:
      type: object
      required: [error]
      additionalProperties: false
      description: The error envelope for every operation except the token endpoint.
      properties:
        error:
          type: object
          required: [code, message, requestId, retryable]
          additionalProperties: false
          properties:
            code:
              type: string
              description: Stable machine-readable code. Switch on this, never on `message`.
            message:
              type: string
              description: Human-safe explanation. Never contains customer data or internals.
            requestId: { type: string }
            retryable:
              type: boolean
              description: True only when retrying the identical request may succeed.
            fields:
              type: array
              items:
                type: object
                required: [field, message]
                additionalProperties: false
                properties:
                  field: { type: string }
                  message: { type: string }

    FleetVehicle:
      type: object
      required:
        - vehicleCode
        - sippCode
        - make
        - model
        - displayName
        - maxPassengers
        - doors
        - airConditioning
        - transmission
        - fuelType
        - securityDeposit
        - mileagePolicy
        - images
        - locationCodes
        - status
        - dataQuality
      additionalProperties: false
      properties:
        vehicleCode: { $ref: '#/components/schemas/VehicleCode' }
        sippCode: { $ref: '#/components/schemas/SippCode' }
        sippDescription:
          type: [string, 'null']
          description: The SIPP code spelled out, e.g. "Standard / SUV / Auto Unspecified Drive / Diesel, Air".
        make: { type: string }
        model: { type: string }
        displayName: { type: string }
        rocketClass:
          type: [string, 'null']
          description: Rocket's internal class label. Informational — SIPP is the interoperable classification.
        rocketCategory:
          type: [string, 'null']
          description: Rocket's marketing category. Informational.
        bodyType: { type: [string, 'null'] }
        maxPassengers: { type: [integer, 'null'], minimum: 1, maximum: 16 }
        doors: { type: [integer, 'null'], minimum: 2, maximum: 6 }
        airConditioning: { type: [boolean, 'null'] }
        transmission:
          type: [string, 'null']
          enum: [automatic, manual, null]
        driveType:
          type: [string, 'null']
          enum: [fwd, rwd, awd, 4wd, null]
        fuelType:
          type: [string, 'null']
          enum: [gasoline, diesel, electric, hybrid, null]
        baggage:
          type: [object, 'null']
          additionalProperties: false
          properties:
            large: { type: [integer, 'null'] }
            small: { type: [integer, 'null'] }
        minDriverAge: { type: [integer, 'null'] }
        securityDeposit:
          allOf:
            - $ref: '#/components/schemas/Money'
          description: Refundable, collected from the driver at pickup. Not part of the rental charge.
        mileagePolicy:
          type: object
          additionalProperties: false
          required: [includedKmPerDay, excessFeePerKm]
          properties:
            includedKmPerDay:
              type: [integer, 'null']
              description: Kilometres included per rental day. `null` means no stored limit.
            excessFeePerKm:
              oneOf:
                - $ref: '#/components/schemas/Money'
                - type: 'null'
              description: Charged per kilometre beyond the allowance. Quoted in VND.
        images:
          type: array
          items: { type: string, format: uri }
        locationCodes:
          type: array
          description: Locations where this vehicle is offered.
          items: { $ref: '#/components/schemas/LocationCode' }
        status:
          type: string
          const: available
        dataQuality:
          type: object
          additionalProperties: false
          required: [status, missingFields]
          description: |
            Rocket's own assessment of this record. Only `complete_unverified` and `verified`
            vehicles are ever returned by this API; the field is present so you can prefer verified
            data and so the contract is honest about the difference.
          properties:
            status:
              type: string
              enum: [incomplete, complete_unverified, verified]
            missingFields:
              type: array
              items: { type: string }

    FleetList:
      type: object
      required: [vehicles, count]
      additionalProperties: false
      properties:
        vehicles:
          type: array
          items: { $ref: '#/components/schemas/FleetVehicle' }
        count: { type: integer }

    Location:
      type: object
      required:
        - locationCode
        - name
        - locationType
        - countryCode
        - city
        - timezone
        - iataCode
        - capabilities
        - branchCode
      additionalProperties: false
      properties:
        locationCode: { $ref: '#/components/schemas/LocationCode' }
        name: { type: string }
        nameLocal: { type: [string, 'null'] }
        locationType:
          type: string
          enum: [city, airport]
        countryCode: { type: string, pattern: '^[A-Z]{2}$' }
        city: { type: string }
        address: { type: [string, 'null'] }
        coordinates:
          type: [object, 'null']
          additionalProperties: false
          required: [latitude, longitude]
          properties:
            latitude: { type: number }
            longitude: { type: number }
        timezone:
          type: string
          description: IANA zone. All local dates/times in requests are interpreted in it.
        iataCode:
          type: [string, 'null']
          pattern: '^[A-Z]{3}$'
          description: >-
            IATA airport identifier. Present only when `locationType` is `airport`. A DIFFERENT
            namespace from `locationCode` — never send one where the other is expected.
        capabilities:
          type: object
          additionalProperties: false
          required: [pickup, dropoff, oneWay, delivery]
          properties:
            pickup: { type: boolean }
            dropoff: { type: boolean }
            oneWay: { type: boolean }
            delivery: { type: boolean }
        openingHours: { type: [string, 'null'] }
        afterHoursPolicy: { type: [string, 'null'] }
        branchCode:
          type: string
          description: Rocket branch this station belongs to. Fleet coverage and one-way fees are branch-scoped.

    LocationList:
      type: object
      required: [locations, count]
      additionalProperties: false
      properties:
        locations:
          type: array
          items: { $ref: '#/components/schemas/Location' }
        count: { type: integer }

    RateList:
      type: object
      required: [rates, count]
      additionalProperties: false
      properties:
        rates:
          type: array
          items:
            type: object
            required: [accountCode, rateCode, rateType, currency, status]
            additionalProperties: false
            properties:
              accountCode: { $ref: '#/components/schemas/CommercialCode' }
              rateCode: { $ref: '#/components/schemas/CommercialCode' }
              rateType:
                type: string
                enum: [retail, net, commissionable, corporate, promotional]
                description: |
                  What the code MEANS commercially. `retail` is Rocket's public price; `net` is a
                  discounted rate you resell at your own price; `commissionable` keeps Rocket's price
                  and pays you commission.
              currency: { type: string, pattern: '^[A-Z]{3}$' }
              validFrom: { type: [string, 'null'], format: date }
              validTo: { type: [string, 'null'], format: date }
              status: { type: string, const: active }
        count: { type: integer }

    AvailabilityRequest:
      allOf:
        - $ref: '#/components/schemas/RentalWindow'
        - type: object
          additionalProperties: false
          properties:
            pickupLocation: true
            dropoffLocation: true
            pickupDate: true
            pickupTime: true
            dropoffDate: true
            dropoffTime: true
            accountCode: true
            rateCode: true
            extras: { $ref: '#/components/schemas/Extras' }
            vehicleCodes:
              type: array
              maxItems: 50
              items: { $ref: '#/components/schemas/VehicleCode' }
              description: Restrict the answer to these vehicles. Omit for the whole offerable fleet.

    AvailabilityOffer:
      type: object
      required: [quoteId, quoteExpiresAt, vehicle, rentalDays, dailyRate, estimatedTotal, currency]
      additionalProperties: false
      properties:
        quoteId:
          type: string
          description: Pass to `POST /reservations`. Valid for 30 minutes.
        quoteExpiresAt: { type: string, format: date-time }
        vehicle: { $ref: '#/components/schemas/FleetVehicle' }
        rentalDays: { type: integer, minimum: 1 }
        dailyRate: { $ref: '#/components/schemas/Money' }
        estimatedTotal: { $ref: '#/components/schemas/Money' }
        currency: { type: string, pattern: '^[A-Z]{3}$' }

    AvailabilityResponse:
      type: object
      required:
        - availabilityModel
        - pickupLocation
        - dropoffLocation
        - pickupDateTime
        - dropoffDateTime
        - rentalDays
        - accountCode
        - rateCode
        - offers
      additionalProperties: false
      properties:
        availabilityModel:
          type: string
          const: model_level_on_request
          description: |
            Restated on every response so it can never be read as a unit-level guarantee: Rocket
            offers vehicle MODELS in quantity and holds no specific car at shop or quote time.
        pickupLocation: { $ref: '#/components/schemas/LocationCode' }
        dropoffLocation: { $ref: '#/components/schemas/LocationCode' }
        pickupDateTime: { type: string }
        dropoffDateTime: { type: string }
        rentalDays: { type: integer, minimum: 1 }
        accountCode: { type: [string, 'null'] }
        rateCode: { type: [string, 'null'] }
        offers:
          type: array
          items: { $ref: '#/components/schemas/AvailabilityOffer' }

    QuoteRequest:
      allOf:
        - $ref: '#/components/schemas/RentalWindow'
        - type: object
          required: [vehicleCode]
          additionalProperties: false
          properties:
            pickupLocation: true
            dropoffLocation: true
            pickupDate: true
            pickupTime: true
            dropoffDate: true
            dropoffTime: true
            accountCode: true
            rateCode: true
            vehicleCode: { $ref: '#/components/schemas/VehicleCode' }
            extras: { $ref: '#/components/schemas/Extras' }

    ExtraLine:
      type: object
      required: [code, description, quantity, total]
      additionalProperties: false
      properties:
        code: { type: string }
        description: { type: string }
        quantity: { type: integer }
        total: { $ref: '#/components/schemas/Money' }

    Quote:
      type: object
      required: [quoteId, expiresAt, currency, vehicle, rental, rate, charges, securityDeposit, payment, inclusions, exclusions]
      additionalProperties: false
      properties:
        quoteId: { type: string }
        expiresAt: { type: string, format: date-time }
        currency: { type: string, pattern: '^[A-Z]{3}$' }
        vehicle: { $ref: '#/components/schemas/FleetVehicle' }
        rental:
          type: object
          additionalProperties: false
          required: [pickupLocation, dropoffLocation, pickupDateTime, dropoffDateTime, rentalDays, timezone]
          properties:
            pickupLocation: { $ref: '#/components/schemas/LocationCode' }
            dropoffLocation: { $ref: '#/components/schemas/LocationCode' }
            pickupDateTime: { type: string }
            dropoffDateTime: { type: string }
            rentalDays: { type: integer, minimum: 1 }
            timezone: { type: string }
        rate:
          type: object
          additionalProperties: false
          required: [accountCode, rateCode, rateType, adjustmentApplied]
          properties:
            accountCode: { type: [string, 'null'] }
            rateCode: { type: [string, 'null'] }
            rateType: { type: string }
            adjustmentApplied:
              type: boolean
              description: Whether an approved partner-specific adjustment moved this off Rocket's public price.
        charges:
          type: object
          additionalProperties: false
          required: [dailyRate, baseTotal, extras, extrasTotal, oneWayFee, discount, total, commission]
          properties:
            dailyRate: { $ref: '#/components/schemas/Money' }
            baseTotal:
              allOf: [{ $ref: '#/components/schemas/Money' }]
              description: Rental days x the applicable daily rate, before extras and fees.
            extras:
              type: array
              items: { $ref: '#/components/schemas/ExtraLine' }
            extrasTotal: { $ref: '#/components/schemas/Money' }
            oneWayFee:
              allOf: [{ $ref: '#/components/schemas/Money' }]
              description: Charged when pickup and dropoff are in different Rocket branches. Zero otherwise.
            discount: { $ref: '#/components/schemas/Money' }
            total:
              allOf: [{ $ref: '#/components/schemas/Money' }]
              description: What is payable for the rental. Excludes the refundable security deposit.
            commission:
              oneOf:
                - $ref: '#/components/schemas/Money'
                - type: 'null'
              description: Commission owed to you out of `total`. Null unless your rate plan is commissionable.
        securityDeposit: { $ref: '#/components/schemas/Money' }
        payment:
          type: object
          additionalProperties: false
          required: [model, prepaymentRequired, dueAtPickup]
          properties:
            model:
              type: string
              const: settled_with_partner
              description: |
                No online consumer payment is taken by this API. Money moves between Rocket and the
                partner on the terms of the partner agreement.
            prepaymentRequired: { $ref: '#/components/schemas/Money' }
            dueAtPickup: { $ref: '#/components/schemas/Money' }
        inclusions:
          type: array
          items: { type: string }
        exclusions:
          type: array
          items: { type: string }

    Driver:
      type: object
      required: [firstName, lastName, email, phone, dateOfBirth]
      additionalProperties: false
      properties:
        title:
          type: string
          enum: [mr, mrs, ms, dr]
        firstName: { type: string, minLength: 1, maxLength: 100 }
        lastName: { type: string, minLength: 1, maxLength: 100 }
        email: { type: string, format: email, maxLength: 200 }
        phone:
          type: string
          description: E.164, including the country code, e.g. `+84901234567`.
          minLength: 5
          maxLength: 20
        dateOfBirth:
          type: string
          format: date
          description: The driver must be 18 or over — Vietnamese civil law, enforced server-side.
        nationality:
          type: string
          pattern: '^[A-Za-z]{2}$'
          description: ISO-3166-1 alpha-2. Omit if unknown; it is never inferred.
        idNumber: { type: string, maxLength: 50 }

    ReservationRequest:
      type: object
      required: [quoteId, driver]
      additionalProperties: false
      properties:
        quoteId: { type: string, minLength: 8, maxLength: 4096 }
        partnerReference:
          type: string
          maxLength: 64
          description: |
            Your own reservation reference. Unique per partner — reusing one with a different
            idempotency key returns `409 DUPLICATE_PARTNER_REFERENCE`.
        driver: { $ref: '#/components/schemas/Driver' }
        remarks: { type: string, maxLength: 1000 }
        flightNumber: { type: string, maxLength: 20 }

    Reservation:
      type: object
      required:
        - reservationId
        - partnerReference
        - rocketBookingCode
        - status
        - vehicleCode
        - pickupLocation
        - dropoffLocation
        - pickupDateTime
        - dropoffDateTime
        - rentalDays
        - currency
        - total
        - commission
        - securityDeposit
        - driver
        - accountCode
        - rateCode
        - createdAt
      additionalProperties: false
      properties:
        reservationId: { type: string }
        partnerReference: { type: [string, 'null'] }
        rocketBookingCode:
          type: string
          description: The confirmation number the traveller quotes at the rental desk.
        status:
          type: string
          enum: [pending, confirmed, in_progress, completed, cancelled, expired, rejected]
        vehicleCode: { type: [string, 'null'] }
        pickupLocation: { type: [string, 'null'] }
        dropoffLocation: { type: [string, 'null'] }
        pickupDateTime: { type: [string, 'null'] }
        dropoffDateTime: { type: [string, 'null'] }
        rentalDays: { type: [integer, 'null'] }
        currency: { type: string, pattern: '^[A-Z]{3}$' }
        total:
          allOf: [{ $ref: '#/components/schemas/Money' }]
          description: >-
            What you are billed for this reservation. Frozen at booking time. Under an approved net
            or markup rate plan this differs from Rocket's own internal gross figure; this is the
            number that matches the quote you booked from.
        commission:
          oneOf:
            - $ref: '#/components/schemas/Money'
            - type: 'null'
          description: Commission Rocket owes you out of `total`. Null unless your plan is commissionable.
        securityDeposit: { $ref: '#/components/schemas/Money' }
        driver:
          type: object
          additionalProperties: false
          required: [firstName, lastName, email]
          description: The minimum needed to identify the booking. Rocket returns no other customer data.
          properties:
            firstName: { type: [string, 'null'] }
            lastName: { type: [string, 'null'] }
            email: { type: [string, 'null'] }
        accountCode: { type: [string, 'null'] }
        rateCode: { type: [string, 'null'] }
        createdAt: { type: string }

    ReservationList:
      type: object
      required: [reservations, count, limit]
      additionalProperties: false
      properties:
        reservations:
          type: array
          items: { $ref: '#/components/schemas/Reservation' }
        count: { type: integer }
        limit: { type: integer }

    CancelRequest:
      type: object
      required: [reason]
      additionalProperties: false
      properties:
        reason: { type: string, minLength: 1, maxLength: 500 }

  responses:
    BadRequest:
      description: The request failed validation or named something that does not exist.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          examples:
            validation:
              $ref: '#/components/examples/ValidationErrorExample'
    Unauthorized:
      description: Missing, malformed, expired or otherwise unacceptable access token.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          examples:
            invalidToken:
              $ref: '#/components/examples/InvalidTokenExample'
    Forbidden:
      description: The token is valid but lacks the required scope, or the partner is not active.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          examples:
            scope:
              $ref: '#/components/examples/InsufficientScopeExample'
    NotFound:
      description: |
        No such resource for THIS partner. A resource belonging to another partner is indistinguishable
        from one that does not exist — deliberately.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          examples:
            notFound:
              $ref: '#/components/examples/NotFoundExample'
    Conflict:
      description: The request is well formed but conflicts with current state.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          examples:
            idempotency:
              $ref: '#/components/examples/IdempotencyConflictExample'
    RateLimited:
      description: Rate limit exceeded. Honour `Retry-After`.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          examples:
            rateLimited:
              $ref: '#/components/examples/RateLimitedExample'
    Unavailable:
      description: The partner API is disabled on this environment, or a dependency is unavailable.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          examples:
            disabled:
              $ref: '#/components/examples/ApiDisabledExample'

  examples:
    FleetVehicleExample:
      summary: A published, verified vehicle
      value:
        vehicleCode: RC0007
        sippCode: SFAD
        sippDescription: Standard / SUV / Auto Unspecified Drive / Diesel, Air
        make: Ford
        model: Everest
        displayName: Ford Everest
        rocketClass: Full-Size
        rocketCategory: full_size
        bodyType: suv
        maxPassengers: 7
        doors: 5
        airConditioning: true
        transmission: automatic
        driveType: 4wd
        fuelType: diesel
        baggage: { large: 3, small: 2 }
        minDriverAge: 21
        securityDeposit: { currency: USD, amount: 70000, exponent: 2, display: '700.00' }
        mileagePolicy:
          includedKmPerDay: 300
          excessFeePerKm: { currency: VND, amount: 5000, exponent: 0, display: '5000' }
        images:
          - https://cdn.rocketcarrentalsvietnam.com/vehicles/optimized/ford_everest_1.webp
        locationCodes: [DN-APT, DN-CITY, HCM-APT, HCM-CITY, HN-APT, HN-CITY, NT-APT, NT-CITY, PQ-APT, PQ-CITY]
        status: available
        dataQuality:
          status: verified
          missingFields: []

    FleetListExample:
      summary: Fleet catalogue (truncated to one vehicle)
      value:
        count: 1
        vehicles:
          - vehicleCode: RC0007
            sippCode: SFAD
            sippDescription: Standard / SUV / Auto Unspecified Drive / Diesel, Air
            make: Ford
            model: Everest
            displayName: Ford Everest
            rocketClass: Full-Size
            rocketCategory: full_size
            bodyType: suv
            maxPassengers: 7
            doors: 5
            airConditioning: true
            transmission: automatic
            driveType: 4wd
            fuelType: diesel
            baggage: { large: 3, small: 2 }
            minDriverAge: 21
            securityDeposit: { currency: USD, amount: 70000, exponent: 2, display: '700.00' }
            mileagePolicy:
              includedKmPerDay: 300
              excessFeePerKm: { currency: VND, amount: 5000, exponent: 0, display: '5000' }
            images:
              - https://cdn.rocketcarrentalsvietnam.com/vehicles/optimized/ford_everest_1.webp
            locationCodes: [HCM-APT, HCM-CITY]
            status: available
            dataQuality:
              status: verified
              missingFields: []

    LocationExample:
      summary: An airport station
      value:
        locationCode: HCM-APT
        name: Tan Son Nhat International Airport
        nameLocal: Sân bay Quốc tế Tân Sơn Nhất
        locationType: airport
        countryCode: VN
        city: Ho Chi Minh City
        address: null
        coordinates: null
        timezone: Asia/Ho_Chi_Minh
        iataCode: SGN
        capabilities: { pickup: true, dropoff: true, oneWay: true, delivery: false }
        openingHours: null
        afterHoursPolicy: null
        branchCode: HCM

    LocationListExample:
      summary: City and airport stations for one branch
      value:
        count: 2
        locations:
          - locationCode: HCM-CITY
            name: Ho Chi Minh City
            nameLocal: TP. Hồ Chí Minh
            locationType: city
            countryCode: VN
            city: Ho Chi Minh City
            address: null
            coordinates: null
            timezone: Asia/Ho_Chi_Minh
            iataCode: null
            capabilities: { pickup: true, dropoff: true, oneWay: true, delivery: false }
            openingHours: null
            afterHoursPolicy: null
            branchCode: HCM
          - locationCode: HCM-APT
            name: Tan Son Nhat International Airport
            nameLocal: Sân bay Quốc tế Tân Sơn Nhất
            locationType: airport
            countryCode: VN
            city: Ho Chi Minh City
            address: null
            coordinates: null
            timezone: Asia/Ho_Chi_Minh
            iataCode: SGN
            capabilities: { pickup: true, dropoff: true, oneWay: true, delivery: false }
            openingHours: null
            afterHoursPolicy: null
            branchCode: HCM

    RateListExample:
      summary: One approved commissionable plan
      value:
        count: 1
        rates:
          - accountCode: DEMOTA-01
            rateCode: NET-STD
            rateType: commissionable
            currency: USD
            validFrom: '2026-09-01'
            validTo: null
            status: active

    AvailabilityRequestExample:
      summary: Three days, airport pickup, city dropoff, no extras
      value:
        pickupLocation: HCM-APT
        dropoffLocation: HCM-CITY
        pickupDate: '2026-09-10'
        pickupTime: '10:00'
        dropoffDate: '2026-09-13'
        dropoffTime: '10:00'

    AvailabilityResponseExample:
      summary: One offer
      value:
        availabilityModel: model_level_on_request
        pickupLocation: HCM-APT
        dropoffLocation: HCM-CITY
        pickupDateTime: '2026-09-10T10:00:00'
        dropoffDateTime: '2026-09-13T10:00:00'
        rentalDays: 3
        accountCode: null
        rateCode: null
        offers:
          - quoteId: q1.PLACEHOLDERQUOTEPAYLOAD.PLACEHOLDERSIGNATURE
            quoteExpiresAt: '2026-09-08T09:30:00.000Z'
            rentalDays: 3
            currency: USD
            dailyRate: { currency: USD, amount: 12500, exponent: 2, display: '125.00' }
            estimatedTotal: { currency: USD, amount: 37500, exponent: 2, display: '375.00' }
            vehicle:
              vehicleCode: RC0007
              sippCode: SFAD
              sippDescription: Standard / SUV / Auto Unspecified Drive / Diesel, Air
              make: Ford
              model: Everest
              displayName: Ford Everest
              rocketClass: Full-Size
              rocketCategory: full_size
              bodyType: suv
              maxPassengers: 7
              doors: 5
              airConditioning: true
              transmission: automatic
              driveType: 4wd
              fuelType: diesel
              baggage: { large: 3, small: 2 }
              minDriverAge: 21
              securityDeposit: { currency: USD, amount: 70000, exponent: 2, display: '700.00' }
              mileagePolicy:
                includedKmPerDay: 300
                excessFeePerKm: { currency: VND, amount: 5000, exponent: 0, display: '5000' }
              images:
                - https://cdn.rocketcarrentalsvietnam.com/vehicles/optimized/ford_everest_1.webp
              locationCodes: [HCM-APT, HCM-CITY]
              status: available
              dataQuality: { status: verified, missingFields: [] }

    QuoteRequestExample:
      summary: Price one vehicle with a child seat
      value:
        vehicleCode: RC0007
        pickupLocation: HCM-APT
        dropoffLocation: HCM-CITY
        pickupDate: '2026-09-10'
        pickupTime: '10:00'
        dropoffDate: '2026-09-13'
        dropoffTime: '10:00'
        extras:
          childSeats: 1

    QuoteExample:
      summary: A priced quote
      value:
        quoteId: q1.PLACEHOLDERQUOTEPAYLOAD.PLACEHOLDERSIGNATURE
        expiresAt: '2026-09-08T09:30:00.000Z'
        currency: USD
        rental:
          pickupLocation: HCM-APT
          dropoffLocation: HCM-CITY
          pickupDateTime: '2026-09-10T10:00:00'
          dropoffDateTime: '2026-09-13T10:00:00'
          rentalDays: 3
          timezone: Asia/Ho_Chi_Minh
        rate:
          accountCode: null
          rateCode: null
          rateType: retail
          adjustmentApplied: false
        charges:
          dailyRate: { currency: USD, amount: 12500, exponent: 2, display: '125.00' }
          baseTotal: { currency: USD, amount: 37500, exponent: 2, display: '375.00' }
          extras:
            - code: childSeats
              description: Child seat
              quantity: 1
              total: { currency: USD, amount: 2000, exponent: 2, display: '20.00' }
          extrasTotal: { currency: USD, amount: 2000, exponent: 2, display: '20.00' }
          oneWayFee: { currency: USD, amount: 0, exponent: 2, display: '0.00' }
          discount: { currency: USD, amount: 0, exponent: 2, display: '0.00' }
          total: { currency: USD, amount: 39500, exponent: 2, display: '395.00' }
          commission: null
        securityDeposit: { currency: USD, amount: 70000, exponent: 2, display: '700.00' }
        payment:
          model: settled_with_partner
          prepaymentRequired: { currency: USD, amount: 0, exponent: 2, display: '0.00' }
          dueAtPickup: { currency: USD, amount: 0, exponent: 2, display: '0.00' }
        inclusions:
          - Compulsory motor third-party liability insurance
          - Vehicle registration and road tax
          - Standard roadside support during business hours
        exclusions:
          - Fuel — the vehicle is supplied and must be returned at the same level
          - Refundable security deposit (collected at pickup, shown separately)
          - Traffic fines, tolls and parking
          - Excess distance beyond the vehicle mileage policy
        vehicle:
          vehicleCode: RC0007
          sippCode: SFAD
          sippDescription: Standard / SUV / Auto Unspecified Drive / Diesel, Air
          make: Ford
          model: Everest
          displayName: Ford Everest
          rocketClass: Full-Size
          rocketCategory: full_size
          bodyType: suv
          maxPassengers: 7
          doors: 5
          airConditioning: true
          transmission: automatic
          driveType: 4wd
          fuelType: diesel
          baggage: { large: 3, small: 2 }
          minDriverAge: 21
          securityDeposit: { currency: USD, amount: 70000, exponent: 2, display: '700.00' }
          mileagePolicy:
            includedKmPerDay: 300
            excessFeePerKm: { currency: VND, amount: 5000, exponent: 0, display: '5000' }
          images:
            - https://cdn.rocketcarrentalsvietnam.com/vehicles/optimized/ford_everest_1.webp
          locationCodes: [HCM-APT, HCM-CITY]
          status: available
          dataQuality: { status: verified, missingFields: [] }

    ReservationRequestExample:
      summary: Book the quote above
      value:
        quoteId: q1.PLACEHOLDERQUOTEPAYLOAD.PLACEHOLDERSIGNATURE
        partnerReference: DEMO-REF-000123
        driver:
          title: ms
          firstName: Alex
          lastName: Sample
          email: alex.sample@example.com
          phone: '+84901234567'
          dateOfBirth: '1990-04-12'
          nationality: GB
        flightNumber: VN123
        remarks: Traveller arrives on an evening flight.

    ReservationExample:
      summary: A confirmed reservation
      value:
        reservationId: 8f14e45f-ceea-467a-9f4e-000000000001
        partnerReference: DEMO-REF-000123
        rocketBookingCode: 7A3C91B2
        status: confirmed
        vehicleCode: RC0007
        pickupLocation: HCM-APT
        dropoffLocation: HCM-CITY
        pickupDateTime: '2026-09-10T10:00:00'
        dropoffDateTime: '2026-09-13T10:00:00'
        rentalDays: 3
        currency: USD
        total: { currency: USD, amount: 39500, exponent: 2, display: '395.00' }
        commission: null
        securityDeposit: { currency: USD, amount: 70000, exponent: 2, display: '700.00' }
        driver:
          firstName: Alex
          lastName: Sample
          email: alex.sample@example.com
        accountCode: null
        rateCode: null
        createdAt: '2026-09-08T09:00:00.000Z'

    ReservationListExample:
      summary: One reservation
      value:
        count: 1
        limit: 25
        reservations:
          - reservationId: 8f14e45f-ceea-467a-9f4e-000000000001
            partnerReference: DEMO-REF-000123
            rocketBookingCode: 7A3C91B2
            status: confirmed
            vehicleCode: RC0007
            pickupLocation: HCM-APT
            dropoffLocation: HCM-CITY
            pickupDateTime: '2026-09-10T10:00:00'
            dropoffDateTime: '2026-09-13T10:00:00'
            rentalDays: 3
            currency: USD
            total: { currency: USD, amount: 39500, exponent: 2, display: '395.00' }
            commission: null
            securityDeposit: { currency: USD, amount: 70000, exponent: 2, display: '700.00' }
            driver:
              firstName: Alex
              lastName: Sample
              email: alex.sample@example.com
            accountCode: null
            rateCode: null
            createdAt: '2026-09-08T09:00:00.000Z'

    CancelledReservationExample:
      summary: After a successful cancellation
      value:
        reservationId: 8f14e45f-ceea-467a-9f4e-000000000001
        partnerReference: DEMO-REF-000123
        rocketBookingCode: 7A3C91B2
        status: cancelled
        vehicleCode: RC0007
        pickupLocation: HCM-APT
        dropoffLocation: HCM-CITY
        pickupDateTime: '2026-09-10T10:00:00'
        dropoffDateTime: '2026-09-13T10:00:00'
        rentalDays: 3
        currency: USD
        total: { currency: USD, amount: 39500, exponent: 2, display: '395.00' }
        commission: null
        securityDeposit: { currency: USD, amount: 70000, exponent: 2, display: '700.00' }
        driver:
          firstName: Alex
          lastName: Sample
          email: alex.sample@example.com
        accountCode: null
        rateCode: null
        createdAt: '2026-09-08T09:00:00.000Z'

    ValidationErrorExample:
      value:
        error:
          code: VALIDATION_ERROR
          message: The request body failed validation.
          requestId: 3f1c9e4a-0000-4000-8000-000000000000
          retryable: false
          fields:
            - field: dropoffDate
              message: The dropoff must be after the pickup

    InvalidTokenExample:
      value:
        error:
          code: INVALID_TOKEN
          message: The access token is missing, malformed, or expired.
          requestId: 3f1c9e4a-0000-4000-8000-000000000001
          retryable: false

    InsufficientScopeExample:
      value:
        error:
          code: INSUFFICIENT_SCOPE
          message: This operation requires the "reservations:write" scope.
          requestId: 3f1c9e4a-0000-4000-8000-000000000002
          retryable: false

    NotFoundExample:
      value:
        error:
          code: NOT_FOUND
          message: The requested resource does not exist.
          requestId: 3f1c9e4a-0000-4000-8000-000000000003
          retryable: false

    IdempotencyConflictExample:
      value:
        error:
          code: IDEMPOTENCY_CONFLICT
          message: This Idempotency-Key was already used with a different request body.
          requestId: 3f1c9e4a-0000-4000-8000-000000000004
          retryable: false

    RateLimitedExample:
      value:
        error:
          code: RATE_LIMITED
          message: Rate limit exceeded.
          requestId: 3f1c9e4a-0000-4000-8000-000000000005
          retryable: true

    ApiDisabledExample:
      value:
        error:
          code: API_DISABLED
          message: The partner API is not enabled on this environment.
          requestId: 3f1c9e4a-0000-4000-8000-000000000006
          retryable: true

    ReservationRejectedExample:
      value:
        error:
          code: RESERVATION_REJECTED
          message: Rocket declined the reservation. Quote the requestId to support for the reason.
          requestId: 3f1c9e4a-0000-4000-8000-000000000007
          retryable: false

    ModificationNotSupportedExample:
      value:
        error:
          code: MODIFICATION_NOT_SUPPORTED
          message: Reservation modification is not supported in v1. Cancel and re-book.
          requestId: 3f1c9e4a-0000-4000-8000-000000000008
          retryable: false

    CancellationNeedsStaffExample:
      value:
        error:
          code: CANCELLATION_NEEDS_STAFF
          message: >-
            Money has already been taken on this reservation, so cancellation is handled by Rocket
            staff. The request has been recorded and Rocket has been alerted.
          requestId: 3f1c9e4a-0000-4000-8000-000000000009
          retryable: false
