Skip to content

Commit f34ae93

Browse files
Merge pull request #380 from rest-sh/docs/toon-blog-post
docs: add TOON output blog post
2 parents 8567bca + 27c66e4 commit f34ae93

5 files changed

Lines changed: 229 additions & 11 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Then format, filter, and page through real API responses:
2727

2828
```bash
2929
restish api.rest.sh/images -o table --rsh-columns name,format,self
30-
restish api.rest.sh/example -f 'body.basics.{name,url,profiles}'
30+
restish api.rest.sh/example -f 'body.basics.{name, label, profiles}'
3131
```
3232

3333
Example table output:
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
---
2+
title: "TOON: Token-Efficient API Output for LLM Workflows"
3+
linkTitle: "TOON: Token-Efficient API Output for LLM Workflows"
4+
date: 2026-06-15
5+
author: "Daniel Taylor"
6+
description: Restish can now render API responses as TOON, a compact text encoding of the JSON data model that cuts token costs when an LLM agent reads the result. Here is where it wins, where it loses, and how to combine it with filtering.
7+
canonical_url: "https://rest.sh/blog/toon-token-efficient-api-output-for-llm-workflows/"
8+
categories:
9+
- AI
10+
tags:
11+
- ai
12+
- llm
13+
- cli
14+
- devtools
15+
---
16+
17+
When a person reads an API response, formatting is free. When an LLM agent
18+
reads one, every character is metered. The response lands in a context window,
19+
the context window is billed by the token, and the format you chose decides how
20+
many tokens the same data costs.
21+
22+
JSON spends a lot of tokens on that job. A list of one hundred records repeats
23+
every key one hundred times, wraps every string in quotes, and spends tokens on
24+
braces and brackets that a model does not need to understand a table of data.
25+
26+
Restish — a CLI for REST-ish HTTP APIs that can make one-off HTTP requests or
27+
turn OpenAPI descriptions into shell-native commands — now has an output format
28+
aimed at exactly this situation. `-o toon` renders the response as
29+
[TOON](https://github.com/toon-format/spec) (Token-Oriented Object Notation), a
30+
compact, lossless text encoding of the JSON data model. It was contributed by
31+
[Omer Amar](https://github.com/omer-amar), and it slots into the same output
32+
pipeline as JSON, YAML, tables, and NDJSON.
33+
34+
This post shows what TOON does to a response, how to pair it with filtering and
35+
pagination, and when you should not use it.
36+
37+
<div class="restish-blog-callout">
38+
<strong>Try it as you read.</strong> Runnable examples below use the browser
39+
preview against the public <code>api.rest.sh</code> API. Local setup commands
40+
are shown as fenced shell snippets.
41+
</div>
42+
43+
## What TOON Does to a List
44+
45+
Here is a small image collection rendered as JSON:
46+
47+
{{< restish-example >}}
48+
restish api.rest.sh/images -o json
49+
{{< /restish-example >}}
50+
51+
Every record repeats `format`, `name`, and `self`. Now the same response as
52+
TOON:
53+
54+
{{< restish-example >}}
55+
restish api.rest.sh/images -o toon
56+
{{< /restish-example >}}
57+
58+
The header `[5]{format,name,self}:` declares the row count and the field names
59+
once. After that, each record is one row. No repeated keys, no braces, and
60+
quotes only where a value actually needs them. The encoding is still lossless:
61+
types, nulls, and special characters survive the round trip, which is why the
62+
header line looks fussier than a plain CSV.
63+
64+
That collapsed form — TOON calls it a tabular array — is where the savings
65+
live. It applies when an array holds uniform, flat objects whose values are
66+
primitives. Five records barely matter; one hundred records of repeated keys is
67+
where JSON starts billing you for the same field names over and over.
68+
69+
## Filter First, Then Encode
70+
71+
Re-encoding is the second-biggest savings. The biggest is not sending data the
72+
model never needed.
73+
74+
Restish filters run before output formatting, so you can project a response
75+
down to the fields that matter and then let TOON collapse what remains:
76+
77+
{{< restish-example >}}
78+
restish api.rest.sh/images -f 'body.{name, format}' -o toon
79+
{{< /restish-example >}}
80+
81+
This combination matters more than either half alone. Filtering drops whole
82+
fields, which saves more tokens than any re-encoding can. And projecting to a
83+
uniform list of primitives is exactly what keeps TOON in its tabular form —
84+
flat, uniform records keep the output collapsed instead of falling back to a
85+
more verbose nested layout.
86+
87+
A good habit for agent-facing commands: decide which fields the model needs,
88+
write the filter, then add `-o toon`.
89+
90+
## Pagination Is Already One Table
91+
92+
There is a detail hiding in the examples above: `api.rest.sh/images` is a
93+
paginated endpoint, and those five records arrived across multiple pages.
94+
Restish followed the `next` links automatically, and document formats like
95+
TOON gather every page into one body before rendering — so a multi-page
96+
collection still comes out as a single table with one header line. No flag
97+
required, and no per-page overhead reaches the model.
98+
99+
Filters work across pages too: without extra flags they run once per paginated
100+
item, which is why `body.{name, format}` projected every record above. Reach
101+
for `--rsh-collect` only when a filter needs to see the whole collection at
102+
once — counting items, for example. The
103+
[pagination guide](/docs/guides/pagination/) covers limits, link following,
104+
and collect semantics.
105+
106+
## The Numbers
107+
108+
Token counts for the same data rendered in each format, counted with
109+
`o200k_base` (GPT-4o-class tokenizer). "Uniform 100" is a 100-row record
110+
collection; "Nested 40" is a collection of nested, irregular objects:
111+
112+
| Format | Uniform 100 | Nested 40 |
113+
| -------------- | ----------: | --------: |
114+
| **toon** | **1,689** | **3,343** |
115+
| json (compact) | 2,903 | 2,762 |
116+
| json (pretty) | 5,002 | 4,842 |
117+
| yaml | 3,700 | 3,360 |
118+
| ndjson | 3,000 | 2,800 |
119+
| gron | 6,403 | 6,783 |
120+
121+
On the uniform collection, TOON costs about 42% fewer tokens than compact JSON
122+
and about two-thirds less than pretty-printed JSON — and the lead grows with
123+
row count, because the per-record overhead is what TOON eliminates.
124+
125+
The second column is where the tradeoff shows, though, and it deserves its own
126+
section.
127+
128+
## Where TOON Loses
129+
130+
On nested, irregular data, compact JSON and NDJSON beat TOON. When records do
131+
not share a flat shape, TOON falls back to an indented layout, and the
132+
per-line indentation costs more than the removed punctuation saves. You can see
133+
the shape change on a nested response:
134+
135+
{{< restish-example >}}
136+
restish api.rest.sh/example -f body.basics -o toon
137+
{{< /restish-example >}}
138+
139+
Still readable, and the embedded `profiles` array still collapses into a table.
140+
But for deeply nested or irregular data, this layout stops being a token win.
141+
The rule of thumb: project to a flat, primitive-valued list first, or stay on
142+
JSON.
143+
144+
Two more tradeoffs worth stating plainly:
145+
146+
- **TOON is output-only.** Restish renders it but does not accept TOON request
147+
bodies. JSON remains the interchange format for anything that talks back to
148+
an API.
149+
- **Token savings only pay off if your model parses TOON as reliably as
150+
JSON.** Models have seen vastly more JSON than TOON in training. The flat
151+
tabular form is simple, but validate against your own model and your own
152+
data before making it a default.
153+
154+
## Where This Fits
155+
156+
The obvious question: if the goal is giving an agent API access, why not an MCP
157+
server?
158+
159+
MCP is the right answer for many setups, and Restish
160+
[can serve registered APIs as MCP tools](/docs/plugins/mcp/). But
161+
a large amount of real agent work happens through plain shell commands — a
162+
coding agent that can run CLI tools already has everything it needs to call an
163+
API through Restish. In that mode, Restish is the tool surface: the agent runs
164+
a command, and stdout goes straight into its context.
165+
166+
That is the niche `-o toon` serves. The agent gets the same request pipeline a
167+
human gets — profiles, auth, TLS, retries, pagination, normalization,
168+
filtering — and the response arrives in its context at a lower token price.
169+
The encoder plugs into the same formatter pipeline as the built-in formats, so
170+
TOON composes with `-f` filters and paginated values like any other `-o`
171+
choice, and it added no new dependencies to the binary.
172+
173+
A practical pattern for an agent-callable script:
174+
175+
```bash
176+
restish myapi list-orders --status open -f 'body.{id, customer, total}' -o toon
177+
```
178+
179+
One line, and the spec-derived command, the credential handling, and the token
180+
budget are all handled.
181+
182+
## Try It
183+
184+
Install Restish:
185+
186+
```bash
187+
brew install restish
188+
restish --version
189+
```
190+
191+
Or with Go:
192+
193+
```bash
194+
go install github.com/rest-sh/restish/v2/cmd/restish@latest
195+
restish --help
196+
```
197+
198+
Then render something as TOON:
199+
200+
```bash
201+
restish api.rest.sh/images -f 'body.{name, format}' -o toon
202+
```
203+
204+
Useful next stops:
205+
206+
- [Output Formats Reference](/docs/reference/output-formats/#toon-for-agents)
207+
covers the full TOON tradeoffs and benchmark details.
208+
- [Filtering Guide](/docs/guides/filtering/) explains projections like
209+
`body.{name, format}`.
210+
- [Output Guide](/docs/guides/output/) covers the processing model and
211+
document-versus-record output.
212+
- [Pagination Guide](/docs/guides/pagination/) explains link following,
213+
limits, and `--rsh-collect`.
214+
215+
Formats are not neutral when the reader is billed by the token. Filter to what
216+
the model needs, collapse the rest with TOON, and spend the saved context on
217+
something useful.

site/content/en/docs/guides/filtering.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ for direct paths and projections; use jq for richer transforms.
1414

1515
{{< restish-query-runner >}}
1616

17+
Use **Trace filter** to inspect before and after values for every step of the
18+
filter.
19+
1720
## Filter Roots
1821

1922
- `proto` for the response protocol string
@@ -52,7 +55,7 @@ restish api.rest.sh/images --rsh-collect -f 'body[format == jpeg].self' -o lines
5255
{{< /restish-example >}}
5356

5457
```bash
55-
restish api.rest.sh/example -f 'body.basics.{name, url, profiles}'
58+
restish api.rest.sh/example -f 'body.basics.{name, label, profiles}'
5659
restish api.rest.sh/images --rsh-no-paginate -f '{next: links.next, first: body[0].self}'
5760
restish api.rest.sh/example -f 'body..url'
5861
```

site/content/en/docs/guides/output.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ Pair it with a filter that projects to a uniform list of records for the
190190
largest savings:
191191

192192
```bash
193-
restish api.rest.sh/images -f '.[] | {name, format}' -o toon
193+
restish api.rest.sh/images -f 'body.{name, format}' -o toon
194194
```
195195

196196
See [Output Formats](/docs/reference/output-formats/#toon-for-agents) for the

site/content/en/docs/reference/output-formats.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ saves more tokens than re-encoding alone, and projecting to a uniform list is
103103
what keeps the tabular form:
104104

105105
```bash
106-
restish api.rest.sh/images -f '.[] | {name, format}' -o toon
106+
restish api.rest.sh/images -f 'body.{name, format}' -o toon
107107
```
108108

109109
```text
@@ -115,13 +115,11 @@ restish api.rest.sh/images -f '.[] | {name, format}' -o toon
115115
heic,Chihuly glass in boats
116116
```
117117

118-
For paginated list endpoints, add `--rsh-collect` so every page is gathered into
119-
one array and rendered as a single table. Without it, each page's items render
120-
as separate documents and the tabular savings are lost:
121-
122-
```bash
123-
restish api.rest.sh/images --rsh-collect -o toon
124-
```
118+
Paginated list endpoints need no extra flags: Restish follows `next` links
119+
automatically, and document formats gather every page into one body, so a
120+
multi-page collection renders as a single table. Use `--rsh-collect` only when
121+
a filter needs to see the whole collection at once; see
122+
[Collect Before Filtering](/docs/guides/pagination/#collect-before-filtering).
125123

126124
### How TOON compares
127125

0 commit comments

Comments
 (0)