Skip to content

Authentication and HMAC

Every Evaluate request uses an API key and the version 2 HMAC-SHA256 signing contract.

Header Required Description
Content-Type Yes Must be application/json.
X-API-Key Yes Identifies and authenticates the calling tenant.
X-Signature-Version Yes Must be v2.
X-Timestamp Yes Current ISO 8601 request time; it must be within five minutes of server time.
X-Request-ID Yes A fresh UUID generated for each request attempt.
X-Signature Yes Hex-encoded HMAC-SHA256 digest of the exact raw request body, using the tenant’s HMAC secret.

The API key and HMAC secret are different credentials. Do not use the API key as the HMAC secret.

signature = lowercase_hex(
HMAC-SHA256(
key = UTF8(hmac_secret),
message = exact_raw_request_body_bytes
)
)

The signature covers the request body only. X-Signature-Version, X-Timestamp, and X-Request-ID are not included in the signed bytes.

  1. Construct the request object.
  2. Serialize it once to UTF-8 JSON.
  3. Calculate HMAC-SHA256 over those exact bytes.
  4. Send those same bytes as the HTTP body.
  5. Generate a fresh UUID for X-Request-ID and the current timestamp for X-Timestamp.
  6. Set X-Signature-Version to v2 and put the hexadecimal digest in X-Signature.

Whitespace, property order, and numeric formatting affect the signature. They may be changed before signing, but not between signing and transmission.

The 5-minute Quickstart contains complete, runnable examples for cURL, Node.js, Python, Java, and Go. Each example builds the payload, serializes it once, computes the signature, sends the exact signed bytes, checks the HTTP status, and prints the response.

Use those complete examples as the language reference; avoid copying isolated HMAC fragments that do not also control HTTP serialization.

  • Omitted X-Timestamp: rejected because the header is required.
  • Malformed X-Timestamp: 400 Bad Request.
  • More than five minutes from server time: 401 Unauthorized.
  • Omitted or malformed X-Request-ID: rejected because every request requires a fresh UUID.

Sending a timestamp and request ID does not make a request idempotent. See Reliability and reconciliation.