Data Export
Download request logs, traces, anomaly snapshots, and security flags as CSV, JSONL, or JSON in one shot. CSV and JSONL stream out of a server-side database cursor, so 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 to 1,000,000. JSON: 1 to 10,000./exports/requests only, other endpoints stay at 10,000. |
| 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. One cursor batch (500 rows) is the only data held in memory at any point, so 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://api.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://api.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://api.spanlens.io/api/v1/exports/anomalies" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-o spanlens-anomalies.csv
# Security flags, from a specific date
curl "https://api.spanlens.io/api/v1/exports/security?from=2026-05-01T00:00:00Z" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-o spanlens-security.csv
JSONL download (large exports)
# One million rows, streamed. Pipe straight into jq for filtering.
curl "https://api.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",...}
JSON download (small, wrapped)
curl "https://api.spanlens.io/api/v1/exports/requests?format=json&limit=1000" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN"
# Response shape (buffered, capped at 10,000 rows):
# {
# "exported_at": "2026-05-19T08:30:00.000Z",
# "count": 1000,
# "data": [
# {
# "id": "req_xxx",
# "project_id": "proj_xxx",
# "provider": "openai",
# "model": "gpt-4o-mini-2024-07-18",
# "prompt_tokens": 512,
# "completion_tokens": 128,
# "total_tokens": 640,
# "cost_usd": 0.000096,
# "latency_ms": 843,
# "status_code": 200,
# "error_message": null,
# "trace_id": null,
# "created_at": "2026-05-15T09:00:00.000Z"
# },
# ...
# ]
# }
BI tool tips
Pandas (Python)
import pandas as pd
token = "YOUR_SUPABASE_ACCESS_TOKEN"
# Small / medium, CSV, single response.
url = "https://api.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://api.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)
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.
Related: Requests, Traces, Anomalies, Security.