OpenAPI errors

Fix additionalProperties: false surprises in OpenAPI codegen

additionalProperties: false combined with allOf composition is spec-valid OpenAPI that rejects payloads it should accept. The schema itself passes every validator with no warning; the break shows up only when you validate an actual request or response body against it, and the rejected fields are exactly the ones inherited from the other allOf branch. The cause is a real, documented JSON Schema behavior, not a bug in any specific tool — additionalProperties only ever looks at properties declared in its own subschema, never at sibling schemas composed alongside it. Reproduced with a real validator on 2026-09-21.

How do you reproduce it?

Build a Message schema by composing a shared BaseEntity (which defines id) with a Message-specific branch (which defines text and locks the object down with additionalProperties: false):

components:
  schemas:
    BaseEntity:
      type: object
      properties:
        id:
          type: string
      required: [id]
    Message:
      allOf:
        - $ref: '#/components/schemas/BaseEntity'
        - type: object
          properties:
            text:
              type: string
          required: [text]
          additionalProperties: false

swagger-cli validate v4.0.4 and redocly lint v2.53.3 (default ruleset) both pass this spec with no errors — a completely valid OpenAPI document. Nothing here looks wrong at the document-structure level.

The break appears when you validate a payload that should obviously be valid — { "id": "1", "text": "hi" }, exactly the shape Message describes — against the compiled JSON Schema. We ran this with Ajv's JSON Schema 2020-12 build:

import Ajv2020 from "ajv/dist/2020.js";
const ajv = new Ajv2020({ strict: false });
const validate = ajv.compile(messageSchema);
validate({ id: "1", text: "hi" }); // -> false

That returns false, with a real, reproduced Ajv error:

must NOT have additional properties
{ additionalProperty: 'id' }

The field being rejected is id — the one field BaseEntity explicitly declares as required. A payload built exactly to the combined schema's own intent fails the combined schema's own validation.

Why does this happen?

Per Understanding JSON Schema: "additionalProperties only recognizes properties declared in the same subschema as itself. So, additionalProperties can restrict you from 'extending' a schema using combining keywords such as allOf." That's exactly what happened above — the additionalProperties: false in the second allOf branch only knows about the properties declared in that same branch (text). It has no visibility into id, declared in the sibling BaseEntity branch. Each allOf subschema is checked against the entire instance independently, so the branch with additionalProperties: false sees id, doesn't recognize it as one of its own declared properties, and rejects it — even though id is valid per the other branch.

This is a documented JSON Schema pitfall, not an OpenAPI-specific quirk, which is why no OpenAPI validator flags it: from a pure schema-structure standpoint, nothing is invalid about the document. The problem exists only at the intersection of two branches' combined intent, a semantic property no structural validator checks.

How do you fix it?

There are two correct fixes, and which one applies depends on your OpenAPI version.

OpenAPI 3.1 (JSON Schema 2019-09+): use unevaluatedProperties instead of additionalProperties. Unlike additionalProperties, unevaluatedProperties is composition-aware — it collects everything successfully validated across every applicable subschema (including sibling allOf branches) before deciding what counts as "extra." We verified this fixes the exact case above:

# OpenAPI 3.1 fix — unevaluatedProperties sees across allOf branches
Message:
  allOf:
    - $ref: '#/components/schemas/BaseEntity'
    - type: object
      properties:
        text:
          type: string
      required: [text]
  unevaluatedProperties: false

Re-run against Ajv 2020-12: { id: "1", text: "hi" } now validates true, and a genuinely unexpected field — { id: "1", text: "hi", extra: "nope" } — still correctly fails with must NOT have unevaluated properties, { unevaluatedProperty: 'extra' }. That's the behavior the original schema was trying to express.

OpenAPI 3.0 (no unevaluatedProperties support): flatten the schema instead of composing it. OpenAPI 3.0's Schema Object predates the JSON Schema draft that introduced unevaluatedProperties, so the fix there is to not combine allOf with additionalProperties: false at all — either drop additionalProperties: false from the composed branch (accepting that the whole point of a strict schema is lost), or write Message as one flat schema with every inherited field repeated:

# OpenAPI 3.0 fix — no allOf, so additionalProperties sees every field
Message:
  type: object
  properties:
    id:
      type: string
    text:
      type: string
  required: [id, text]
  additionalProperties: false

Flattening duplicates fields across schemas that share a base — the real tradeoff of strict-object validation under 3.0. There's no clean way to have both allOf reuse and composition-aware additionalProperties: false until you're on 3.1.

How different tools react

Check additionalProperties: false on an allOf branch
swagger-cli validate (schema-shape) Passes — no structural violation
redocly lint (default ruleset) Passes — no rule for this composition pitfall
Ajv validating a real payload against the compiled schema Fails — rejects fields declared in sibling allOf branches
Same payload against unevaluatedProperties: false (3.1) Passes for legitimate fields, still fails for genuinely extra ones

The gap here is the widest of any error in this series: neither spec validator we tested has a rule for this at all, because it's not a spec violation — it's a JSON Schema composition pitfall that only manifests against real data.

How to catch this before it ships

  1. Never combine additionalProperties: false with an allOf branch that isn't the only branch — if a schema composes a shared base via allOf, either flatten it (3.0) or use unevaluatedProperties: false at the composition level instead (3.1).
  2. Test your schemas against real example payloads, not just against a spec validator. As shown above, a spec-valid document can still reject every legitimate request body it's supposed to describe — the only way to catch that is to actually validate data against the compiled schema.
  3. Run the free in-browser OpenAPI validator to confirm document structure, then separately confirm a real example payload validates against every strict (additionalProperties: false) schema in the spec — those are two different checks, and this post is about the one a structural validator can't perform.

If your spec uses allOf composition with strict object schemas, Sourced's generated SDK and docs preview validate against real example payloads, not just document structure, so a schema that rejects its own intended shape shows up before a customer's integration does. Create hosted docs from your repo or start a free report.

FAQ

Is additionalProperties: false itself the problem?

No — on a flat, non-composed schema it works exactly as expected: it rejects genuinely unexpected fields. The problem is specific to combining it with allOf composition, where each branch only sees its own declared properties.

Does this affect anyOf or oneOf the same way?

The same per-subschema scoping applies, but the practical trigger is rarer — anyOf/oneOf branches are usually alternatives, not combined field sets, so you're less likely to expect one branch's fields to be visible to another's additionalProperties: false. allOf composition is where teams hit this, because the whole point of allOf is combining fields from multiple schemas into one payload shape.

Why doesn't redocly lint have a rule for this?

Because it's not a document-structure violation — the OpenAPI document is completely valid per the spec. Catching this requires understanding the combined semantic effect of allOf plus additionalProperties: false against real data, which is a data-validation concern, not a document-linting one. That's also why this post recommends testing against example payloads rather than relying on a linter to catch it.

Does OpenAPI 3.0 have any equivalent to unevaluatedProperties?

No. unevaluatedProperties was introduced in JSON Schema draft 2019-09; OpenAPI 3.0's Schema Object is based on an older draft that doesn't include it. If you need composition-aware strict validation and are stuck on 3.0, flattening the schema (repeating inherited fields directly) is the only reliable option.

Will my SDK generator warn me about this?

Not reliably. Most generators use allOf for inheritance-style code generation and largely ignore additionalProperties: false for typed output, since strict extra-field rejection is a runtime concern, not a static-typing one. The failure shows up wherever your actual request/response validation happens — often a separate middleware layer, not the generated SDK.

Is this the same root cause as the exclusiveMinimum/nullable version-boundary bugs?

No — those are about a keyword changing meaning between OpenAPI 3.0 and 3.1. This is a JSON Schema composition behavior identical in both versions; it's a standing pitfall of mixing allOf with additionalProperties: false, not a version-migration bug.