Skip to main content

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.

SafarAPI uses two layers of authentication:
  1. Bearer API key (on every request) — proves your identity
  2. HMAC signature (on writes only: POST/PUT/DELETE/PATCH) — proves the request body has not been tampered with and is not a replay

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

Every write request must include three headers:
HeaderValue
Idempotency-KeyUUID v4, unique per logical operation
X-TimestampUnix epoch in seconds, must be within ±5 minutes of server time
X-Signaturehex(HMAC_SHA256(secret, canonical))
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: '/v1/bookings',
  body,
  timestamp,
});

await fetch('https://api.safarapi.com/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,
});

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.