Every API that answers questions has to decide how much of its answer is machine readable. The two ends of the range are familiar. A strict schema gives fields with names and types, parseable without ambiguity, breakable by any change. Free text gives a paragraph that can express anything and can be relied on for nothing.
For a service read by models rather than only by application code, the choice looks superficially easier. Models handle prose well. They can read a paragraph describing a flood risk and extract what matters. That capability tempts designers towards text, and the temptation should be resisted, because the model is rarely the last consumer. Whatever it extracts gets passed to code, put in a report, compared against a threshold, or summed with something else.
Spatial answers make the stakes concrete. A number describing distance, elevation, area or risk is meaningless without units, and dangerous without confidence and provenance. Getting the boundary between structure and prose wrong produces answers that look authoritative and cannot be checked.
What the two ends actually cost
The strict schema has the properties that make automation possible. A parser either works or fails loudly. Types are enforceable. A field either exists or does not, and a consumer can be written against it once. Validation happens at the boundary rather than in the head of whoever reads the output.
Its cost is inflexibility in the face of things that do not fit. Every real dataset produces cases the schema did not anticipate: a value that is known only as a range, a figure derived by two different methods for different records, a caveat that applies to one region and not others. A rigid schema has three options for these, and all are bad. Drop the information. Force it into a field that means something else. Or add a field, which is a breaking change if consumers are strict about it.
Free text has the opposite profile. Anything can be expressed, including the caveat nobody anticipated. Nothing can be relied on. Two responses to similar questions may use different wording, different units, different levels of hedging. A consumer parsing prose is writing a heuristic, and the heuristic will be wrong on some input it has not seen.
The failure mode of over-structuring is lost nuance. The failure mode of under-structuring is silent misparsing. The second is worse, because the first is visible and the second is not.
Why a spatial number needs company
A bare number in a spatial answer is not an answer. Consider a response that says the elevation is 42.
Forty two what. Metres, feet, or something else. Above what: mean sea level, a specific geoid model, the local datum, ground level at some reference point. Measured how: a survey, an interpolated digital elevation model, a value derived from contour lines. Measured when, which matters more than it sounds in places with subsidence or active construction. And with what uncertainty, since a global elevation model and a local survey differ by amounts that decide whether a building floods.
None of that is pedantry. Every one of those questions changes what a downstream decision should be. A consumer that receives 42 and nothing else will assume metres, assume sea level, assume the number is exact, and will be wrong in a way nobody can detect from the output.
The minimum structure for any spatial figure therefore includes the value, its unit, its reference frame where one applies, the source it came from, when that source was current, and some expression of uncertainty. Six fields for one number seems heavy until the alternative is considered, which is a number that cannot be validated, compared, or defended.
Confidence deserves its own note. It should be a field, not a hedge in prose. A model reading "approximately 42 metres, though this area has limited data coverage" has to convert prose into a decision about reliability, and different models will convert it differently. A numeric or categorical confidence field converts identically every time. Prose can explain the confidence. It should not be the only place the confidence exists.
Provenance is the field most often skipped and most often needed later. When two sources disagree, and they will, the only way to adjudicate is to know which is which. Provenance also carries the licence obligation, which for open data is not optional and cannot be reconstructed after the fact if the response did not record where the value came from.
Where free text genuinely earns its place
There are cases where forcing structure destroys the information, and they are not edge cases.
The first is explanation of a model's own judgement. A vision model tags a building as commercial. The structured output records the label and a confidence. What it cannot record is why: signage visible on the ground floor, a loading bay at the rear, a car park sized for customers rather than residents. That reasoning is genuinely open ended. Any attempt to enumerate the possible evidence types produces a taxonomy that is wrong within a month. Prose holds it, and a human or a model reading the answer can judge whether the reasoning is sound. This is often the difference between a label somebody trusts and a label somebody ignores.
The second is caveats that do not fit a field. The underlying data for this district was collected before a major redevelopment. The administrative boundary here is disputed and the answer depends on which authority is used. The two available sources disagree by more than either one's stated error. These are specific, consequential, and unenumerable. A boolean flag cannot express any of them.
The third is comparison and context. Whether a figure is unusual for its area, how it relates to a neighbouring measurement, what a reasonable interpretation looks like given local conditions. This is analytical content, not data, and it belongs in prose because it is prose.
The fourth is the honest description of a limit. When a question cannot be answered properly, an explanation of what was attempted and why it fell short is more useful than an empty structured response, and there is no schema for the space of reasons things fail.
The hybrid, and the discipline it requires
The pattern that works is neither end of the range. Every figure is structured. Alongside it sits a described reason, in prose, that explains, qualifies, or contextualises.
Concretely, a response about a site's flood exposure carries a structured figure with value, units, confidence, source and date, and a text field explaining that the estimate comes from a regional hazard model rather than a local study, that the surrounding terrain is flat enough that small elevation errors matter, and that the nearest gauge is some distance away.
Two rules make this work, and skipping either collapses the pattern.
The prose never contains information that is not also in the structure. If the explanation says the data is old, there is a date field saying how old. If it says confidence is low, there is a confidence field. A consumer that ignores the prose entirely must still get a correct, if less informative, answer. The moment a critical fact exists only in the text, every automated consumer is silently wrong.
The structure never contains prose. A field whose value is a sentence is a field that will be parsed by regular expression eventually. Keep types clean: numbers are numbers, categories come from a closed set, dates are dates.
The practical result is that structure carries what must be acted on and prose carries what should be understood. A model can use either. Code uses the first. A human reads the second.
Deciding per field, not per endpoint
The choice is usually framed as structured against unstructured for a whole response, and that framing is what leads to bad compromises. The decision is per field, and a few questions settle it quickly.
- Will anything compare this value to a threshold, sum it, sort by it, or feed it into arithmetic. If so it is structured, with units, always.
- Does it come from a closed set that changes rarely. If so it is an enumerated field, not a string, so consumers can switch on it.
- Is the space of possible values genuinely open and unenumerable. If so it is text, and forcing a taxonomy on it will produce a wrong taxonomy.
- Would a consumer that ignored this field entirely be misled about the main answer. If so it must be structured, because prose is optional to a machine consumer and this content is not.
That last question is the one that catches most mistakes. Anything load bearing has to be in the structure. Prose is for content that improves an answer, never for content that corrects it.
Schema evolution without breaking the consumers you have
Schemas change. A field is added, a meaning is refined, an enumeration grows. The question is whether existing consumers survive it.
Additive change is the safe kind and should be the overwhelming majority. Adding an optional field breaks nothing that ignored it. This requires consumers to tolerate unknown fields, which most do by default and strict validators do not, so the tolerance should be stated in the documentation rather than assumed.
Enumerations are the common trap. A consumer that switches on a category and has no default breaks the day a new category is introduced. The response format should therefore establish from the start that enumerations may grow, and consumers should be told to handle unknown values rather than treating them as errors. Publishing the enumeration through a discovery endpoint rather than only in prose documentation helps, because a caller can then check what exists rather than hard coding a list from the examples.
Semantic change without structural change is the dangerous kind, and it is invisible. A field called population that meant residents and now means daytime population has the same name, the same type, and a different meaning. No parser notices. Every consumer is now wrong. This case must be handled by renaming or versioning, never by editing the definition in place, because the whole point of a schema is that a name means one thing.
Removals need a deprecation path with real time in it: mark deprecated, keep serving, publish a replacement, watch usage decline, then remove. For agent callers there is an extra wrinkle, which is that a model may have learned the old field from documentation or examples in its training data and will keep requesting it. Returning a clear error naming the replacement field is worth more than silence, because that error is the only documentation the model will read.
Versioning is a blunt instrument and should be reserved for changes that genuinely cannot be made additively. Every version is a shape you have to keep serving. Two or three coexisting versions is manageable. Eight is a maintenance burden that will eventually be resolved by breaking somebody.
The failure where JSON arrives wrapped in prose
The specific failure worth naming, because it is common and because it fails silently, is this. A model is asked to produce JSON. It produces a short explanation, then the JSON, then a closing remark. Or it wraps the JSON in a code fence. Or it produces valid JSON containing a field whose value is itself a string of JSON.
The consuming parser then does one of several bad things. It extracts the first brace to the last brace and picks up an example object mentioned in the preamble. It finds a code fence and takes its contents, which happen to be an illustration of the wrong shape. It succeeds in parsing something structurally valid and semantically wrong, and nothing raises.
The pattern is dangerous precisely because it usually works. Most of the time the preamble is absent, the extraction is correct, and the pipeline runs. The failure appears on some fraction of inputs, produces plausible values, and gets discovered when somebody notices a number that cannot be right.
The mitigations are all about eliminating ambiguity rather than parsing more cleverly:
- Use constrained generation or a schema enforced output mode where the platform provides one. This removes the failure at the source rather than detecting it downstream.
- Keep the structured payload and any narrative in separate fields of one object, so the explanation has a legitimate home and never has to be stripped out of the payload.
- Validate against the schema after parsing, not just for syntactic validity. A response that parses but lacks required fields must fail loudly.
- Never extract by scanning for the first and last brace. If extraction is unavoidable, require an unambiguous delimiter and fail when it is missing.
- Log what failed to parse. Silent fallbacks to a default object are how this failure survives for months.
The underlying principle is that a parser should fail rather than guess. Guessing produces a value, the value flows downstream, and the error surfaces somewhere far from its cause.
The choice that is easy to get backwards
The instinct, when a spatial answer feels too rigid, is to loosen it: add a notes field, let the model explain, trust the reader to interpret. That instinct produces answers that read well and cannot be verified.
The better move is almost always the opposite. Add structure to what must be trusted, and add prose alongside it rather than instead of it. A confidence field and a paragraph explaining the confidence serve different consumers and neither replaces the other.
The test to apply before shipping any response format: if every prose field were stripped out, would the remaining structure still be correct, or would it now be misleading. If it would be misleading, something load bearing is sitting in text, and the first automated consumer to read it will get the wrong answer without ever knowing.