Crux Read real OpenAPI YAML fragments, a oasdiff verdict, and a generated client, then pick the highest-leverage fix to keep the contract honest.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at senior altitude — in orbit
◷ 14 min
Contract problems are caught in the spec file, the diff output, and the generated client. Read each fragment, predict what it does to consumers, and choose the fix a senior engineer makes first.
Goal
Practise the loop every API change runs through: read the schema, predict the breaking-change and codegen consequences, and reach for the fix that keeps the spec load-bearing.
Snippet 1 — the loose schema
components: schemas: Order: type: object properties: id: { type: string } items: type: object # untyped blob total: {} # no type at all additionalProperties: true # anything else is allowed # no `required:` array
Quiz
Completed
A generator turns this Order schema into a client. What will the generated type look like, and what is the fix?
Heads-up Generators do not invent constraints the schema omits. An untyped object becomes any, a missing required array makes everything optional, and open additionalProperties forbids a closed type. The output is faithfully loose.
Heads-up This schema is valid OpenAPI — loose, but legal. It generates fine; it just produces a weak, any-riddled client that defeats the point of codegen.
Heads-up There is nothing to validate against — additionalProperties: true and untyped fields accept almost anything. Loose schemas weaken both the generated types and edge validation.
Snippet 2 — the oasdiff verdict
$ oasdiff breaking main.yaml pr.yaml1 breaking changes: 1 error, 0 warningerror api-required-request-property-added in API POST /orders added the required request property 'tenantId'
Quiz
Completed
The PR adds tenantId as a required field on POST /orders and oasdiff reports this in CI. What should the pipeline do, and what is the compatible alternative?
Heads-up Adding an optional field is compatible; adding a required one is the textbook breaking change, which is exactly why oasdiff classifies it as an error, not a warning.
Heads-up Regenerated docs describe the change but do not prevent the breakage. The whole value of the gate is that the breaking change fails the build before any client breaks.
Heads-up The break is on the request, not the response. Touching the response does not make a newly-required request field compatible.
You migrate the spec to 3.1 and rewrite nullability like this. What must you watch for in tooling and diffs?
Heads-up 3.1 removed nullable entirely; it is ignored or invalid. You must use type: [string, 'null'], and tools built for 3.0 may not understand it.
Heads-up Both express 'string or null' — the contract is identical. The migration is syntactic; the allowed values are the same.
Heads-up type: [string, 'null'] is a union of allowed types, unrelated to whether the field is required. Required-ness is controlled only by the required array.
Snippet 4 — code-first annotations
class CreateOrder(BaseModel): id: str items: list[Item] coupon: str | None = None # optional, nullable# FastAPI generates the OpenAPI spec from this model.# A later edit makes coupon required: coupon: str # now required, no default
Quiz
Completed
In a code-first setup, an engineer makes coupon required by removing its default. The generated spec updates automatically. What is the risk, and what closes it?
Heads-up Code-first prevents drift between spec and handler, not between the contract and what clients depend on. A required-field change is breaking regardless of how the spec was produced.
Heads-up The model is valid Python; FastAPI starts fine and faithfully publishes coupon as required. The danger is downstream, on clients, not at startup.
Heads-up Spec-first surfaces the change for review but still does not block it without a diff gate. The fix is enforcement (oasdiff in CI), which both models need equally.
Recap
Every contract change is read in three places: the schema (loose types and open additionalProperties produce any-riddled clients — tighten with explicit types, required arrays, and $ref), the diff (oasdiff classifies adding a required field, removing a response field, or narrowing a type as breaking — fail the PR and migrate compatibly), and the generation direction (code-first stays in sync with code but not with consumers, so it needs the same gate spec-first does). OpenAPI 3.1 moves nullability into the type array and aligns with JSON Schema 2020-12 — verify tool support before you migrate. Read the artifact, predict the consumer impact, then fix at the contract level.