API
Usage and analytics
What your runs cost and how they behaved: the platform's charges summed or row by row, what each agent consumed, one run's charge, and the time series the console's charts are drawn from.
None of these checks a credential's project scope
Every route here answers for the whole tenant. project narrows the answer when you pass it; it is not checked against the project a service user was issued for, so a scoped credential reads every project's costs, usage and run history.
A window is half-open, and naive times are UTC
since is inclusive and until exclusive, so adjacent windows never count one charge twice. On the /v1/analytics series a timestamp with no zone is read as UTC; send one with a zone everywhere else.
The samples use $API and $TOKEN, which the API overview sets. Your own balance and your users' wallets are on the wallets page; what a run did is GET /v1/runs/{invocation_id} on the invocations page.
Which route answers which question
| You want | Call | From |
|---|---|---|
| what the platform charged you over a period | GET /v1/cost/summary | the ledger |
| how one of those charges was priced | GET /v1/cost | the ledger |
| what each agent has consumed, all time | GET /v1/usage | the ledger |
| which run a charge came from | GET /v1/usage/holds | the ledger |
| what one run cost, by model and tool | GET /v1/usage/invocations/{id} | the ledger, plus the run record |
| what your users paid you, over time | GET /v1/analytics/revenue | the ledger |
| platform charges and settled tokens, over time | GET /v1/analytics/cost, /tokens | the ledger |
| runs, failures, latency and tool calls, over time | GET /v1/analytics/invocations, /latency, /tools | the analytics warehouse |
| recent runs, and one run's trace | GET /v1/analytics/runs, /runs/{id}/trace | the analytics warehouse, plus the run record |
Money is micro-USD; your tenant's own holds are tokens
Every micros and charge_micros here is a whole number of micro-USD: 1,000,000 is one dollar. A hold's reserved_units and settled_units are in the unit of the account that paid — tokens for your tenant, micro-USD for a user's wallet — so never chart them as either without reading account_id first.
What the platform charged you, summed
GET/v1/cost/summarydeploy-service
Your platform charges over one window, summed per project, workload, agent and SKU — and per time bucket as well when you pass a grain, so a chart and the table under it come from one read.
A window is required
A sum means nothing without the period it covers, so since and until are both required here; leave either out and the route answers 422 before it asks the ledger anything. Summing the bucketed rows gives the unbucketed answer exactly.
unpriced says a figure is incomplete, not free
A row no rate card has priced yet carries a charge of zero, and unpriced counts how many such rows went into the sum. Unpriced usage is charged once it is priced, not dropped.
The ledger's own refusals come back as 502
This route passes since, until and grain to the ledger unchecked. An unknown grain or a timestamp the ledger cannot parse is refused there, and reaches you as a 502 whose message carries the ledger's sentence.
curl "$API/v1/cost/summary?since=2026-10-01T00:00:00Z\ &until=2026-11-01T00:00:00Z&grain=day&project=salmon" \ -H "Authorization: Bearer $TOKEN"
Parameters
| Field | Type | Description | |
|---|---|---|---|
| since | query | required | ISO timestamp, inclusive. |
| until | query | required | ISO timestamp, exclusive. |
| grain | query | optional | hour, day, week or month. Omitted, one row per group for the whole window and no bucket. |
| project | query | optional | One project. Omitted, every project in the tenant. |
Responses
| Status | Meaning |
|---|---|
| 200 | {rows, since, until, grain}. |
| 401 | No token, or one that did not verify. |
| 422 | since or until missing, or a project that could not be a slug. |
| 501 | This deployment has no ledger wired, so runs are unbilled. A configuration, not a fault. |
| 502 | The ledger refused or could not answer — including an unknown grain or an unparseable timestamp. |
Response body
{
"since": "2026-10-01T00:00:00Z",
"until": "2026-11-01T00:00:00Z",
"grain": "day",
"rows": [
{
"bucket": "2026-10-14T00:00:00+00:00",
"project_slug": "salmon",
"workload": "hotel-concierge",
"agent": "hotel-concierge",
"sku": "compute.cpu",
"sku_name": "CPU reserved",
"unit": "core-second",
"quantity": 21600.0,
"charge_micros": 108000,
"unpriced": 0,
"periods": 24,
"first": "2026-10-14T00:00:00+00:00",
"last": "2026-10-14T23:00:00+00:00"
}
]
}The charges, row by row
GET/v1/costdeploy-service
The raw accruals behind those sums: one row per workload, SKU and metering period, newest first, each with the rate it was priced at.
Do not add up a page of these and call it a total
A month of hourly rows is thousands, and the default page is 200. Ask /v1/cost/summary for a total; read these to check how one figure was arrived at.
priced and charged answer different questions
priced is whether a rate card has set a price for the row; charged is whether money moved, which a row priced at zero never does. On a tiered price, quantity × rate_micros does not reproduce charge_micros — tier_prior_quantity is where in the month the row was priced from.
curl "$API/v1/cost?project=salmon&since=2026-10-14T00:00:00Z&limit=100" \ -H "Authorization: Bearer $TOKEN"
Parameters
| Field | Type | Description | |
|---|---|---|---|
| since | query | optional | ISO timestamp. Omitted, no lower bound. One the ledger cannot parse is a 502. |
| until | query | optional | ISO timestamp. Omitted, no upper bound. |
| project | query | optional | One project. Omitted, every project in the tenant. |
| limit | query | optional | Default 200, clamped to 1–1000. |
| offset | query | optional | Default 0. |
Responses
| Status | Meaning |
|---|---|
| 200 | {cost, total}. |
| 401 | No token, or one that did not verify. |
| 422 | A project that could not be a slug. |
| 501 | This deployment has no ledger wired, so runs are unbilled. A configuration, not a fault. |
| 502 | The ledger refused or could not answer, including an unparseable date. |
Response body
{
"total": 742,
"cost": [
{
"tenant_id": "acme",
"project_slug": "salmon",
"workload": "hotel-concierge",
"agent": "hotel-concierge",
"sku": "compute.memory",
"sku_name": "Memory reserved",
"quantity": 1800.0,
"unit": "gib-second",
"period_start": "2026-10-14T13:00:00+00:00",
"period_seconds": 3600,
"rate_card": "rc-2026-10",
"rate_micros": 1,
"charge_micros": 1800,
"tier_prior_quantity": 0,
"priced": true,
"charged": true
}
]
}What each agent has consumed
GET/v1/usagedeploy-service
Lifetime totals per project, agent and version: how many runs were held for, what settled, what is still reserved, and how many tokens were used.
Per version, deliberately
A version bump changes what an agent costs, so folding versions together would hide the change that explains a bill.
Read total_tokens for consumption, not settled_units
A hold's units are in the unit of the account that paid: tokens for a run your tenant paid for, micro-USD for a run one of your users' wallets paid for. This route sums holds from both, so for an agent with wallet-funded runs settled_units and reserved_units add tokens to money. total_tokens is a token count either way.
curl "$API/v1/usage?project=salmon" \ -H "Authorization: Bearer $TOKEN"
Parameters
| Field | Type | Description | |
|---|---|---|---|
| project | query | optional | One project. Omitted, every project in the tenant. |
Responses
| Status | Meaning |
|---|---|
| 200 | {usage}. |
| 401 | No token, or one that did not verify. |
| 422 | A project that could not be a slug. |
| 501 | This deployment has no ledger wired, so runs are unbilled. A configuration, not a fault. |
| 502 | The ledger could not answer. Retry. |
Response body
{
"usage": [
{
"project_slug": "salmon",
"target_slug": "hotel-concierge",
"target_version": "1.4.0",
"holds": 128,
"settled_units": 412880,
"reserved_units": 0,
"total_tokens": 412880
}
]
}The individual holds
GET/v1/usage/holdsdeploy-service
The reservations behind those totals, one per run segment, newest first — each carrying the invocation id that explains it.
One hold per run, plus one per resume
A run opens a hold when it starts and settles it when it ends. A paused run that is resumed opens a second hold for the resumed turn, so a run can appear more than once here. /v1/usage/invocations/{invocation_id} sums them.
account_id says whose money it was
Your tenant's account is your tenant's own id, and its units are tokens. A wallet's is <tenant>:w:<user>, and its units are micro-USD.
curl "$API/v1/usage/holds?agent=hotel-concierge&minutes=1440" \ -H "Authorization: Bearer $TOKEN"
Parameters
| Field | Type | Description | |
|---|---|---|---|
| limit | query | optional | Default 50, clamped to 1–200. |
| offset | query | optional | Default 0. |
| project | query | optional | One project. Omitted, every project in the tenant. |
| agent | query | optional | One agent or workflow slug. |
| minutes | query | optional | How far back, in minutes, up to 90 days. 0, the default, is all of it. |
Responses
| Status | Meaning |
|---|---|
| 200 | {holds, total}. |
| 401 | No token, or one that did not verify. |
| 422 | A project that could not be a slug. |
| 501 | This deployment has no ledger wired, so runs are unbilled. A configuration, not a fault. |
| 502 | The ledger could not answer. Retry. |
Response body
{
"total": 128,
"holds": [
{
"hold_id": "hold_5e2a…",
"invocation_id": "f27bfbb4a1c34e…",
"account_id": "acme:w:ada",
"sku": "acme/salmon/concierge",
"reserved_units": 12000,
"settled_units": 2186,
"status": "settled",
"project_slug": "salmon",
"target_slug": "hotel-concierge",
"target_version": "1.4.0",
"total_tokens": 3433,
"created_at": "2026-10-14T13:02:11+00:00",
"expires_at": "2026-10-14T13:17:11+00:00"
}
]
}What one run cost
GET/v1/usage/invocations/{invocation_id}deploy-service
One run's charge summed across its segments, which models spent it, which tools were charged — and, where it was recorded, the run's status and events.
It needs a hold, so an unbilled agent has nothing here
A target that holds nothing for a run never opens one, and this route answers 404 for its runs. What a run did, as against what it cost, is GET /v1/runs/{invocation_id} on the invocations page.
run and events are additional
They come from the run record, and are null and [] when that record is unavailable — the charge is still answered.
Tenant only
A credential scoped to one project can read a sibling project's run here by its invocation id.
curl "$API/v1/usage/invocations/$INVOCATION_ID" \ -H "Authorization: Bearer $TOKEN"
Responses
| Status | Meaning |
|---|---|
| 200 | The charge, with run and events. |
| 401 | No token, or one that did not verify. |
| 404 | No hold for that id in your tenant — absent, another tenant's, or a run nothing was held for. The three are not told apart. |
| 501 | This deployment has no ledger wired, so runs are unbilled. A configuration, not a fault. |
| 502 | The ledger could not answer. Retry. |
Response body
{
"invocation_id": "f27bfbb4a1c34e…",
"hold_id": "hold_5e2a…",
"account_id": "acme",
"sku": "acme/salmon/concierge",
"status": "settled",
"reserved_units": 12000,
"settled_units": 3433,
"segments": 1,
"project_slug": "salmon",
"target_slug": "hotel-concierge",
"target_version": "1.4.0",
"total_tokens": 3433,
"created_at": "2026-10-14T13:02:11+00:00",
"expires_at": "2026-10-14T13:17:11+00:00",
"tools": [{"tool": "request_late_checkout", "calls": 1,
"charge_micros": 500, "capped_calls": 0}],
"usage": [{"model": "gemini-flash-latest", "prompt_tokens": 2925,
"completion_tokens": 508, "total_tokens": 3433,
"created_at": "2026-10-14T13:02:14+00:00"}],
"run": {"status": "finished", "error": null,
"started_at": "2026-10-14T13:02:11+00:00",
"finished_at": "2026-10-14T13:02:15+00:00",
"session_id": "web-9a3f1b2c4d5e"},
"events": []
}What your users paid you, over time
GET/v1/analytics/revenuedeploy-service
Your own book: what your end users' wallets were charged for your agents, per bucket, project, agent and SKU, in micro-USD.
Theirs to you, not ours to you
This is money your customers owe you. What the platform charges you is /v1/analytics/cost. Two routes rather than one with a flag, so neither can be mistaken for the other.
The SKU is the name you gave it
Your agent's billing SKU, as declared in its manifest — not the longer form the ledger stores it under.
curl "$API/v1/analytics/revenue?grain=week&project=salmon" \ -H "Authorization: Bearer $TOKEN"
Parameters
| Field | Type | Description | |
|---|---|---|---|
| grain | query | optional | hour, day, week or month; day by default. Anything else is refused rather than defaulted. |
| since | query | optional | ISO timestamp, inclusive. Thirty days ago by default. A value with no zone is read as UTC. |
| until | query | optional | ISO timestamp, exclusive. Now by default. Half-open, so two adjacent windows never count the same charge twice. |
| project | query | optional | One project. Omitted, every project in the tenant — whatever project the credential was issued for. |
Responses
| Status | Meaning |
|---|---|
| 200 | {grain, buckets}. |
| 400 | An unknown grain, a since or until that is not an ISO timestamp, or an until not after since. The message names the valid grains. |
| 401 | No token, or one that did not verify. |
| 422 | A project that could not be a slug. |
| 501 | This deployment has no ledger wired, so runs are unbilled. A configuration, not a fault. |
| 502 | The ledger could not answer. Retry. |
Response body
{
"grain": "day",
"buckets": [
{"bucket": "2026-10-08T00:00:00+00:00", "project_slug": "salmon",
"target_slug": "hotel-concierge", "sku": "concierge",
"micros": 6820, "runs": 5, "tokens": 4201}
]
}What the platform charged you, over time
GET/v1/analytics/costdeploy-service
The platform's charges to you per bucket, project and SKU, in micro-USD — compute, storage and the platform's own metered operations.
Not model tokens
Your model provider bills you for tokens directly, against the key you set as a secret. This series is what the platform itself charges. For a table of the same charges, /v1/cost/summary takes a grain too.
curl "$API/v1/analytics/cost?since=2026-10-01T00:00:00Z" \ -H "Authorization: Bearer $TOKEN"
Parameters
| Field | Type | Description | |
|---|---|---|---|
| grain | query | optional | hour, day, week or month; day by default. Anything else is refused rather than defaulted. |
| since | query | optional | ISO timestamp, inclusive. Thirty days ago by default. A value with no zone is read as UTC. |
| until | query | optional | ISO timestamp, exclusive. Now by default. Half-open, so two adjacent windows never count the same charge twice. |
| project | query | optional | One project. Omitted, every project in the tenant — whatever project the credential was issued for. |
Responses
| Status | Meaning |
|---|---|
| 200 | {grain, buckets}. |
| 400 | An unknown grain, a since or until that is not an ISO timestamp, or an until not after since. The message names the valid grains. |
| 401 | No token, or one that did not verify. |
| 422 | A project that could not be a slug. |
| 501 | This deployment has no ledger wired, so runs are unbilled. A configuration, not a fault. |
| 502 | The ledger could not answer. Retry. |
Response body
{
"grain": "day",
"buckets": [
{"bucket": "2026-10-08T00:00:00+00:00", "project_slug": "salmon",
"sku": "compute.cpu", "sku_name": "CPU reserved", "micros": 108000}
]
}Tokens settled, over time
GET/v1/analytics/tokensdeploy-service
Settled tokens per bucket, project and agent, split into billable input, cached input and output — the counts the charges were computed from.
The three bands stack to the total
input_tokens is already the prompt minus its cached part, because a provider's cached count is a subset of its prompt count. Adding cached_tokens to a raw prompt figure would count a cache hit twice.
Settled runs only
A row exists because a run ended and was priced. A run still in flight has reserved an estimate and settled nothing, so it is not here yet.
curl "$API/v1/analytics/tokens?project=salmon&target=hotel-concierge" \ -H "Authorization: Bearer $TOKEN"
Parameters
| Field | Type | Description | |
|---|---|---|---|
| grain | query | optional | hour, day, week or month; day by default. Anything else is refused rather than defaulted. |
| since | query | optional | ISO timestamp, inclusive. Thirty days ago by default. A value with no zone is read as UTC. |
| until | query | optional | ISO timestamp, exclusive. Now by default. Half-open, so two adjacent windows never count the same charge twice. |
| project | query | optional | One project. Omitted, every project in the tenant — whatever project the credential was issued for. |
| target | query | optional | One agent or workflow slug. |
Responses
| Status | Meaning |
|---|---|
| 200 | {grain, buckets}. |
| 400 | An unknown grain, a since or until that is not an ISO timestamp, or an until not after since. The message names the valid grains. |
| 401 | No token, or one that did not verify. |
| 422 | A project, target or kind that could not be one — a slug, or lowercase letters for kind. |
| 501 | This deployment has no ledger wired, so runs are unbilled. A configuration, not a fault. |
| 502 | The ledger could not answer. Retry. |
Response body
{
"grain": "day",
"buckets": [
{"bucket": "2026-10-08T00:00:00+00:00", "project_slug": "salmon",
"target_slug": "hotel-concierge", "input_tokens": 14625,
"cached_tokens": 0, "output_tokens": 2540, "total_tokens": 17165,
"runs": 5}
]
}Runs over time, succeeded and failed
GET/v1/analytics/invocationsdeploy-service
Runs per bucket, kind and agent, split into succeeded and failed, with their model calls, tool calls, events and tokens.
A run appears once it ends
One still running or paused for a person is not a row yet, and a run whose pod died before it emitted a terminal event never becomes one. These are completed runs, bucketed by when they started.
curl "$API/v1/analytics/invocations?grain=day&kind=agent" \ -H "Authorization: Bearer $TOKEN"
Parameters
| Field | Type | Description | |
|---|---|---|---|
| grain | query | optional | hour, day, week or month; day by default. Anything else is refused rather than defaulted. |
| since | query | optional | ISO timestamp, inclusive. Thirty days ago by default. A value with no zone is read as UTC. |
| until | query | optional | ISO timestamp, exclusive. Now by default. Half-open, so two adjacent windows never count the same charge twice. |
| project | query | optional | One project. Omitted, every project in the tenant — whatever project the credential was issued for. |
| kind | query | optional | agent or workflow. An unknown value matches nothing rather than widening to both. |
| target | query | optional | One agent or workflow slug. |
Responses
| Status | Meaning |
|---|---|
| 200 | {grain, buckets}. |
| 400 | An unknown grain, a since or until that is not an ISO timestamp, or an until not after since. The message names the valid grains. |
| 401 | No token, or one that did not verify. |
| 422 | A project, target or kind that could not be one — a slug, or lowercase letters for kind. |
| 501 | This deployment has no analytics warehouse configured. The money series can still work when this does not. |
| 502 | The warehouse could not answer. Retry. |
Response body
{
"grain": "day",
"buckets": [
{"bucket": "2026-10-09T00:00:00+00:00", "kind": "agent",
"target": "hotel-concierge", "runs": 42, "ok": 39, "failed": 3,
"llm_calls": 96, "tool_calls": 51, "events": 1180, "tokens": 144180}
]
}Run duration percentiles, over time
GET/v1/analytics/latencydeploy-service
p50, p95 and p99 of run duration per bucket and kind, in milliseconds.
Failures are left out unless you ask
A run that crashed in 200 ms is a fast row and a bad outcome; folded into the percentiles it pulls p50 down, so a spike in failures would read as an improvement. A failed run's duration is time to failure. Pass failures=1 to include them anyway; the failure count is on /v1/analytics/invocations.
Always split by kind
A workflow's duration includes waiting for a person, so one percentile across agents and workflows describes neither.
curl "$API/v1/analytics/latency?kind=workflow&failures=1" \ -H "Authorization: Bearer $TOKEN"
Parameters
| Field | Type | Description | |
|---|---|---|---|
| grain | query | optional | hour, day, week or month; day by default. Anything else is refused rather than defaulted. |
| since | query | optional | ISO timestamp, inclusive. Thirty days ago by default. A value with no zone is read as UTC. |
| until | query | optional | ISO timestamp, exclusive. Now by default. Half-open, so two adjacent windows never count the same charge twice. |
| project | query | optional | One project. Omitted, every project in the tenant — whatever project the credential was issued for. |
| kind | query | optional | agent or workflow. An unknown value matches nothing rather than widening to both. |
| target | query | optional | One agent or workflow slug. |
| failures | query | optional | 1, true or yes to include failed runs. Off by default. |
Responses
| Status | Meaning |
|---|---|
| 200 | {grain, failures_included, buckets}. |
| 400 | An unknown grain, a since or until that is not an ISO timestamp, or an until not after since. The message names the valid grains. |
| 401 | No token, or one that did not verify. |
| 422 | A project, target or kind that could not be one — a slug, or lowercase letters for kind. |
| 501 | This deployment has no analytics warehouse configured. The money series can still work when this does not. |
| 502 | The warehouse could not answer. Retry. |
Response body
{
"grain": "day",
"failures_included": false,
"buckets": [
{"bucket": "2026-10-09T00:00:00+00:00", "kind": "agent",
"runs": 39, "p50": 2140, "p95": 7800, "p99": 8522}
]
}Tool calls over time
GET/v1/analytics/toolsdeploy-service
Tool calls per bucket and tool name, with how many runs made them and that tool's own latency percentiles.
timed is how many calls had a measurable duration
A call whose result never arrived is counted and not timed, and a tool that waited for a person's confirmation is left undurated, because its result is on the far side of that wait. Key a chart on timed, not on a percentile being non-zero.
Only runs that called a tool
There is nothing to plot for the others, and counting them under an unnamed tool would invent a series.
curl "$API/v1/analytics/tools?target=hotel-concierge" \ -H "Authorization: Bearer $TOKEN"
Parameters
| Field | Type | Description | |
|---|---|---|---|
| grain | query | optional | hour, day, week or month; day by default. Anything else is refused rather than defaulted. |
| since | query | optional | ISO timestamp, inclusive. Thirty days ago by default. A value with no zone is read as UTC. |
| until | query | optional | ISO timestamp, exclusive. Now by default. Half-open, so two adjacent windows never count the same charge twice. |
| project | query | optional | One project. Omitted, every project in the tenant — whatever project the credential was issued for. |
| kind | query | optional | agent or workflow. An unknown value matches nothing rather than widening to both. |
| target | query | optional | One agent or workflow slug. |
Responses
| Status | Meaning |
|---|---|
| 200 | {grain, buckets}. |
| 400 | An unknown grain, a since or until that is not an ISO timestamp, or an until not after since. The message names the valid grains. |
| 401 | No token, or one that did not verify. |
| 422 | A project, target or kind that could not be one — a slug, or lowercase letters for kind. |
| 501 | This deployment has no analytics warehouse configured. The money series can still work when this does not. |
| 502 | The warehouse could not answer. Retry. |
Response body
{
"grain": "day",
"buckets": [
{"bucket": "2026-10-09T00:00:00+00:00", "tool": "request_late_checkout",
"calls": 14, "runs": 12, "timed": 14, "p50": 310, "p95": 980, "p99": 1204}
]
}Recent runs, for opening a trace
GET/v1/analytics/runsdeploy-service
The most recent completed runs in the window, one row each, with their outcome, duration, model and tool calls and tokens.
Every run that ended, whether or not money moved
Unlike the holds list, a run that crashed before it settled is here — which is usually the run somebody wants a trace of. A run still going or paused is not, because it has not ended; GET /v1/runs on the invocations page lists those.
curl "$API/v1/analytics/runs?session=web-9a3f1b2c4d5e" \ -H "Authorization: Bearer $TOKEN"
Parameters
| Field | Type | Description | |
|---|---|---|---|
| since | query | optional | ISO timestamp. Thirty days ago by default. |
| until | query | optional | ISO timestamp. Now by default. |
| project | query | optional | One project. Omitted, every project in the tenant. |
| kind | query | optional | agent or workflow. An unknown value matches nothing rather than widening to both. |
| target | query | optional | One agent or workflow slug. |
| session | query | optional | One conversation's runs — the turn that asked, the turns after, a resume somebody approved. |
| limit | query | optional | Default 50, clamped to 1–200. |
Responses
| Status | Meaning |
|---|---|
| 200 | {runs}, newest first. |
| 400 | A since or until that is not an ISO timestamp, or an until not after since. |
| 401 | No token, or one that did not verify. |
| 422 | A project, target or kind that could not be one — a slug, or lowercase letters for kind. |
| 501 | This deployment has no analytics warehouse configured. The money series can still work when this does not. |
| 502 | The warehouse could not answer. Retry. |
Response body
{
"runs": [
{
"invocation_id": "f27bfbb4a1c34e…",
"started_at": "2026-10-14T13:02:11+00:00",
"finished_at": "2026-10-14T13:02:15+00:00",
"duration_ms": 3840,
"outcome": "finished",
"error": "",
"target": "hotel-concierge",
"version": "1.4.0",
"kind": "agent",
"user_id": "eu:ada",
"session_id": "web-9a3f1b2c4d5e",
"llm_calls": 2,
"tool_calls": 1,
"event_count": 14,
"prompt_tokens": 2925,
"completion_tokens": 508,
"total_tokens": 3433,
"tools": [{"name": "request_late_checkout", "calls": 1, "ms": [310]}]
}
]
}One run's trace
GET/v1/analytics/runs/{invocation_id}/tracedeploy-service
The run's row from the list above, plus its recorded event timeline.
The run must have started inside the window
The lookup uses the same since and until as the list, thirty days by default, so an older run is a 404 until you widen the window to include its start.
timeline: false means the events are not available
The run is real and its summary is in run; the event record could not be read, or this deployment keeps none. Not an error.
Tenant only, whatever project says
project narrows the lookup when you pass it and is not checked against the credential, so a credential scoped to one project can read a sibling project's trace by leaving it out.
curl "$API/v1/analytics/runs/$INVOCATION_ID/trace" \ -H "Authorization: Bearer $TOKEN"
Parameters
| Field | Type | Description | |
|---|---|---|---|
| since | query | optional | ISO timestamp. Thirty days ago by default. |
| until | query | optional | ISO timestamp. Now by default. |
| project | query | optional | Narrow the lookup to one project. |
| kind | query | optional | agent or workflow. An unknown value matches nothing rather than widening to both. |
| target | query | optional | One agent or workflow slug. |
Responses
| Status | Meaning |
|---|---|
| 200 | {run, events, timeline}. |
| 400 | A since or until that is not an ISO timestamp, or an until not after since. |
| 401 | No token, or one that did not verify. |
| 404 | No such run in your tenant within the window — or one that has not ended yet. Not told apart. |
| 422 | A project, target or kind that could not be one — a slug, or lowercase letters for kind. |
| 501 | This deployment has no analytics warehouse configured. The money series can still work when this does not. |
| 502 | The warehouse could not answer. Retry. |
Response body
{
"run": {"invocation_id": "f27bfbb4a1c34e…", "outcome": "finished",
"duration_ms": 3840, "target": "hotel-concierge", "…": "…"},
"timeline": true,
"events": [
{"seq": 1, "segment": "", "kind": "started", "payload": {"…": "…"},
"emitted_at": "2026-10-14T13:02:11.204000+00:00"}
]
}