API Reference
Insight REST API

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:

CredentialValue
UsernameYour public key (pk-lf-...)
PasswordYour 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/traces
import 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/traces

Returns a paginated list of traces.

Query Parameters

ParameterTypeDescription
pageintegerPage number (default: 1)
limitintegerItems per page (default: 50)
userIdstringFilter by user ID
namestringFilter by trace name
tagsstring | string[]Filter by tags
environmentstring | string[]Filter by environment (e.g. production, staging)
sessionIdstringFilter by session ID
versionstringFilter by version
releasestringFilter by release
fromTimestampISO 8601Start of time range
toTimestampISO 8601End of time range
orderBystringSort field and direction, e.g. timestamp.desc
fieldsstringComma-separated field groups: core, io, scores, observations, metrics
filterJSON stringAdvanced 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

ParameterTypeDescription
traceIdstringThe 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 trace
  • latency — total latency in seconds
  • totalCost — aggregated cost across all observations
  • htmlPath — 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/observations

Returns a paginated list of observations (LLM generations, spans, tool calls).

Query Parameters

ParameterTypeDescription
pageintegerPage number
limitintegerItems per page
traceIdstringFilter by parent trace
userIdstringFilter by user ID
namestringFilter by observation name
typestringFilter by type (GENERATION, SPAN, EVENT)
levelstringFilter by level (DEBUG, DEFAULT, WARNING, ERROR)
environmentstringFilter by environment
parentObservationIdstringFilter by parent observation
fromStartTimeISO 8601Start of time range
toStartTimeISO 8601End of time range
versionstringFilter 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/scores

Returns a paginated list of scores.

Query Parameters

ParameterTypeDescription
pageintegerPage number
limitintegerItems per page
userIdstringFilter by user ID
namestringFilter by score name
configIdstringFilter by score config
dataTypestringFilter by data type
sourcestringFilter by source (API, EVAL, ANNOTATION)
fromTimestampISO 8601Start of time range
toTimestampISO 8601End of time range
environmentstringFilter by environment
valuenumberFilter by value
operatorstringComparison 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

ModeEndpointBest ForTradeoff
Direct/api/public/scoresSimpler single-score writes and immediate per-score responsesMore HTTP overhead at scale
Batch/api/public/ingestion (score-create events)High-volume score ingestion pipelinesSlightly 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:

CodeMeaning
200Success
401Invalid or missing API keys
403API key doesn't have access to the requested resource
404Resource not found (e.g. invalid trace ID)
429Rate limited — back off and retry
500Internal 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