Alerts
Alerts turn completed traces and spend controls into operational signals. In closed beta, Olyx supports two webhook alert paths: optimization grade alerts after trace completion and circuit breaker alerts when an API key crosses its hourly spend cap.
The practical rule is: use alert webhooks for immediate operational events; use insights for review and optimization workflows. Webhook alerts are asynchronous and best-effort. They are useful for notifying your systems, but they are not a replacement for your own incident management, retry queues, or provider-side budget controls.
What Alerts Detect
Olyx evaluates alerts from data it already records: completed trace summaries, API key spend counters, and project insights. The table below separates what can deliver a webhook today from what is currently exposed as an insight.
| Signal | Delivery | What it means |
|---|---|---|
| Optimization grade | Webhook | A completed trace received a worse grade than the project’s configured threshold. |
| Circuit breaker tripped | Webhook | An API key crossed its hourly spend cap and was paused. |
| Migration insight | Dashboard/API | A task pattern may be a candidate for a cheaper model. Validate with replays before changing routing. |
| Latency insight | Dashboard/API | A model or task pattern has high P99 latency over the lookback window. |
| Intent pattern insight | Dashboard/API | A task type is common enough that it may deserve its own routing rule. |
During closed beta, avoid treating these signals as contractual quality benchmarks. They are operational cues for debugging, cost review, and safe rollout planning.
Webhook Delivery
Alert webhooks are configured per project. You can add them from Settings -> Alert Webhooks or through the API. Each webhook has a URL and a secret. Olyx signs each payload with that secret so your endpoint can reject spoofed requests.
Webhook delivery happens in a background job. Olyx expects a 2xx response from your endpoint; network errors,
timeouts, and non-2xx responses are retried by the job runner. The current beta job retries up to five attempts with
backoff. Keep handlers fast and idempotent, because webhook delivery can be retried.
Create a Webhook
Use a session-authenticated request from the dashboard or your internal admin tooling. The secret is returned on
create; store it in your application secret manager because it is needed to verify X-Olyx-Signature.
POST /api/v1/projects/:project_id/alert_webhooks
Content-Type: application/json
{
"alert_webhook": {
"url": "https://yourapp.example.com/olyx/alerts",
"secret": "your-signing-secret"
}
}
import os
import httpx
session_token = os.environ["OLYX_SESSION_TOKEN"]
project_id = os.environ["OLYX_PROJECT_ID"]
response = httpx.post(
f"https://api.olyx.ai/api/v1/projects/{project_id}/alert_webhooks",
headers={"Authorization": f"Bearer {session_token}"},
json={
"alert_webhook": {
"url": "https://yourapp.example.com/olyx/alerts",
"secret": os.environ["OLYX_WEBHOOK_SECRET"],
}
},
)
response.raise_for_status()
webhook = response.json()
print(webhook["id"])
print(webhook["secret_last4"])
Payload Fields
Payloads vary by alert type, but these fields are stable enough to build handlers around in closed beta.
| Field | Type | Description |
|---|---|---|
alert_type | string | The alert category, such as optimization_grade or circuit_breaker_tripped. |
project_id | string/integer | The project that generated the alert. |
triggered_at | ISO 8601 string | When Olyx detected the alert condition. |
trace_id | string | Present for trace-grade alerts. Points to the completed trace. |
grade | string | Present for optimization-grade alerts. The trace grade that crossed the threshold. |
threshold | string | Present for optimization-grade alerts. The configured minimum acceptable grade. |
key_id | string | Present for circuit breaker alerts. The API key that was paused. |
current_spend | number | Present for circuit breaker alerts. The spend observed in the active hourly window. |
Example Payloads
An optimization-grade alert is emitted after a trace completes and its grade is worse than the configured threshold.
{
"alert_type": "optimization_grade",
"project_id": 42,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"grade": "D",
"threshold": "B",
"total_cost": 0.0182,
"triggered_at": "2026-04-12T09:00:00Z"
}
A circuit-breaker alert is emitted when an API key crosses its hourly spend cap.
{
"alert_type": "circuit_breaker_tripped",
"key_id": "ak_abc123",
"key_name": "Production",
"project_id": 42,
"hourly_limit": 5.0,
"current_spend": 5.18,
"status": "tripped",
"triggered_at": "2026-04-12T09:00:00Z"
}
Signature Verification
Each configured webhook delivery includes X-Olyx-Signature. The value is an HMAC-SHA256 digest of the raw request
body using the webhook secret.
Verify the raw body before parsing the payload. Do not recompute the signature from a decoded JSON object, because formatting differences change the digest.
import hashlib
import hmac
import json
import os
from fastapi import FastAPI, Header, HTTPException, Request
app = FastAPI()
def valid_signature(raw_body: bytes, signature: str | None) -> bool:
secret = os.environ["OLYX_WEBHOOK_SECRET"].encode()
expected = "sha256=" + hmac.new(secret, raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature or "")
@app.post("/olyx/alerts")
async def receive_olyx_alert(
request: Request,
x_olyx_signature: str | None = Header(default=None),
):
raw_body = await request.body()
if not valid_signature(raw_body, x_olyx_signature):
raise HTTPException(status_code=401, detail="Invalid signature")
payload = json.loads(raw_body)
if payload["alert_type"] == "circuit_breaker_tripped":
notify_oncall(payload)
elif payload["alert_type"] == "optimization_grade":
open_cost_review(payload)
return {"ok": True}
Circuit Breaker Alerts
Circuit breaker alerts are tied to API key hourly spend caps. When the cap is crossed, Olyx marks the key as tripped
and rejects new requests made with that key until you reset the key or adjust the cap.
Reset the key only after you understand the cause. If the spend counter is still above the limit, the key can trip again on the next request.
PATCH /api/v1/keys/:key_id
Content-Type: application/json
{
"status": "active"
}
import os
import httpx
response = httpx.patch(
"https://api.olyx.ai/api/v1/keys/ak_abc123",
headers={"Authorization": f"Bearer {os.environ['OLYX_SESSION_TOKEN']}"},
json={"status": "active"},
)
response.raise_for_status()
Thresholds & Configuration
Closed-beta alert configuration is intentionally small. Prefer simple thresholds that catch obvious waste or runaway traffic, then tune them after you have real trace volume.
Optimization Grade Threshold
The optimization-grade alert compares a completed trace grade to settings.alerts.min_optimization_grade. The default
threshold is B.
PATCH /api/v1/projects/:project_id
Content-Type: application/json
{
"project": {
"settings": {
"alerts": {
"min_optimization_grade": "B"
}
}
}
}
API Key Spend Caps
Set hourly_limit on an API key to pause that key when the rolling hourly spend counter crosses the cap.
PATCH /api/v1/keys/:key_id
Content-Type: application/json
{
"hourly_limit": 10.0
}
Setting hourly_limit to null removes the per-key cap. Keep provider-side limits in place if you need hard budget
enforcement outside Olyx.
Webhook Management
Webhook records are project scoped and require owner or admin access.
| Operation | Endpoint | Notes |
|---|---|---|
| List webhooks | GET /api/v1/projects/:project_id/alert_webhooks | Returns URL, active state, secret suffix, and delivery timestamps. |
| Create webhook | POST /api/v1/projects/:project_id/alert_webhooks | Accepts url and optional secret. Generates a secret when omitted. |
| Delete webhook | DELETE /api/v1/projects/:project_id/alert_webhooks/:id | Removes the destination. Delivery stops immediately. |
Insights vs. Alerts
Insights are optimization recommendations, not webhook incidents. They are generated from trace history and exposed in the dashboard and API so you can review them before changing routing.
| Insight | Trigger | Recommended action |
|---|---|---|
migration_alert | A task pattern may be cheaper on another registered model. | Use Replays to validate the model before changing routing. |
latency_alert | P99 latency exceeds the beta threshold for a model or task pattern. | Inspect slow traces and check prompt size, provider behavior, and tool latency. |
intent_pattern | A recurring intent lacks a dedicated routing rule. | Add or adjust a routing tier so that traffic lands on the intended model family. |
You can refresh insights manually:
POST /api/v1/projects/:project_id/insights/refresh
import os
import httpx
project_id = os.environ["OLYX_PROJECT_ID"]
response = httpx.post(
f"https://api.olyx.ai/api/v1/projects/{project_id}/insights/refresh",
headers={"Authorization": f"Bearer {os.environ['OLYX_SESSION_TOKEN']}"},
)
response.raise_for_status()
Integration Targets
Olyx sends HTTP payloads. Your webhook endpoint decides where the signal goes next: Slack, PagerDuty, Linear, an internal queue, or a custom remediation workflow.
For production handlers, make the webhook endpoint boring:
- verify
X-Olyx-Signature - write the payload to your own queue or database
- return
2xxquickly - process slow downstream work outside the request cycle
- make every action idempotent because retries can happen
def route_alert(payload: dict) -> None:
alert_type = payload["alert_type"]
if alert_type == "circuit_breaker_tripped":
page_oncall(
title="Olyx API key paused",
body=f"{payload['key_name']} crossed ${payload['hourly_limit']} hourly spend",
)
return
if alert_type == "optimization_grade":
create_review_ticket(
title=f"Trace {payload['trace_id']} crossed grade threshold",
metadata=payload,
)
return
log_unknown_alert(payload)
Why This Matters
Alerts help you notice when AI traffic becomes expensive, slow, or blocked by a spend guard. They are intentionally small in closed beta: a few high-signal webhook events, signed delivery, and project insights that point you toward the next investigation.