Download request logs, traces, anomaly snapshots, and security flags as CSV, JSONL, or JSON in one shot. CSV and JSONL stream directly from ClickHouse, a million-row export runs in ~30 MB of memory and finishes inside the function-execution window. Connect to Pandas, BigQuery, Redash, Metabase, or your own pipeline.
Endpoints
Endpoint
Data
GET /api/v1/exports/requests
Request logs, provider, model, tokens, cost, latency, etc.
GET /api/v1/exports/traces
Traces, span count, total cost, duration, etc.
GET /api/v1/exports/anomalies
Anomaly snapshots, daily history of buckets that exceeded 3σ
GET /api/v1/exports/security
Security flags, PII detections and prompt injection hits
All endpoints require JWT authentication (authJwt middleware). Include Authorization: Bearer <supabase_access_token> in the request header.
Common query parameters
Parameter
Default
Description
format
csv
csv · jsonl · json. CSV and JSONL stream; JSON materialises a wrapper object. See Formats below.
from
,
ISO 8601 start time (e.g. 2026-05-01T00:00:00Z). Defaults to 30 days ago if omitted.
to
,
ISO 8601 end time. Defaults to now if omitted.
limit
format-dependent
CSV / JSONL: 1 – 1,000,000. JSON: 1 – 10,000./exports/requests only, other endpoints stay at 10,000.
Formats, when to pick each
Format
Streamed?
Row cap
Best for
csv
Yes
1,000,000
BI tools, spreadsheets, ad-hoc analysis. Default.
jsonl
Yes
1,000,000
Pipelines that preserve typing (jq, pandas.read_json(lines=True), BigQuery, ClickHouse). One JSON object per line, newline-delimited.
json
No, buffered
10,000
Wrapper object { exported_at, count, data: [...] } for code that wants a single parseable response. Use jsonl for anything larger.
Streamed responses set Cache-Control: no-storeso intermediaries don't buffer the full body. Each ClickHouse batch (~64 KB) is the only data held in memory at any point, heap usage stays flat regardless of limit.
Additional parameters for requests
Extra filters available only on GET /api/v1/exports/requests.
Parameter
Description
projectId
Export only requests belonging to a specific project.
provider
One of openai / anthropic / gemini / azure.
model
Partial match, case-insensitive (e.g. mini).
providerKeyId
Only requests that used a specific provider key.
status
ok (2xx) / 4xx / 5xx.
File names
The response includes a Content-Disposition header with a date-stamped filename.
Endpoint
Example filename
/exports/requests
spanlens-requests-2026-05-15.csv
/exports/traces
spanlens-traces-2026-05-15.csv
/exports/anomalies
spanlens-anomalies-2026-05-15.csv
/exports/security
spanlens-security-2026-05-15.csv
CSV columns, requests
Column
Description
id
Unique request ID
project_id
Project this request belongs to
provider
openai / anthropic / gemini / azure
model
Dated variant returned by the provider (e.g. gpt-4o-mini-2024-07-18)
prompt_tokens
Input token count (gross, including cached portion)
completion_tokens
Output token count
total_tokens
prompt + completion
cost_usd
Calculated cost in USD. Empty if the model is not in the price table.
latency_ms
Time from proxy receiving the request to last byte sent (ms)
status_code
HTTP status code returned by the provider
error_message
Error string. Empty for successful requests.
trace_id
Linked trace ID. Empty if the call was not made inside an SDK observe().
created_at
When the request arrived at the proxy (ISO 8601 UTC)
CSV columns, traces
Column
Description
id
Unique trace ID
project_id
Project this trace belongs to
name
Trace name (specified in the SDK)
status
ok / error
error_message
Error string. Empty for successful traces.
duration_ms
First span start to last span end (ms)
total_cost_usd
Sum of costs across all requests in the trace (USD)
total_tokens
Sum of tokens across all requests in the trace
span_count
Number of spans in the trace
started_at
Trace start time (ISO 8601 UTC)
ended_at
Trace end time (ISO 8601 UTC)
created_at
When the row was saved to the database (ISO 8601 UTC)
curl examples
CSV download
# Request logs, specific date range, GPT-4o only, CSV
curl "https://server.spanlens.io/api/v1/exports/requests?from=2026-05-01T00:00:00Z&to=2026-05-15T23:59:59Z&provider=openai&model=gpt-4o&format=csv" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-o spanlens-requests.csv
# Traces, last 7 days, JSON
curl "https://server.spanlens.io/api/v1/exports/traces?from=2026-05-08T00:00:00Z&format=json" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-o spanlens-traces.json
# Anomaly history, defaults (30 days, CSV)
curl "https://server.spanlens.io/api/v1/exports/anomalies" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-o spanlens-anomalies.csv
# Security flags, from a specific date
curl "https://server.spanlens.io/api/v1/exports/security?from=2026-05-01T00:00:00Z" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-o spanlens-security.csv
bash
JSONL download (large exports)
# One million rows, streamed. Pipe straight into jq for filtering.
curl "https://server.spanlens.io/api/v1/exports/requests?format=jsonl&from=2026-01-01T00:00:00Z&limit=1000000" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
| jq -c 'select(.cost_usd != null and (.cost_usd | tonumber) > 0.01)' \
> expensive-requests.jsonl
# Each line is a self-contained JSON object:
# {"id":"req_xxx","provider":"openai","model":"gpt-4o-mini-2024-07-18",...}
# {"id":"req_yyy","provider":"anthropic","model":"claude-sonnet-4-5",...}
import pandas as pd
token = "YOUR_SUPABASE_ACCESS_TOKEN"
# Small / medium, CSV, single response.
url = "https://server.spanlens.io/api/v1/exports/requests?from=2026-05-01T00:00:00Z&format=csv"
df = pd.read_csv(url, storage_options={"Authorization": f"Bearer {token}"})
# Million-row pipeline, JSONL, streamed line-by-line. Pandas reads it in
# chunks so peak memory stays bounded.
url = "https://server.spanlens.io/api/v1/exports/requests?format=jsonl&limit=1000000"
chunks = pd.read_json(url, lines=True, chunksize=50_000,
storage_options={"Authorization": f"Bearer {token}"})
totals = pd.concat(chunk.groupby("model")["cost_usd"].sum() for chunk in chunks).groupby(level=0).sum()
print(totals)
python
Excel
Download the .csv file with curl, then import it into Excel via Data → From Text/CSV. The created_at column is an ISO 8601 string, convert it with DATEVALUE + TIMEVALUEor Power Query's date/time type conversion before using it in pivot tables.
Limitations
Row caps./exports/requests goes up to 1,000,000 rows on the streamed formats (csv, jsonl) and 10,000 on json. The other endpoints (/traces, /security, /anomalies) stay at 10,000. For datasets above the cap, paginate by splitting the time range with from / to, or contact support, multi-GB exports with completion emails / S3 pre-signed URLs are on the roadmap.
request_body / response_body are not included. Body content is excluded for security and size reasons. View individual request bodies in the /requests detail view or via GET /api/v1/requests/:id.
Not real-time. Exports are a point-in-time snapshot. In-flight streaming requests or async logging delays may mean the most recent rows are not yet present.
Rate limit. Export endpoints are capped at 10 requests per minute. Space out calls in bulk batch pipelines.
Plan retention applies.The window of accessible rows is bounded by your plan's log retention (Free 14d / Pro 90d / Team 365d). Older rows are unavailable even via from.