Developer Tools

Generating API Docs That Don't Go Stale: Comparing Scalar, Swagger UI, and Postman's Built-In Docs

Three ways to generate API documentation from an OpenAPI spec, tested on the same endpoint, and the specific reason our docs kept drifting from the real API before we fixed the actual root cause.

By Aissam Ait Ahmed Developer Tools 0 comments

Our API documentation drifted out of sync with the real API three separate times before anyone addressed the actual cause, and the actual cause wasn't "we forgot to update the docs" — it was that the docs lived as a hand-maintained document entirely separate from the code, with no mechanism forcing the two to agree. This post covers three tools for generating docs directly from an OpenAPI spec instead of writing them by hand, tested on the same real endpoint, plus the process fix that mattered more than any single tool choice.

The endpoint used for comparison

A single real endpoint — creating a booking, POST /api/bookings — with required fields, one enum, and a documented error response, described in an OpenAPI 3.1 YAML spec:

paths:
  /api/bookings:
    post:
      summary: Create a booking
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [customer_id, slot, status]
              properties:
                customer_id: { type: integer }
                slot: { type: string, format: date-time }
                status:
                  type: string
                  enum: [pending, confirmed, cancelled]
      responses:
        '201': { description: Booking created }
        '422': { description: Validation failed }

All three tools below render this exact same spec — the comparison is entirely about the rendering and interaction layer, not about writing the spec differently for each one.

Swagger UI: the long-standing default, and it shows

Swagger UI is the tool most people picture when they hear "API docs from OpenAPI," and rendering the booking endpoint spec through it produces a familiar, functional page: expandable endpoint, a schema viewer, and a "Try it out" button that fires a real request from the browser. It works, and it's free, self-hostable, and has the widest tooling ecosystem of the three by a wide margin — most OpenAPI-adjacent tools assume Swagger UI compatibility as a baseline.

The rough edges show up in the details: the generated request-body example for the status enum defaults to the first listed value without visually distinguishing it as a placeholder versus a meaningful default, which confused more than one teammate into assuming "pending" was the required default status rather than just alphabetically or declaration-order first. The visual density also gets genuinely cluttered on a spec with many endpoints, without paid customization.

Scalar: the newer option, noticeably better first impression

Scalar renders the same spec with a cleaner three-panel layout — endpoint list, request/response detail, and a live "try it" panel that generates copy-pasteable code snippets in several languages (curl, JavaScript fetch, PHP) automatically from the same spec, which Swagger UI doesn't do without an additional plugin. For a booking creation request, seeing a ready-to-paste PHP Http::post() snippet next to the raw curl version measurably reduced "how do I actually call this" questions from developers integrating against the API for the first time.

# Both tools work from the same source, this is the config difference:
# Swagger UI needs its own HTML wrapper referencing the spec file
# Scalar's CLI can serve directly from a spec with one command:
npx @scalar/cli document serve openapi.yaml

Scalar is younger than Swagger UI, which cuts both ways — the plugin and integration ecosystem is smaller, and a few very specific OpenAPI features (certain complex oneOf/discriminator schema patterns) rendered slightly less clearly than in Swagger UI during testing, though this gap has been closing steadily as the project matures.

Postman's built-in docs: the least separate step, with a real trade-off

Since the team already maintains a Postman collection for manual API testing (the workflow covered in a real workflow with Postman), Postman can publish documentation generated directly from that same collection, including the example values and test scripts already written for testing purposes. This is the version requiring the least new tooling and the fewest new artifacts to keep in sync, since the collection used for actual manual testing and the published docs are now the same underlying source.

The trade-off: Postman-generated docs are tied to the collection's structure and examples, not to the OpenAPI spec directly, so if your team treats the OpenAPI spec (rather than the Postman collection) as the source of truth for client code generation or contract testing, you now have two documents that both need to describe the same API and could drift from each other independently. For a team that's fully collection-first rather than spec-first, this isn't a real cost. For a team split between the two, it reintroduces exactly the two-sources-of-truth problem this whole comparison exists to avoid.

Search: the feature that matters more than it gets credit for

On a spec with a handful of endpoints, search barely matters. On our real API — north of 60 endpoints across a dozen resource groups — how well a tool's search actually works became the thing developers complained about or praised unprompted, more than layout or styling. Swagger UI's default search is a simple client-side filter against endpoint names and tags, which works fine but misses matches inside request/response schema field names — searching "cancellation_reason" (a field, not an endpoint name) returns nothing. Scalar's search indexes schema field names as well as endpoint paths, so the same query correctly surfaces every endpoint whose request or response body includes that field, which is a genuinely more useful result when you're trying to figure out which endpoints touch a specific piece of data you already know the name of. Postman's docs search inherits whatever the underlying collection's naming and folder structure supports, which circles back to the same "your docs are only as good as the source they're generated from" point that shaped the CI fix below.

Authentication docs: showing a real request beats describing one

All three tools support documenting an auth scheme (API key, bearer token) from the OpenAPI spec's securitySchemes section, but they differ in what a reader can actually do with that documentation. Swagger UI's "Authorize" button lets a reader plug in a real API key once and have it applied to every subsequent "Try it out" request on the page — useful, and slightly hidden behind a modal that first-time users sometimes miss entirely. Scalar surfaces the same capability more visibly, with an inline auth field directly in the request panel rather than a separate modal, which measurably reduced "how do I actually authenticate against this" questions during onboarding for new API consumers. Postman's docs, since they're generated from a collection that already has environment variables wired up for auth in daily use internally, make this closer to a non-issue for the internal audience but require a reader to understand Postman's own environment-variable concept first if they've never used the tool before — a real cost for a completely external, first-time integrator who doesn't already know Postman and has never opened the desktop app before landing on a docs page, let alone set up an environment and understand variable substitution inside it just to make one single authenticated test call against an API they have genuinely never worked with before today.

The actual fix: generating docs in CI, not by remembering to

All three tools solve "docs can be generated from a spec instead of written by hand," but that alone doesn't solve staleness — a spec that's manually updated has exactly the same drift risk as hand-written docs, just one layer removed. The change that actually stopped our docs from going stale was adding a CI check that fails the build if the checked-in OpenAPI spec doesn't match the actual route definitions, generated automatically from route annotations at build time:

# .github/workflows/docs-check.yml (relevant step)
- name: Verify OpenAPI spec matches current routes
  run: |
    php artisan openapi:generate --output=/tmp/generated-spec.yaml
    diff openapi.yaml /tmp/generated-spec.yaml || (
      echo "openapi.yaml is out of date — regenerate and commit it."
      exit 1
    )

This is the actual root-cause fix, and it's tool-agnostic — it would have caught every one of our three previous drift incidents regardless of which of the three rendering tools above was displaying the (already-stale) spec. Rendering tools solve presentation. Only a check that fails a build catches the underlying problem of a spec and the real API disagreeing in the first place.

Which one we actually picked, and why

  • Scalar for the public-facing developer docs, mainly for the automatic multi-language code snippets — the single feature that reduced the most support questions from external integrators.
  • Postman's built-in docs stayed as an internal reference specifically because the team was already collection-first for manual testing, and adding a second spec-first tool internally would have reintroduced the two-sources problem for no real benefit to an internal audience.
  • The CI spec-check regardless of which rendering tool is downstream of it, because that's the piece that actually prevents the recurring problem this whole comparison started from.

If you're sharing a hosted docs link with a mobile-first audience or at a conference booth, a QR code scanner pointed at a generated QR code for the docs URL gets people to the right page faster than reading a URL off a slide — a small thing, but it's the kind of detail that's easy to forget until you're standing in front of an audience holding a slide with a URL nobody wants to type.

None of these three tools would have prevented the original drift on their own, which is the point worth remembering above the specific feature comparisons: pick whichever rendering tool your audience and workflow actually favor, and treat that choice as separate from the CI check that keeps the underlying spec honest. Conflating "which tool renders our docs" with "why did our docs go stale" is exactly how a team ends up migrating rendering tools twice without ever fixing the actual problem, spending real engineering time on a presentation-layer change while the root cause quietly waits to cause a third incident.

Comments

Join the conversation on this article.

Comments are rendered server-side so the discussion stays visible to readers without relying on a separate widget or client-side app.

No comments yet.

Be the first visitor to add a thoughtful comment on this article.

Leave a comment

Share a useful thought, question, or response.

Be constructive, stay on topic, and avoid posting personal or sensitive information.

Back to Blog More in Developer Tools Free Resources Explore Tools