# Contract-First with OpenAPI — API Design and Versioning

Source: https://www.geekswithgeeks.com/en/api-design/qg-openapi

> Describe the API in an OpenAPI document and lint and diff it automatically.

## One document, many uses

An **OpenAPI** document (YAML or JSON) describes paths, parameters, request and response schemas, errors and security in a machine-readable form. Designing it **first** (contract-first) lets teams review and mock the API before implementation, and the same file then drives **interactive docs**, **client SDK and server stub generation**, **request/response validation**, **contract tests** and **breaking-change checks** between versions. Reuse definitions with `$ref` under `components`, give every operation a unique `operationId`, and describe error responses too. Keep the spec in version control next to the code and fail the build if the implementation drifts from it.

## Keep the promise in code

A machine-readable contract, automated checks and shared standards keep many teams consistent.

![Three layers: spec, tests, standards.](assets/figures/api-design/section-7-map.svg) — Figure 7.1 — Spec, tests and standards.

## A small OpenAPI 3.1 document

Describes the v2 list and get endpoints with a reusable `Item` schema, a problem+json error and a rate-limit response. I parsed it with PyYAML in the next snippet.

```yaml
openapi: 3.1.0
info:
  title: Items API
  version: "2.0.0"
servers:
  - url: https://api.example.com
paths:
  /v2/items:
    get:
      operationId: listItems
      summary: List items (cursor pagination)
      parameters:
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
        - name: cursor
          in: query
          schema: { type: string }
      responses:
        "200":
          description: A page of items
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ItemPage" }
        "429":
          $ref: "#/components/responses/RateLimited"
  /v2/items/{id}:
    get:
      operationId: getItem
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: One item
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Item" }
        "404":
          description: Not found
          content:
            application/problem+json:
              schema: { $ref: "#/components/schemas/Problem" }
components:
  schemas:
    Item:
      type: object
      required: [id, first_name, last_name]
      properties:
        id: { type: integer }
        first_name: { type: string }
        last_name: { type: string }
        plan: { type: string, enum: [free, pro] }
    ItemPage:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items: { $ref: "#/components/schemas/Item" }
        next_cursor: { type: [string, "null"] }
    Problem:
      type: object
      required: [title, status]
      properties:
        type: { type: string }
        title: { type: string }
        status: { type: integer }
        detail: { type: string }
  responses:
    RateLimited:
      description: Too many requests
      headers:
        Retry-After: { schema: { type: integer } }

```

## Checking the spec structure, run

I ran this plain-Python example. Save the YAML above as `openapi.yaml`; this script loads it, lists the operations, checks `operationId` uniqueness and confirms all 5 `$ref` pointers resolve inside the document. It is a teaching check; real projects use dedicated validators and linters.

```python
import yaml
spec = yaml.safe_load(open("openapi.yaml"))
ops, refs = [], []
def walk(o):
    if isinstance(o, dict):
        if "$ref" in o: refs.append(o["$ref"])
        for v in o.values(): walk(v)
    elif isinstance(o, list):
        for v in o: walk(v)
walk(spec)
for path, item in spec["paths"].items():
    for method, op in item.items(): ops.append((method.upper(), path, op["operationId"]))
def resolve(ref):
    node = spec
    for part in ref.lstrip("#/").split("/"): node = node[part]
    return node
missing = [r for r in refs if not _ok(r)] if False else []
for r in refs:
    try: resolve(r)
    except KeyError: missing.append(r)
print(spec["openapi"], len(spec["paths"]), "paths")
print(ops)
print("unique operationIds:", len({o[2] for o in ops}) == len(ops), "| refs:", len(refs), "| unresolved:", missing)

```

Output:

```
3.1.0 2 paths
[('GET', '/v2/items', 'listItems'), ('GET', '/v2/items/{id}', 'getItem')]
unique operationIds: True | refs: 5 | unresolved: []
```

**Quiz:** What is a benefit of designing the OpenAPI contract first?

- [x] Teams can review, mock and generate code before implementing
- [ ] It removes the need for any code
- [ ] It makes APIs slower
- [ ] It prevents all bugs

*Answer:* Teams can review, mock and generate code before implementing. A shared, reviewable contract aligns producers and consumers early.
