REST API
Complete REST API reference.
Complete reference for the Risicare REST API.
Base URLs
Risicare has two API surfaces, each with its own base URL:
| API | Base URL | Purpose |
|---|---|---|
| Management API | https://app.risicare.ai/api/v1 | Query traces, manage fixes, CRUD operations |
| Gateway API | https://app.risicare.ai/v1 | SDK span ingestion (you rarely call this directly) |
All endpoints in this reference use the Management API (/api/v1/) unless marked as Gateway endpoints.
Authentication
All requests require API-key authentication. The key may be supplied via either header:
# Canonical (used by the SDKs)
curl -H "Authorization: Bearer rsk-..."
# Alternative (for legacy / non-RFC-7235 clients)
curl -H "X-API-Key: rsk-..."Header precedence: Authorization is consulted first. If an Authorization header is present at all, X-API-Key is ignored — even when the Authorization value is malformed (missing the Bearer prefix). A malformed Authorization header therefore returns 401 even if a valid X-API-Key is also present. Send only one of the two headers; for new integrations prefer Authorization: Bearer rsk-....
Admin routes deny API-key auth
The /api/v1/api-keys/* routes (list / create / revoke) reject API-key authentication entirely and return 403 regardless of which header carries the key. Key management is an OAuth-only operation — use the dashboard's authenticated UI. See API Keys below.
Role-gated endpoints
Some write/delete operations require a minimum role on the authenticating key. A key with the viewer role receives 403 on these endpoints; per-endpoint requirements are annotated below.
| Endpoint | Minimum role |
|---|---|
DELETE /api/v1/traces/{trace_id} | admin |
DELETE /api/v1/sessions/{session_id} | admin |
PATCH /api/v1/projects | admin |
POST /api/v1/fixes/{fix_id}/promote | admin |
POST /api/v1/data/delete-by-subject | admin |
POST /api/v1/scores | member |
All other documented endpoints accept any valid API key (Bearer rsk-...).
Traces
List Traces
GET /api/v1/tracesQuery parameters:
| Parameter | Type | Description |
|---|---|---|
limit | int | Max results (default 50, max 100) |
cursor | string | Pagination cursor |
session_id | string | Filter by session |
agent_name | string | Filter by agent name |
has_error | boolean | Filter by error status |
error_code | string | Filter by error code |
min_duration_ms | float | Minimum duration |
max_duration_ms | float | Maximum duration |
start_time_from | ISO8601 | Start time lower bound |
start_time_to | ISO8601 | Start time upper bound |
environment | string | Filter by environment |
Get Trace
GET /api/v1/traces/{trace_id}Response includes full span tree, max_depth (deepest nesting level), span_count, duration_ms, total_cost_usd, and error_count.
Delete Trace
DELETE /api/v1/traces/{trace_id}Deletes a trace and all its spans (GDPR Art. 17). Returns 204 on success. Requires the admin (or owner) role — keys with a lower role (member or viewer) receive 403.
Sessions
List Sessions
GET /api/v1/sessionsGet Session
GET /api/v1/sessions/{session_id}Response includes all traces in session.
Delete Session
DELETE /api/v1/sessions/{session_id}Deletes a session and all associated data (GDPR Art. 17). Returns 204 on success. Requires the admin (or owner) role — keys with a lower role (member or viewer) receive 403.
Agents
List Agents
GET /api/v1/agentsReturns agent performance statistics.
Get Agent
GET /api/v1/agents/{agent_id}Diagnoses
List Diagnoses
GET /api/v1/diagnosesGet Diagnosis
GET /api/v1/diagnoses/{diagnosis_id}Trigger Diagnosis
POST /api/v1/diagnoses
Content-Type: application/json
{
"trace_id": "trace-abc123",
"span_id": "span-def456"
}Both trace_id and span_id are required. Set "force": true to bypass the 24-hour diagnosis cache.
Fixes
List Fixes
GET /api/v1/fixesGet Fix
GET /api/v1/fixes/{fix_id}Active Fixes
GET /api/v1/fixes/activeReturns all currently deployed fixes for the project. Used by the SDK Fix Runtime to load active fix configurations.
Create Fix
POST /api/v1/fixes
Content-Type: application/json
{
"title": "Retry on tool timeout",
"description": "Add exponential backoff retry for TOOL.EXECUTION.TIMEOUT errors",
"fix_type": "retry",
"error_code": "TOOL.EXECUTION.TIMEOUT",
"fix_config": {
"max_retries": 3,
"initial_delay_ms": 1000,
"exponential_base": 2.0,
"max_delay_ms": 30000,
"jitter": true
},
"confidence": 0.8
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
title | string | Yes | — | Human-readable fix title |
description | string | Yes | — | What this fix does |
fix_type | string | Yes | — | One of: prompt, parameter, tool, retry, fallback, guard, routing |
error_code | string | Yes | — | Error code this fix targets |
fix_config | object | Yes | — | Type-specific configuration |
agent_id | string | No | null | Scope to specific agent |
diagnosis_id | string | No | null | Link to triggering diagnosis |
confidence | float | No | 0.5 | Confidence score (0.0–1.0) |
Update Fix
PATCH /api/v1/fixes/{fix_id}
Content-Type: application/json
{
"status": "active"
}Promote Fix
POST /api/v1/fixes/{fix_id}/promote
Content-Type: application/json
{
"traffic_percentage": 5.0
}Promotes a draft fix to a canary deployment: creates a deployments row (status=canary) and advances the fix's deployment_status to canary. This is the only path that moves a fix from draft to canary.
Requires the admin (or owner) role — keys with a lower role (member or viewer) receive 403. Only draft fixes may be promoted; any other current status returns 409 (this makes retries safe).
Deployments
List Deployments
GET /api/v1/deploymentsGet Deployment
GET /api/v1/deployments/{deployment_id}Create Deployment
POST /api/v1/deployments
Content-Type: application/json
{
"fix_id": "fix-abc123",
"initial_traffic_percentage": 5.0
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
fix_id | string | Yes | — | Fix to deploy |
initial_traffic_percentage | float | No | 5.0 | Starting traffic % (0.0–100.0) |
Rollback Deployment
DELETE /api/v1/deployments/{deployment_id}?reason=Manual+rollbackThe reason is an optional query parameter, not a request body.
Projects
Get Project
GET /api/v1/projectsReturns the project associated with the authenticating API key (singular, not a list).
Update Project
PATCH /api/v1/projects
Content-Type: application/json
{
"settings": {
"retention_days": 30
}
}The project is identified by the API key — no path parameter needed. Requires the admin (or owner) role — keys with a lower role (member or viewer) receive 403.
API Keys
OAuth-only — API keys return 403
The endpoints below are documented for completeness, but they deny API-key authentication. Calling them with Authorization: Bearer rsk-... (or X-API-Key) returns 403 by design. Key management is performed through the dashboard's OAuth-protected UI, not the REST API.
List Keys
GET /api/v1/api-keysCreate Key
POST /api/v1/api-keys
Content-Type: application/json
{
"name": "production-sdk"
}The key is automatically scoped to the project associated with the API key used for authentication. To create a key for a different project within your organization, pass projectId:
POST /api/v1/api-keys
Content-Type: application/json
{
"name": "staging-sdk",
"projectId": "your-project-uuid"
}Revoke Key
DELETE /api/v1/api-keys/{key_id}Evaluations
List Evaluations
GET /api/v1/evaluationsQuery parameters:
| Parameter | Type | Description |
|---|---|---|
status | string | Filter by status (pending, running, completed, failed, cancelled) |
evaluation_type | string | Filter by type (llm_judge, rule_based, human, composite) |
Get Evaluation
GET /api/v1/evaluations/{evaluation_id}Create Evaluation
POST /api/v1/evaluations
Content-Type: application/json
{
"name": "Quality check v2",
"evaluation_type": "llm_judge",
"trace_ids": ["trace-abc123", "trace-def456"],
"criteria": ["faithfulness", "toxicity"],
"config": {}
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes | — | Evaluation run name |
evaluation_type | string | Yes | — | One of: llm_judge, rule_based, human, composite |
dataset_id | string | No | null | Dataset to evaluate against |
trace_ids | string[] | No | null | Specific traces to evaluate |
config | object | No | {} | Evaluation configuration |
criteria | string[] | No | [] | Scorer/criteria names |
Datasets
List Datasets
GET /api/v1/datasetsGet Dataset
GET /api/v1/datasets/{dataset_id}Create Dataset
POST /api/v1/datasets
Content-Type: application/json
{
"name": "evaluation-set-v1",
"description": "Golden test cases",
"tags": ["production", "v1"]
}Update Dataset
PATCH /api/v1/datasets/{dataset_id}
Content-Type: application/json
{
"description": "Updated description",
"tags": ["production", "v2"]
}Delete Dataset
DELETE /api/v1/datasets/{dataset_id}Alerts
List Alerts
GET /api/v1/alertsCreate Alert
POST /api/v1/alerts
Content-Type: application/json
{
"name": "High Error Rate",
"metric": "error_rate",
"operator": "gt",
"threshold": 0.1,
"window_minutes": 5,
"severity": "warning",
"channels": ["slack"]
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes | — | Alert rule name |
metric | string | Yes | — | Metric to monitor |
operator | string | Yes | — | Comparison operator |
threshold | float | Yes | — | Threshold value |
description | string | No | null | Rule description |
window_minutes | int | No | 5 | Evaluation window |
severity | string | No | "warning" | One of: info, warning, error, critical |
channels | string[] | No | [] | Notification channels |
Get Alert
GET /api/v1/alerts/{rule_id}Update Alert
PATCH /api/v1/alerts/{rule_id}
Content-Type: application/json
{
"enabled": false
}Delete Alert
DELETE /api/v1/alerts/{rule_id}Scores
Create Score
POST /api/v1/scores
Content-Type: application/json
{
"trace_id": "abc123def456789012345678abcdef01",
"name": "accuracy",
"score": 0.95,
"comment": "Response matched expected output",
"source": "api"
}Request body:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
trace_id | string | Yes | — | Trace to annotate |
span_id | string | No | null | Specific span (optional) |
name | string | No | "default" | Score name/label |
score | float | Yes | — | Score value between 0.0 and 1.0. Out-of-range values return 422. |
comment | string | No | null | Human-readable comment |
source | string | No | "api" | Score source identifier |
Returns 201 with the created score object. Requires the member role (or higher) — a viewer-role key receives 403.
List Scores
GET /api/v1/scores?trace_id={trace_id}Query parameters:
| Parameter | Type | Description |
|---|---|---|
trace_id | string | Filter by trace ID |
limit | int | Max results (default 50, max 200) |
Webhooks
Delivery is implemented but has never been observed end to end
Webhook CRUD is fully functional — you can create, list, update, and delete webhook configurations, and that path is exercised.
The delivery path is built and wired: dispatch code is embedded in the
deployed spans, diagnosis and alert workers, and it signs each delivery with
HMAC-SHA256 (Stripe-style t={ts},v1={hex} headers). But no delivery has ever
been recorded. On a full prod-parity corpus the only registered webhook shows
delivery_count = 0, failure_count = 0, and last_delivered_at unset —
meaning nothing has been seen to arrive at a real endpoint, successfully or
otherwise. We cannot currently tell you whether delivery works.
Do not build a flow that depends on receiving these events until you have
verified delivery against your own endpoint. The receiver helpers are real and
usable: Python verify_webhook_signature (risicare>=0.1.12) and JavaScript
verifyWebhookSignature (risicare@0.4.0).
List Webhooks
GET /api/v1/webhooksGet Webhook
GET /api/v1/webhooks/{webhook_id}Create Webhook
POST /api/v1/webhooks
Content-Type: application/json
{
"name": "Production alerts",
"url": "https://your-server.com/risicare-webhook",
"events": ["trace.completed", "trace.error", "diagnosis.completed"],
"secret": "your-webhook-secret",
"retry_count": 3,
"timeout_seconds": 30
}Request body:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes | — | Webhook name |
url | string | Yes | — | Delivery URL (must be public, no internal IPs) |
events | string[] | Yes | — | Events to subscribe to |
secret | string | No | null | Signing secret for payload verification |
headers | object | No | {} | Custom headers sent with each delivery |
retry_count | int | No | 3 | Number of delivery retries |
timeout_seconds | int | No | 30 | Delivery timeout |
Supported events:
| Event | Description |
|---|---|
trace.completed | A trace has finished processing |
trace.error | A trace completed with errors |
diagnosis.completed | Error diagnosis finished |
fix.deployed | A fix was deployed |
fix.graduated | A fix graduated to 100% |
fix.rolled_back | A fix was rolled back |
alert.triggered | An alert rule fired |
evaluation.completed | An evaluation run finished |
Update Webhook
PATCH /api/v1/webhooks/{webhook_id}
Content-Type: application/json
{
"enabled": false
}Delete Webhook
DELETE /api/v1/webhooks/{webhook_id}Agent Topology
Get Topology
GET /api/v1/agents/topologyReturns the agent interaction graph:
{
"agents": [
{"id": "orchestrator", "role": "orchestrator", "traces": 1000},
{"id": "researcher", "role": "worker", "traces": 2500}
],
"edges": [
{"from": "orchestrator", "to": "researcher", "count": 500, "type": "delegate"}
]
}Data Management
Delete by Subject (GDPR)
POST /api/v1/data/delete-by-subject
Content-Type: application/json
{
"session_id": "user-session-abc",
"agent_id": "support-agent",
"trace_ids": ["trace-id-1", "trace-id-2"]
}At least one identifier is required (otherwise 422). Requires the admin (or owner) role — keys with a lower role (member or viewer) receive 403.
The response is polymorphic — you must branch on the status code
This endpoint returns 200 when the erasure finished inside the request
budget, and 202 when it did not. A large subject will routinely return
202. Code that assumes 200 and reads spans_deleted will silently
mis-report a partially-completed erasure — always check the status first.
200 — finished in-request
{
"spans_deleted": 42,
"sessions_deleted": 1,
"traces_affected": 3,
"redis_entries_purged": 0,
"warnings": [],
"complete": true,
"erasure_job_id": "6f1c…",
"stores_outstanding": []
}| Field | Type | Description |
|---|---|---|
spans_deleted | int | Number of span rows deleted |
sessions_deleted | int | Number of session rows deleted |
traces_affected | int | Number of distinct traces touched |
redis_entries_purged | int | Buffered span entries purged from Redis |
warnings | string[] | Per-store failure messages (empty when everything deleted cleanly) |
complete | bool | true when every store deleted successfully; false if any warnings were recorded |
erasure_job_id | string | null | Forensic handle for this erasure; also the id the remainder is addressable by |
stores_outstanding | string[] | Stores not yet erased — empty on a complete erasure |
202 — accepted, still running
The body carries the job id and a Location header points at the status resource:
HTTP/1.1 202 Accepted
Location: /v1/data/erasure-jobs/{job_id}{
"job_id": "6f1c…",
"erasure_job_id": "6f1c…",
"location": "/v1/data/erasure-jobs/6f1c…",
"status": "running",
"stores_outstanding": ["spans", "sessions"]
}The same id is emitted under both job_id and erasure_job_id — they are the same value, published under two names so readers of either shape keep working.
Checking a job
Follow the Location header from the 202 (also returned in the body as
location) rather than constructing the URL yourself:
GET {Location}
Authorization: Bearer rsk-...Also admin-only and project-scoped. Poll until status leaves the pending
set (pending, running). Terminal states are succeeded, partial,
and failed — only succeeded means the erasure completed; partial
leaves data behind and names it in stores_outstanding.
Re-issuing the identical request adopts the subject's unfinished job rather than starting a new one, so a retry is safe and stays addressable.
What “deleted” guarantees
ClickHouse deletes run with mutations_sync=1, so rows covered by a completed
leg are physically gone — not queued behind a background merge. Two bounds
remain: work reported under stores_outstanding has not run yet, and a span
the ingestion worker has already read into its in-memory batch is purged
within roughly one flush interval rather than instantly.
See Data Management for details.
Gateway Endpoints
SDK-facing endpoints for span ingestion:
Single Span
POST /v1/spans
Content-Type: application/json
Authorization: Bearer rsk-...
{
"trace_id": "abc123",
"span_id": "def456",
"name": "llm.call",
"start_time": "2024-01-15T10:00:00Z",
"end_time": "2024-01-15T10:00:01Z",
"attributes": {...}
}Batch Spans
POST /v1/spans/batch
Content-Type: application/json
Authorization: Bearer rsk-...
{
"spans": [
{"trace_id": "abc", "span_id": "001", ...},
{"trace_id": "abc", "span_id": "002", ...}
]
}Trace Ingestion
POST /v1/traces
Content-Type: application/json
Authorization: Bearer rsk-...
{
"trace_id": "abc123",
"spans": [...],
"metadata": {...}
}Event Ingestion
POST /v1/events
Content-Type: application/json
Authorization: Bearer rsk-...
{
"trace_id": "abc123",
"span_id": "def456",
"name": "user.feedback",
"timestamp": "2024-01-15T10:00:00Z",
"attributes": {"rating": 5}
}OTLP Ingestion
The OTLP endpoint accepts OTLP/JSON only. The gateway parses the body with serde_json — binary protobuf (application/x-protobuf) is not supported. Configure your OTel exporter for the JSON protocol (e.g. OTEL_EXPORTER_OTLP_PROTOCOL=http/json).
POST /v1/otlp/v1/traces
Content-Type: application/json
Authorization: Bearer rsk-...
{
"resourceSpans": [
{
"resource": {
"attributes": [
{"key": "service.name", "value": {"stringValue": "my-agent"}}
]
},
"scopeSpans": [
{
"spans": [
{
"traceId": "5b8efff798038103d269b633813fc60c",
"spanId": "eee19b7ec3c1b174",
"name": "llm.call",
"startTimeUnixNano": "1544712660000000000",
"endTimeUnixNano": "1544712661000000000"
}
]
}
]
}
]
}Gzip request bodies are supported via Content-Encoding: gzip (the decompressed payload must still be OTLP/JSON). The double /v1/ is intentional — gateway prefix + OTLP protocol path.
Health Check
GET /healthBasic health check. No authentication required.
Readiness Check
GET /health/readyReturns 200 only when all dependencies (ClickHouse, PostgreSQL, Redis) are reachable. Use for Kubernetes readiness probes.
Liveness Check
GET /health/liveReturns 200 if the server process is running. Use for Kubernetes liveness probes.
Pagination
All list endpoints support cursor-based pagination:
GET /api/v1/traces?limit=50&cursor=eyJpZCI6ImFiYzEyMyJ9Response includes the next cursor and a has_more flag at the top level:
{
"data": [...],
"next_cursor": "eyJpZCI6ImRlZjQ1NiJ9",
"has_more": true,
"total_count": 1284
}Pass next_cursor back as the cursor query parameter to fetch the next page. total_count is included when available, null otherwise.
Error Responses
{
"error": {
"code": "not_found",
"message": "Trace not found",
"details": {
"trace_id": "trace-abc123"
}
}
}| Code | HTTP Status | Description |
|---|---|---|
invalid_request | 400 | Malformed request |
unauthorized | 401 | Invalid API key |
forbidden | 403 | Insufficient permissions |
not_found | 404 | Resource not found |
rate_limited | 429 | Too many requests |
internal_error | 500 | Server error |
Rate Limits
Headers included in responses:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 950
X-RateLimit-Reset: 1705312800
When rate limited:
HTTP/1.1 429 Too Many Requests
Retry-After: 60