Skip to main content

POST/1.0/caliper/traces

Send a batch of traces. A trace is one agent run, the reply to one turn: what came in, what went out, and the steps in between. Each trace names its source, your name for the app sending it, and a new name starts a new source.

Call it after your agent replies, or batch several replies into one request.

Required scope: caliper:traces:write (scopes reference). Workspace-kind keys only. The scope can send and nothing else.

Returns 202 Accepted.

Request body

tracesarrayRequired

1 to 500 traces.

Each trace

Only source is required. Anything you don't send isn't stored, so leave out whatever you'd rather not keep.

sourcestringRequired

Your name for the app sending it, such as "support-bot". Names starting with workbench/ belong to Workbench flows and are refused.

idstring

Your id for this trace. Sending a trace again with the same id replaces it, so a retry never counts twice. Leave it out and Caliper assigns one.

messagesarray

The conversation as OpenAI-style chat messages (role, content, tool_calls, tool_call_id). Each assistant message becomes a model call and each tool call a tool step, with its result taken from the matching tool message. Send this or steps.

stepsarray

What the agent did, in order. See Steps below.

inputany

What came in. Text, a message list, or any JSON. With messages, defaults to the conversation up to the last user message.

outputany

What your agent answered. With messages, defaults to the last assistant reply.

conversationIdstring

Groups turns of one conversation.

userIdstring

Your id for the person on the other end.

startedAtstring

When it started, ISO-8601 with a timezone. Defaults to when it arrives. With endedAt or durationMs, any two of the three are enough.

endedAtstring

When it ended.

durationMsnumber

How long it took, in milliseconds.

status"ok" | "error"

Defaults to "error" when any step failed, otherwise "ok".

errorstring

What went wrong, for a failed trace.

metadataobject

Anything else worth keeping: plan, region, prompt version. Other top-level fields you send are kept here too.

tagsstring[]

Up to 50 labels.

Steps

Every field but type is optional, and any extra field you include is kept.

type"model" | "tool" | "lookup" | "other"Required

What kind of step it is.

namestring

The tool's name for a tool call; a label for anything else.

idstring

Your id for the step.

startedAtstring

When the step started. Steps with start times are drawn on the trace's timeline.

durationMsnumber

How long the step took.

status"ok" | "error"

Whether the step succeeded.

inputany

A tool's arguments, or what a model was sent.

outputany

A tool's result, or what a model returned.

modelstring

For a model call: which model.

inputTokensnumber

Tokens in. Also outputTokens, reasoningTokens, and cachedTokens.

costUsdnumber

What the step cost, in US dollars.

reasoningstring

The model's reasoning, when you have it.

querystring

For a lookup: what was searched for.

documentsarray

For a lookup: what came back, as { name, id?, score? }. These are the documents a source's Lookups chart counts.

Example

await fetch("https://api.zerowidth.ai/1.0/caliper/traces", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ZEROWIDTH_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    traces: [
      {
        source: "support-bot",
        id: runId,
        conversationId,
        durationMs: Date.now() - startedAt,
        messages, // the conversation, tool calls included
      },
    ],
  }),
})

With explicit steps:

curl -X POST https://api.zerowidth.ai/1.0/caliper/traces \
  -H "Authorization: Bearer $ZEROWIDTH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "traces": [{
      "source": "support-bot",
      "id": "run_8123",
      "input": "Do you ship to Canada?",
      "output": "Yes, in 5 to 7 business days.",
      "steps": [
        { "type": "lookup", "query": "shipping canada",
          "documents": [{ "name": "shipping-faq.md", "score": 0.83 }] },
        { "type": "model", "model": "gpt-5-mini",
          "inputTokens": 1180, "outputTokens": 42, "costUsd": 0.0011 }
      ]
    }]
  }'

Response (202)

creatednumber

Traces stored for the first time.

updatednumber

Traces that replaced one sent before with the same id.

idsstring[]

Each trace's id, in the order sent: yours, or the one Caliper assigned.

Limits

Your plan sets how many traces a workspace can send each month, how many requests a key can make to this endpoint each minute, and how long conversations are kept (see pricing). A batch that would go past the month's limit is refused whole with 402 and code plan_limit, so nothing is half-stored. Going past the per-minute limit returns 429 with a Retry-After header; send larger batches less often.

4 min read