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

# Preview the cancellation refund

> Computes — **read-only, no side effect** — what cancelling this booking right now would refund: the cancellation tier that applies, the net amount deducted from the next settlement, the indicative gross to refund the customer, and the deadline until which cancellation is still free. Uses the same calculator as `POST /bookings/{booking_number}/cancel`, so the figures match exactly. Evaluated at the current server time.



## OpenAPI

````yaml /api-reference/openapi.yaml get /bookings/{booking_number}/cancellation-quote
openapi: 3.1.0
info:
  title: SafarAPI
  version: 1.0.0
  summary: B2B API for banking partners — Safariat catalog and booking
  description: >
    SafarAPI lets banking partners integrate the Safariat travel catalog into
    their

    authenticated customer areas and book on behalf of their own end customers
    who are

    already authenticated and KYC-verified on the bank side.


    ## Channel model

    - The traveler never authenticates with Safariat.

    - Payment is collected on the banking platform (out-of-band from Safariat).

    - The bank submits the HMAC-signed payment confirmation at booking time.

    - Monthly post-payment settlement (see `GET /settlements`).


    ## Authentication

    Bearer API key in the `Authorization` header. Format
    `sk_live_<prefix>_<secret>` (production)

    or `sk_test_<prefix>_<secret>` (sandbox). The secret is shown only once at
    key generation —

    Safariat stores only an argon2id hash.


    ## Write request signing

    Production write requests (`POST`, `PUT`, `DELETE`) additionally require:

    - `X-Timestamp`: Unix timestamp in seconds, validity window ±5 minutes.

    - `X-Signature`: `hex(HMAC_SHA256(secret,
    "{timestamp}\n{method}\n{path}\n{body}"))`.

    - `Idempotency-Key`: opaque string unique per operation (UUID v4
    recommended),
      retained for 24 h. Replayed responses carry an `Idempotent-Replayed: true` header.

    Request signing applies only to production `sk_live_*` keys. Sandbox
    `sk_test_*` keys are

    exempt from request signing: `X-Signature` and `X-Timestamp` are not
    required for writes

    in the sandbox (so the developer-portal "Try it" playground works end to
    end).

    `Idempotency-Key` is still required on writes in both environments.


    ## Error format

    All errors follow the `Error` schema with a stable i18n code, an English
    message, and the

    `request_id` for correlation with Safariat logs.


    ## Versioning

    URL-based versioning (`/v1`). No breaking changes within a version. Removals
    are announced

    6 months in advance via the `Sunset` header (RFC 8594).


    ## Rate limiting

    Default limit of 600 req/min per key, contractually adjustable. The
    `X-RateLimit-Limit`,

    `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers are returned on
    every response.

    Exceeding the limit → `429 Too Many Requests` plus the `Retry-After` header.


    ## Sandbox

    An `sk_test_*` key targets the same infrastructure as production but
    isolates bookings in

    `test_mode=true`: no real email is sent, no settlement is issued, and
    webhooks are marked

    `test: true`.
  contact:
    name: SafarAPI Team
    email: api@safariat.ma
    url: https://developers.safariat.ma
  license:
    name: Proprietary — use subject to a signed partner agreement
  termsOfService: https://safariat.ma/legal/partner-terms
servers:
  - url: https://api.safarapi.com/api/partner/v1
    description: >-
      Production and sandbox share this host. The environment is selected by the
      API key prefix: sk_live_ targets production, sk_test_ targets the sandbox.
security:
  - apiKey: []
tags:
  - name: Meta
    description: Metadata for the current key, service health.
  - name: Catalog
    description: Browsing the Safariat adventure catalog.
  - name: Quotes
    description: Price-locked quotes (30 min TTL) ahead of booking.
  - name: Bookings
    description: Creating, retrieving, and cancelling bookings.
  - name: Settlements
    description: Monthly Safariat → partner payout invoices.
  - name: Webhooks
    description: Managing endpoints that receive Safariat events.
  - name: Team
    description: Members of the partner console workspace and their roles.
  - name: Reviews
    description: Read-only access to traveler reviews on the Safariat catalog.
  - name: Files
    description: Presigned uploads for partner-supplied files (currently review photos).
paths:
  /bookings/{booking_number}/cancellation-quote:
    get:
      tags:
        - Bookings
      summary: Preview the cancellation refund
      description: >-
        Computes — **read-only, no side effect** — what cancelling this booking
        right now would refund: the cancellation tier that applies, the net
        amount deducted from the next settlement, the indicative gross to refund
        the customer, and the deadline until which cancellation is still free.
        Uses the same calculator as `POST /bookings/{booking_number}/cancel`, so
        the figures match exactly. Evaluated at the current server time.
      operationId: getBookingCancellationQuote
      parameters:
        - $ref: '#/components/parameters/BookingNumber'
      responses:
        '200':
          description: Cancellation quote computed (no change made)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CancellationQuote'
              example:
                booking_number: MV-AB7X92
                evaluated_at: '2026-06-20T14:12:09Z'
                tier_applied:
                  days_before: 30
                  refund_percent: 100
                refund:
                  net_to_partner:
                    amount: '5400.00'
                    currency: MAD
                  gross_to_traveler_indicative:
                    amount: '6210.00'
                    currency: MAD
                free_cancellation_deadline: '2026-07-05T09:00:00Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: >-
            The booking is not cancellable (already cancelled or completed) or
            does not belong to the partner channel.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                code: booking.not.cancellable
                message: >-
                  This booking cannot be cancelled — it is already cancelled or
                  completed.
                request_id: req_8f3a1c9e2b7d40
        '429':
          $ref: '#/components/responses/TooManyRequests'
components:
  parameters:
    BookingNumber:
      in: path
      name: booking_number
      required: true
      schema:
        type: string
        pattern: ^MV-[A-Z0-9]{6}$
      example: MV-AB7X92
      description: >-
        Safariat booking number (format `MV-XXXXXX`). In the sandbox, create a
        booking first (`POST /bookings`) and use the returned number here.
  schemas:
    CancellationQuote:
      type: object
      required:
        - booking_number
        - evaluated_at
        - refund
      properties:
        booking_number:
          type: string
        evaluated_at:
          type: string
          format: date-time
          description: Server time at which the quote was computed.
        tier_applied:
          type: object
          nullable: true
          description: Omitted when no tier matched (full or zero refund applies).
          properties:
            days_before:
              type: integer
            refund_percent:
              type: integer
        refund:
          type: object
          required:
            - net_to_partner
            - gross_to_traveler_indicative
          properties:
            net_to_partner:
              $ref: '#/components/schemas/Money'
              description: >-
                Amount that would be deducted from the next settlement. No
                transfer.
            gross_to_traveler_indicative:
              $ref: '#/components/schemas/Money'
              description: Indicative amount to refund the customer on the bank side.
        free_cancellation_deadline:
          type: string
          format: date-time
          nullable: true
          description: >-
            Last instant a cancellation still yields the maximum refund tier.
            `null` when there is no free-cancellation tier.
    Error:
      type: object
      required:
        - code
        - message
        - request_id
      properties:
        code:
          type: string
          description: |
            Stable, documented i18n code. Form `domain.subdomain.detail`
            (e.g. `payment.amount.mismatch`, `auth.api_key.invalid`).
          example: validation.required.field
        message:
          type: string
          description: >-
            English message (for bank logs). API errors are never localized on
            the Safariat side.
          example: Field 'rate_pack_id' is required.
        request_id:
          type: string
          description: Unique request ID on the Safariat side.
        details:
          type: object
          description: Optional structured details (offending field, expected value, etc.).
          additionalProperties: true
    Money:
      type: object
      required:
        - amount
        - currency
      properties:
        amount:
          type: string
          pattern: ^-?\d+\.\d{2}$
          description: Amount as a decimal string with 2 decimals to preserve precision.
          example: '3200.00'
        currency:
          type: string
          enum:
            - MAD
          description: Currency — MAD only in V1.
          example: MAD
  responses:
    Unauthorized:
      description: API key missing, invalid, expired, or incorrect HMAC signature.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            missing:
              value:
                code: auth.api_key.missing
                message: Authorization header is required.
                request_id: 01HXY2ZRPM3R8K9PSBTRQYNFHC
            invalid:
              value:
                code: auth.api_key.invalid
                message: The provided API key is invalid or has been revoked.
                request_id: 01HXY2ZRPM3R8K9PSBTRQYNFHC
            signatureInvalid:
              value:
                code: auth.signature.invalid
                message: HMAC signature does not match.
                request_id: 01HXY2ZRPM3R8K9PSBTRQYNFHC
            timestampSkew:
              value:
                code: auth.timestamp.skew
                message: Timestamp is more than 5 minutes off server time.
                request_id: 01HXY2ZRPM3R8K9PSBTRQYNFHC
    NotFound:
      description: Resource does not exist or is inaccessible for this partner.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    TooManyRequests:
      description: Rate limit exceeded for the current key.
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        X-RateLimit-Limit:
          $ref: '#/components/headers/XRateLimitLimit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/XRateLimitRemaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/XRateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: rate_limit.exceeded
            message: Rate limit exceeded for this API key.
            request_id: 01HXY2ZRPM3R8K9PSBTRQYNFHC
  headers:
    RetryAfter:
      schema:
        type: integer
      description: Seconds to wait before the next attempt (RFC 7231).
    XRateLimitLimit:
      schema:
        type: integer
      description: Request limit per minute for the current key.
    XRateLimitRemaining:
      schema:
        type: integer
      description: Requests remaining in the current window.
    XRateLimitReset:
      schema:
        type: integer
        format: int64
      description: Unix timestamp (s) at which the window resets.
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: sk_live_<prefix>_<secret> or sk_test_<prefix>_<secret>
      x-default: sk_test_DEMO0000_replace_with_your_sandbox_key
      description: >
        API key authentication. Issued by the Safariat admin or via the partner
        portal.

        The secret is shown only once at generation.


        Production `sk_live_*` keys must additionally sign every write request

        (`POST`/`PUT`/`DELETE`) with the `X-Timestamp` and `X-Signature`
        headers.

        Sandbox `sk_test_*` keys are exempt from request signing: `X-Signature`
        and

        `X-Timestamp` are not required for writes in the sandbox (so the
        developer-portal

        "Try it" playground works end to end). The `Idempotency-Key` header
        remains

        required on writes in both environments.

````