|
| 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. |
0 commit comments