Currently TypeScript-only. The Python SDK does not yet expose an
outcome() method. Python customers (and anyone reporting from a non-SDK backend) call POST /v1/ingest/outcomes directly — see Reporting without the TypeScript SDK below. Field names on the wire are the same camelCase (valueUsd, traceId) either way.Why out-of-band
Most outcomes aren’t known when the trace runs. An agent answers a support message; whether that ticket actually stayed resolved is only known when it doesn’t reopen a few hours later, or a refund is only “avoided” once the customer’s session ends without one.outcome() is built for that gap: it’s a direct, one-shot call your backend makes whenever the result becomes known — a webhook handler, a nightly reconciliation job, a support-desk close event — not something you call inside the same request that produced the trace.
Because of that, an explicit traceId is the common case, not the exception. outcome() also works from inside an active trace (it falls back to the current trace context when traceId is omitted), but most real call sites are in a different process, minutes or hours after the trace that they’re describing has already finished.
Reporting an outcome
outcome() lives on the client, the same place checkGuardrails() and datasets do — get it from zespan.getClient() (or construct/import a ZespanClient directly) rather than calling it on the zespan object itself.
OutcomeInput fields
string
required
A short label for what this outcome measures —
"ticket_deflected", "refund_avoided", "sla_met", or any other string your team uses consistently. There’s no fixed enum; kind is whatever your business tracks, and it’s what the Value dashboard groups and filters by. Max 100 characters.boolean
required
Whether this outcome was actually achieved. A deflected-ticket check that failed (the ticket reopened) is still worth reporting — send
success: false rather than skipping the call — since a kind’s success rate is only meaningful if failures are reported too.number
The dollar value of this outcome, if you can put a number on it (an avoided refund’s amount, an estimated support-cost saving). Omit it entirely rather than passing
0 for “unknown” — omitted, it’s left out of the request body rather than sent as null, and the dashboard’s value/cost ratios treat “no value reported” differently from “reported as zero.”Record<string, string | number | boolean>
Free-form key-value metadata for this outcome. Values are coerced to strings before the request is sent —
attributes: { ticketId: 4821, tier: "gold" } is transmitted as { "ticketId": "4821", "tier": "gold" } — because every other Zespan SDK surface treats span/event attributes as strings, and this keeps outcomes consistent with that rather than introducing a second, wider attribute type.string
The trace this outcome is attributed to. Explicit
traceId always wins over an active trace, even if outcome() happens to be called from inside one — see Resolving traceId below. Required in practice: outcome() throws synchronously if it can’t resolve one.string
The session this outcome belongs to, if your traces are grouped into multi-turn sessions. Purely descriptive — not used to resolve
traceId.string
The agent that produced the trace, if known. Populating this is what lets the Value dashboard’s By agent breakdown attribute the outcome correctly; an outcome reported with no
agentName still counts toward totals but groups under an empty agent name.Resolving traceId
input.traceId is always used as-is, never overridden by an active trace even if one exists. When traceId is omitted, outcome() falls back to the trace currently active in this process (the same context withAgent and the LLM wrappers read). If neither is available, outcome() throws synchronously — it never silently no-ops or sends a request with a missing traceId.
Response and errors
outcome() resolves to { accepted: number } — 1 for the single outcome this call sent. On a non-2xx response from the API, it throws an Error whose message includes the HTTP status and, when the server returned one, a truncated response body — for example, a kind over the 100-character limit fails with a 400 and the server’s validation message.
Reporting without the TypeScript SDK
Python customers, and anyone reporting from a backend that isn’t a Zespan SDK at all, call the ingest endpoint directly. It’s the same endpointoutcome() wraps — see the full request/response contract in the Outcomes group of the API Reference.
Python
Field names on the wire are camelCase (
valueUsd, traceId, sessionId, agentName) even from Python — this is the raw ingest API’s shape, not a Python-idiomatic wrapper, since no Python SDK method exists yet.Next steps
- Value — the dashboard page this data powers, with per-agent and per-model breakdowns
- API Reference — the Outcomes group covers the full request/response contract for
POST /v1/ingest/outcomesand the two read endpoints - Agent tracing —
withAgent, for producing the traces you’ll later attribute outcomes to

