← Back to the section

When an API has no description, every developer writes requests by guesswork, client libraries are generated with cryptic names, and the documentation goes stale on day one. OpenAPI solves this: it describes the whole API in a single file that both humans and tools can read.

What OpenAPI is and why you need it

APIs used to be documented in Word files, Confluence pages, or purely "by word of mouth". The problem is obvious: the documentation lives separately from the code, goes stale, and lies.

OpenAPI is a standard for a machine-readable description of a REST API in YAML or JSON format. A single file describes every endpoint: URL, methods, parameters, request and response bodies, error codes.

What this gives you in practice:

  • Swagger UI / Redoc — interactive documentation is generated automatically.
  • Client SDK — openapi-generator produces a typed client in Java, TypeScript, Python, and other languages straight from the specification.
  • Postman — request collections are imported from the file instead of being written by hand.
  • Contract testing — tests verify that the implementation matches the specification.

A well-crafted OpenAPI file is not just documentation, it is the infrastructure around your API.

operationId: the operation name

Every endpoint in OpenAPI gets an operationId field. This is a unique operation name — it is exactly what SDK generators use as the method name.

Without an operationId, the generator invents a name itself. The result usually looks like postOrdersOrderIdConfirm — awkward to read and even worse to use.

Rule: unique, camelCase, format action + resource.

/api/v1/orders:
  get:
    operationId: getOrders       # list
  post:
    operationId: createOrder     # creation

/api/v1/orders/{orderId}:
  get:
    operationId: getOrder        # single item
  put:
    operationId: updateOrder     # full replacement
  patch:
    operationId: patchOrder      # partial update
  delete:
    operationId: deleteOrder     # deletion

/api/v1/orders/{orderId}/confirm:
  post:
    operationId: confirmOrder    # action on a resource

This way the client code reads clearly: orderService.confirmOrder(orderId), rather than some cryptic auto-generated variant.

Naming convention:

  • get{Resource} — a single object (getOrder)
  • get{Resources} — a list (getOrders)
  • create{Resource} — creation via POST
  • update{Resource} — full replacement via PUT
  • patch{Resource} — partial update via PATCH
  • delete{Resource} — deletion
  • {verb}{Resource} — business action (confirmOrder, cancelOrder)
  • search{Resources} — search via POST with a body

tags: grouping endpoints

Without grouping, Swagger UI shows a flat list of a hundred endpoints — finding anything is impossible. The tags field solves this: endpoints are grouped into sections by resource.

Rule: one tag per resource, name in the plural, capitalized.

tags:
  - name: Orders
    description: 'Order management'
  - name: Users
    description: 'User management'
  - name: Payments
    description: 'Payments'

/api/v1/orders:
  get:
    tags: [Orders]
  post:
    tags: [Orders]

/api/v1/orders/{orderId}/confirm:
  post:
    tags: [Orders]    # the action belongs to the parent resource's tag

A common mistake is to create a separate tag for actions: OrderActions, OrderOperations. This is not needed. The endpoint POST /orders/{orderId}/confirm belongs to the Orders tag, not to an invented Confirmations.

Path parameters: unique names in the specification

There is a subtlety here that surprises newcomers. In code and when designing URLs, people usually write {id} — the context is already clear from the path. But in OpenAPI you cannot do that:

# This doesn't work in Swagger UI — identical names within one path
/api/v1/orders/{id}/items/{id}:
  get:
    parameters:
      - name: id   # which id? the first or the second?
        in: path

Swagger UI and Redoc cannot handle identical parameter names within a single path. That is why in an OpenAPI specification parameters are named uniquely:

/api/v1/orders/{orderId}/items/{itemId}:
  get:
    parameters:
      - name: orderId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: itemId
        in: path
        required: true
        schema:
          type: string
          format: uuid

This is a tooling requirement, not a semantic rule. In the API's own documentation and in code you can keep writing {id} — the context removes the ambiguity.

summary and description

summary is a short description of the endpoint, shown in Swagger UI next to the URL. The rule is simple: up to 80 characters, a phrase, not a sentence.

description is an extended explanation in Markdown format. Add it only when the endpoint's logic is non-obvious: special conditions, state transitions, constraints.

/api/v1/orders/{orderId}/confirm:
  post:
    operationId: confirmOrder
    tags: [Orders]
    summary: 'Confirm the order'
    description: |
      Moves the order from status CREATED to CONFIRMED.
      The order must contain at least one item.
      After confirmation the order's contents can no longer be changed.

An empty description is better than description: 'Confirm order' — a meaningless duplication of the summary.

Common mistakes in REST design

This is a breakdown of frequent problems encountered when designing and reviewing REST APIs.

Verb in the URL

# Wrong
GET  /api/v1/getOrders
POST /api/v1/cancelOrder
POST /api/v1/orders/doConfirm

# Right
GET  /api/v1/orders
POST /api/v1/orders/{id}/cancel
POST /api/v1/orders/{id}/confirm

In REST, the HTTP method already carries the meaning of the action. A verb in the URL is duplication and a violation of conventions. The only place where a verb in the URL is appropriate is business actions (/cancel, /confirm, /approve), and there it goes at the end of the path after the resource ID.

camelCase and snake_case in the path

URL paths are written in kebab-case (words separated by hyphens):

# Wrong
/api/v1/orderItems
/api/v1/order_items

# Right
/api/v1/order-items

Versioning

The version always goes in the path, and only in the path:

# Wrong
/api/orders?version=1
/api/orders?v=2
/2026/orders

# Right
/api/v1/orders
/api/v2/orders

Minor versions (v1.1, v1.2) are not needed — if a change does not break clients, the version does not change. If it does break them, that's v2.

Also: every path must start with /api/. This lets you separate the API from static content at the nginx or gateway level without conflicts.

ID in the request body instead of the path

// Wrong — PUT with the ID in the body
PUT /api/v1/orders
{ "id": "123", "status": "confirmed" }

// Right — ID in the path
PUT /api/v1/orders/123
{ "status": "confirmed" }

GET with a side effect

GET requests must be safe (idempotent): they can be repeated, cached, and logged without consequences. If a request changes something — use POST.

# Wrong
GET /api/v1/orders/123/cancel

# Right
POST /api/v1/orders/123/cancel

Query parameters: arrays and pagination

Arrays in query parameters are passed by repeating the parameter, not through a comma:

# Wrong
GET /api/v1/orders?status=NEW,CONFIRMED,DELIVERED

# Right
GET /api/v1/orders?status=NEW&status=CONFIRMED&status=DELIVERED

Zero-based pagination confuses API users: page=0 means "the first page", but looks like "the zeroth". In public contracts, use page=1 for the first page.

Responses: envelope and null

An envelope is a wrapper like { "success": true, "data": {...} }. It seems convenient, but it breaks standards: the HTTP code already tells you about success or failure, data adds an extra level of nesting, and clients have to unwrap the wrapper every time.

// Wrong — envelope
{ "success": true, "data": { "id": "123", "status": "CONFIRMED" } }

// Right — a flat resource
{ "id": "123", "status": "CONFIRMED" }

Fields with a null value are better left out of the response entirely — an absent field and null carry different meaning, and clients often cannot tell them apart. An empty string "" should not be used for "no value" either — only the absence of the field.

Errors

Errors must be returned in the RFC 9457 (Problem Details) format with content-type application/problem+json:

{
  "type": "urn:problem:orders:order-not-found",
  "title": "Not Found",
  "status": 404,
  "detail": "Order 123 not found"
}

Common problems:

  • type: "about:blank" — a meaningless placeholder type that carries no information
  • stack traces in a 500 response — leaking implementation details to the outside; the client needs a code and a general detail, not a stack trace
  • application/json instead of application/problem+json — violates the standard

Headers and deprecation

Custom headers should not start with X- — this prefix is officially deprecated. Use a domain prefix:

# Deprecated approach
X-Request-Id: abc123
X-Rate-Limit: 100

# Right
Shop-Request-Id: abc123
RateLimit-Limit: 100

If an endpoint is being marked as deprecated — add a Sunset header with the shutdown date. Without it, clients do not know when to stop using it.

When rate-limiting requests (429 Too Many Requests), return Retry-After — the client needs to know when it can retry the request.

In short

  • OpenAPI describes the whole API in a single file — documentation, SDKs, and tests are generated from it.
  • operationId — unique, camelCase, format action + resource (createOrder, confirmOrder).
  • tags — one tag per resource, plural and capitalized (Orders, Users); actions belong to the parent resource's tag.
  • In OpenAPI, path parameters are named uniquely ({orderId}, {itemId}) — a Swagger/Redoc requirement.
  • summary — up to 80 characters; description — only if the logic is non-obvious.
  • A verb in the URL for CRUD is a mistake; business actions go at the end of the path (/confirm, /cancel).
  • Paths in kebab-case, version in the path (/api/v1/), arrays by repeating the parameter.
  • An envelope (success/data) is not needed — use a flat resource and HTTP codes.
  • Errors are returned in the application/problem+json format without stack traces.
  • Custom headers without X-; deprecated endpoints — with a Sunset header.
  • URLs and resources in REST — rules for naming paths, nesting, resources.
  • HTTP methods and status codes — when to use which method and status code.
  • API versioning — v1/v2 strategies, backward compatibility.
  • Errors and Problem Details — RFC 9457 in practice.