Core concepts
The output contract
Tether does not return a paragraph for your frontend to guess at. It returns a validated array of typed blocks, and guarantees you always get something renderable.
Shape
{
"content_type": "application/vnd.tether.blocks.v1+json",
"version": "tether.blocks.v1",
"title": "Open tickets",
"blocks": [
{ "type": "kpi", "id": "headline",
"items": [
{ "label": "Open", "value": 7 },
{ "label": "Oldest", "value": 41, "intent": "negative" }
] },
{ "type": "text", "id": "summary", "variant": "body",
"markdown": "Seven tickets are open. **T-4417** has been open 41 minutes." },
{ "type": "quick_replies", "id": "followups",
"replies": [
{ "id": "r1", "label": "Show the oldest" },
{ "id": "r2", "label": "Group by server" }
] }
],
"meta": { "period_start": "…", "period_end": "…" },
"session_id": "0f2c…",
"pending_actions": []
}
Every block carries a type and a stable id. The full set of types and their fields is in
the block type reference.
Why not prose
Three reasons, in order of how much they will cost you if ignored.
Rendering. A number the model wrote into a sentence cannot be right-aligned, formatted to
your locale, coloured by sign, or made tappable. A kpi block can.
Testing. You can assert that a question returns a table with a total column. You
cannot meaningfully assert on a paragraph.
Change. Prose output means every model upgrade is a silent regression risk across your whole UI. A schema means the contract either holds or fails loudly.
Degradation
Models produce malformed JSON. The contract survives it in three steps:
Validate. The payload is checked against the schema.
Repair. On failure, one repair pass runs — recoverable problems like a missing wrapper, a stray code fence, or a block missing its
idare corrected.Fall back. If it still does not validate, the raw text is wrapped in a single
textblock and returned as a valid response.
Metadata
meta carries the period the answer covers and diagnostic fields. Two response fields are
worth wiring up:
session_id — echo it back on the next request to continue the conversation.
pending_actions — staged writes from this turn. Empty on a read-only turn, which is
most of them.
Versioning
content_type and version are constants on every response. A future breaking change to the
block schema increments them; your client can branch on the value rather than sniffing shapes.
The response normalizer also accepts the legacy vellin. version prefix from before the
service was renamed, so a model echoing an older constant is not treated as malformed.