> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dev.gojinko.com/llms.txt
> Use this file to discover all available pages before exploring further.

# car-exchange-commit

> Exchange a car rental booking for a previewed alternative

Execute one previewed exchange. When the replacement costs the same or less, the booking is rewritten in this call and any difference is refunded to the original payment, or parked for manual review, flagged by `refund_pending_review`. When it costs more, the response is `awaiting_payment: true` with a `checkout_url`: send the customer there; the booking is rewritten only once the difference is paid, poll car\_exchange\_status. A provider rejection is a 200 with `state: rejected` and the booking stands as it was. Safe to call twice, a committed exchange is reported as it stands rather than repeated. 409 means the exchange cannot run as previewed: another exchange is in flight, or the preview / offer went stale, run a new preview.


## OpenAPI

````yaml api-reference/public-api.yaml POST /v1/car_exchange_commit
openapi: 3.0.0
info:
  title: Jinko Public API
  version: 0.5.0
  description: >-
    Curated public REST surface for Jinko. Authenticated with jnk_ API keys. See
    https://docs.gojinko.com for guides.


    ### Per-end-user attribution


    On booking calls you may send an optional `X-End-User-Id` request header to
    attribute the booking to one of your own end users (for per-end-user
    attribution and rate-limiting). The value is an **opaque, tenant-scoped**
    identifier that you choose — not a Jinko account id. Omit it to book as the
    tenant. WorkOS-shaped values (prefixed `user_` or `org_`) are rejected.
servers: []
security: []
paths:
  /v1/car_exchange_commit:
    post:
      tags:
        - Cars
      summary: Exchange a car rental booking for a previewed alternative
      description: >-
        Execute one previewed exchange. When the replacement costs the same or
        less, the booking is rewritten in this call and any difference is
        refunded to the original payment — or parked for manual review, flagged
        by `refund_pending_review`. When it costs more, the response is
        `awaiting_payment: true` with a `checkout_url`: send the customer there;
        the booking is rewritten only once the difference is paid — poll
        car_exchange_status. A provider rejection is a 200 with `state:
        rejected` and the booking stands as it was. Safe to call twice — a
        committed exchange is reported as it stands rather than repeated. 409
        means the exchange cannot run as previewed: another exchange is in
        flight, or the preview / offer went stale — run a new preview.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CarExchangeCommitRequest'
      responses:
        '200':
          description: Exchange result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CarExchangeCommitResponse'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: BAD_REQUEST
                  message: Malformed JSON in request body.
                  doc_url: https://docs.gojinko.com/concepts/errors
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: AUTH_REQUIRED
                  message: Invalid or expired API key.
                  doc_url: https://docs.gojinko.com/api-reference/authentication
        '402':
          description: Payment required — organization balance exhausted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: PAYMENT_REQUIRED
                  message: >-
                    Insufficient balance — this call costs $0.0150 and your
                    organization has $0.0000 available. Top up at
                    https://dashboard.gojinko.com/developers/billing/topup
                  doc_url: https://docs.gojinko.com/concepts/errors
        '404':
          description: Unknown booking_ref + last_name pair.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: NOT_FOUND
                  message: booking not found
        '409':
          description: >-
            An exchange is already in progress for this booking, or the preview
            / offer has expired — run car_exchange_preview again and choose
            afresh.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: CONFLICT
                  message: >-
                    the exchange preview is out of date — run a new preview and
                    choose again
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: BAD_REQUEST
                  message: >-
                    origins: origins is required; trip_type: trip_type is
                    required
                  doc_url: https://docs.gojinko.com/concepts/errors
        '429':
          description: Rate limit or quota exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: RATE_LIMITED
                  message: Rate limit or quota exceeded.
                  doc_url: https://docs.gojinko.com/concepts/errors
        '502':
          description: The travel provider rejected the request, or an upstream call failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: UPSTREAM_REJECTED
                  message: >-
                    sabre-rest BargainFinderMaxRQ failed with status 400: 27131
                    - Number of connection locations exceeds maximum allowed
                  doc_url: https://docs.gojinko.com/concepts/errors
        '503':
          description: No travel provider can serve the request right now
          headers:
            Retry-After:
              description: >-
                Seconds to wait before retrying, forwarded verbatim from the
                upstream service. Absent when the upstream named no interval.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: UPSTREAM_UNAVAILABLE
                  message: >-
                    All flight providers are temporarily unable to serve this
                    search. Please retry later. Provider reasons: sabre-rest:
                    provider temporarily closed; travelfusion: quota exhausted
                  doc_url: https://docs.gojinko.com/concepts/errors
        '504':
          description: The travel provider did not answer in time
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: UPSTREAM_TIMEOUT
                  message: sabre-rest BargainFinderMaxRQ timed out after 30s
                  doc_url: https://docs.gojinko.com/concepts/errors
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
components:
  schemas:
    CarExchangeCommitRequest:
      type: object
      properties:
        booking_ref:
          type: string
          minLength: 1
          description: Jinko booking reference from the confirmation email.
          example: JNK-8PT9VS
        last_name:
          type: string
          minLength: 1
          description: Lead traveler's last name, exactly as on the booking.
          example: Carrard
        intent:
          $ref: '#/components/schemas/IntentInput'
        exchange_id:
          type: string
          minLength: 1
          description: >-
            The "exc_…" reference from a preceding car_exchange_preview. Binds
            the commit to the prices the customer saw.
          example: exc_7c1e4b9a2d8f4e6b9a3c5d7e1f2a4b6c
        exchange_offer_token:
          type: string
          minLength: 1
          description: The "cxo_…" token of the chosen offer from that same preview.
          example: cxo_3f9a1c7e5b2d4a8c6e1f3b5d7a9c2e4f
      required:
        - booking_ref
        - last_name
        - exchange_id
        - exchange_offer_token
      example:
        booking_ref: JNK-8PT9VS
        last_name: Carrard
        exchange_id: exc_7c1e4b9a2d8f4e6b9a3c5d7e1f2a4b6c
        exchange_offer_token: cxo_3f9a1c7e5b2d4a8c6e1f3b5d7a9c2e4f
    CarExchangeCommitResponse:
      type: object
      properties:
        exchange_id:
          type: string
          example: exc_7c1e4b9a2d8f4e6b9a3c5d7e1f2a4b6c
        state:
          type: string
          description: >-
            One of `preview` (previewed, nothing committed), `awaiting_payment`,
            `processing`, `confirmed`, `rejected`, `failed`.
          example: confirmed
        delta:
          $ref: '#/components/schemas/CarExchangeDelta'
        new_total:
          allOf:
            - $ref: '#/components/schemas/CarAmount'
            - description: The replacement rental's pay-now total.
        paid:
          allOf:
            - $ref: '#/components/schemas/CarAmount'
            - description: What the customer had paid before the exchange.
        refund_pending_review:
          type: boolean
          description: >-
            true means the booking IS exchanged but the refund of the difference
            needs manual review before it is issued.
        stripe_refund_id:
          type: string
        rejected_reason:
          type: string
          description: >-
            Why the provider declined the exchange. The booking stands as it
            was.
        completed_at:
          type: string
          description: RFC 3339, set once the attempt is terminal.
          example: '2026-09-02T15:18:00Z'
        awaiting_payment:
          type: boolean
          description: >-
            true means the replacement costs more and the difference must be
            paid first: send the customer to `checkout_url`. The booking is
            rewritten only after the payment is authorized — poll
            car_exchange_status.
        checkout_url:
          type: string
          description: >-
            Hosted payment page for the price difference. Present only while
            `awaiting_payment`.
          example: https://book.gojinko.com/checkout?sid=1048576
        stripe_checkout_url:
          type: string
          description: >-
            The Stripe Checkout session behind `checkout_url`, for callers that
            redirect to Stripe directly.
    ErrorResponse:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              enum:
                - AUTH_REQUIRED
                - PAYMENT_REQUIRED
                - RATE_LIMITED
                - BAD_REQUEST
                - FORBIDDEN
                - NOT_FOUND
                - CONFLICT
                - PRICE_CHANGED
                - QUOTE_EXPIRED
                - TRIP_EXPIRED
                - TRIP_STATE_CONFLICT
                - OFFER_EXPIRED
                - OFFER_UNAVAILABLE
                - MISSING_CUSTOMER_DETAILS
                - CURRENCY_UNSUPPORTED
                - UPSTREAM_REJECTED
                - UPSTREAM_UNAVAILABLE
                - UPSTREAM_TIMEOUT
                - UPSTREAM_ERROR
                - INTERNAL
              description: >-
                What went wrong, as a stable machine-readable code. This is a
                closed set — branch on it rather than on `message`, which is
                prose and may change. Codes are only ever ADDED, in a minor
                version, so treat an unknown one as its HTTP status.
              example: BAD_REQUEST
            message:
              type: string
            doc_url:
              type: string
          required:
            - code
            - message
      required:
        - error
    IntentInput:
      type: object
      properties:
        user_intent:
          type: string
          nullable: true
          description: >-
            The user's natural-language intent: the Alpic PII-stripped
            paraphrase when available, else a best-effort fallback to the
            client-provided NL query.
          example: find a cheap flight to Tokyo
    CarExchangeDelta:
      type: object
      properties:
        direction:
          type: string
          description: >-
            Which way money moves: `add_collect` — the replacement costs more
            and the difference is collected; `refund` — it costs less and the
            difference is refunded; `even` — same price.
          example: add_collect
        amount:
          allOf:
            - $ref: '#/components/schemas/CarAmount'
            - description: The ABSOLUTE difference. Absent when `direction` is `even`.
      description: >-
        The price difference between a candidate and what was already paid, and
        which way the money moves. Compared against `paid` on the preview, never
        against the candidate total.
    CarAmount:
      type: object
      properties:
        value:
          type: integer
          description: >-
            Integer amount in MINOR units, always paired with decimal_places —
            divide by 10 ** decimal_places to display. Example: value 15977 with
            decimal_places 2 is 159.77 USD. Rendering this field directly shows
            prices 100x too high for 2-decimal currencies.
          example: 41250
        currency:
          type: string
          description: ISO 4217 currency code.
          example: EUR
        decimal_places:
          type: integer
          description: >-
            Scale of the integer value/amount: display = integer / 10 **
            decimal_places. Always sent alongside minor-unit amounts. Usually
            the ISO 4217 digits of the currency (2 for USD/EUR/GBP, 0 for
            JPY/KRW, 3 for BHD/JOD/KWD) but currency-converted prices can carry
            a different scale — ALWAYS use the decimal_places sent with the
            amount, never a hardcoded 2. Only if the field is genuinely absent
            on a value-shaped object, fall back to the ISO digits for the
            currency.
          example: 2
      description: The only amount Jinko charges at checkout.
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
    BearerAuth:
      type: http
      scheme: bearer

````