API Response Inspector
Paste a completion response and get every field explained, plus the arithmetic checks that catch a truncated or malformed one.
Everything on this page runs in your browser. Nothing you paste is uploaded, logged, or written into the URL — only the settings are, so a configured tool can still be linked to. Responses carry ids, prompts and sometimes customer data. This one stays in the tab.
0 fields on this response are not in this page's glossary — they are listed below rather than quietly dropped.
| field | value | what it means |
|---|---|---|
| id | "resp_7f21c9" | the identifier for this request. Log it — it is the only thing support can look anything up by. |
| object | "chat.completion" | the kind of thing this is, so a client can dispatch on it. |
| created | 1785312000 | when the response was produced, in seconds since the epoch. |
| model | "example-model-v1" | which model actually served the request. Worth asserting on: a routing layer can silently give you a different one from the one you asked for. |
| choices | 1 item | the completions returned. One entry unless you asked for more, and asking for more multiplies the output tokens you pay for. |
| choices[0] | 3 fields | the completions returned. One entry unless you asked for more, and asking for more multiplies the output tokens you pay for. |
| choices[0].index | 0 | the position of this choice, for when there is more than one. |
| choices[0].message | 2 fields | the assistant's turn — role, plus content and/or tool calls. |
| choices[0].message.role | "assistant" | who is speaking. Everything in choices is the assistant. |
| choices[0].message.content | "{\"invoice_id\": \"INV-2201\", \"total\": 1250" | the text. Null or empty is normal when the model called a tool instead of answering. |
| choices[0].finish_reason | "length" | why generation stopped. "stop" is a complete answer; "length" means it was cut off; "tool_calls" means it wants a tool; "content_filter" means something blocked it. |
| usage | 5 fields | the token counts you are billed on. This is the block to log on every call. |
| usage.prompt_tokens | 1840 | input tokens. Includes the system prompt, the whole conversation history and every tool schema — which is why it grows even when your user's message did not. |
| usage.completion_tokens | 256 | output tokens. Priced higher than input almost everywhere, and the number worth optimising first. |
| usage.total_tokens | 2096 | input plus output. Derived, so it should always equal the sum — if it does not, one of the three is measuring something else. |
| usage.prompt_tokens_details | 1 field | a breakdown of the input tokens, usually into cached and uncached. |
| usage.prompt_tokens_details.cached_tokens | 1536 | input tokens served from a prompt cache. Billed at a lower rate where caching exists — check whether this is a subset of prompt_tokens or additional to it, because providers differ and it changes your cost model. |
| usage.completion_tokens_details | 1 field | a breakdown of the output tokens, usually separating reasoning from visible text. |
| usage.completion_tokens_details.reasoning_tokens | 192 | output tokens spent thinking rather than answering. You are billed for them and you usually cannot see them, so a short answer can carry a large bill. |
| system_fingerprint | "fp_examp1e" | an opaque identifier for the backend configuration. When it changes, output can change even with identical inputs and a fixed seed. |
Arrays are expanded for their first three entries. Nesting is followed six levels deep and 200 fields at most.
- noteusage.prompt_tokens_details.cached_tokens1,536 of 1,840 input tokens came from cache — 83% of the prompt
Expected: you to check which convention your provider uses before multiplying anything by a cached rate. Both conventions exist and they differ by the whole cached amount
- noteusage.completion_tokens_details.reasoning_tokens192 of 256 output tokens were reasoning — 75% of the output you paid for is not in the text you received
Expected: you to price on completion_tokens, not on the length of the visible answer. A two-line reply can carry a four-figure token count
- errorchoices[0].finish_reason"length" — this answer was cut off mid-generation, not completed
Expected: "stop". The content you have is a prefix, and if it was meant to be JSON it will not parse. Raise max_tokens, or shorten what you asked for — retrying unchanged will truncate at the same place
- errorchoices[0].message.contentthe content looks like JSON and does not parse — Expected ',' or '}' after property value in JSON at position 40 (line 1 column 41)
Expected: valid JSON, if you are relying on it. Truncation is the usual cause and finish_reason will say so; a leading markdown fence is the other
The two fields that decide whether you have an answer
The finish reason and the content are the whole verdict, and code that reads only the content gets it wrong in two directions. A length finish means the string you are holding is a prefix — it will be valid prose and invalid JSON, which is precisely the combination that passes a smoke test and fails in production. And an empty content with tool calls is a completely successful response that a null check will treat as a failure. Branch on the finish reason first, then read the content.
Log the usage block, not a computed cost
Token counts are facts the provider asserts; a cost is your arithmetic over a rate that changes. Store the raw counts — input, output, cached, reasoning — with the model name and the request id, and compute money when you report on it. Do it the other way round and a price change, or a discovery that cached tokens were being double-counted, means the history is wrong and unrecoverable. It is also the only way to notice the thing that quietly doubles most bills: an input token count that grows every turn because the whole conversation is being resent.