Usage
The usage object, how each kind is metered, and how budgets behave on streams.
The usage object
Successful responses and stream usage.updated / terminal frames carry a usage object. All fields are optional and never negative:
{
"duration_ms": 61000,
"characters": 512,
"input_tokens": 1800,
"cached_input_tokens": 900,
"output_tokens": 240,
"reasoning_tokens": 60
}| Field | Kind | Meaning |
|---|---|---|
duration_ms | STT | Audio duration transcribed |
characters | TTS | Characters accepted for synthesis |
input_tokens | LLM | Fresh (uncached) input tokens |
cached_input_tokens | LLM | Input tokens served from provider cache |
output_tokens | LLM | Visible output tokens |
reasoning_tokens | LLM | Hidden reasoning tokens |
When a provider reports split usage, the lines are mutually exclusive: cached_input_tokens are not double-counted inside input_tokens, and reasoning_tokens are not inside output_tokens. If a provider does not report a reasoning split, its inclusive output count remains in output_tokens; Relay does not estimate hidden tokens.
How each kind meters
- STT — parsed audio duration, rounded up to whole seconds. Derived from the audio itself, never from a caller declaration.
- TTS — characters at the moment input is accepted. One-shot requests accept the whole
input; streaming sessions accept perinput.append. Cancelling later does not refund accepted characters. - LLM — provider-reported token counts, normalized where the provider exposes a cache or reasoning split.
max_output_tokensbounds reasoning plus visible output.
Admission ceilings vs. billed usage
Before work starts, Speko computes a conservative ceiling (the most the request could cost) and admits the request against your credit. You are billed for reported usage, not the ceiling. For LLM requests the output ceiling is exactly your max_output_tokens — another reason it is required.
Budgets on streams
Long-lived streams extend their budgets as they run:
- Streaming TTS admits an initial character budget and tops it up transparently as you append text.
- Streaming STT admits an initial duration slice and renews it while audio flows.
When an extension is refused — credit exhausted, or the session's lease could not be renewed — the stream terminates with a final error frame carrying budget_exhausted or lease_expired. These two codes never appear as HTTP statuses; they exist only on already-established streams.
Correlating with your invoice
Every response and stream carries Speko-Request-ID and per-attempt Speko-Attempt-ID values. Usage is recorded per attempt with prices frozen at attempt start, so a failover mid-request produces attempts that are individually priced and individually visible in the dashboard's activity view.
For a BYOK route, the upstream provider bills your account and Speko does not charge provider usage for that attempt.