One Bookstore API · one evolving contract

OpenAPI,
one promise at a time

Start with one GET. Add inputs, payloads, failures, reuse, and security only when the HTTP promise needs them. By the end, the same small example is ready for teams and tools—not just a documentation page.

The durable idea: OpenAPI describes the messages an HTTP API may receive and send. It is a shared contract for people and tools; the actual requests and responses still travel at runtime.

One progressive example13 chaptersOpenAPI 3.1 syntaxNo tooling required

Chapter 1 · Mental model

A map of HTTP promises

The server implementation and the OpenAPI description are two representations of the same public behavior. Neither replaces the other.

REQUESTruntime

GET /books/bk_123

Accept: application/json

RESPONSEruntime

200 OK

{ "id": "bk_123", "title": "A Wizard of Earthsea" }

Keep the layers separate: OpenAPI is usually read at design/build time. JSON, headers, and status codes cross the network at runtime.

Chapter 2 · Document anatomy

Read the map from outside in

A description starts with identity and location, then moves through endpoints to operations, messages, and reusable pieces.

openapi ≠ info.version

openapi: 3.1.0 selects specification behavior. info.version: 1.0.0 versions this API description.

YAML is only the notation

The same description can be JSON. Request and response bodies may use JSON, XML, images, files, or other media types.

A common trap

An endpoint is a path such as /books/{bookId}. An operation is a method on it, such as GET. One path can hold several operations.

Chapter 3 · First operation

Describe the smallest useful call

Our first contract says where the Bookstore API lives, names one operation, and promises one successful response.

Now: identity + GET + path IDNext: optional inputsThen: bodiesLater: reuse + security
bookstore.openapi.yaml · stage 1
openapi: 3.1.0
info:
  title: Bookstore API
  version: 1.0.0
servers:
  - url: https://api.bookstore.test
paths:
  /books/{bookId}:
    get:
      operationId: getBook
      summary: Get one book
      parameters:
        - name: bookId
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Book found

Chapter 4 · Inputs

Put each input where HTTP puts it

The first call needed a required path value. Now add an optional query value—and keep both distinct from a request body.

Identity + GET + path IDNow: optional queryNext: request bodyLater: response branches
getBook · new excerpt
# under /books/{bookId} → get
parameters:
  - name: bookId # already present
    in: path
    required: true
    schema: { type: string }
  - name: include
    in: query
    required: false
    schema:
      type: array
      items: { enum: [reviews, availability] }
    style: form
    explode: false

Chapter 5 · Request bodies

Add a write operation, not a bigger parameter

Creating a book sends a structured representation. requestBody describes its media type and schema.

Identity + GETPath + queryNow: POST bodyNext: all outcomes
/books → post · stage 3
/books:
  post:
    operationId: createBook
    requestBody:
      required: true
      content:
        application/json:
          schema:
            type: object
            required: [title, author]
            properties:
              title: { type: string, minLength: 1 }
              author: { type: string, minLength: 1 }
    responses:
      '201':
        description: Book created
Parameter

Small request metadata

Path IDs, filters, pagination cursors, locale headers.

?limit=20&cursor=abc

Request body

Structured representation

An object, document, upload, or other payload with a media type.

{ "title": "The Left Hand of Darkness" }

Schema

Shape and constraints

Types, required fields, ranges, formats, composition, and more.

type: string · minLength: 1

Chapter 6 · Response branches

Model the fork, not just the happy path

An operation produces one branch per call. Document each status, its headers, media type, and body independently.

GET + POSTInputs + bodyNow: 201 / 400 / 500Next: reuse shapes
createBook → responses · focused excerpt
responses:
  '201':
    description: Book created
    headers:
      Location: { schema: { type: string, format: uri-reference } }
    content:
      application/json:
        schema: { type: object } # Book shape for now
  '400':
    description: Invalid request
    content:
      application/problem+json:
        schema: { type: object } # Problem shape
  '500':
    description: Unexpected failure
201

Creation succeeded

The body is a Book. Location points at the new resource.

Runtime message

HTTP/1.1 201 Created
Location: /books/bk_124
Content-Type: application/json

{ "id": "bk_124", "title": "The Dispossessed" }
These are alternatives, not a sequence. OpenAPI tells the server what it may produce and the client what it must be prepared to handle.

Chapter 7 · Reuse with $ref

Name repeated meaning once

Inline shapes teach the first call clearly. Once Book and Problem repeat, move them to components and point to them.

OperationsAll message branchesNow: components + $refNext: security
bookstore.openapi.yaml · reusable shapes
components:
  schemas:
    Book:
      type: object
      required: [id, title, author]
      properties:
        id: { type: string }
        title: { type: string }
        author: { type: string }
    Problem:
      type: object
      required: [type, title, status]

# A use site becomes small:
schema:
  $ref: '#/components/schemas/Book'

Local component

Best default. It evolves with one API and resolves without a network.

#/components/schemas/Book

External file

Useful for a larger service description split across owned files.

./models.yaml#/Book

Published package

Use for deliberately shared semantics. Pin an immutable release.

…/common/2.3.0.yaml#/Problem

Chapter 8 · Security

Name the mechanism, then require it

A security scheme defines how credentials travel. A security requirement says which operations need that scheme.

Paths + messagesComponents + refsNow: API key requirementUsable contract
security · final contract layer
components:
  securitySchemes:
    BookstoreKey:
      type: apiKey
      in: header
      name: X-API-Key

security:
  - BookstoreKey: []

# Optional override on a public operation:
get:
  security: []
Read security arrays carefully: separate objects are alternatives (OR); multiple scheme names inside one object must all be satisfied (AND).

Chapter 9 · Ownership + scale

Centralise discovery, not every edit

As servers multiply, operation ownership should remain legible. A registry can centralise validation and distribution without becoming one giant hand-edited source file.

The registry is a hub, not necessarily the edit location. Publish generated bundles for consumers; keep team-owned sources authoritative.
One server

One local description

Keep components local. One team releases paths and schemas together.

Many servers

One description per service

Paths follow runtime ownership. Share only stable wire vocabulary.

Organisation

One discovery surface

Index immutable releases and fan them out to tools. Do not erase local ownership.

Chapter 10 · Contract workflow

Treat the description like product code

A correct file can still drift from production. The reliable loop connects design, implementation, compatibility, and publication.

Design the HTTP promise

Review names, messages, and failure semantics before implementation hardens them.

Validate structure

Lint the description and resolve every local or external reference.

Check compatibility

Diff against the last published release; classify removals and tighter constraints.

Test the provider

Exercise the running server against examples, schemas, statuses, and headers.

Publish immutably

Record exact inputs, versions, and digests. Never silently replace a release.

Generate downstream

Build docs, SDKs, mocks, and gateway configuration from the published artifact.

Design-first and code-first can both work. The non-negotiable property is convergence: reviewed contract and observed server behavior must agree.

Chapter 11 · Design checklist

Walk the promise in order

If an early answer is fuzzy, later schema detail will only make the ambiguity more expensive.

What capability is promised?

Name one clear operation with a stable operationId.

Where is it called?

Resolve server URL + path template + HTTP method.

What enters the request?

Place each value in path, query, header, cookie, or body.

How is each value encoded?

State media types and parameter serialization where defaults are insufficient.

What can come back?

Model success, expected client errors, and server failures separately.

What is truly reusable?

Name common semantics in components; keep coincidental similarity local.

Who may call it?

Define the security mechanism and apply the correct requirement.

How will change stay safe?

Assign ownership, diff releases, test conformance, and publish immutably.

Chapter 12 · Concept atlas

Reach for these when pressure appears

The core example covers most daily work. These adjacent features solve specific problems; open them when the use case demands one.

Examples vs schemas

A schema constrains a family of valid values. An example is one concrete value for docs, tests, or mocks. An example does not add validation rules.

Callbacks vs webhooks

A callback is an out-of-band request tied to a parent operation and runtime expression. Top-level webhooks describe incoming requests initiated independently of another API call.

Links

A Link Object describes how values from one response can become inputs to another operation. It expresses navigability; it does not execute the next request.

Discriminators and polymorphism

JSON Schema composition keywords such as oneOf express alternatives. A discriminator can help select a schema using a payload field, but it should clarify—not substitute for—valid schemas.

Pagination

Model cursor or page inputs as query parameters and the page envelope as a response schema. Document ordering stability and cursor lifetime in prose; shapes alone cannot express them.

Deprecation and versioning

deprecated: true warns consumers away from an operation. It does not remove it or define a retirement date. Pair it with migration guidance and a compatibility policy.

Vendor extensions

Fields beginning with x- carry tool- or organisation-specific metadata. Use them deliberately and document which consumer owns their meaning.

Bundling and dereferencing

Bundling collects referenced resources into one distributable document while retaining references. Dereferencing replaces references with targets and can become large or encounter cycles. Choose for the consumer.

Chapter 13 · Sources

Primary references

Examples use widely supported OpenAPI 3.1 syntax. OpenAPI 3.2.0 is the latest published specification as of this artifact’s August 2026 revision.