Event Intelligence API
A machine-readable interface to the Event Intelligence Desk. The pipeline that produces desk memos — catalyst extraction, source reconciliation, materiality assessment, narrative-dislocation measurement, signal classification, and invalidation framing — is exposed over REST, gRPC, a multiplexed streaming channel, and a signed webhook bus, under a hardened cryptographic protocol.
Engineered for systematic desks integrating event intelligence directly into research, execution, and risk systems. Every request is authenticated, signed, replay-bounded, and audited. Access is provisioned per tenant and is not anonymous. No market data is redistributed; outputs are interpretive research artifacts.
Transport & cryptographic profile
All traffic terminates over TLS 1.3 with downgrade protection and HSTS preload. Key agreement defaults to the hybrid post-quantum group X25519MLKEM768, combining ML-KEM-768 with X25519 so that recorded sessions remain confidential against a future quantum adversary. Dedicated tenants may additionally pin mutual TLS at the edge with SPIFFE-issued client certificates.
Identity & access tokens
Tenants authenticate via OAuth2 client-credentials. Access tokens are sender-constrained — bound either to the mTLS client certificate (RFC 8705) or to a DPoP proof key (RFC 9449) — so a captured token cannot be replayed from another channel. Tokens are short-lived (≤ 600 s), audience-restricted, and issued as PASETO v4.public (Ed25519) or JWT/EdDSA.
POST /v2/oauth/token # presented over the mTLS channel
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&scope=memos:read signals:read stream:subscribe
&audience=https://api.aster-ridge.com/v2
→ 200 OK
{ "access_token": "v4.public.eyJ…", # PASETO v4, Ed25519
"token_type": "DPoP", "expires_in": 600,
"cnf": { "x5t#S256": "9f2c…" } } # confirmed cert thumbprintRequest authentication — message signatures
Aster Ridge implements HTTP Message Signatures (RFC 9421). The client signs a canonical base composed of the request method, path, authority, the Content-Digest (RFC 9530), the key id, and a server-issued single-use nonce, with the created timestamp inside the signature parameters. Requests outside a 120-second window, or reusing a nonce, are rejected with 401 invalid_signature.
POST /v2/memos HTTP/2
Host: api.aster-ridge.com
AR-Version: 2025-09-30
AR-Key-Id: ak_live_7f3c9b21
AR-Nonce: 0r5Qd1mN8t2hVb9w # single-use, server-issued
Content-Digest: sha-256=:RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=:
Idempotency-Key: 1f0b8d2e-7a44-4c0e-9b6e-2c8a4f0d1e22
Signature-Input: sig1=("@method" "@path" "@authority" \
"content-digest" "ar-key-id" "ar-nonce");\
created=1790000000;keyid="ak_live_7f3c9b21";alg="ed25519"
Signature: sig1=:k8s9…Ed25519…detached…signature==:
Content-Type: application/json
{"ticker":"AAPL"}"@method": POST
"@path": /v2/memos
"@authority": api.aster-ridge.com
"content-digest": sha-256=:RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=:
"ar-key-id": ak_live_7f3c9b21
"ar-nonce": 0r5Qd1mN8t2hVb9w
"@signature-params": ("@method" "@path" "@authority" "content-digest" \
"ar-key-id" "ar-nonce");created=1790000000;keyid="ak_live_7f3c9b21";alg="ed25519"Nonces are obtained from POST /v2/auth/nonce (cheap, unsigned) or piggybacked on the prior response's AR-Nonce-Next header. Responses are themselves signed; verify them against the tenant JWKS for end-to-end integrity.
Authorization, scopes & entitlements
A token's scopes constrain which routes it may call. Independently, a per-tenant coverage entitlement constrains which securities are visible — an attribute-based filter applied at the data layer. A call with the right scope but outside entitlement returns 403 entitlement_denied, never a silent empty set.
Conventions & header reference
| Header | Direction | Purpose |
|---|---|---|
| AR-Version | request | Pins the date-versioned API contract, e.g. 2025-09-30. |
| AR-Key-Id | request | Identifies the signing key (ak_live_…). |
| AR-Nonce | request | Server-issued single-use nonce for replay defense. |
| Signature-Input / Signature | request | RFC 9421 covered components and Ed25519 signature. |
| Content-Digest | request/response | RFC 9530 SHA-256/512 digest of the representation. |
| Idempotency-Key | request | Dedupe key for POST; 24 h replay of the original result. |
| If-Match / ETag | request/response | Optimistic concurrency on mutable resources. |
| DPoP / Authorization | request | Sender-constrained proof + bearer/DPoP access token. |
| traceparent / tracestate | request/response | W3C Trace Context for distributed correlation. |
| RateLimit / Retry-After | response | Token-bucket budget, reset, and backoff guidance. |
| AR-Request-Id | response | Stable per-request id; quote in support and audit. |
| Deprecation / Sunset | response | RFC 8594 lifecycle signaling on retiring routes. |
REST resources
# 1 — enqueue (signed, idempotent)
POST /v2/memos → 202 Accepted
{ "job_id": "job_29x…", "status": "queued",
"poll_after_ms": 1500, "ticker": "AAPL" }
# 2 — resolve (poll or receive memo.completed webhook)
GET /v2/memos/mem_5b2… → 200 OK (response is signature-verified)
{ "memo_id": "mem_5b2…", "ticker": "AAPL", "company_name": "Apple Inc.",
"signal": "HOLD", "confidence": 0.62, "horizon": "3–5 weeks",
"estimated_return": { "low": -2.5, "high": 3.2 }, "risk_level": "medium",
"event_classification": "post-event narrative digestion",
"materiality_score": 0.41, "narrative_dislocation_score": 0.55,
"insight_quality_score": 0.90, "source_set_status": "partial",
"invalidation": "close > 211.40 on volume > 1.5×ADV invalidates HOLD",
"sources": 8, "generated_at": "2026-06-25T14:18:02.114Z" }Streaming — WebSocket / gRPC / SSE
Subscribe over WebSocket, server-streaming gRPC, or SSE. The upgrade is authenticated with a short-lived DPoP-bound stream token from POST /v2/stream/tokens. Frames carry a monotonic seq; on reconnect, present the last seq to replay the gap (exactly-once via consumer-side dedupe). Slow consumers are signaled via credit-based backpressure before the server sheds; heartbeats every 15 s.
// wss://stream.aster-ridge.com/v2?token=st_live_… (DPoP-bound)
→ { "op": "subscribe",
"channels": ["signals", "memos", "board"],
"filter": { "tickers": ["AAPL","NVDA","MSFT"], "min_confidence": 0.5 },
"resume_from_seq": 81419 }
← { "op": "ack", "channels": ["signals","memos","board"], "seq": 81420 }
← { "ch": "signals", "seq": 81421, "type": "signal.updated",
"data": { "ticker": "NVDA", "signal": "BUY", "confidence": 0.71,
"invalidation": "loses 168.00 intraday" } }
← { "op": "credit", "available": 512 } // backpressure window
← { "op": "heartbeat", "seq": 81422, "ts": "2026-06-25T14:18:17.004Z" }service EventIntelligence {
rpc Subscribe(SubscribeRequest) returns (stream Frame);
rpc GetMemo(GetMemoRequest) returns (Memo);
}
// channel binding: ALPN h2, per-RPC deadline, mTLS-bound token in metadataWebhooks & delivery guarantees
Each delivery carries an HMAC-SHA256 over the raw body under your endpoint secret AND a detached Ed25519 signature verifiable against the published JWKS, plus X-AR-Timestamp and X-AR-Delivery-Id. Optionally require mTLS to your endpoint. Respond 2xx within 5 s; non-2xx retries 8× over ~6 h with jittered backoff, then dead-letters. Deliveries are at-least-once and ordered per ticker — dedupe on delivery id.
POST https://desk.your-fund.com/hooks/aster
AR-Version: 2025-09-30
X-AR-Event: signal.invalidated
X-AR-Delivery-Id: dlv_a91f3c
X-AR-Timestamp: 1790000189
X-AR-Signature: t=1790000189,
v1=7b9f…hmac_sha256(endpoint_secret),
v2=:Ed25519…detached…(verify via JWKS):
{ "id": "evt_77c…", "type": "signal.invalidated",
"created": "2026-06-25T14:31:09.512Z", "sequence": 998211,
"data": { "ticker": "AAPL", "prior_signal": "HOLD",
"reason": "invalidation level breached", "memo_id": "mem_5b2…" } }Data model
{
"memo_id": "string", // mem_…
"ticker": "string",
"company_name": "string|null",
"signal": "BUY|SELL|HOLD",
"confidence": "number[0,1]",
"horizon": "string", // human range, e.g. "3–5 weeks"
"estimated_return": { "low": "number", "high": "number" }, // percent
"risk_level": "low|medium|high",
"event_classification": "string|null",
"materiality_score": "number[0,1]|null",
"narrative_dislocation_score":"number[0,1]|null",
"insight_quality_score": "number[0,1]|null",
"source_set_status": "full|partial|thin",
"invalidation": "string", // the condition under which it is wrong
"executive_desk_summary": "string|null",
"sources": "Source[]",
"etag": "string", // for If-Match
"generated_at": "rfc3339"
}Signal {
"ticker": "string", "signal": "BUY|SELL|HOLD",
"confidence": "number[0,1]", "horizon": "string",
"estimated_return": { "low": "number", "high": "number" },
"invalidation": "string", "memo_id": "string|null",
"classified_at": "rfc3339", "superseded_at": "rfc3339|null" }
Source {
"source_id": "string", "publisher": "string|null", "url": "string|null",
"published_at": "rfc3339|null", "relevance_score": "number[0,1]",
"materiality_score": "number[0,1]", "title": "string|null" }Rate limits & quotas
| Dimension | Standard | Dedicated | Colocated |
|---|---|---|---|
| REST sustained (token bucket) | 10 req/s | 100 req/s | Negotiated |
| REST burst | 30 | 300 | Negotiated |
| gRPC concurrent RPCs | 16 | 256 | Dedicated |
| Concurrent streams | 2 | 32 | 256 |
| Subscriptions / socket | 50 | 1,000 | 10,000 |
| Webhook endpoints | 3 | 50 | Unlimited |
| Memo jobs / day | 250 | Negotiated | Negotiated |
| Export volume / day | 5 GB | 500 GB | Dedicated |
| History retention | 90 days | 7 years | Custom |
Errors — problem+json
| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Malformed syntax, bad parameter, or unparseable body. |
| 401 | invalid_signature | Missing/invalid message signature, nonce, or skewed timestamp. |
| 401 | invalid_token | Expired, malformed, or unbound (mTLS/DPoP) access token. |
| 403 | insufficient_scope | Authenticated but lacking the scope for this resource. |
| 403 | entitlement_denied | Resource is outside the tenant's coverage entitlement. |
| 404 | not_found | Resource does not exist or is not visible to the caller. |
| 409 | idempotency_conflict | Idempotency-Key reused with a different request body. |
| 412 | precondition_failed | If-Match did not match the current ETag. |
| 422 | unprocessable_entity | Well-formed but semantically invalid (e.g., uncovered ticker). |
| 429 | rate_limited | Quota exceeded. Honor Retry-After and RateLimit-* headers. |
| 500 | internal_error | Unexpected condition. Retry idempotent calls with backoff. |
| 503 | capacity_exceeded | Synthesis capacity saturated; retry with exponential backoff. |
HTTP/2 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 1
RateLimit-Limit: 100, RateLimit-Remaining: 0, RateLimit-Reset: 1
{ "type": "https://aster-ridge.com/problems/rate_limited",
"title": "Sustained rate exceeded",
"status": 429, "code": "rate_limited",
"request_id": "req_3a8…", "trace_id": "4bf92f3577b34da6…",
"retry_after_ms": 740 }Bulk, exports & point-in-time
Exports materialize columnar Parquet/Arrow to a pre-signed object-store URL (your bucket via cross-account role, or ours with short-lived credentials), with a manifest of row counts and per-file content digests. Point-in-time queries reconstruct the exact signal/memo state as of any historical instant for leakage-free backtests.
POST /v2/exports
{ "dataset": "signals", "format": "parquet",
"range": { "from": "2025-01-01", "to": "2025-12-31" },
"destination": { "type": "s3", "role_arn": "arn:aws:iam::…:role/ar-export" } }
→ 202 { "export_id": "exp_5d…", "status": "running" }
GET /v2/exports/exp_5d…
→ 200 { "status": "completed", "rows": 4821339,
"files": [ { "url": "https://…signed…", "sha-256": "…" } ] }Key management & crypto governance
Reliability, DR & compliance
Versioning & deprecation
Breaking changes ship under a new date version; you pin one via the AR-Version header and migrate on your schedule. Retiring routes carry Deprecation and Sunset headers (RFC 8594) for a minimum of 180 days. Additive fields are never breaking.
| Spec | Released | Notes |
|---|---|---|
| 1.0 | GA | REST · WebSocket · signed webhooks · SDK GA across all languages. |
| 0.9 | Private beta | gRPC transport; point-in-time query API; signed cursors. |
| 0.8 | Private beta | RFC 9421 message signatures; DPoP token binding; problem+json errors. |
| 0.5 | Private beta | Initial tenants; mTLS-bound tokens (RFC 8705); per-ticker webhook ordering. |
SDKs & tooling
# example shape
from asterridge import Client
ar = Client(key_id="ak_live_…", signing_key="ed25519.pem", mtls=("client.crt","client.key"))
memo = ar.memos.create(ticker="AAPL").wait() # blocks to resolution
print(memo.signal, memo.confidence, memo.invalidation)
for ev in ar.stream(channels=["signals"], tickers=["NVDA"]):
if ev.type == "signal.invalidated":
risk.flatten(ev.data["ticker"])OpenAPI 3.1 + AsyncAPI 2.6 specifications, a Protobuf bundle, and a Postman collection will ship with tenant credentials. Sandbox keys return deterministic fixtures for CI; a staging environment mirrors production crypto.
Access & Provisioning
- ·Dedicated scoped keys, mTLS + DPoP, sandbox
- ·REST · gRPC · WebSocket · signed webhooks
- ·Per-tenant coverage entitlement
- ·OpenAPI 3.1 · AsyncAPI 2.6 · Protobuf · SDKs
- ·Solutions engineering + onboarding
- ·PrivateLink, BYOK/HYOK, SIEM audit export
No public performance guarantees. API outputs are informational research artifacts; signal classifications are conditional on stated invalidation levels. Access does not create an advisory relationship. No market-data redistribution.