Skip to main content
SafarAPI uses two layers of authentication:
  1. Bearer API key (on every request) — proves your identity
  2. HMAC signature (production writes only: POST/PUT/DELETE/PATCH) — proves the request body has not been tampered with and is not a replay. Sandbox sk_test_* keys are exempt (see below).

API key format

sk_live_ABCDWXYZ_aN5vKjL9mQrT8Hp7Yx2Wc1Bs6Vd3Fz4Eg
   │       │        │
   │       │        └── 48-char secret (shown once)
   │       └─── 8-char prefix (stored in clear for identification)
   └─── environment marker (live or test)
The full bearer token is Authorization: Bearer sk_live_<prefix>_<secret>.
Test keys (sk_test_*) and live keys (sk_live_*) are issued separately. Test keys only access sandbox data; live keys only access production data.

Signing writes

HeaderValueRequired
Idempotency-KeyUUID v4, unique per logical operationBoth environments
X-TimestampUnix epoch in seconds, within ±5 minutes of server timeProduction only
X-Signaturehex(HMAC_SHA256(secret, canonical))Production only
Sandbox sk_test_* keys are exempt from request signing: omit X-Timestamp and X-Signature on writes. This lets the interactive playground run write calls end to end. Idempotency-Key is still required. Production sk_live_* keys must sign every write — the canonical string below applies.
The canonical string is:
{X-Timestamp}\n{METHOD}\n{path-with-query}\n{body}
The body is the exact bytes you send. Reformatting (whitespace, key order in JSON) breaks the signature.

Code samples

import crypto from 'node:crypto';

function signRequest({ secret, method, path, body, timestamp }) {
  const canonical = `${timestamp}\n${method}\n${path}\n${body}`;
  return crypto.createHmac('sha256', secret).update(canonical).digest('hex');
}

const timestamp = Math.floor(Date.now() / 1000);
const body = JSON.stringify({ quote_id: '...', /* ... */ });
const signature = signRequest({
  secret: 'aN5vKjL9...',
  method: 'POST',
  path: '/api/partner/v1/bookings',
  body,
  timestamp,
});

await fetch('https://api.safarapi.com/api/partner/v1/bookings', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer sk_live_ABCDWXYZ_${secret}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
    'X-Timestamp': String(timestamp),
    'X-Signature': signature,
  },
  body,
});
import hmac
import hashlib
import time
import uuid
import json
import requests

def sign(secret: str, method: str, path: str, body: str, timestamp: int) -> str:
    canonical = f"{timestamp}\n{method}\n{path}\n{body}"
    return hmac.new(secret.encode(), canonical.encode(), hashlib.sha256).hexdigest()

secret = "aN5vKjL9..."
timestamp = int(time.time())
body = json.dumps({"quote_id": "...", "partner_reference": "YBANK-BOOK-001"})
signature = sign(secret, "POST", "/api/partner/v1/bookings", body, timestamp)

requests.post(
    "https://api.safarapi.com/api/partner/v1/bookings",
    data=body,
    headers={
        "Authorization": f"Bearer sk_live_ABCDWXYZ_{secret}",
        "Content-Type": "application/json",
        "Idempotency-Key": str(uuid.uuid4()),
        "X-Timestamp": str(timestamp),
        "X-Signature": signature,
    },
)
<?php
function safarapi_sign(string $secret, string $method, string $path, string $body, int $timestamp): string {
    $canonical = $timestamp . "\n" . $method . "\n" . $path . "\n" . $body;
    return hash_hmac('sha256', $canonical, $secret);
}

$secret = 'aN5vKjL9...';
$timestamp = time();
$body = json_encode(['quote_id' => '...', 'partner_reference' => 'YBANK-BOOK-001']);
$signature = safarapi_sign($secret, 'POST', '/api/partner/v1/bookings', $body, $timestamp);

$curl = curl_init('https://api.safarapi.com/api/partner/v1/bookings');
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $body,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer sk_live_ABCDWXYZ_' . $secret,
        'Content-Type: application/json',
        'Idempotency-Key: ' . bin2hex(random_bytes(16)),
        'X-Timestamp: ' . $timestamp,
        'X-Signature: ' . $signature,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($curl);
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.HexFormat;
import java.util.UUID;

class SafarApiSigner {
    static String sign(String secret, long timestamp, String method, String path, String body) throws Exception {
        String canonical = timestamp + "\n" + method + "\n" + path + "\n" + body;
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        return HexFormat.of().formatHex(mac.doFinal(canonical.getBytes(StandardCharsets.UTF_8)));
    }

    public static void main(String[] args) throws Exception {
        String secret = "aN5vKjL9...";
        long ts = System.currentTimeMillis() / 1000;
        String body = "{\"quote_id\":\"...\"}";
        String signature = sign(secret, ts, "POST", "/api/partner/v1/bookings", body);

        HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.safarapi.com/api/partner/v1/bookings"))
                .header("Authorization", "Bearer sk_live_ABCDWXYZ_" + secret)
                .header("Content-Type", "application/json")
                .header("Idempotency-Key", UUID.randomUUID().toString())
                .header("X-Timestamp", String.valueOf(ts))
                .header("X-Signature", signature)
                .POST(HttpRequest.BodyPublishers.ofString(body))
                .build();

        HttpClient.newHttpClient().send(request, java.net.http.HttpResponse.BodyHandlers.ofString());
    }
}

Common errors

CodeReason
auth.api_key.missingAuthorization header absent
auth.api_key.malformedHeader doesn’t follow Bearer sk_(live|test)_<prefix>_<secret>
auth.api_key.invalidPrefix unknown or secret mismatch
auth.timestamp.missingX-Timestamp absent on write
auth.timestamp.skewTimestamp more than 5 min off server time
auth.signature.missingX-Signature absent on write
auth.signature.invalidHMAC computation mismatch (body altered, wrong secret, etc.)
idempotency.key.requiredIdempotency-Key missing on write
idempotency.key.conflictSame key reused with a different body
All errors return JSON with code, message, request_id.