A schema for structured output does two jobs at once. It constrains what your code will accept, and it instructs the model on what to produce. Those are different jobs, and the second is the one people underinvest in.
Descriptions carry more weight than constraints. A model reads the schema as part of its instructions, and a property described as "one sentence, under twenty words, no trailing full stop" produces markedly better results than the same property with no description and a maxLength. The constraint catches the failure; the description prevents it.
Keep the structure shallow. Deeply nested objects are harder for models to produce reliably, and each level of nesting increases the chance of a malformed response. If you find yourself four levels deep, consider whether the task should be two calls rather than one.
Set additionalProperties to false unless you have a specific reason not to. Models add helpful-looking fields, and without this keyword they pass validation and reach your code. With it, they become an error you can see and act on.
Use enum wherever the answer belongs to a fixed set. It is the most effective single constraint available, because it removes the possibility of a plausible-but-wrong category rather than detecting one after the fact. List the allowed values in the prompt too, since belt and braces costs nothing here.
Be deliberate about what is required. Marking everything required guarantees intermittent failures whenever the model genuinely has nothing to put in a field. Marking nothing required means you handle undefined everywhere in your code. The right answer is usually a small required core and a larger optional remainder.
Finally, test the schema against a real response before you ship it. A schema that compiles is not the same as a schema that describes what the model actually returns.