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

# Detailed adventure record

> Full record for one adventure: description, itinerary, inclusions, accommodations and bookable rate packs. The `slug` is stable and safe to use as an identifier in partner links.



## OpenAPI

````yaml /api-reference/openapi.yaml get /adventures/{slug}
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:
  /adventures/{slug}:
    get:
      tags:
        - Catalog
      summary: Detailed adventure record
      description: >-
        Full record for one adventure: description, itinerary, inclusions,
        accommodations and bookable rate packs. The `slug` is stable and safe to
        use as an identifier in partner links.
      operationId: getAdventure
      parameters:
        - $ref: '#/components/parameters/AcceptLanguage'
        - $ref: '#/components/parameters/AdventureSlug'
      responses:
        '200':
          description: Adventure found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Adventure'
              example:
                id: ad000002-0000-4000-8000-0000000000d2
                slug: marrakech-3-jours-desert-sandbox
                type: TRIP
                title: '[DEMO] 3-day Sahara desert circuit from Marrakech'
                subtitle: null
                duration:
                  days: 3
                  hours: 0
                  minutes: 0
                cover_image_url: https://placehold.co/600x400?text=DEMO
                destinations:
                  - Marrakech
                categories:
                  - desert
                price_from:
                  amount: '2400.00'
                  currency: MAD
                rating:
                  average: 4.7
                  count: 23
                agency:
                  id: 1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f
                  name: Sahara Demo Trekking
                description: >-
                  A 3-day circuit from Marrakech to the Sahara dunes, with
                  overnight stays in the desert.
                highlights:
                  - Camel trek at sunset over the Erg Chebbi dunes
                  - Night under the stars in a desert camp
                includes:
                  - Air-conditioned 4x4 transport
                  - 2 nights accommodation
                  - Half board
                excludes:
                  - Personal expenses
                  - Travel insurance
                what_to_bring:
                  - Warm layer for the desert night
                  - Sunscreen
                good_to_know:
                  - Departure at 07:00 from central Marrakech
                gallery:
                  - https://placehold.co/600x400?text=DEMO
                itinerary:
                  - day_number: 1
                    title: Marrakech to Dades Valley
                    description: Drive over the Tizi n'Tichka pass with photo stops.
                    steps:
                      - order: 1
                        title: Departure from Marrakech
                        description: Pickup at the central meeting point.
                        duration: 30 min
                  - day_number: 2
                    title: Dades Valley to Merzouga
                    description: Camel trek into the dunes and night at the camp.
                    steps:
                      - order: 1
                        title: Camel trek
                        description: Sunset ride over the Erg Chebbi dunes.
                        duration: 2h
                  - day_number: 3
                    title: Merzouga back to Marrakech
                    description: Sunrise over the dunes then return drive.
                    steps:
                      - order: 1
                        title: Sunrise viewpoint
                        description: Early morning over the dunes before breakfast.
                        duration: 1h
                accommodations:
                  - name: Desert Luxury Camp
                    type: Camp
                    stars: 4
                  - name: Hotel Dades
                    type: Hotel
                    stars: 3
                age_brackets:
                  infant_max_age: 2
                  child_max_age: 11
                rate_packs:
                  - id: 4a700002-0000-4000-8000-0000000000f2
                    name: Comfort
                    description: Standard comfort package with half board.
                    base_price:
                      amount: '2400.00'
                      currency: MAD
                    child_supplement:
                      applies_below_age: 12
                      percent_of_adult: 50
                    extra_bed_supplement: null
                    room_allocation: PER_ROOM
                    capacity: 16
                    cancellation_tiers:
                      - days_before: 30
                        refund_percent: 100
                      - days_before: 14
                        refund_percent: 50
                      - days_before: 7
                        refund_percent: 0
                departure_cities:
                  - Marrakech
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
components:
  parameters:
    AcceptLanguage:
      in: header
      name: Accept-Language
      required: false
      schema:
        type: string
        enum:
          - fr
          - en
          - ar
        default: fr
      description: Language of the returned labels (titles, descriptions, categories).
    AdventureSlug:
      in: path
      name: slug
      required: true
      schema:
        type: string
        pattern: ^[a-z0-9-]+$
        maxLength: 200
      example: marrakech-3-jours-desert-sandbox
      description: >-
        Stable adventure slug (usable as an ID in partner links). The example is
        illustrative — obtain a real value from `GET /adventures`. The sandbox
        catalogue mirrors production with every slug suffixed `-sandbox`;
        production slugs have no suffix.
  schemas:
    Adventure:
      allOf:
        - $ref: '#/components/schemas/AdventureSummary'
        - type: object
          required:
            - description
            - includes
            - excludes
            - rate_packs
          properties:
            description:
              type: string
            highlights:
              type: array
              items:
                type: string
            includes:
              type: array
              items:
                type: string
            excludes:
              type: array
              items:
                type: string
            what_to_bring:
              type: array
              items:
                type: string
            good_to_know:
              type: array
              items:
                type: string
            gallery:
              type: array
              items:
                type: string
                format: uri
            itinerary:
              type: array
              items:
                $ref: '#/components/schemas/ItineraryDay'
            itinerary_periods:
              type: array
              description: >
                Multi-day periods grouping consecutive itinerary days (e.g. days
                1-3 = "Marrakech to dunes").

                Provided alongside `itinerary` for richer day-by-day rendering
                on long trips. Empty for

                SHORT_DURATION adventures.
              items:
                $ref: '#/components/schemas/ItineraryPeriod'
            accommodations:
              type: array
              items:
                $ref: '#/components/schemas/Accommodation'
            meeting_points:
              type: array
              nullable: true
              description: Geo-located meeting points (SHORT_DURATION only).
              items:
                type: object
                required:
                  - name
                properties:
                  name:
                    type: string
                  latitude:
                    type: number
                    format: double
                    nullable: true
                  longitude:
                    type: number
                    format: double
                    nullable: true
            faq:
              type: array
              description: Frequently asked questions, ordered by display index.
              items:
                type: object
                required:
                  - question
                  - answer
                properties:
                  question:
                    type: string
                  answer:
                    type: string
            host:
              type: object
              nullable: true
              description: Host profile (SHORT_DURATION adventures only).
              required:
                - name
              properties:
                name:
                  type: string
                image:
                  type: string
                  format: uri
                  nullable: true
                experience:
                  type: string
                  nullable: true
                languages:
                  type: array
                  items:
                    type: string
            rate_packs:
              type: array
              items:
                $ref: '#/components/schemas/RatePack'
            departure_cities:
              type: array
              items:
                type: string
            departure_dates:
              type: array
              description: Available departure dates (MULTI_DAY adventures only).
              items:
                type: string
                format: date
            next_departure_date:
              type: string
              format: date
              nullable: true
              description: >-
                Soonest bookable future departure (respects per-pack booking
                cutoff).
            translation_source:
              type: string
              nullable: true
              enum:
                - auto
                - manual
              description: >
                Set when the returned content has been translated from the
                source language for the

                requested `Accept-Language`. `null` if the requested locale
                equals the adventure's

                source language (content delivered as authored).
            age_brackets:
              $ref: '#/components/schemas/AgeBrackets'
    AdventureSummary:
      type: object
      required:
        - id
        - slug
        - type
        - title
        - duration
        - cover_image_url
        - price_from
        - rating
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
          example: marrakech-3-jours-desert-sandbox
        type:
          $ref: '#/components/schemas/AdventureType'
        title:
          type: string
        subtitle:
          type: string
          nullable: true
        duration:
          type: object
          properties:
            days:
              type: integer
              minimum: 0
            hours:
              type: integer
              minimum: 0
            minutes:
              type: integer
              minimum: 0
        cover_image_url:
          type: string
          format: uri
        cover_image_blurhash:
          type: string
          nullable: true
          description: >-
            Compact placeholder (BlurHash) for instant rendering before the
            cover image loads.
        cover_image_variants:
          type: object
          nullable: true
          description: Responsive variant URLs of the cover image.
          properties:
            small:
              type: string
              format: uri
              nullable: true
            medium:
              type: string
              format: uri
              nullable: true
        destinations:
          type: array
          items:
            type: string
          description: Main destination labels.
        categories:
          type: array
          items:
            type: string
        price_from:
          $ref: '#/components/schemas/Money'
        pricing:
          $ref: '#/components/schemas/PartnerPricing'
          description: Indicative revenue breakdown on `price_from` (`indicative = true`).
        rating:
          type: object
          properties:
            average:
              type: number
              format: float
              minimum: 0
              maximum: 5
            count:
              type: integer
              minimum: 0
        agency:
          type: object
          nullable: true
          description: >-
            Owning agency. `name` may be null if the agency record was deleted
            between indexing and read.
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
              nullable: true
        availability_hint:
          type: string
          nullable: true
          enum:
            - AVAILABLE
            - FEW_LEFT
            - SOLD_OUT
          description: |
            Computed at search-time from booking ratios. Surfaces UI badges
            ("plus que 2 places", "complet"). Null if no signal available.
        distance_km:
          type: number
          format: double
          nullable: true
          minimum: 0
          description: >
            Haversine distance between the search reference point (`lat`/`lng`
            query params)

            and the adventure's primary location. Null if the search was not
            geo-anchored.
    ItineraryDay:
      type: object
      required:
        - day_number
        - title
      properties:
        day_number:
          type: integer
          minimum: 1
        title:
          type: string
        description:
          type: string
          nullable: true
        steps:
          type: array
          items:
            type: object
            required:
              - order
              - title
            properties:
              order:
                type: integer
                minimum: 1
              title:
                type: string
              description:
                type: string
                nullable: true
              duration:
                type: string
                nullable: true
                example: 2h
    ItineraryPeriod:
      type: object
      required:
        - day_from
        - day_to
        - title
      properties:
        id:
          type: string
          nullable: true
        day_from:
          type: integer
          minimum: 1
        day_to:
          type: integer
          minimum: 1
        title:
          type: string
        description:
          type: string
          nullable: true
        location:
          type: string
          nullable: true
        image:
          type: string
          format: uri
          nullable: true
        activities:
          type: array
          items:
            type: string
        meals:
          type: array
          description: Meal codes (e.g. `BREAKFAST`, `LUNCH`, `DINNER`).
          items:
            type: string
        accommodation_id:
          type: string
          format: uuid
          nullable: true
    Accommodation:
      type: object
      required:
        - name
        - type
      properties:
        name:
          type: string
        type:
          type: string
          example: Riad
        stars:
          type: integer
          minimum: 0
          maximum: 5
          nullable: true
    RatePack:
      type: object
      required:
        - id
        - name
        - base_price
        - room_allocation
        - capacity
        - cancellation_tiers
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
          nullable: true
        base_price:
          $ref: '#/components/schemas/Money'
          description: >
            Public B2C reference price, per adult traveler (the `b2c_price`,
            price floor).

            For the partner revenue split on this price, see `pricing`.
        pricing:
          $ref: '#/components/schemas/PartnerPricing'
          description: Indicative revenue breakdown on `base_price` (`indicative = true`).
        child_supplement:
          type: object
          nullable: true
          properties:
            applies_below_age:
              type: integer
            percent_of_adult:
              type: integer
              minimum: 0
              maximum: 100
        extra_bed_supplement:
          $ref: '#/components/schemas/Money'
          nullable: true
        room_allocation:
          type: string
          enum:
            - PER_ROOM
            - PER_PERSON
          description: >
            - `PER_ROOM`: per-room pricing, adults + children share (Morocco
            standard).

            - `PER_PERSON`: per-traveler pricing (Umrah, Hajj, charters).
        capacity:
          type: integer
          description: Total capacity per slot for this pack.
        cancellation_tiers:
          type: array
          maxItems: 3
          items:
            type: object
            required:
              - days_before
              - refund_percent
            properties:
              days_before:
                type: integer
                minimum: 0
                description: >-
                  Cancellation at D − N → this tier applies. Tiers ordered by
                  days_before descending.
              refund_percent:
                type: integer
                minimum: 0
                maximum: 100
    AgeBrackets:
      type: object
      description: >-
        Age boundaries used to price travellers: 0..infant_max_age is an infant,
        infant_max_age+1..child_max_age a child, above child_max_age an adult.
      required:
        - infant_max_age
        - child_max_age
      properties:
        infant_max_age:
          type: integer
          minimum: 0
        child_max_age:
          type: integer
          minimum: 1
    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
    AdventureType:
      type: string
      enum:
        - EXPERIENCE
        - TRIP
      description: |
        - `EXPERIENCE`: short adventure (a few hours to 1 day), hourly slots.
        - `TRIP`: multi-day tour with an itinerary.
    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
    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
    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.

````