Skip to content

Authentication

All Aleatoric Data feeds require API key authentication. This page covers the security model, protocol-specific authentication methods, key scopes, rate limits, and key rotation procedures.

You must register an Aleatoric account before any authenticated Data or MCP request will succeed. Discovery endpoints such as /.well-known/mcp.json are public, but JSON-RPC, gRPC, SSE, and MCP tool calls require a provisioned ak_live_... key.

The same account and API key are valid across both public Aleatoric surfaces:

  • data.aleatoric.systems for live data feeds and the app console
  • sim.aleatoric.systems / mcp.aleatoric.systems for deterministic simulation and MCP access

Registration and login flows now persist registration and auth metadata, including source attribution such as web, smithery_mcp, lobrunner, and UTM/referrer context when provided.

Authentication is performed at the Aleatoric service edge before requests are admitted to the data plane.

Aleatoric never stores your API key in plaintext. When you create a key, the system computes an HMAC-SHA256 hash and stores only the hash. On every request, the same operation is performed on the submitted key and compared against the stored value.

The verification function is:

H=HMAC-SHA256(ksecret,  kapi)H = \text{HMAC-SHA256}(k_{\text{secret}},\; k_{\text{api}})

where:

  • kapik_{\text{api}} is the API key transmitted in the request (e.g., ak_live_c51nSdWI9ms...)
  • ksecretk_{\text{secret}} is a server-side secret that is never exposed to clients
  • HH is the stored hash used for constant-time comparison

The verification is performed at the gateway layer before any request reaches the data plane. Timing-safe comparison prevents side-channel attacks:

auth={allowif HstoredHcomputedreject (401)otherwise\text{auth} = \begin{cases} \text{allow} & \text{if } H_{\text{stored}} \equiv H_{\text{computed}} \\[4pt] \text{reject (401)} & \text{otherwise} \end{cases}

Security properties — The HMAC construction ensures that even if the hash store is compromised, an attacker cannot derive valid API keys without possession of ksecretk_{\text{secret}}. Key hashes are cached in-process with a 5-minute TTL to minimize lookup latency.

Each transport protocol accepts API keys through specific mechanisms:

ProtocolAuthentication MethodHeader / Field
JSON-RPC (HTTPS)HTTP headerx-api-key: ak_live_...
gRPC (HTTP/2)Metadata entryx-api-key: ak_live_... or authorization: Bearer ak_live_...
Unified Stream (SSE)HTTP headerx-api-key: ak_live_... or Authorization: Bearer ak_live_...
Disk-Sync WebSocketHeader or query paramx-api-key header or ?api_key=ak_live_... query parameter

Pass the key as an x-api-key header on every HTTP POST:

Terminal window
curl -s -X POST https://rpc.aleatoric.systems \
-H "Content-Type: application/json" \
-H "x-api-key: ak_live_YOUR_KEY_HERE" \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'

Attach the key as gRPC metadata. Both x-api-key and authorization metadata keys are accepted:

import grpc
channel = grpc.secure_channel(
"hl.grpc.aleatoric.systems:443",
grpc.ssl_channel_credentials(),
)
stub = PriceServiceStub(channel)
# Option 1: x-api-key metadata
response = stub.GetMidPrice(
MidPriceRequest(coin="BTC"),
metadata=[("x-api-key", "ak_live_YOUR_KEY_HERE")],
)
# Option 2: Bearer token metadata
response = stub.GetMidPrice(
MidPriceRequest(coin="BTC"),
metadata=[("authorization", "Bearer ak_live_YOUR_KEY_HERE")],
)

For persistent streaming RPCs, the metadata is sent once at stream initiation and remains valid for the lifetime of the connection.

Pass the key as an HTTP header when opening the SSE connection:

Terminal window
# x-api-key header
curl -N -H "x-api-key: ak_live_YOUR_KEY_HERE" \
"https://unified.grpc.aleatoric.systems/api/v1/unified/stream"
# Bearer token
curl -N -H "Authorization: Bearer ak_live_YOUR_KEY_HERE" \
"https://unified.grpc.aleatoric.systems/api/v1/unified/stream"

WebSocket connections accept the key either as a header during the upgrade handshake or as a query parameter:

// Query parameter (when headers are not available, e.g., browser WebSocket API)
const ws = new WebSocket(
'wss://disk.grpc.aleatoric.systems?api_key=ak_live_YOUR_KEY_HERE'
);
ws.onopen = () => console.log('Connected to Disk-Sync');
ws.onmessage = (msg) => console.log(JSON.parse(msg.data));
import websockets
# Header-based auth (preferred in server-side clients)
async with websockets.connect(
"wss://disk.grpc.aleatoric.systems",
extra_headers={"x-api-key": "ak_live_YOUR_KEY_HERE"},
) as ws:
async for message in ws:
print(message)

Security note — When using query parameter authentication, the key may appear in server access logs and browser history. Use header-based authentication whenever possible.

API keys can be scoped to restrict access to specific services and regions. Scopes are configured when creating or editing a key in App Console > API Keys.

ScopeDescriptionExample
chain:hyperliquidAccess to Hyperliquid L1 feeds onlyRestrict keys to a single chain
region:usUS region endpoints onlyregion:jp for Japan-only access
stream:readUnified Stream read accessRequired for SSE event subscriptions
status:readStatus API read accessHealth checks, uptime monitoring
status:adminStatus API admin accessIncident management, maintenance windows

Scopes follow an additive model. A key with no explicit scopes defaults to full access for its tier. Adding scopes restricts the key to only those permissions:

effective_access={all permissions for tierif scopes=iscopeiotherwise\text{effective\_access} = \begin{cases} \text{all permissions for tier} & \text{if scopes} = \emptyset \\[4pt] \bigcup_{i} \text{scope}_i & \text{otherwise} \end{cases}

Rate limits are enforced per API key at the gateway layer. Exceeding the limit returns HTTP 429 Too Many Requests (JSON-RPC / SSE) or gRPC status RESOURCE_EXHAUSTED.

TierRequests / minuteRequests / secondBurst Allowance
Basic10025 req burst
Pro120,0002,000500 req burst
QuantUnlimitedUnlimitedN/A

The rate limiter uses a token bucket algorithm. For a given tier with rate rr requests/second and burst capacity bb:

tokens(t)=min ⁣(b,  tokens(tprev)+r(ttprev))\text{tokens}(t) = \min\!\Big(b,\; \text{tokens}(t_{\text{prev}}) + r \cdot (t - t_{\text{prev}})\Big)

A request is admitted if tokens(t)1\text{tokens}(t) \geq 1, in which case one token is consumed. Otherwise, the request is rejected with a Retry-After header indicating the wait time in seconds.

For streaming protocols (gRPC server-streaming, SSE), the rate limit applies to connection initiation, not to individual messages within an established stream.

See Rate Limits for detailed per-endpoint limits, retry strategies, and backoff recommendations.

Keys can be rotated from App Console > API Keys with zero downtime using the following procedure:

Navigate to Dashboard > API Keys > Create Key. The new key is immediately active alongside your existing key. Both keys will work simultaneously.

Deploy the new key to all clients. For rolling deployments, the overlap window ensures no requests are rejected:

Terminal window
# Update environment variable
export ALEATORIC_API_KEY="ak_live_NEW_KEY_HERE"
# Verify the new key works
curl -s -X POST https://rpc.aleatoric.systems \
-H "Content-Type: application/json" \
-H "x-api-key: $ALEATORIC_API_KEY" \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'

Once all clients are updated, revoke the old key from Dashboard > API Keys > Revoke. Revocation is immediate and irreversible. Any in-flight requests using the old key will receive a 401 Unauthorized response.

Best practice — Rotate keys on a regular schedule (e.g., every 90 days) and immediately after any suspected compromise. The app console shows the last-used timestamp for each key to help identify unused keys.

HTTP StatusgRPC StatusMeaning
401 UnauthorizedUNAUTHENTICATEDMissing, invalid, or revoked API key
403 ForbiddenPERMISSION_DENIEDKey lacks required scope for the requested resource
429 Too Many RequestsRESOURCE_EXHAUSTEDRate limit exceeded; check Retry-After header

See Error Codes for the complete error reference.