Why this matters
OpenAPI (formerly Swagger) is the industry standard for describing HTTP APIs in a machine- and human-readable way. As an API Engineer, you will:
- Define endpoints, parameters, request/response schemas, and errors before writing code.
- Generate client SDKs and server stubs to speed up delivery.
- Produce interactive docs that reduce support tickets and onboarding time.
- Automate contract tests and breaking-change detection in CI.
- Coordinate changes across backend, frontend, mobile, and partner teams.
Real-world tasks you’ll do with OpenAPI
- Design a new endpoint, review with stakeholders, then implement to match the contract.
- Negotiate versioning and deprecations while keeping docs up-to-date.
- Codify auth schemes (API keys, OAuth2, JWT bearer) and error formats consistently.
- Publish a single source of truth that powers documentation and testing.
Concept explained simply
OpenAPI is a structured description (usually YAML) of your API: what URLs exist, what they accept, what they return, and how they authenticate. Tools can read this file to generate docs, SDKs, tests, and mocks.
Mental model
- Contract first: The spec is the contract; code follows it.
- Paths are verbs; components are nouns. Paths describe operations; components store reusable data shapes and bits.
- Content-negotiation map: content maps media types to schemas (e.g., application/json).
Core building blocks
- openapi: version string, e.g., 3.0.3 or 3.1.0
- info: title, version, description
- servers: base URLs and variables
- paths: endpoints; each has operations (get, post, etc.)
- components: reusable schemas, parameters, responses, requestBodies, securitySchemes, headers, examples, links, callbacks
- security: global or per-operation auth requirements
- tags: group operations for readable docs
Typical flow: Define schema in components ➜ reference it in requestBody/responses ➜ bind auth with security.
Worked examples
Example 1: Minimal health endpoint
openapi: 3.0.3
info:
title: Demo API
version: 1.0.0
servers:
- url: https://api.example.com
paths:
/health:
get:
summary: Liveness check
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
status:
type: string
required: [status]
example: { status: ok }
Note: responses map status codes (as strings) to description and content.
Example 2: Path + query params with reusable schema
openapi: 3.0.3
info: { title: Users API, version: 1.0.0 }
servers: [ { url: https://api.example.com } ]
components:
schemas:
User:
type: object
properties:
id: { type: string, format: uuid }
email: { type: string, format: email }
createdAt: { type: string, format: date-time }
required: [id, email, createdAt]
paths:
/users/{id}:
get:
summary: Get user by ID
parameters:
- name: id
in: path
required: true
schema: { type: string, format: uuid }
- name: fields
in: query
required: false
description: Comma-separated sparse fields (e.g., email,createdAt)
schema: { type: string }
responses:
'200':
description: User found
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
description: Not found
Example 3: Request body + validation + errors
openapi: 3.0.3
info: { title: Todos API, version: 1.0.0 }
servers: [ { url: https://api.example.com' } ]
components:
schemas:
TodoCreate:
type: object
properties:
title: { type: string, minLength: 1 }
dueDate: { type: string, format: date, nullable: true }
required: [title]
Todo:
allOf:
- $ref: '#/components/schemas/TodoCreate'
- type: object
properties:
id: { type: string }
done: { type: boolean, default: false }
required: [id, done]
responses:
Error400:
description: Bad Request
content:
application/json:
schema:
type: object
properties:
code: { type: string }
message: { type: string }
required: [code, message]
paths:
/todos:
post:
summary: Create todo
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/TodoCreate' }
responses:
'201':
description: Created
content:
application/json:
schema: { $ref: '#/components/schemas/Todo' }
'400':
$ref: '#/components/responses/Error400'
Example 4: Security (Bearer JWT) + per-operation override
openapi: 3.0.3
info: { title: Secured API, version: 1.0.0 }
servers: [ { url: https://api.example.com } ]
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
paths:
/me:
get:
summary: Current user
security:
- bearerAuth: []
responses:
'200': { description: OK }
/health:
get:
summary: Public health
security: [] # no auth required for this operation
responses:
'200': { description: OK }
Who this is for
- API engineers and backend developers designing or maintaining HTTP APIs.
- Tech leads needing consistent, reviewable API contracts.
- Developer advocates improving partner integration experience.
Prerequisites
- Comfortable with HTTP methods, status codes, headers, and JSON.
- Basic YAML or JSON familiarity.
- Optional: Experience with API auth (API keys, OAuth2, JWT).
Learning path
- Read the examples above and replicate them in a local YAML file.
- Add components and references to remove duplication.
- Introduce security schemes and error models.
- Validate your spec with a linter and fix warnings.
- Use the spec to generate docs or mocks and iterate with feedback.
Exercises
Do these short, focused tasks. Validate with a linter if you have one, but you can also self-check with the checklist below. Solutions are provided in collapsible blocks.
Exercise 1 — Minimal GET with JSON body
Goal: Define a GET /status endpoint that returns 200 and a JSON body { "status": "ok" }.
- Use openapi 3.0.3
- Add info, servers
- Define /status GET with application/json schema
Exercise 2 — Create resource with requestBody and error
Goal: Add POST /todos to create a todo item with title (required) and optional dueDate. Return 201 with the created object (id, title, dueDate, done) and 400 for validation failures. Reuse schemas via components and define a bearer JWT security scheme (but allow POST /todos without auth for now).
- Use components.schemas TodoCreate and Todo
- Use components.responses Error400
- Keep security empty for this operation
Self-check checklist
- All status codes are strings ('200', '201', '400').
- Path parameters (if any) are marked required and appear in the path template.
- requestBody only used for body payloads; parameters used for query/header/path/cookie.
- content maps correct media types to schemas.
- Reusable parts live under components and are referenced via $ref.
- No extra siblings exist alongside a $ref node where not allowed.
Common mistakes and how to self-check
- Mixing parameters and requestBody: query/path/header/cookie go under parameters; body goes under requestBody.
- Forgetting required: true on path params: OAS requires it; also ensure the {name} is in the path.
- Status codes as numbers: must be strings like '200'.
- Mismatched schema vs example: run a validator; keep examples aligned with schema.
- Overusing inline schemas: move shared shapes to components.schemas to reduce drift.
- Forgetting content type: a response without content type won’t render examples as JSON.
Quick self-test mini task
Add a 422 response with a JSON error body to one operation. Then verify:
- Response key is '422'
- content.application/json.schema is defined
- Error example matches the schema
Practical projects
- Spec a simple CRUD: /items with GET list, POST create, GET/PUT/DELETE by id. Include pagination, filtering, and standard error responses.
- Add authentication: API key for read-only endpoints and bearer JWT for write operations.
- Versioning practice: Publish v1 and a beta v1.1 change behind a new field; deprecate one older query parameter with a description note.
Next steps
- Extend schemas with enums, oneOf/anyOf/allOf, and nullable fields.
- Add examples and examples objects to show real payloads.
- Introduce callbacks or webhooks if your API pushes events.
- Automate contract checks in CI to detect breaking changes early.
Quick Test
Take the quick test below to check your understanding. Anyone can take it for free; if you’re logged in, your progress is saved automatically.