Model APIs and structured output
So far you've used AI through tools. Now you'll call models from your own code, to build AI features into a product. The API itself is simple. The skill is getting back data your program can trust, every time, which is what structured output is for.
1. The idea
Every model API works the same way. You send a request:
- model: which model and tier (Module 04).
- system prompt (or "instructions"): the role and rules for this feature.
- messages: the conversation so far: user turns, assistant turns, tool results. The API keeps no memory; you send the history every time (Module 02).
- limits and options: maximum output tokens, effort, tools, and the output format.
You get back the content, a stop reason (finished, hit the token limit, wants a tool, refused) and usage (input and output tokens, which is your bill). Add streaming to show the answer as it's written.
Structured output is the key feature for products: you give the API a JSON Schema (the form), and the model's output is constrained to match it. All three major APIs now guarantee schema-valid JSON when you use it, so you no longer parse prose or retry broken JSON.
2. How it works
Step through extracting an invoice from an email: first as prose, then with a schema.
3. Try it yourself
The same request in three APIs
Extract an invoice into a fixed shape. Choose an API to see the code; the structure is identical, only the names differ. All three were checked against the vendors' docs in October 2026.
Check the form
Even with structured output, your code should validate what it stores, because refusals, truncation and schema mistakes happen. Edit the model's output and see what a validator for this schema would accept.
4. Why it matters at work
Rules for any AI feature in a product. Agree them before you build:
| Rule | Why |
|---|---|
| The model suggests; code or a person decides anything involving money, safety or compliance | Models are probabilistic. Final decisions need deterministic checks or human sign-off. |
| Everything that becomes data is validated against a schema | Invalid output is retried once, then refused, never stored. Structured output makes this rare, not impossible. |
| Untrusted input (customer text, supplier PDFs) is data, never instructions | Put it inside clearly marked sections, and give the feature no tools that could leak or delete (Module 18). |
Check stop_reason on every call | "Max tokens" means truncated output; "refusal" means no data. Neither should be stored as if it were a result. |
Log usage on every call | Cost per feature and per customer comes from these numbers (Module 19). |
| Hide the vendor behind your own interface | One LlmProvider interface with Claude, OpenAI and Gemini adapters lets you switch by configuration, compare on your evals, and fail over in an outage. |
| Set timeouts and retries with backoff | APIs have rate limits and occasional overloads; handle them like any network dependency. |
5. In the three APIs
Checked against each vendor's documentation, October 2026. Your coding agent can write all of this for you; knowing the shape lets you review it.
| API | Main endpoint | Structured output | Strict tools |
|---|---|---|---|
| Claude | Messages API (client.messages.create) | output_config.format with type: "json_schema" (generally available) | strict: true on a tool definition |
| OpenAI | Responses API (client.responses.create) | text.format with type: "json_schema" and strict: true; responses.parse with a Pydantic or Zod model | strict: true on a function |
| Gemini | Interactions API (client.interactions.create) | response_format with mime_type: "application/json" and a schema | Function calling mode validated |
additionalProperties: false and don't support numeric ranges or string lengths, so check those in your own validation code. Read your vendor's list before designing a schema.6. Remember it
How this hook works: what “Ask for a form, not a letter” means
What the slogan means, phrase by phrase
- “Ask for a form”
- Send a JSON Schema with structured output turned on. The model can only produce output that fits the boxes.
- “not a letter”
- Prose is for people. A program parsing prose with regular expressions breaks on the first unusual phrasing.
- Implied: check before filing
- Validate types, required fields and business rules in your code, and look at the stop reason. A refused or truncated form isn't a form.
Why this picture fits: it turns a technical feature into an obvious habit. You'd never ask a clerk for a free-text letter and then try to extract the invoice total from it; give them the form.
The story → the concept
| Paperwork | The API |
|---|---|
| The blank form | A JSON Schema |
| "Fill in every box" | required fields, additionalProperties: false |
| A box for a date | "format": "date", or a string you check |
| The clerk declines to fill it in | A refusal stop reason |
| The clerk runs out of paper | A max-tokens stop reason (truncated) |
| Checking the form before filing | Your validator and business rules |
Watch it on an example
- A letter: "This invoice from Acme, dated 3 October, totals £1,240.50…" Your regex expects a dollar sign and fails.
- A form:
{"supplier": "Acme", "date": "2026-10-03", "total": 1240.5, "currency": "GBP"}. Every field in its box, every time. - Your validator checks the total is positive and the currency is one you support, then stores it.
The trap it saves you from
"Please reply in JSON" in the prompt, then parsing whatever comes back. It works in the demo and fails in production on the 1 in 200 response with an apology or a code fence wrapped around it.
It still works when…
You use tools (Module 06): a tool's input schema is a form the model fills in to call it. With strict tool use, those forms are guaranteed too.
7. Common mistakes
8. Hands-on lab (roadmap week 15)
- Get keys for two vendors and store them as environment variables, never in code.
- Extract structured data from three real documents (invoices, labels, emails) with structured output in both APIs.
- Add a validator for business rules, and handle the stop reasons: refusal, max tokens.
- Log usage per call and compute the cost per document at each vendor's price (Module 04's calculator).
- Wrap both behind one interface (
extract_invoice(text) -> Invoice) and switch vendors with a config value.
9. Self-check quiz
Your code needs the same five fields from every document. What do you use?
The stop reason says the maximum token limit was reached. What does that mean?
Where should calls to the model API come from?
Does a schema-valid response mean the data is correct?
10. Go deeper
| Resource | Time | Why |
|---|---|---|
| Structured outputs (Claude) | 15 min | JSON outputs, strict tools, and the supported schema subset. |
| Structured Outputs (OpenAI) | 15 min | Schemas, Pydantic and Zod helpers, and refusals. |
| Structured output (Gemini) | 15 min | The same feature in the Gemini API. |
| Anthropic Academy API course | 3–4 h | Free, hands-on API fundamentals. |