Traces
GET /v1/traces returns a record of the requests your organization has made through the API — model, timing, token counts, and whether the request succeeded. GET /v1/traces/{trace_id} returns one of those records in full, with its span waterfall and payloads.
This is health and debugging data, not billing. Token counts appear as request metadata; spend lives in your billing dashboard.
Traces are available where your organization has them enabled. Everywhere else both endpoints answer 404.
Endpoint
GET https://api.mindshub.ai/v1/traces
- Python
- TypeScript
import os
import httpx
headers = {"Authorization": f"Bearer {os.environ['MINDSHUB_API_KEY']}"}
page = httpx.get(
"https://api.mindshub.ai/v1/traces",
params={"page": 1, "limit": 25, "model": "sonnet"},
headers=headers,
).json()
for trace in page["traces"]:
print(trace["id"], trace["model"])
const headers = { Authorization: `Bearer ${process.env.MINDSHUB_API_KEY}` };
const params = new URLSearchParams({ page: "1", limit: "25", model: "sonnet" });
const page = await fetch(`https://api.mindshub.ai/v1/traces?${params}`, { headers }).then((r) => r.json());
for (const trace of page.traces) console.log(trace.id, trace.model);
The generated reference pages are GET /v1/traces and GET /v1/traces/{trace_id}.
Who sees what
Every response is scoped to your organization. Within it, what you see depends on your role:
- Members see their own requests.
- Organization administrators see the whole organization's requests, and can narrow to one person with
user_id.
Request and response content is always your own, for every role. An administrator can see that a colleague made a request, with its model, timing and outcome, but opening it returns 404. There is no setting that changes this today.
Request parameters
All optional.
| Parameter | Meaning |
|---|---|
page, limit | 1-based page number and page size. limit defaults to 25, maximum 100. |
from, to | ISO-8601 instants bounding the request time. A value without an offset is read as UTC. |
hours | Legacy relative window. Combines with from/to when both are given; prefer the absolute pair. |
session_id | Only requests belonging to this session. |
model, exclude_model | Model aliases. Repeatable. |
provider, exclude_provider | Upstream providers. Repeatable. |
level, exclude_level | DEFAULT (succeeded), ERROR (failed), UNKNOWN (no outcome recorded). Repeatable, OR-ed. |
stream | true for streamed requests, false for non-streamed. |
include_probes | true to include connectivity health-check requests, which are hidden by default. |
sort, order | timestamp or latency_ms, asc or desc. Defaults to newest first. |
user_id | Administrators only: narrow to one person. Ignored for members. |
Every filter runs server-side over your whole history, not over one page. The exclusions are null-permissive: excluding a model never hides requests whose model was not recorded.
Response
{
"traces": [
{
"id": "3f1c8a90d4e24b7f9a6c5e2d",
"name": "cowork:turn-3",
"timestamp": "2026-09-16T09:41:22.118Z",
"user_id": "0f2b5c71-9e3a-4d18-bb44-7c6a1d2e5f30",
"session_id": "b7d1e4a2-5c88-4f30-9a61-2e7c4b9d8f15",
"model": "sonnet",
"provider": "anthropic",
"input_tokens": 1840,
"output_tokens": 302,
"cached_input_tokens": 1536,
"latency_ms": 4120,
"stream": true,
"level": "DEFAULT",
"http_status": 200,
"error_class": null,
"span_count": 0,
"kind": null,
"preview": null,
"tags": []
}
],
"page": 1,
"limit": 25,
"total": 412,
"total_capped": false,
"has_more": true,
"langfuse_available": true
}
| Field | Meaning |
|---|---|
id | The trace id. Pass it to GET /v1/traces/{trace_id}. A request recorded while tracing was off carries an internal id instead. That id opens only for a stored Responses turn, whose payload we hold ourselves, and only while the turn is live; every other untraced request returns 404. |
name | What the request was, when we can say: the calling client and turn (cowork:turn-3), otherwise the operation (chat.completion, response, embedding). |
timestamp | When the request started. |
model, provider | The alias you called, and the upstream that served it. |
input_tokens, output_tokens, cached_input_tokens | Token counts. cached_input_tokens are the discounted prompt-cache reads; they are not included in input_tokens. |
latency_ms | End to end, in milliseconds. For a streamed request this is time to the last token and includes your own read time. stream tells the two apart, so compare like with like. |
level | DEFAULT succeeded, ERROR failed, null when no outcome was recorded. |
http_status, error_class | The status we returned and the failure category, when recorded. |
session_id | Groups a multi-turn conversation. null for a request that carried no session header, which is most direct API traffic. |
kind | probe for a connectivity health check, otherwise null. |
span_count | Always 0 on the list. The real count comes with the detail. |
preview, tags | Reserved. Always null and [] today. |
total | Matching requests, or null when it could not be counted cheaply — see below. |
total_capped | true when counting stopped at the cap, so total is null rather than approximate. |
has_more | Whether another page is reachable. Page on this, not on total. |
langfuse_available | Whether the tracing backend is configured in this environment. The list never depends on it, and a stored Responses turn still opens when it is false — from our own payload, so without its span waterfall. |
Counts and paging
total is exact or null, never an estimate. It is null in two cases: when more than 10,000 requests match, and when the query filters on anything other than time, because counting those cannot be done cheaply. has_more is always correct, so page on that.
Paging stops at an offset of 10,000. Beyond it the endpoint returns 400; narrow with from/to or user_id instead of paging deeper.
Getting one trace
GET https://api.mindshub.ai/v1/traces/{trace_id}
- Python
- TypeScript
import os
import httpx
headers = {"Authorization": f"Bearer {os.environ['MINDSHUB_API_KEY']}"}
trace = httpx.get("https://api.mindshub.ai/v1/traces/tr_1a2b3c", headers=headers).json()
print(trace["level"], len(trace["spans"]))
const headers = { Authorization: `Bearer ${process.env.MINDSHUB_API_KEY}` };
const trace = await fetch("https://api.mindshub.ai/v1/traces/tr_1a2b3c", { headers }).then((r) => r.json());
console.log(trace.level, trace.spans.length);
The response is a list row plus input, output, metadata, and spans — the nested operations the request ran, each with its own timing, model, level and payloads, ordered by start time.
Lane-specific behaviour
Health checks are hidden. Some MindsHub clients send a small connectivity probe on the normal completion path to verify your key. Those are excluded from the list by default so it reflects real activity. Pass include_probes=true to see them; they drop out of the counts while hidden.
Detail can lag the list. Trace ingestion is asynchronous, so a request that appears in the list immediately may not be retrievable by id for a few seconds.
Detail can expire before the list does. The list is served from our own records and keeps its history. The span waterfall is held by the tracing backend under whatever retention it is configured with, and a stored Responses turn's payload is held by us for 30 days. Neither is tied to how long the list keeps a request, so an old request can list correctly and still return 404 when opened.
Errors you can hit
| Status | When |
|---|---|
400 | Paging past the offset limit. Narrow the window instead. |
401 | Missing or invalid key. |
404 | Traces are not enabled for your organization; or the trace does not exist; or it is not yours to see. All three answer 404 rather than 403, so a disabled surface is invisible rather than visibly gated. The last two are indistinguishable by design, down to the response body: we never confirm that someone else's trace exists. |
503 | The trace is yours, but the detail backend is temporarily unavailable. The list keeps working. Retry the drill-down. |
See Errors for the response shape.