API reference
Outcomes
How many conversations a business had, how many were answered from its live system or ended in a verified action, how many were handed to a person, and what the model cost. Every number is derived from what Tether already recorded about each turn and each action, not from a survey, and reading it writes nothing.
Who reads which report
| Route | Credential | Reports on |
|---|---|---|
GET /v1/outcomes | The business's secret key | That business |
GET /v1/provider/outcomes | A provider key | Each business the provider created, and their total |
GET /v1/tenants/outcomes | The operator key | Every tenant, or one with ?tenantId= |
Any other credential gets 403 insufficient_scope: a business cannot read another business's
report, a provider cannot read a business it did not create, and a publishable key reaches none of
them.
curl "$TETHER_URL/v1/outcomes?from=2026-10-01&to=2026-11-01" \
-H "X-Api-Key: $TETHER_SECRET_KEY"
| Parameter | Default | Notes |
|---|---|---|
from | open | Conversations that started, and actions staged, at or after this time. A date such as 2026-10-01 means midnight UTC |
to | open | ... before this time, same formats. A to earlier than from answers 400 invalid_period |
The report
{
"tenantId": 206,
"name": "Venue One",
"period": { "from": "2026-10-01T00:00:00Z", "to": "2026-11-01T00:00:00Z", "asOf": "2026-11-02T09:00:00Z" },
"outcomes": {
"conversations": {
"total": 35, "verifiedCustomer": 32, "unverifiedVisitor": 3, "staff": 0, "turns": 36,
"withLiveToolCall": 15, "withStagedAction": 2, "handedOff": 7,
"verifiedTaskSuccess": 16, "automated": 28
},
"actions": {
"staged": 8, "stagedInConversation": 2, "executed": 5, "verified": 4, "unverified": 1,
"executedNotVerified": 0, "rejected": 1, "expired": 1, "preconditionFailed": 1, "providerRefused": 0, "failed": 0
},
"handoffs": { "total": 7, "customerRequest": 5, "agent": 1, "unverifiedOutcome": 1 },
"replyLanguages": [
{ "customer": "ka", "reply": "ka", "turns": 12 },
{ "customer": "ka-Latn", "reply": "ka-Latn", "turns": 6 },
{ "customer": "ru", "reply": "ru", "turns": 3 }
],
"rates": { "verifiedTaskSuccess": 0.4571, "supportAutomation": 0.8 },
"cost": {
"currency": "USD", "total": 0.4182, "modelCalls": 41,
"perConversation": 0.011949, "perResolvedRequest": 0.026138, "invoiceGrade": false
}
},
"milestones": {
"createdAt": "2026-10-01T08:00:00Z",
"firstExecutedActionAt": "2026-10-01T08:14:02Z",
"firstVerifiedActionAt": "2026-10-01T08:14:03Z",
"secondsToFirstVerifiedAction": 843.0
},
"definitions": { "verifiedTaskSuccess": "...", "supportAutomation": "...", "...": "..." },
"notDerived": ["Integration hours: ...", "..."]
}
definitions and notDerived come with every response, so the numbers never travel without what
they mean. The same definitions, in short:
| Number | Means |
|---|---|
| A conversation | A chat session with at least one recorded message, counted in the period in which it started. verifiedCustomer carried a signed customer token, staff a user id and no token, unverifiedVisitor neither |
withLiveToolCall | At least one of the business's own read capabilities was called and answered from its system. A staged write, a refused or failed read, a knowledge document and the handoff capability are not live tool calls |
handedOff | A turn handed to a person (customer_request or agent), or an action the conversation staged whose outcome was unverified |
verifiedTaskSuccess | An executed action that outcome verification confirmed, or a live tool call, and no handoff. The rate divides it by conversations |
automated | No handoff of any trigger. rates.supportAutomation divides it by conversations |
actions | Actions staged in the period, each counted under every outcome it has reached by asOf. executedNotVerified is executed with neither verified nor unverified, which only a crash between the two leaves; it is not an unverified outcome. providerRefused is a write your imported API refused without applying it, counted apart from failed |
handoffs.unverifiedOutcome | One per action staged in the period whose outcome was unverified, including actions staged with POST /v1/actions, which belong to no conversation |
replyLanguages | Turns by the form the customer wrote in and the form the turn replied in: ka, ka-Latn, ru, en, other, unknown |
cost | The usage ledger's estimated USD for model calls in the period, including calls outside a conversation. perResolvedRequest divides it by verifiedTaskSuccess |
secondsToFirstVerifiedAction | From the tenant's creation to its first verified action, at any time |
A period's outcomes keep maturing: an action staged on its last day may be approved and verified
the next, and the report counts it under the period it was staged in. asOf says when it was read.
What the report cannot know is listed in notDerived: integration hours (record them by hand),
writes refused before staging by a precondition (they leave no row), and whether the model's reply
was actually in the decided language (graded live only).
For a provider
curl "$TETHER_URL/v1/provider/outcomes?from=2026-10-01" \
-H "X-Api-Key: $TETHER_PROVIDER_KEY"
{
"providerTenantId": 205,
"period": { "from": "2026-10-01T00:00:00Z", "to": null, "asOf": "2026-11-02T09:00:00Z" },
"visibility": "Counts only, per business and in total. A provider sees none of a business's conversations, messages, actions or audit.",
"businessCount": 2,
"businesses": [
{ "tenantId": 206, "name": "Venue One", "outcomes": { "...": "..." }, "milestones": { "...": "..." } },
{ "tenantId": 207, "name": "Venue Two", "outcomes": { "...": "..." }, "milestones": { "...": "..." } }
],
"total": { "conversations": { "total": 36, "...": "..." }, "...": "..." }
}
Each business's outcomes are exactly what that business reads from GET /v1/outcomes. total
adds the counts and computes its rates and per-unit costs from the sums, so a large business
weighs as much as its conversations do. No session, action, customer or message appears.
The operator's GET /v1/tenants/outcomes has the same shape with tenants and tenantCount in
place of businesses and businessCount.
Export as CSV
curl "$TETHER_URL/v1/outcomes?from=2026-10-01" \
-H "X-Api-Key: $TETHER_SECRET_KEY" \
-H "Accept: text/csv" > outcomes.csv
Every route answers Accept: text/csv with one metric per row:
tenant_id,tenant_name,metric,value
206,Venue One,period.from,2026-10-01T00:00:00.0000000Z
206,Venue One,conversations.total,35
206,Venue One,reply_language.ka.ka,12
206,Venue One,rates.verified_task_success,0.4571
206,Venue One,cost.total_usd,0.418200
Metrics come in a fixed order, numbers in invariant formatting and times in round-trip UTC, and
asOf is left out, so the same data renders to the same bytes. A value that does not apply, such
as a rate with no conversations, is empty. The provider and operator exports end with total rows,
whose tenant_id is empty.
Related
- Audit — the trail of each action behind the action counts
- Handoff to a person — the triggers counted under
handoffs - Providers — the businesses a provider's report covers