Design schemas
A schema does two jobs: it guarantees the shape your code receives, and it tells the model what you want. Write it for both readers.
Use enums for closed choices
{ "category": { "type": "string", "enum": ["billing", "technical", "account", "other"] } }An enum is both validated and visible to the model. Always include an escape value such as other or unknown; without one the model is forced to pick a wrong answer when none fits.
Say “not present” explicitly
Prefer a required field that can be null over an optional field:
{ "order_number": { "type": ["string", "null"], "description": "The order number if the email gives one, else null." } }Required-but-nullable means your code always finds the key, and the model is reminded to look for the value instead of quietly leaving it out.
Descriptions are instructions
Field descriptions are read by the model. Put format rules there, close to where they apply:
{ "amount": { "type": "string", "description": "Decimal with two places and no currency symbol, e.g. 49.00" } }Order fields for reasoning
The model writes fields in schema order. When one field depends on judgment, put a short reason field before the verdict so the verdict is written after the reasoning:
{ "properties": { "reason": { "type": "string" }, "verdict": { "enum": ["approve", "reject"] } }, "required": ["reason", "verdict"] }Keep arrays bounded
Use maxItems on arrays the model fills. An unbounded list of extracted entities on a long document can run into max_tokens and be cut off mid-object.
What to avoid
| Pattern | Problem | Instead |
|---|---|---|
Deep anyOf / oneOf trees | Slower validation, more retries | A type discriminator field plus a flat object |
Free-form object with no properties | Nothing is constrained | Name the keys you expect |
| Very long enums (hundreds of values) | Costs tokens on every call; error-prone | A string field plus your own lookup |
pattern regexes for dates | Not supported in every position | format: "date" and a description |
The full list of supported keywords is in JSON output.