AI Automation

Structured outputs for AI workflows: validate, then write

Structured outputs for AI workflows: validate, then write

A free-text reply cannot be the input to a CRM or ticket write. Structured outputs for AI workflows constrain the model to a JSON schema so the reply has named fields, declared types, and only the keys you listed, but that shape is not permission to store anything. The write should happen only after a validator in your application accepts both the schema and the business rules the schema does not know. If the check fails, reject the write, retry once when a second call can actually fix the fault, or send the case to a person.

A paragraph is not a record

A CRM row and a ticket are objects with names. Account, priority, owner, due date, and status are separate fields with separate rules. A paragraph can mention those facts, skip them, or invent them. A system of record should not guess which is which.

The format those systems already parse is JSON. RFC 8259, published in 2017, defines JSON as a text format for the serialization of structured data. A JSON value MUST be an object, array, number, or string, or one of the following three literal names: false, null, and true [5]. A chat reply is not that grammar.

Looking like JSON is still not enough. OpenAI's JSON mode will not guarantee the output matches any specific schema, only that it is valid and parses without errors [1]. While both JSON mode and Structured Outputs ensure valid JSON, only Structured Outputs ensure schema adherence [1]. Without a schema constraint, Anthropic says Claude can still produce malformed JSON, missing fields, and inconsistent types [2]. Gemini can be configured so a response adheres to a JSON schema, for names, dates, or a category you define [3]. None of that makes the object true, current, or allowed in your database.

What a schema-constrained reply actually constrains

JSON Schema is a declarative language for defining structure and constraints for JSON data. The project describes 2020-12 as the latest version [4]. The language is not the check: validating an instance requires a JSON Schema validator, and that validator checks whether the document conforms [4]. Each model API implements a subset, and the subsets differ. Your application still decides whether the instance is fit to store.

On the OpenAI API, Structured Outputs ensures the model will always generate responses that adhere to your supplied JSON schema, so a required key is not dropped and an enum is not filled outside the list [1]. You enable it with a json_schema format and strict set to true, or with that same strict setting on function calling [1]. Developers must set additionalProperties to false. All fields must be specified as required, and an optional fact is a union type with null. The root must be an object and must not use anyOf. The supported slice covers the common types plus some string formats, numeric bounds, and array lengths. Keywords such as allOf, not, and if/then/else are outside it. Strict true with an unsupported schema returns an error, and matching output follows schema key order [1].

The same guide documents the holes. In some cases, the model might not generate a valid response that matches the provided JSON schema, including a safety refusal, which does not have to follow the schema, and a reply cut off by the output token limit [1]. The API exposes a refusal indicator so you do not parse the refusal as the record [1]. Structured Outputs can still contain mistakes [1]. The model will always try to adhere to the provided schema, which can result in hallucinations if the input is completely unrelated to the schema [1]. OpenAI presents adherence as removing the need to validate or retry incorrectly formatted responses, and still tells you to handle refusals, incomplete output, and bad values [1].

Anthropic constrains Claude to a schema you supply, either as JSON through output_config.format with type json_schema, or as strict tool use, which the docs describe as guaranteeing schema validation on tool names and inputs [2]. Objects must set additionalProperties to false. Numerical constraints such as minimum, maximum, and multipleOf are not supported, nor are string length limits. An unsupported feature produces a 400 error with details [2]. Required properties are emitted first. Enum strings are not guaranteed to keep your capitalization, and a capital-letter difference can return as a normal completion with no error [2]. While structured outputs guarantee schema compliance in most cases, there are scenarios where the output may not match your schema [2]. A refusal still returns a 200 status code, and the output may not match because the refusal takes precedence [2]. A max_tokens cutoff can leave incomplete JSON, and the documented recovery is a retry with a higher limit [2]. A field that asks for step-by-step thinking may itself cause a refusal [2].

Gemini's structured output mode supports a subset of the JSON Schema specification. You set response_format, use the MIME type application/json, and pass the schema. Supported types include string, number, integer, boolean, object, array, and null [3]. The page states the rule we follow: "While output is syntactically correct JSON, always validate values in your application," and it says to handle schema-compliant but semantically incorrect outputs [3]. Structured outputs format the final answer. Function calling is how the model asks your system to act before that answer exists [3]. Very large or deeply nested schemas may be rejected [3]. A provider can force a shape. It cannot know which accounts exist, which status moves are legal, or which amounts need a second approver.

Validate types and business rules before any write

We put one gate between the model and the system of record. Nothing is inserted or updated until that gate returns accept.

First, classify the response before parsing it as data. A refusal or a token cutoff is not a partial ticket to finish with defaults. A 200 status code can still be a refusal [2]. A finished HTTP call is not a finished record.

Second, parse the text as JSON and run a schema validator. If the text is not a JSON value as RFC 8259 defines one, stop [5]. Then check required names, types, enums, and formats, including rules the model API dropped. A priority band cannot live only in an Anthropic request, because that API does not enforce numeric minimum and maximum [2]. OpenAI says that when schema-constrained generation is unavailable, use a validation library, and retries if needed, so the output matches the schema you wanted [1]. We keep that check even when strict mode is on [1][2].

Third, apply business rules that read live records. The account identifier must match exactly one active account. Priority, queue, and status must be values your system accepts today, and the status move must be legal. The owner must be an active user for that queue. A due date must be a real date inside policy, and it fails if the source text never stated one, because the model will try to fill a required field anyway [1]. An amount must sit inside the approval band. Do not assume a provider keyword checked the band: Anthropic does not support those numeric keywords, and Gemini still says to validate values in your application [2][3].

A schema match plus a failed lookup is still a failed write. Keep the raw text, schema version, stop or refusal indicator, and reason codes. Do not paste the model reply into the ticket body.

Reject, retry once, or send it to a person

Decide the failure policy before the first request. These outcomes are yours, not a mode inside the model.

Reject means no write. That is the default for a new or changed customer record, a charge, or a ticket status. A safety refusal is a reject. Do not rephrase until the model gives in, and do not file the refusal text as the customer's issue. Branch on OpenAI's refusal indicator and on Anthropic's stop_reason, not only on the HTTP result [1][2].

Retry once when the response was cut off, or when the schema failed in a way a second call can repair, such as an enum outside the list when the source text clearly held an allowed value. Anthropic's documented recovery for a max_tokens cutoff is a retry with a higher limit [2]. OpenAI points to a validation library and possible retries when schema-constrained output is unavailable [1]. Put the validator's error in that second request. If it fails again, stop. A retry will not create a missing account.

Send the case to a person when the text is ambiguous, the lookup does not resolve, two rules conflict, or a refusal interrupted ordinary work. In the 2023 NIST AI Risk Management Framework, Map 3.5 reads: "Processes for human oversight are defined, assessed, and documented in accordance with organizational policies from the govern function" [6]. The reviewer sees the original text, the failed fields, and the reason codes, not a half-written ticket. A correction still passes the same validator. Malformed or truncated JSON, or a schema miss the source text can support, can earn the single retry. A missing fact, an unstated date, or an unresolved account does not.

Design the schema as the record, not as the conversation

Send the record you are willing to store, plus short quotes a reviewer can check. Do not send a transcript of the model's reasoning. OpenAI recommends clear names and descriptions, and Gemini says to use descriptions and specific types such as enums [1][3]. Set additionalProperties to false. OpenAI and Anthropic require it. Gemini allows that keyword to be a boolean or a schema, so reject unknown keys in your own check [1][2][3]. Use null when a provider requires every field and the fact is optional [1]. Do not depend on order: OpenAI keeps schema order, and Anthropic emits required properties first [1][2]. A step-by-step thinking field may cause a refusal, so leave it out of the object you will write [2].

When ticket fields change, treat that as a contract change. OpenAI recommends SDK helpers or a build check so the schema and your code types do not diverge, and old replies should fail closed [1]. A rule the provider subset cannot express stays in the validator [2][3].

From an inbound note to a ticket, without an early write

A mailbox note might say: "Please bump the Acme renewal. Jordan said it is urgent and it should land before Friday. Do not assign it to me."

The object is small: account_name, priority limited to low, normal, or high, due_date or null, assignee or null, a short summary, and source_spans that quote the note. The call uses that schema with strict structured output. A refusal or an incomplete response does not open a ticket. Read the refusal indicator or stop reason before treating the HTTP call as a record [1][2].

If the schema accepts the object, the business rules still run. "Acme" must match one account. Two matches go to a person. "urgent" is not in the enum, and a capital-letter variant can arrive with no error flag, so the check stays case-sensitive unless you published a map [2]. "before Friday" is not a date. Null is acceptable. A calendar day the note never stated is not written, because the model will try to satisfy the schema anyway [1]. Assignee stays null. If the queue requires an owner, a person assigns it. Do not default the owner to the sender or the model. Only a fully accepted object is written, with the schema version and the raw response stored beside it.

Practical takeaways

  • Do not pass free text into a create or update call. Require JSON that has already survived a parser.
  • Use schema-constrained generation where it is supported, and still validate. Refusals, cutoffs, enum-case drift, and wrong-but-well-shaped values are documented [1][2][3].
  • Keep identity lookup, legal status moves, approval bands, and source-backed dates in your code, even when an API subset cannot express them [2][3].
  • Reject by default. Retry once only for truncation or a repairable schema miss. Send missing facts and unresolved identity to a person, and validate that person's edit too [6].
  • Version the schema with the write path, and fail closed when they disagree [1].

How we can help

Have more questions or want to get in touch? Our team can walk through how a validator should sit in front of a CRM or ticket write, including the schema, the business rules, and the path for rejects. Start at our contact page. You can call us at (980) 322-4500. You can also email [email protected].

Citations

  1. OpenAI, "Structured model outputs" (accessed 2026-10-07)
  2. Anthropic, "Structured outputs" (accessed 2026-10-07)
  3. Google AI for Developers, "Structured outputs" (accessed 2026-10-07)
  4. JSON Schema, "What is JSON Schema?" (accessed 2026-10-07)
  5. IETF, "RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format" (December 2017)
  6. NIST AI Resource Center, "AI RMF Core" (2023)
Our Strongest Offering

Forge Your Next Website

Forged Sites are custom-built, static-first websites with a full AI content engine on board — no CMS to log into, no plugins to break, no builder to fight.

  • Working target: WCAG 2.2 AA
  • During work hours, an account manager still reviews material changes
  • DraftDash auto-drafted blogs keep your content engine running
  • Ethel AI-powered forms filter spam and capture genuine leads