Who this is for
- Backend engineers who want to define or maintain HTTP APIs clearly and consistently.
- Developers moving from ad-hoc API docs to a standard like OpenAPI 3.x.
- Anyone collaborating across teams (backend, frontend, QA, DevRel) on API contracts.
Prerequisites
- Basics of HTTP: methods (GET/POST/PUT/PATCH/DELETE), status codes.
- Comfort with JSON and/or YAML.
- Familiarity with REST-style endpoints.
Why this matters
In real backend work, clear API documentation saves time and prevents bugs. With OpenAPI you can:
- Design endpoints before writing code (contract-first) or document existing ones (code-first).
- Auto-generate SDKs, server stubs, and test clients.
- Align backend, frontend, QA, and stakeholders with one reliable source of truth.
Concept explained simply
OpenAPI is a standardized way to describe your HTTP API in a single file (YAML or JSON). You list your endpoints, parameters, request bodies, responses, and reusable pieces. Tools can read that file to generate docs, clients, tests, and more.
Mental model
- Info and servers: who you are and where your API lives.
- Paths: each endpoint with its operations (get/post/etc).
- Components: your reusable toolbox (schemas, parameters, responses, security, etc.).
- Security: how clients authenticate (e.g., bearer tokens).
Core building blocks of an OpenAPI file
Minimal skeleton
openapi: 3.0.3
info:
title: Sample API
version: 1.0.0
servers:
- url: https://api.example.com/v1
paths:
/health:
get:
summary: Health check
responses:
'200':
description: OK
Adding a request body and response schemas
openapi: 3.0.3
info:
title: Users API
version: 1.0.0
paths:
/users:
post:
summary: Create user
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [email, password]
properties:
email:
type: string
format: email
password:
type: string
minLength: 8
responses:
'201':
description: Created
content:
application/json:
schema:
type: object
required: [id, email]
properties:
id:
type: integer
email:
type: string
format: email
Reusables with components and security
openapi: 3.0.3
info:
title: Profiles API
version: 1.0.0
components:
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
responses:
NotFound:
description: Resource not found
UnauthorizedError:
description: Access token is missing or invalid
schemas:
Profile:
type: object
required: [id, name]
properties:
id:
type: string
name:
type: string
paths:
/profiles/{id}:
get:
security:
- BearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Profile'
'401':
$ref: '#/components/responses/UnauthorizedError'
'404':
$ref: '#/components/responses/NotFound'
Worked examples
1) Simple GET health endpoint
openapi: 3.0.3
info:
title: Health API
version: 1.0.0
paths:
/health:
get:
summary: Health check
tags: [System]
responses:
'200':
description: Service is healthy
Why it works: No body needed; a 200 with a short description is enough.
2) GET with query params and typed response
openapi: 3.0.3
info:
title: Todo API
version: 1.0.0
paths:
/todos:
get:
summary: List todos
parameters:
- name: completed
in: query
description: Filter by completion status
required: false
schema:
type: boolean
responses:
'200':
description: List of todos
content:
application/json:
schema:
type: array
items:
type: object
required: [id, title, completed]
properties:
id: { type: integer }
title: { type: string }
completed: { type: boolean }
Why it works: Query parameter declared at operation level; response schema is an array of objects.
3) POST create with examples and error reuse
openapi: 3.0.3
info:
title: Orders API
version: 1.0.0
components:
responses:
BadRequest:
description: Invalid input
schemas:
Order:
type: object
required: [id, status]
properties:
id: { type: integer }
status: { type: string, enum: [processing, shipped, canceled] }
paths:
/orders:
post:
summary: Create order
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [items]
properties:
items:
type: array
items:
type: object
required: [sku, qty]
properties:
sku: { type: string }
qty: { type: integer, minimum: 1 }
examples:
valid:
value: { items: [ { sku: 'ABC-1', qty: 2 } ] }
responses:
'201':
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
'400':
$ref: '#/components/responses/BadRequest'
Why it works: Request example helps consumers; 400 response reuses a component for consistency.
Step-by-step: Write your first spec
- State the version and info: add
openapi: 3.0.3, title, and version. - List servers: your base URL(s), e.g., production and staging.
- Add one path with one operation. Start minimal: summary + a 200 response.
- Introduce requestBody or parameters as needed.
- Extract repeated shapes into
components.schemas. - Extract repeated responses or parameters into
components. - Add security schemes and reference them on operations that require auth.
- Validate the file with a linter and iterate on feedback from teammates.
Exercises
Work through these directly in your editor. Keep them small and valid. Then compare with the solutions below.
-
Exercise 1: Describe
GET /todoswith an optional boolean query parametercompletedand a 200 JSON array of todos (each todo hasidinteger,titlestring,completedboolean). -
Exercise 2: Describe
PATCH /orders/{id}with a required path parameterid(integer) and a JSON request body allowing the client to changestatusto one ofprocessing|shipped|canceled. Return 200 with the updated order and 404 when not found. Reuse aNotFoundresponse from components.
Checklist to self-check
- Did you put
completedunderparameterswithin: query? - Did you use
requestBodyfor JSON payloads (not query/headers)? - Are status codes written as strings like
'200'? - Are required fields explicitly listed under
required:arrays? - Did you reference components with
$refcorrectly (absolute path from#/components/...)?
Common mistakes and how to self-check
- Putting the body in parameters: In OpenAPI 3, use
requestBody. If you seein: body, it’s wrong (that was Swagger 2.0).
Fix: Move payload intorequestBody -> content -> application/json. - Missing required flags: Required path params must have
required: true.
Self-check: All{param}in paths appear as parameters within: pathandrequired: true. - Un-typed schemas: Forgetting
type:leads to ambiguous clients.
Self-check: Every schema has explicittypeand property types. - Status codes as numbers: Keys must be strings.
Self-check: Quotes around status codes like'201'. - Duplicated shapes: Copy-paste schemas across operations.
Fix: Extract tocomponents.schemasand$ref.
Practical projects
- Project A: Describe a minimal Blog API with posts and comments.
Include: GET/POST for posts, GET for comments, reusablePostandCommentschemas, pagination query params. - Project B: Secure endpoints with bearer auth.
Include:components.securitySchemes.BearerAuth, mark selected operations withsecurity, and add a sharedUnauthorizedresponse. - Project C: Error model.
Include: a reusableErrorschema andBadRequest/NotFoundresponses referencing it.
Learning path
- Start: OpenAPI file structure, paths, request/response basics.
- Next: Components and reusability (schemas, parameters, responses, security).
- Then: Examples, enums, validation keywords (minLength, format, etc.).
- Finally: API versioning strategy, pagination patterns, and mock testing based on the spec.
Mini challenge
Extend a GET /users endpoint with pagination and errors using components. Requirements:
- Query parameters:
page(integer, min 1),pageSize(integer, 1–100). - 200 response returns an object with
items(array ofUser) andtotal(integer). - Use a reusable
Userschema. - Include a reusable
BadRequestresponse for invalid parameters.
Next steps
- Complete the exercises below and compare to the provided solutions.
- Take the Quick Test to check your understanding. Note: anyone can take the test; only logged-in users get saved progress.
- Refactor one of your team’s existing endpoints into a clean, reusable OpenAPI component set.