Quick answer: Splitting an OpenAPI document across files is the right move for large APIs, but every tool that consumes the spec — docs, mock servers, code generators, gateways — needs a bundled, resolvable document rather than a pile of relative references. Bundling is the build step that resolves every $ref into one portable file, and getting it wrong produces silent failures: broken references, duplicated inline schemas, or circular references that a tool cannot follow. Treat the bundle as a build artifact produced in CI, not as something you hand-edit.

Multi-file specs exist because one giant YAML becomes unmaintainable. Paths, components, and reusable schemas live in separate files and reference each other, which mirrors how the code is organized. The trade-off is that the entry document is no longer self-contained: a relative $ref only resolves when the file layout is preserved. A docs generator pointed at the entry may follow references locally, but a mock server deployed elsewhere or a generated client receiving a single file cannot. That gap is what the bundling step closes.

What a reference actually is

A $ref is a JSON Reference: a pointer to a node, expressed as a URI plus a JSON Pointer fragment. Local references point within the same document; external references point to another file and optionally a node inside it. The distinction matters for bundling strategy.

# paths/orders.yaml — external reference to a separate file
post:
  summary: Create an order
  requestBody:
    content:
      application/json:
        schema:
          $ref: "../components/schemas/Order.yaml#/Order"
  responses:
    "201":
      $ref: "../components/responses/OrderCreated.yaml#/OrderCreated"

During development those relative paths are convenient. At build time they must be resolved so the output document can move on its own without the original directory tree.

Bundling versus dereferencing

These are often conflated but produce different artifacts, and choosing correctly matters for output size and maintainability.

AspectBundlingDereferencing (inline)
External refsResolved into componentsInlined at every use
Local refsKept as pointersFully inlined
Repeated schemasDefined once, referencedDuplicated per use
Output sizeSmallerCan balloon
Circular refsPreserved, validCannot fully inline
Best forPortable single fileTools that need no pointers

Bundling is the default you want: it pulls external files into the components section and rewrites references to point internally, keeping each schema defined once. Dereferencing is reserved for a consumer that cannot follow pointers at all, because inlining a schema used in twenty places creates twenty copies and breaks circular structures.

Failure modes to watch

  • Unresolved references. A typo or a moved file leaves a dangling pointer; some tools warn while others render an empty schema. Fail the build on any unresolved ref.
  • Reference collisions. Two external files define components with the same name, and bundling must rename or namespace them rather than silently overwrite.
  • Circular references. Self-referential models are valid OpenAPI but only survive if the bundle keeps pointers; a naive inliner hangs or errors.
  • Path and URL semantics. Mixing relative file paths with remote URLs needs consistent resolution against a base URI.

Run it as a deterministic build

Keep the source files in git and produce the bundle in CI from the entry document, so the committed multi-file spec remains the source of truth while every downstream consumer receives a validated single file. The same build can lint the result and verify all references resolve, then feed the bundle into docs generation, mocks, and code generation so they all consume identical input. A CLI-based OpenAPI workflow makes this reproducible without a cloud platform owning the files: the bundle is generated, checked, and published on every change rather than maintained by hand.

Frequently asked questions

Can I just commit the bundled file and edit it directly?

That makes the bundle a second source of truth that drifts from the multi-file originals. Keep hand edits in the source files and treat the bundle as generated output in CI; if a downstream tool needs a single file, produce it rather than maintaining one manually.

How do I handle circular references?

Use a bundler that preserves them as pointers rather than attempting full inline dereferencing. Circular models are legitimate in OpenAPI; the requirement is that the output keeps the reference loop intact and valid.

What's the difference between a broken ref and a lint warning?

A broken reference points to a node that does not exist and should fail the build, because the resulting schema is wrong. Treat unresolved references as hard errors rather than warnings so a moved file cannot reach downstream tools silently.

Should I bundle before or after linting?

Lint the source for style and run reference resolution as part of the build, then validate the bundle so both the authored files and the generated artifact are checked. Validating the bundle confirms every consumer receives a complete document.

This is part of the Powerduck vs Redocly comparison — start there for the full decision around OpenAPI tooling, linting, and CLI workflows.