Guides · Authentication
Authentication
Every API request authenticates with an API key. Cost-aware ingestion also signs the request with HMAC. This page covers all three: keys, the scopes that gate each endpoint, and how to sign an ingestion request.
API keys
Create a key in your dashboard (Settings → API Keys) and send it on every request in the Authorization header as a bearer token.
curl https://api.bearlumen.com/v1/ping \
-H "Authorization: Bearer sk_test_YOUR_API_KEY"
Keep your keys secret
A key grants access to your organization's data. Never commit one to version control or ship it in client-side code. Load it from an environment variable in production, and rotate any key that may have been exposed.
Scopes
Each key carries scopes that limit what it can do. An endpoint returns 403 if your key is missing the scope it requires, so grant a key only the scopes it needs.
| Scope | Grants | Used by |
|---|---|---|
costs:read | Read cost summaries, breakdowns, trends, attribution, and per-end-user costs. | Attribution Data, Cost Data, Dimension Data, End User Costs, Insights, Rate Cards |
dimensions:read | Read custom dimension definitions. | Dimension Data |
revenue:read | Read margin and profitability analysis. | Insights, Margin Data |
usage:read | Query recorded usage events and aggregations. | Usage Tracking |
usage:write | Send usage events (POST /usage-events and /usage-events/batch). | Usage Events |
Signing ingestion requests (HMAC)
The usage-ingestion endpoints (POST /usage-events and /usage-events/batch) require an HMAC-SHA256 signature in addition to your API key. Read endpoints do not.
Using an SDK? This is automatic
The Node.js and Python SDKs sign every ingestion request for you. You only need this recipe when you call the ingestion API directly over HTTP.
- Set
X-Timestampto the current Unix time in seconds. - Build the payload string
{timestamp}.{body}, wherebodyis the raw JSON request body exactly as sent. - Compute HMAC-SHA256 of that payload keyed with your API key, hex-encoded.
- Send
X-Signature: v1={hex digest}.
API_KEY="sk_test_YOUR_API_KEY"
TS=$(date +%s)
BODY='{"idempotency_key":"3f9a2b1c-8d7e-4f6a-9b2c-1a2b3c4d5e6f","model":"gpt-4o","input_tokens":1500,"output_tokens":420,"event_invoked_at":"2026-01-15T10:30:00.000Z","provider":"openai"}'
# HMAC-SHA256 over "{timestamp}.{raw body}", keyed with your API key, hex-encoded.
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$API_KEY" -r | cut -d' ' -f1)
curl -X POST https://api.bearlumen.com/v1/usage-events \
-H "Authorization: Bearer $API_KEY" \
-H "X-Timestamp: $TS" \
-H "X-Signature: v1=$SIG" \
-H "Content-Type: application/json" \
-d "$BODY"
Two details that trip people up
Sign the raw body bytes exactly as sent. If you re-serialize the JSON after signing (different key order or whitespace), the signature will not match.
The X-Timestamp must be current: requests whose timestamp is more than 5 minutes from server time are rejected with a 401. This is replay protection, not a limit on how old your event_invoked_at data can be (backdated events are fine).
Request headers
| Header | Value |
|---|---|
Authorization | Bearer YOUR_API_KEY |
X-Timestamp | Current Unix time in seconds |
X-Signature | v1={hex HMAC-SHA256 of "{timestamp}.{body}"} |
Content-Type | application/json |