curl --request GET \
--url https://api.safarapi.com/api/partner/v1/adventures \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.safarapi.com/api/partner/v1/adventures"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.safarapi.com/api/partner/v1/adventures', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.safarapi.com/api/partner/v1/adventures",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.safarapi.com/api/partner/v1/adventures"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.safarapi.com/api/partner/v1/adventures")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.safarapi.com/api/partner/v1/adventures")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_bodyPaginated adventure search
Search the Safariat catalog. Returned prices are the net prices for the partner (Safariat public price − Safariat margin ± negotiated partner margin). The bank then applies its own commission on its front end to compute the customer price.
Pagination is cursor-based: pass the cursor received in meta.next_cursor to fetch
the next page. No next_cursor → end of the result set.
curl --request GET \
--url https://api.safarapi.com/api/partner/v1/adventures \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.safarapi.com/api/partner/v1/adventures"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.safarapi.com/api/partner/v1/adventures', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.safarapi.com/api/partner/v1/adventures",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.safarapi.com/api/partner/v1/adventures"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.safarapi.com/api/partner/v1/adventures")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.safarapi.com/api/partner/v1/adventures")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_bodyAuthorizations
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.
Headers
Language of the returned labels (titles, descriptions, categories).
fr, en, ar Query Parameters
Full-text search on title / description / destination.
100Filter on EXPERIENCE (short-duration) or TRIP (multi-day). The internal spellings SHORT_DURATION and MULTI_DAY are also accepted on input; responses always use EXPERIENCE / TRIP.
EXPERIENCE: short adventure (a few hours to 1 day), hourly slots.TRIP: multi-day tour with an itinerary.
EXPERIENCE, TRIP One or more category codes (e.g. trek, desert, cultural). Repeat the parameter to combine.
Free-text locality match (city or area name, e.g. marrakech).
100Restrict results to a single agency.
Keep adventures bookable on or after this date (inclusive). Combined with date_to
it forms a range; alone it leaves the upper bound open.
Keep adventures bookable on or before this date (inclusive). Alone it leaves the lower
bound open. Availability is evaluated on the calendar that actually applies to each rate
pack, so results agree with GET /adventures/{slug}/availability.
One or more months in YYYY-MM format for flexible monthly availability. Up to 12 entries.
12^[0-9]{4}-(0[1-9]|1[0-2])$Total number of travelers (adults + children).
1 <= x <= 30Minimum net price per traveler in MAD (inclusive).
x >= 0Maximum net price per traveler in MAD (inclusive). Must be greater than or equal to price_min.
x >= 0Minimum total adventure duration in hours.
x >= 0Maximum total adventure duration in hours. Must be greater than or equal to duration_min_hours.
x >= 0Minimum average customer rating (1-5).
1 <= x <= 5Latitude of the reference point for geo-proximity search. Required if lng or radius_km is set.
-90 <= x <= 90Longitude of the reference point. Required if lat or radius_km is set.
-180 <= x <= 180Search radius in kilometers from the reference point. Required if lat or lng is set.
Sort order. Default rating_desc (best-rated first). Available options:
rating_desc— highest customer rating firstprice_asc/price_desc— by base price (MAD)created_desc— most recently added first
rating_desc, price_asc, price_desc, created_desc 1-based page number. Alternative to cursor for random access and backward
navigation. When both are supplied, page wins. Combine with meta.total_results
to build a classic pager. Note that page-based paging can drift if the catalogue
changes between two calls.
x >= 1Opaque pagination cursor returned by the previous response in meta.next_cursor.
Pass null/omit on the first request; absence of next_cursor in the response
signals the last page. Treat the cursor as fully opaque — its format is internal
and may evolve.
Keyset pagination (all listing endpoints except /adventures). The cursor
is anchored on the last row of the previous page, so results are stable under
concurrent writes — no duplicates, no skipped rows when items are inserted
between page fetches. Two cursors are not interchangeable across endpoints:
a cursor obtained from /reviews will be rejected with 400 on /bookings.
/adventures is offset-based by design (dynamic sort: popularity, price,
distance) and will not migrate to keyset. Its cursor cannot be passed to other
endpoints.
Behavior change (Phase B): /webhooks/deliveries now sorts by created_at
instead of updated_at. The previous sort field is mutable (retry attempts),
which is incompatible with the keyset invariant. Use the status filter to
surface deliveries currently being retried (status=PENDING,FAILED).
256Maximum number of items per page.
1 <= x <= 100