Request Headers
Headers required and optional on every Starship API request — auth, HMAC signing, idempotency, and content negotiation
Request Headers
This page lists every header the Starship API recognizes on client (/api/v1/*) requests, when each one is required, and how to construct the signing and idempotency headers correctly.
At a Glance
| Header | Required on | Purpose |
|---|---|---|
X-API-Key | All /api/v1/* calls | Your public API key. |
X-API-Secret | All /api/v1/* calls | Your paired API secret. Sent over TLS only. |
X-Signature | Credentials with HMAC enabled | HMAC-SHA256 signature of the request. Format: t=<unix_ts>,v1=<hex_hmac>. |
Idempotency-Key | POST /orders, POST /cart/checkout, POST /payouts | 8–256-char client-generated key that makes retries safe. |
Content-Type | All POST/PUT/PATCH | application/json unless uploading files. |
Accept | Optional | Default application/json. |
X-Request-Id | Optional | Your trace ID — echoed back in responses for cross-log correlation. |
Retry-After | Response-only | Server sends this on 429; honor its value. |
Authentication Headers
X-API-Key + X-API-Secret
The primary authentication mechanism. Your key pair is provisioned through the Admin console and must be rotated on any suspected compromise.
curl https://api.starshiprewards.com/api/v1/products \
-H 'X-API-Key: sk_live_abc123...' \
-H 'X-API-Secret: ***never-log-this***'Never put X-API-Secret in client-side code, version control, or logs. Server-to-server only. If secret is exposed, rotate immediately via the Admin console.
JWT Bearer tokens are no longer supported on the public /api/v1/* API. All client integrations must use X-API-Key + X-API-Secret.
HMAC Signature — X-Signature
HMAC request signing is an optional per-credential control. When your credential has HMAC enabled, every /api/v1/* request must carry a valid signature or the server returns 401 E_UNAUTHORIZED_ACCESS. Credentials without HMAC enabled proceed with just the key/secret check. Ask your Starship contact to enable HMAC on your credential for production.
See the HMAC Signing guide for signing-secret provisioning and test vectors.
Header Format
X-Signature: t=<unix_timestamp>,v1=<hex_hmac_sha256>t— Unix seconds when the signature was generated. Must be within ±5 minutes of server time (300 sdrift window), otherwise the request is rejected with401 E_UNAUTHORIZED_ACCESS: request timestamp expired.v1— Lowercase hex-encoded HMAC-SHA256 of the canonical string using your signing secret as the key.
The v1 prefix is a version tag — if Starship ever adopts a new signing scheme, older clients using v1 continue to work during the deprecation window.
Canonical String — five lines
The string you HMAC is exactly five lines joined by \n, in this order:
METHOD
PATH
SORTED_QUERY
SHA256_HEX(body)
TIMESTAMPMETHOD— uppercase HTTP verb (GET,POST,PUT,PATCH,DELETE).PATH— request path without the query string (e.g.,/api/v1/products, not/api/v1/products?limit=50).SORTED_QUERY— query parameters sorted alphabetically by key, joined ask=v&k=v. Empty string if there are no query params. Repeated keys keep their relative order within the key group.SHA256_HEX(body)— lowercase hex SHA-256 of the raw request body. For requests with no body (e.g.,GET), hash the empty string:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.TIMESTAMP— the same integer you put in thet=parameter of the header.
Common footgun. The path does not include query string — the query is line 3, separately, and must be sorted alphabetically by key. Order of how your HTTP client assembled the URL does not matter. If you sign ?b=2&a=1 but the server parses a=1&b=2, you will mismatch unless you sort.
Minimal Example (bash + openssl + curl)
Compute the signature in the shell and pass it straight to curl. Substitute your own SIGNING_SECRET, method, path, query, and body. Language-specific reference implementations will be published later.
#!/usr/bin/env bash
set -euo pipefail
SIGNING_SECRET="your_signing_secret"
METHOD="GET"
PATH_ONLY="/api/v1/products" # no query string here
QUERY="category_id=1&per_page=50" # sorted alphabetically by key
BODY="" # request body, empty for GET
# Line 1: METHOD
# Line 2: PATH (no query)
# Line 3: SORTED_QUERY
# Line 4: SHA256_HEX(body)
# Line 5: TIMESTAMP
TS="$(date -u +%s)"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $2}')"
CANONICAL="$(printf '%s\n%s\n%s\n%s\n%s' "$METHOD" "$PATH_ONLY" "$QUERY" "$BODY_HASH" "$TS")"
HMAC="$(printf '%s' "$CANONICAL" | openssl dgst -sha256 -hmac "$SIGNING_SECRET" -hex | awk '{print $2}')"
SIG="t=${TS},v1=${HMAC}"
# Send the request — note the query string IS on the URL even though it's
# on its own line in the canonical string. Only signing treats them separately.
curl -sS "https://api.starshiprewards.com${PATH_ONLY}?${QUERY}" \
-H "X-API-Key: ${API_KEY}" \
-H "X-API-Secret: ${API_SECRET}" \
-H "X-Signature: ${SIG}"Why openssl not hmac256 or similar. openssl dgst ships on macOS, Linux, and most CI images out of the box — no install step. For production code, use your language's native crypto library; this shell example is for quick testing and copy-paste reproduction against a live API.
Idempotency-Key
Required on any endpoint that creates a financial obligation — orders, cart checkouts, payouts. Prevents duplicate charges when a network error leaves you uncertain whether your request succeeded.
Rules
| Property | Constraint |
|---|---|
| Length | 8–256 characters |
| Characters | Any printable ASCII |
| Uniqueness | Unique per request body — retrying with the same key returns the cached response |
| Window | Keys are remembered for 24 hours, then may be reused |
Good Key Formats
ord_<your_internal_order_id>_<unix_ts>
checkout_<user_id>_<cart_hash>
payout_<request_uuid>What the Server Does
- First request with a key — processed normally. Response is cached.
- Retry with same key + same body — returns cached response. No side effects.
- Retry with same key + different body — returns
409 RESOURCE_CONFLICT. Fix your client; this almost always means two independent operations are reusing the same key. - Key older than 24h — treated as a fresh request.
See the full Idempotency Guide for retry patterns.
Content Negotiation
Content-Type
Required on all requests with a body.
Content-Type: application/json; charset=utf-8Starship does not currently accept form-encoded bodies on /api/v1/*. charset=utf-8 is optional — UTF-8 is assumed.
Accept
Optional. Defaults to application/json. No other media types are supported today; reserved for future use (e.g., CSV exports).
Tracing & Observability
X-Request-Id
Optional on requests. If you send one, Starship echoes it on the response and includes it in server-side logs — making cross-log correlation trivial when you need support to trace an issue.
X-Request-Id: 01HQCA5V7Z8K2MNPQRTXYW9J3BIf you do not send one, the server generates one and returns it in X-Request-Id on the response.
User-Agent
Not required, but strongly recommended. A specific UA helps support identify which of your integrations is making a request.
User-Agent: acme-gift-platform/2.4.1 (+https://acme.example)Response Headers You Should Know
| Header | When present | Purpose |
|---|---|---|
X-Request-Id | Always | Trace ID (echoed from request or newly generated). |
Retry-After | 429 / 503 responses | Seconds (or HTTP-date) to wait before retrying. |
X-Page / X-Per-Page | Paginated GET responses | Current page + items per page. |
X-Total-Count / X-Total-Pages | Paginated GET responses | Total records / pages for the current filter set. |
X-Has-More | Paginated GET responses | true/false convenience for infinite scroll. |
Forbidden Header
X-Automation-Request
Used internally to mark automated test traffic. Starship blocks production write traffic that carries this header. If your client accidentally injects it, you will see 403 FORBIDDEN_ACTION on every POST/PUT/PATCH to /api/v1/* in production. Remove it.
Next Steps
- HMAC Signing deep dive — algorithm and test vectors
- Error Codes — what comes back when a header is wrong
- Idempotency Guide — pick a good key strategy