Neither is better on its own. A code-first description, generated from your framework, tracks the routes that exist but only records what annotations say. A spec-first file states intent but can drift from the code. For scanning, use whichever you can keep accurate, and compare the two in CI.
Definitions
Code-first means the description is generated from code at runtime or build time. FastAPI does this by default. Spring generates one through springdoc and NestJS through @nestjs/swagger and decorators, as covered in which frameworks serve docs by default.
Spec-first means a person writes and reviews the OpenAPI file, and code is built or checked against it. Rails API mode, which generates no description in the base framework, often lands here by necessity.
Comparison
| Aspect | Code-first | Spec-first |
|---|---|---|
| Matches deployed routes | Usually yes | Only if kept in sync |
| Captures security intent | Only what annotations or dependencies declare | Whatever the author wrote |
| Review in pull requests | Output changes are noisy | Small readable diffs |
| Risk of drift | Low for routes, high for descriptions of behavior | High |
| Scanner coverage | Good for existing routes | Good if complete, but may list routes that do not exist |
| Runs without exposing docs in production | Needs an export step | Already a file |
What a scanner needs from either
The scanner needs routes, parameters, request bodies and security entries. The OpenAPI Specification says servers defaults to a single entry with a url of a slash if the field is absent or empty, and that an operation's security replaces the root one. Check both in whichever file you hand over. OWASP also advises generating documentation automatically from open standards as part of the CI/CD pipeline, which favors code-first plus a build artifact.
A hybrid that works
- Generate the description from code in CI and save it as an artifact.
- Commit a reviewed copy or a baseline.
- Diff the two on every pull request.
- Fail the build on unexplained changes, such as a new route or a newly empty security entry. The empty security array post explains that last case.
- Feed the generated file, not the live endpoint, to scanners, so production docs can stay off.
Steps to choose
- Does your framework generate a description? If yes, start code-first.
- Do request bodies and responses carry types in code? If not, the output will be thin, so add annotations or models.
- Do you have a design review process for APIs? If yes, spec-first may fit and the diff is the review.
- Whatever you choose, set an owner and a check. Unowned files rot within a few releases.
- Run a scan and measure coverage: how many documented operations received a test request, and how many deployed routes are missing from the file.
More on drift: OpenAPI spec drift, how to detect and fix it and writing an OpenAPI spec that helps security testing.
Try it yourself
Click through it at your own pace, or open it full screen. Notice how coverage depends on what the scan was given.
Safeguard's SBOM and dependency scanning is a companion step, and a free scan is available.
Quick check
Can a generated description be wrong?
Yes. It reflects code annotations, so missing metadata produces missing or empty entries.
Should scanners read the live docs endpoint?
They can, but exporting the file in CI lets you keep production docs disabled.
Sources
- OpenAPI Specification
- OWASP API9:2023 Improper Inventory Management
- Using Rails for API-only applications
Last reviewed: 19 October 2026.