> ## 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.

# hotel-cancel-preview

> Preview what cancelling a hotel booking would cost

Quote what cancelling would return to the customer right now: the refund, the penalty in force, and the policy behind them. NOTHING is cancelled. `refund` is the customer figure, what they paid for the item less the penalty; the supplier's own net figures are under `provider_figures` and are not what the customer receives. `commitable: false` means a commit would be refused and `not_commitable_reason` says why. The handle this returns is named `quote`: it is what POST /v1/hotel\_cancel\_commit consumes, and it stops binding at `expires_at`. Every call here needs API authentication; this is about which mode identifies the booking. Use EXACTLY ONE: provider\_reference, which additionally requires a credential that OWNS the booking, or booking\_ref + last\_name, which identifies it without one. Sending both is a 422.


## OpenAPI

````yaml api-reference/public-api.yaml POST /v1/hotel_cancel_preview
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/hotel_cancel_preview:
    post:
      tags:
        - Post-booking
      summary: Preview what cancelling a hotel booking would cost
      description: >-
        Quote what cancelling would return to the customer right now: the
        refund, the penalty in force, and the policy behind them. NOTHING is
        cancelled. `refund` is the customer figure — what they paid for the item
        less the penalty; the supplier's own net figures are under
        `provider_figures` and are not what the customer receives. `commitable:
        false` means a commit would be refused and `not_commitable_reason` says
        why. The handle this returns is named `quote`: it is what POST
        /v1/hotel_cancel_commit consumes, and it stops binding at `expires_at`.
        Every call here needs API authentication; this is about which mode
        identifies the booking. Use EXACTLY ONE: provider_reference, which
        additionally requires a credential that OWNS the booking, or booking_ref
        + last_name, which identifies it without one. Sending both is a 422.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HotelCancelPreviewRequest'
      responses:
        '200':
          description: What cancelling would cost
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HotelCancelPreviewResponse'
        '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, or one this credential may not read.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: NOT_FOUND
                  message: booking not found
        '409':
          description: The operation conflicts with the current state of the resource
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: CONFLICT
                  message: an exchange is already in progress for this booking
                  doc_url: https://docs.gojinko.com/concepts/errors
        '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:
    HotelCancelPreviewRequest:
      type: object
      properties:
        provider_reference:
          type: string
          description: >-
            The supplier's own booking reference. OWNER mode: beyond the API
            authentication every call needs, this one requires a credential that
            OWNS the booking — a tenant-bound key reaches its whole tenant, any
            other credential must belong to the booking's own user. Mutually
            exclusive with booking_ref + last_name; sending both is rejected
            with 422. An unknown reference, another tenant's booking and another
            user's booking are all answered identically, so this field cannot be
            used to discover which supplier references exist.
          example: NUITEE-88231
        booking_ref:
          type: string
          description: >-
            The Jinko reference. GUEST mode: pair it with last_name and the pair
            identifies the booking on its own — API authentication is still
            required, as on every call here, but the credential does not have to
            own the booking. Mutually exclusive with provider_reference; one
            without the other is rejected with 422.
          example: JNK-H1ZK90
        last_name:
          type: string
          description: >-
            The lead traveller's surname. Required with booking_ref, and only
            with it.
          example: Carrard
        intent:
          $ref: '#/components/schemas/IntentInput'
      example:
        booking_ref: JNK-H1ZK90
        last_name: Carrard
    HotelCancelPreviewResponse:
      type: object
      properties:
        quote:
          type: string
          description: >-
            This quote ("svq_…"). Pass it to POST /v1/hotel_cancel_commit — it
            binds the commit to the figures below. Opaque; the format may
            evolve.
          example: svq_01J7ZR3M8FKX2P9C
        state:
          type: string
          description: >-
            Lifecycle of the QUOTE, not of a cancellation — nothing has been
            cancelled by this call. `completed` means the figures are final
            until `expires_at`.
          example: completed
        commitable:
          type: boolean
          description: >-
            Whether a commit against this quote would be accepted right now.
            When false, `not_commitable_reason` says why and committing is
            pointless.
          example: true
        support_level:
          $ref: '#/components/schemas/ServicingSupportLevel'
        expires_at:
          type: string
          description: >-
            When this quote stops binding. Committing after it answers 409
            `quote_expired`; take a fresh quote and show the customer the new
            figure before committing again.
          example: '2026-09-03T12:15:00Z'
        item:
          type: string
          description: >-
            The booked item this quote would cancel ("itm_…"). A booking holding
            several items is quoted and cancelled one item at a time.
          example: itm_7f2c9a4e8b1d
        refund:
          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
            amount:
              type: number
              description: >-
                Alternative to `value` on some endpoints (the two never appear
                together); its scale depends on decimal_places. When this object
                carries decimal_places, amount is an INTEGER in minor units —
                divide by 10 ** decimal_places (e.g. select_ancillaries
                total_with_ancillaries). When there is no decimal_places field,
                amount is a decimal in MAJOR units, safe to display as-is (e.g.
                trip and checkout totals).
            currency:
              type: string
              description: ISO 4217 currency code.
              example: USD
            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
            basis:
              type: string
              description: >-
                How the figure was computed. `sell_minus_penalty` — what the
                customer paid for this item, less the penalty. `original_charge`
                — the entire charge is reversed, penalty-free (a void). The
                platform chooses; the caller cannot ask for one.
              example: sell_minus_penalty
          description: >-
            THE CUSTOMER FIGURE: what would go back to the payment method, on
            the basis named in `basis`. Show this one. Absent is not zero.
        penalty:
          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
            amount:
              type: number
              description: >-
                Alternative to `value` on some endpoints (the two never appear
                together); its scale depends on decimal_places. When this object
                carries decimal_places, amount is an INTEGER in minor units —
                divide by 10 ** decimal_places (e.g. select_ancillaries
                total_with_ancillaries). When there is no decimal_places field,
                amount is a decimal in MAJOR units, safe to display as-is (e.g.
                trip and checkout totals).
            currency:
              type: string
              description: ISO 4217 currency code.
              example: USD
            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
            fee_known:
              type: boolean
              description: >-
                false means the penalty could not be established — UNKNOWN, not
                zero. Render it as "we will confirm the fee", never as free
                cancellation, and expect the final figure on the operation
                status.
              example: true
          description: >-
            What the customer would forfeit — read `fee_known` before showing
            it.
        policy:
          type: object
          properties:
            free_cancel_until:
              type: string
              nullable: true
              description: >-
                The last instant at which cancelling costs nothing. `null` means
                there is no free window — either the booking never had one or it
                has passed.
              example: '2026-09-01T00:00:00Z'
            is_refundable_now:
              type: boolean
              example: true
            tiers:
              type: array
              items:
                type: object
                properties:
                  from:
                    type: string
                    description: When this step starts applying.
                    example: '2026-09-01T00:00:00Z'
                  amount:
                    type: object
                    properties:
                      amount:
                        type: number
                        description: >-
                          Alternative to `value` on some endpoints (the two
                          never appear together); its scale depends on
                          decimal_places. When this object carries
                          decimal_places, amount is an INTEGER in minor units —
                          divide by 10 ** decimal_places (e.g.
                          select_ancillaries total_with_ancillaries). When there
                          is no decimal_places field, amount is a decimal in
                          MAJOR units, safe to display as-is (e.g. trip and
                          checkout totals).
                      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: USD
                      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
                  fraction:
                    type: number
                    description: >-
                      The share of the paid price withheld from `from` onwards,
                      when the supplier states the step as a proportion rather
                      than a sum. A step carries `amount` or `fraction`, not
                      both.
                    example: 0.5
              description: >-
                The supplier's policy steps in time order. Informational: the
                figure that binds is `penalty`, computed for right now.
          description: >-
            The supplier's cancellation policy as it stands, for explaining the
            figures to the customer.
        provider_figures:
          $ref: '#/components/schemas/ProviderFigures'
        not_commitable_reason:
          type: string
          description: >-
            Why `commitable` is false. Today: `provider_unsupported` (the
            supplier offers no cancellation through the API), `not_cancellable`
            (this booking cannot be cancelled as it stands),
            `penalty_exceeds_sell` (the penalty is at least what the customer
            paid), `multi_currency_basis` (the booking was paid across
            currencies and no single refund figure exists), `funds_in_flight`
            (the original payment is still moving). New reasons may be added, so
            treat an unrecognised value as "not right now".
          example: not_cancellable
    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
    ServicingSupportLevel:
      type: string
      enum:
        - AUTO
        - MANUAL_REQUIRED
      description: >-
        How this cancellation would be carried out. `AUTO` — the platform
        completes it end to end. `MANUAL_REQUIRED` — it is possible, but a Jinko
        agent has to act; expect a delay rather than an immediate result, and do
        not treat it as a failure. There is no "unsupported" level: a supplier
        that cannot be cancelled through the API arrives as `commitable: false`
        with `not_commitable_reason: provider_unsupported`, or as a 409
        `not_cancellable` on commit.
    ProviderFigures:
      type: object
      properties:
        refund_net:
          type: object
          properties:
            amount:
              type: number
              description: >-
                Alternative to `value` on some endpoints (the two never appear
                together); its scale depends on decimal_places. When this object
                carries decimal_places, amount is an INTEGER in minor units —
                divide by 10 ** decimal_places (e.g. select_ancillaries
                total_with_ancillaries). When there is no decimal_places field,
                amount is a decimal in MAJOR units, safe to display as-is (e.g.
                trip and checkout totals).
            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: USD
            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
        penalty_net:
          type: object
          properties:
            amount:
              type: number
              description: >-
                Alternative to `value` on some endpoints (the two never appear
                together); its scale depends on decimal_places. When this object
                carries decimal_places, amount is an INTEGER in minor units —
                divide by 10 ** decimal_places (e.g. select_ancillaries
                total_with_ancillaries). When there is no decimal_places field,
                amount is a decimal in MAJOR units, safe to display as-is (e.g.
                trip and checkout totals).
            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: USD
            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
        currency:
          type: string
          example: USD
      description: >-
        The supplier's own net figures, for reconciliation — never what the
        customer receives. `refund_net` is what the supplier returns to Jinko
        and `penalty_net` what it withheld from Jinko; on a booking sold at a
        margin both differ from the customer figures. Show the customer the
        top-level `refund_amount` / `penalty_amount`, never these. `currency` is
        the currency the supplier settles in, which can differ from the currency
        charged.
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
    BearerAuth:
      type: http
      scheme: bearer

````