Skip to main content

Why your OpenAPI spec passes linting and still fails your developers

· 6 min read
Faycal Alami-Hassani
Founder of Farowave

API documentation is only as good as it helps a developer reach their goal without consulting external resources, reverse-engineering silent errors, or opening a support ticket to decipher a cryptic response.

A spec that passes linting is officially done. The tooling says it's valid, the CI pipeline goes green, and the documentation ships alongside the release. What linting cannot measure is whether a developer facing the API for the first time can actually use it: whether the descriptions explain what the API does rather than merely naming it, whether the error responses tell the user why a request failed and how to fix it, and whether the edge cases that will inevitably be hit in production are documented before they become support tickets.

To illustrate the difference concretely, we audited the Frankfurter v1 OpenAPI specification — a real, publicly available API with a technically valid spec. The Frankfurter API provides endpoints to retrieve the latest currency rates, historical data for specific dates, time series over a given period, and currency metadata.

The original spec lints cleanly. What it doesn't do is tell a developer everything they need, as the following table shows.

What the audit found

GapOriginal specImproved spec
Error response descriptionsdescription: Resource not foundDescribes specific causes: invalid currency code, date before 1999-01-04, end date before start date
Silent date adjustmentNot documentedDocuments that a start date before 1999-01-04 is silently adjusted to 1999-01-04, and that the adjusted date appears in the response body
Today's rates instabilityNot mentionedExplicitly states that data returned for today may update if new rates are published during the day
Working days constraintNot mentionedAll time series endpoints document that results only include working days — weekends and bank holidays are excluded
Base currency defaultNo default declared on the base parameterdefault: EUR declared and visible in the rendered output
Response examplesNo examples on any schemaReal response examples on all schemas, sourced from live API calls

The details

Error response descriptions

A 404 with no context forces the developer to guess what went wrong. Naming the cause cuts debugging time and reduces support overhead.

responses:
NotFound:
description: Resource not found

Silent date adjustment

Without this, a developer whose requested date doesn't match the response date will assume a bug in their own code rather than an API behaviour. This is one of the more insidious gaps in developer-written specs — the API does something unexpected and says nothing about it.

/{start_date}..:
get:
description: Returns historical rates for every day within a
time period starting from the provided date until today.

Today's rates instability

A developer caching today's rates or comparing them across calls needs to know the data isn't stable until the daily update completes around 16:00 CET. The original spec gave no indication that today's response is a moving target.

/latest:
get:
description: Returns the last working day's rates.

Working days constraint

A developer expecting continuous date coverage will be confused by gaps in the time series response. Weekends and bank holidays produce no data — documenting this prevents incorrect assumptions about missing entries.

/{start_date}..{end_date}:
get:
description: Returns historical rates for every day within
a time period.

Base currency default

A developer who omits the base parameter needs to know what the API assumes. Without a declared default they have to test or read external documentation to find out. Declaring it in the spec makes it visible in every tool that renders the spec — Redocly, Swagger UI, Postman.

parameters:
base:
name: base
in: query
description: Base currency to convert from
required: false
schema:
$ref: "#/components/schemas/base"

Response examples

Examples let a developer verify their integration against a known-good response shape before making a real API call. They also make schema definitions immediately readable — a developer can scan the example and understand the data structure in seconds rather than parsing nested schema definitions.

singleDateRates:
type: object
properties:
amount:
type: number
base:
type: string
date:
type: string
rates:
type: object

What this means for your API documentation

Each of the gaps above passed linting. None of them were bugs in the spec — the original was structurally valid and rendered correctly. What it lacked was the layer of context that turns a technically correct document into one a developer can actually rely on.

That layer doesn't come from the API. It comes from someone whose job is to read the spec the way a developer would encounter it for the first time, verify its claims against the live API, and fill in what the implementation knows but the documentation doesn't say.

The full before and after specs are available here: