Insight REST API
Fetch traces, observations, scores, and sessions from Mantis Insight via the public REST API.
Overview
Mantis Insight exposes a set of authenticated REST endpoints under /api/public/ that let you programmatically retrieve data from your projects. This is useful for:
- Building custom dashboards or reports
- Exporting data for offline analysis
- Integrating Insight data into CI/CD pipelines
- Feeding evaluation results back into training workflows
Authentication
All endpoints use HTTP Basic Auth with your project's API keys:
| Credential | Value |
|---|---|
| Username | Your public key (pk-lf-...) |
| Password | Your secret key (sk-lf-...) |
You can find these in the Insight UI under Project Settings → API Keys.
# Basic Auth header
curl -u "pk-lf-your-public-key:sk-lf-your-secret-key" \
https://insight.withmetis.ai/api/public/tracesimport requests
PUBLIC_KEY = "pk-lf-..."
SECRET_KEY = "sk-lf-..."
HOST = "https://insight.withmetis.ai"
response = requests.get(
f"{HOST}/api/public/traces",
auth=(PUBLIC_KEY, SECRET_KEY),
)
traces = response.json()API keys are scoped to a single project — all returned data belongs to that project.
Traces
List Traces
GET /api/public/tracesReturns a paginated list of traces.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
page | integer | Page number (default: 1) |
limit | integer | Items per page (default: 50) |
userId | string | Filter by user ID |
name | string | Filter by trace name |
tags | string | string[] | Filter by tags |
environment | string | string[] | Filter by environment (e.g. production, staging) |
sessionId | string | Filter by session ID |
version | string | Filter by version |
release | string | Filter by release |
fromTimestamp | ISO 8601 | Start of time range |
toTimestamp | ISO 8601 | End of time range |
orderBy | string | Sort field and direction, e.g. timestamp.desc |
fields | string | Comma-separated field groups: core, io, scores, observations, metrics |
filter | JSON string | Advanced filter expression (JSON array) |
Response
{
"data": [
{
"id": "trace-abc-123",
"timestamp": "2026-02-23T10:30:00.000Z",
"name": "research-agent",
"userId": "user-42",
"sessionId": "session-789",
"environment": "production",
"tags": ["v2", "research"],
"release": "1.2.0",
"version": "2",
"public": false,
"bookmarked": false,
"input": { "query": "What is quantum computing?" },
"output": { "answer": "Quantum computing is..." },
"metadata": { "source": "api" },
"projectId": "proj-...",
"createdAt": "2026-02-23T10:30:00.000Z",
"updatedAt": "2026-02-23T10:30:05.000Z",
"totalCost": 0.0023,
"latency": 4.5,
"htmlPath": "/project/proj-.../traces/trace-abc-123",
"observations": ["obs-1", "obs-2"],
"scores": ["score-1"]
}
],
"meta": {
"page": 1,
"limit": 50,
"totalItems": 142,
"totalPages": 3
}
}Examples
Fetch the 10 most recent traces:
curl -u "$PK:$SK" \
"https://insight.withmetis.ai/api/public/traces?limit=10&orderBy=timestamp.desc"Filter by session and time range:
curl -u "$PK:$SK" \
"https://insight.withmetis.ai/api/public/traces?sessionId=sess-123&fromTimestamp=2026-02-01T00:00:00Z&toTimestamp=2026-02-23T23:59:59Z"Python — paginate through all traces:
import requests
PUBLIC_KEY = "pk-lf-..."
SECRET_KEY = "sk-lf-..."
HOST = "https://insight.withmetis.ai"
all_traces = []
page = 1
while True:
resp = requests.get(
f"{HOST}/api/public/traces",
auth=(PUBLIC_KEY, SECRET_KEY),
params={"page": page, "limit": 100, "orderBy": "timestamp.desc"},
)
resp.raise_for_status()
data = resp.json()
all_traces.extend(data["data"])
if page >= data["meta"]["totalPages"]:
break
page += 1
print(f"Fetched {len(all_traces)} traces")Filter by tags:
resp = requests.get(
f"{HOST}/api/public/traces",
auth=(PUBLIC_KEY, SECRET_KEY),
params={"tags": ["production", "v2"], "limit": 50},
)Get Single Trace
GET /api/public/traces/{traceId}Returns a single trace with its full observation tree and scores.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
traceId | string | The trace ID |
Response
Returns the trace object plus:
observations— full array of observation objects (LLM calls, tool calls, spans)scores— array of score objects attached to this tracelatency— total latency in secondstotalCost— aggregated cost across all observationshtmlPath— relative URL to view the trace in the Insight UI
Examples
curl -u "$PK:$SK" \
"https://insight.withmetis.ai/api/public/traces/trace-abc-123"trace_id = "trace-abc-123"
resp = requests.get(
f"{HOST}/api/public/traces/{trace_id}",
auth=(PUBLIC_KEY, SECRET_KEY),
)
trace = resp.json()
print(f"Trace: {trace['name']}")
print(f"Latency: {trace['latency']}s")
print(f"Cost: ${trace['totalCost']:.4f}")
print(f"Observations: {len(trace['observations'])}")
print(f"Scores: {len(trace['scores'])}")Delete Traces
Single trace:
DELETE /api/public/traces/{traceId}Bulk delete (up to 1,000):
DELETE /api/public/traces
Content-Type: application/json
{ "traceIds": ["trace-1", "trace-2", "trace-3"] }Observations
List Observations
GET /api/public/observationsReturns a paginated list of observations (LLM generations, spans, tool calls).
Query Parameters
| Parameter | Type | Description |
|---|---|---|
page | integer | Page number |
limit | integer | Items per page |
traceId | string | Filter by parent trace |
userId | string | Filter by user ID |
name | string | Filter by observation name |
type | string | Filter by type (GENERATION, SPAN, EVENT) |
level | string | Filter by level (DEBUG, DEFAULT, WARNING, ERROR) |
environment | string | Filter by environment |
parentObservationId | string | Filter by parent observation |
fromStartTime | ISO 8601 | Start of time range |
toStartTime | ISO 8601 | End of time range |
version | string | Filter by version |
Example
resp = requests.get(
f"{HOST}/api/public/observations",
auth=(PUBLIC_KEY, SECRET_KEY),
params={"traceId": "trace-abc-123", "type": "GENERATION"},
)
generations = resp.json()["data"]
for gen in generations:
print(f"{gen['name']}: {gen['model']} — {gen['totalTokens']} tokens")Get Single Observation
GET /api/public/observations/{observationId}Scores
List Scores
GET /api/public/scoresReturns a paginated list of scores.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
page | integer | Page number |
limit | integer | Items per page |
userId | string | Filter by user ID |
name | string | Filter by score name |
configId | string | Filter by score config |
dataType | string | Filter by data type |
source | string | Filter by source (API, EVAL, ANNOTATION) |
fromTimestamp | ISO 8601 | Start of time range |
toTimestamp | ISO 8601 | End of time range |
environment | string | Filter by environment |
value | number | Filter by value |
operator | string | Comparison operator for value filter |
Example
resp = requests.get(
f"{HOST}/api/public/scores",
auth=(PUBLIC_KEY, SECRET_KEY),
params={"name": "correctness", "source": "EVAL", "limit": 100},
)
scores = resp.json()["data"]
avg_score = sum(s["value"] for s in scores) / len(scores) if scores else 0
print(f"Average correctness: {avg_score:.2f}")Create Score
POST /api/public/scores
Content-Type: application/json
{
"traceId": "trace-abc-123",
"name": "correctness",
"value": 0.95,
"dataType": "NUMERIC",
"comment": "Factually accurate response"
}Batch score-create via ingestion endpoint
When throughput matters, send score-create events in batches through /api/public/ingestion instead of issuing one /api/public/scores request per score.
POST /api/public/ingestion
Content-Type: application/json
{
"batch": [
{
"id": "event-1",
"type": "score-create",
"timestamp": "2026-03-18T00:00:00.000Z",
"body": {
"id": "score-1",
"name": "task_success",
"value": 0.94,
"dataType": "NUMERIC",
"traceId": "trace-abc-123",
"environment": "production"
}
},
{
"id": "event-2",
"type": "score-create",
"timestamp": "2026-03-18T00:00:00.000Z",
"body": {
"id": "score-2",
"name": "policy_compliant",
"value": 1,
"dataType": "BOOLEAN",
"traceId": "trace-abc-123",
"environment": "production"
}
}
]
}Direct scores API vs ingestion-batch
| Mode | Endpoint | Best For | Tradeoff |
|---|---|---|---|
| Direct | /api/public/scores | Simpler single-score writes and immediate per-score responses | More HTTP overhead at scale |
| Batch | /api/public/ingestion (score-create events) | High-volume score ingestion pipelines | Slightly more payload shaping required |
Use direct mode when workflow simplicity matters. Use ingestion-batch mode for efficient bulk ingestion.
Sessions
Get Session
GET /api/public/sessions/{sessionId}Returns a session with all its traces.
Example
session_id = "session-789"
resp = requests.get(
f"{HOST}/api/public/sessions/{session_id}",
auth=(PUBLIC_KEY, SECRET_KEY),
)
session = resp.json()
print(f"Session: {session['id']}")
print(f"Environment: {session['environment']}")
print(f"Traces: {len(session['traces'])}")Complete Example: Export Traces for Analysis
#!/usr/bin/env python3
"""Export traces and scores from Insight for offline analysis."""
import json
from datetime import datetime, timedelta
import requests
PUBLIC_KEY = "pk-lf-..."
SECRET_KEY = "sk-lf-..."
HOST = "https://insight.withmetis.ai"
AUTH = (PUBLIC_KEY, SECRET_KEY)
def fetch_recent_traces(days=7, limit=100):
"""Fetch traces from the last N days."""
from_ts = (datetime.utcnow() - timedelta(days=days)).isoformat() + "Z"
all_traces = []
page = 1
while True:
resp = requests.get(
f"{HOST}/api/public/traces",
auth=AUTH,
params={
"page": page,
"limit": limit,
"fromTimestamp": from_ts,
"orderBy": "timestamp.desc",
},
)
resp.raise_for_status()
data = resp.json()
all_traces.extend(data["data"])
if page >= data["meta"]["totalPages"]:
break
page += 1
return all_traces
def fetch_trace_details(trace_id):
"""Fetch a single trace with observations and scores."""
resp = requests.get(
f"{HOST}/api/public/traces/{trace_id}",
auth=AUTH,
)
resp.raise_for_status()
return resp.json()
def main():
print("Fetching recent traces...")
traces = fetch_recent_traces(days=7)
print(f"Found {len(traces)} traces")
results = []
for i, trace in enumerate(traces[:10]):
detail = fetch_trace_details(trace["id"])
results.append({
"id": detail["id"],
"name": detail["name"],
"latency": detail["latency"],
"cost": detail["totalCost"],
"num_observations": len(detail["observations"]),
"scores": {
s["name"]: s["value"] for s in detail["scores"]
},
})
print(f" [{i+1}/{min(len(traces), 10)}] {detail['name']} — "
f"{detail['latency']:.1f}s, ${detail['totalCost']:.4f}")
with open("trace_export.json", "w") as f:
json.dump(results, f, indent=2, default=str)
print(f"\nExported {len(results)} traces to trace_export.json")
if __name__ == "__main__":
main()Error Handling
The API returns standard HTTP status codes:
| Code | Meaning |
|---|---|
200 | Success |
401 | Invalid or missing API keys |
403 | API key doesn't have access to the requested resource |
404 | Resource not found (e.g. invalid trace ID) |
429 | Rate limited — back off and retry |
500 | Internal server error |
resp = requests.get(
f"{HOST}/api/public/traces/{trace_id}",
auth=AUTH,
)
if resp.status_code == 404:
print(f"Trace {trace_id} not found")
elif resp.status_code == 429:
print("Rate limited, retrying after backoff...")
elif resp.status_code != 200:
print(f"Error {resp.status_code}: {resp.text}")
else:
trace = resp.json()Rate Limits
API endpoints are rate-limited per project. If you hit a rate limit (429 response), back off exponentially before retrying. For bulk exports, use reasonable page sizes (50–100) and add a small delay between requests.
Next Steps
- Insight Export — Send traces to Insight from your application
- Tracing Overview — Understand traces, spans, and attributes
- Semantic Conventions — Standard attribute names