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

# Retrieve a booking

> Full booking record: traveler, room allocation, payment confirmation, the immutable adventure snapshot id, and the cancellation tiers that apply to this booking.



## OpenAPI

````yaml /api-reference/openapi.yaml get /bookings/{booking_number}
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}:
    get:
      tags:
        - Bookings
      summary: Retrieve a booking
      description: >-
        Full booking record: traveler, room allocation, payment confirmation,
        the immutable adventure snapshot id, and the cancellation tiers that
        apply to this booking.
      operationId: getBooking
      parameters:
        - $ref: '#/components/parameters/BookingNumber'
      responses:
        '200':
          description: Booking found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Booking'
              example:
                booking_number: MV-AB7X92
                partner_reference: YBANK-BOOK-001
                status: CONFIRMED
                adventure_slug: marrakech-3-jours-desert-sandbox
                adventure_title: '[DEMO] 3-day Sahara desert circuit from Marrakech'
                travel_date: '2026-07-12'
                traveler_name: A. B.
                pricing:
                  b2c_price:
                    amount: '5250.00'
                    currency: MAD
                  customer_price:
                    amount: '6210.00'
                    currency: MAD
                  amount_due_to_safariat:
                    amount: '5162.50'
                    currency: MAD
                  awb_commission_share:
                    amount: '87.50'
                    currency: MAD
                  sandbox_surcharge:
                    amount: '960.00'
                    currency: MAD
                  indicative: false
                settlement_id: 0a9f8e7d-6c5b-4a3f-8e2d-1c0b9a8f7e6d
                created_at: '2026-07-12T10:05:00Z'
                traveler:
                  first_name: Amina
                  last_name: Benali
                  email: amina.benali@example.com
                  phone: '+212600112233'
                  language: fr
                  customer_reference: YBANK-CUST-77310
                rooms_allocation:
                  - room_index: 0
                    occupants:
                      - type: ADULT
                      - type: ADULT
                      - type: CHILD
                        age: 8
                      - type: CHILD
                        age: 12
                special_requests: null
                payment_confirmation:
                  bank_payment_ref: YBANK-PAY-2026-07-12-00891
                  paid_at: '2026-07-12T10:04:30Z'
                  payment_method: ACCOUNT_DEBIT
                snapshot_id: 5e6d7c8b-9a0f-4b1c-8d2e-3f4a5b6c7d8e
                cancellation_tiers:
                  - days_before: 30
                    refund_percent: 100
                    refundable_amount:
                      amount: '5400.00'
                      currency: MAD
                  - days_before: 14
                    refund_percent: 50
                    refundable_amount:
                      amount: '2700.00'
                      currency: MAD
                  - days_before: 7
                    refund_percent: 0
                    refundable_amount:
                      amount: '0.00'
                      currency: MAD
                voucher_url: https://placehold.co/600x400?text=DEMO
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: >
            Booking not found, OR belongs to another partner (404 rather than
            403 to

            prevent enumeration).
        '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:
    Booking:
      allOf:
        - $ref: '#/components/schemas/BookingSummary'
        - type: object
          required:
            - traveler
            - rooms_allocation
            - payment_confirmation
            - snapshot_id
            - cancellation_tiers
          properties:
            traveler:
              $ref: '#/components/schemas/Traveler'
            rooms_allocation:
              type: array
              items:
                $ref: '#/components/schemas/RoomAllocation'
            special_requests:
              type: string
              nullable: true
            payment_confirmation:
              $ref: '#/components/schemas/PaymentConfirmationView'
            snapshot_id:
              type: string
              format: uuid
              description: >
                ID of the adventure snapshot captured at creation (see ADR-013).
                Guarantees

                the traveler receives the experience as it was at the time of
                booking.
            cancellation_tiers:
              type: array
              items:
                type: object
                properties:
                  days_before:
                    type: integer
                  refund_percent:
                    type: integer
                  refundable_amount:
                    $ref: '#/components/schemas/Money'
            voucher_url:
              type: string
              format: uri
              description: Pre-signed URL to the PDF voucher (7-day TTL).
    BookingSummary:
      type: object
      required:
        - booking_number
        - partner_reference
        - status
        - adventure_slug
        - travel_date
        - pricing
        - created_at
      properties:
        booking_number:
          type: string
          example: MV-AB7X92
        partner_reference:
          type: string
        status:
          $ref: '#/components/schemas/BookingStatus'
        adventure_slug:
          type: string
        adventure_title:
          type: string
        travel_date:
          type: string
          format: date
        traveler_name:
          type: string
          description: Initialed (e.g. 'A. B.') for PII protection in lists.
        pricing:
          $ref: '#/components/schemas/PartnerPricing'
          description: >-
            Revenue breakdown with the real surcharge recorded at booking
            (`indicative = false`).
        settlement_id:
          type: string
          format: uuid
          nullable: true
          description: >-
            ID of the settlement this booking is included in (null if not yet
            invoiced).
        created_at:
          type: string
          format: date-time
    Traveler:
      type: object
      required:
        - first_name
        - last_name
        - email
        - phone
        - language
        - customer_reference
      properties:
        first_name:
          type: string
          minLength: 1
          maxLength: 100
        last_name:
          type: string
          minLength: 1
          maxLength: 100
        email:
          type: string
          format: email
        phone:
          type: string
          pattern: ^\+?[1-9]\d{6,14}$
          description: E.164 format recommended.
        language:
          type: string
          enum:
            - fr
            - en
            - ar
        customer_reference:
          type: string
          minLength: 1
          maxLength: 100
          description: >
            The bank's internal customer ID. **Pseudonymized server-side with
            keyed

            HMAC-SHA256, namespaced per partner** before storage
            (non-reversible,

            deterministic per partner). Lets the bank locate the traveler in its
            own

            systems, with no cross-bank correlation in the event of a Safariat
            breach.

            Also accepted as the `customer_reference` filter on `GET /bookings`.
    RoomAllocation:
      type: object
      required:
        - room_index
        - occupants
      properties:
        room_index:
          type: integer
          minimum: 0
          description: Room index (correlated with the position in `quote.rooms`).
        occupants:
          type: array
          minItems: 1
          items:
            type: object
            required:
              - type
            properties:
              type:
                type: string
                enum:
                  - ADULT
                  - CHILD
              age:
                type: integer
                minimum: 0
                maximum: 17
                description: Required if type=CHILD.
    PaymentConfirmationView:
      type: object
      required:
        - bank_payment_ref
        - paid_at
        - payment_method
      description: >
        Payment confirmation echoed back on the booking (response projection).
        The monetary

        amounts (customer total, net due to Safariat, surcharge) are exposed via
        `pricing`.
      properties:
        bank_payment_ref:
          type: string
          description: The bank's internal payment reference (for reconciliation).
        paid_at:
          type: string
          format: date-time
        payment_method:
          type: string
          enum:
            - CARD
            - BANK_TRANSFER
            - ACCOUNT_DEBIT
            - CASH
            - WAFACASH
            - OTHER
    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
    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
    BookingStatus:
      type: string
      enum:
        - CONFIRMED
        - COMPLETED
        - CANCELLED
      description: >
        The partner channel skips the `PENDING` status (no payment-wait window).

        Additional internal Safariat statuses (`PAYMENT_PROCESSING`,
        `REFUND_PENDING`)

        are never exposed on this API.
    PartnerPricing:
      type: object
      description: >
        Revenue breakdown for the AWB channel, in MAD. Canonical home for all
        partner-facing amounts.

        On catalog (search / adventure detail) and quote responses `indicative`
        is `true` and

        `sandbox_surcharge` is `0` (the bank sets its surcharge at booking
        time), so `customer_price`

        equals `b2c_price`. On booking responses `indicative` is `false` and the
        values reflect the

        amounts actually recorded.

        Invariant: `amount_due_to_safariat + awb_commission_share +
        sandbox_surcharge = customer_price`.
      required:
        - b2c_price
        - customer_price
        - amount_due_to_safariat
        - awb_commission_share
        - sandbox_surcharge
        - indicative
      properties:
        b2c_price:
          $ref: '#/components/schemas/Money'
          description: Public B2C reference price (price floor guaranteed to the customer).
        customer_price:
          $ref: '#/components/schemas/Money'
          description: >-
            Total price paid by the customer = `b2c_price + sandbox_surcharge`.
            Equals `b2c_price` before booking.
        amount_due_to_safariat:
          $ref: '#/components/schemas/Money'
          description: >-
            Net reversed to Safariat = operator share + 65% of the base
            commission (the daily wire amount).
        awb_commission_share:
          $ref: '#/components/schemas/Money'
          description: AWB's 35% share of the base commission.
        sandbox_surcharge:
          $ref: '#/components/schemas/Money'
          description: >-
            AWB's own surcharge (100% AWB), derived as `customer_price −
            b2c_price`. `0` before booking.
        indicative:
          type: boolean
          description: >
            `true` for catalog/quote "from" prices (final amounts confirmed at
            booking);

            `false` on booking responses.
  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
    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.

````