smaple.tr
API documentation

API Documentation Guide: OpenAPI, Postman, and Contract Testing [2026]

Mehmet Kurtipek
August 2, 2026
13 min read
API documentation
OpenAPI
Swagger
Postman
contract testing
API testing

Poor API documentation kills adoption faster than any technical deficiency. Developer teams spend an average of 30% of their integration time deciphering underdocumented APIs — time that converts directly into project delays, support burden, and abandoned integrations. The solution is not more documentation; it is structured, machine-readable documentation that stays synchronized with the code.

This guide covers the full API documentation and testing stack: OpenAPI specification design, Swagger UI setup, Postman collection management, automated testing with Jest and Supertest, contract testing with Pact, and CI/CD pipeline integration. By the end, you will have a complete documentation and quality assurance system that scales from a two-person team to an enterprise integration program.

API Documentation: Why Structure Matters

Documentation that lives in a shared document or Confluence page starts drifting from the API on day two. When a developer adds a new required field, renames an endpoint, or changes a response schema, the documentation update is the step that gets skipped under deadline pressure.

The core problem is coupling documentation to a human update process. OpenAPI solves this by treating the API specification as a machine-readable contract — one that tools can validate against, generate client SDKs from, and render as interactive documentation automatically.

The difference between documented and underdocumented APIs is measurable. Integration time drops from weeks to days. Support ticket volume falls. External developers adopt the API without needing a call with the engineering team. These are not soft benefits; they directly reduce the cost of each integration.

OpenAPI Specification: The Industry Standard

OpenAPI (formerly Swagger) is the dominant standard for API description. An OpenAPI spec defines every aspect of your API in YAML or JSON: endpoints, request parameters, response schemas, authentication mechanisms, and error codes. Any tool in the ecosystem — from Swagger UI to API gateways to code generators — can consume this specification.

OpenAPI 3.1 aligns fully with JSON Schema, enabling precise validation of complex nested data structures. The upgrade from 3.0 to 3.1 matters specifically for APIs that return polymorphic types or use advanced schema composition with oneOf, anyOf, and not.

Writing an OpenAPI Specification

A minimal but complete specification for a customer management API looks like this:

openapi: 3.1.0
info:
  title: Customer Management API
  version: 1.2.0
  contact:
    name: API Support
    email: api@yourcompany.com
servers:
  - url: https://api.yourcompany.com/v1
    description: Production
  - url: https://staging-api.yourcompany.com/v1
    description: Staging

paths:
  /customers:
    get:
      summary: List customers
      operationId: listCustomers
      security:
        - bearerAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Customer list with pagination
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CustomerList"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"

components:
  schemas:
    Customer:
      type: object
      required: [id, email, status]
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        name:
          type: string
        status:
          type: string
          enum: [active, inactive, suspended]
        createdAt:
          type: string
          format: date-time

This specification is the single source of truth. It drives Swagger UI, validates incoming requests at the API gateway level, and generates typed client SDKs in Node.js, Python, and Go.

API-First vs. Code-First Development

The choice between writing the spec first or generating it from code is architectural, not cosmetic.

Approach Advantages When to Use
API-First Frontend and backend teams develop in parallel; spec reviewed before any code is written; mock servers available on day one New APIs, cross-team projects, API products
Code-First Faster initial development; spec always reflects actual code Internal tools, prototypes, single-team projects
Hybrid Core spec written API-first; annotations keep it synchronized with code changes Most production systems

In production systems at Smart Maple — including a scheduling optimization platform that processes thousands of API requests daily — we use the hybrid approach: a versioned OpenAPI spec checked into the repository, with automated checks that fail the CI build when code annotations drift from the spec.

Automatic Documentation with NestJS

Modern frameworks eliminate the manual synchronization problem by generating OpenAPI specs from code annotations. NestJS with @nestjs/swagger is the most complete implementation for Node.js:

import { ApiOperation, ApiResponse, ApiTags, ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsOptional, IsEnum } from 'class-validator';

export enum CustomerStatus {
  ACTIVE = 'active',
  INACTIVE = 'inactive',
  SUSPENDED = 'suspended',
}

export class CreateCustomerDto {
  @ApiProperty({ description: 'Customer email address', example: 'user@example.com' })
  @IsEmail()
  email: string;

  @ApiProperty({ required: false })
  @IsOptional()
  name?: string;
}

@ApiTags('customers')
@Controller('customers')
export class CustomersController {
  @Post()
  @ApiOperation({ summary: 'Create a new customer' })
  @ApiResponse({ status: 201, description: 'Customer created', type: CustomerDto })
  @ApiResponse({ status: 400, description: 'Validation error' })
  @ApiResponse({ status: 409, description: 'Email already registered' })
  async create(@Body() dto: CreateCustomerDto): Promise<CustomerDto> {
    return this.customersService.create(dto);
  }
}

Every annotation becomes part of the live OpenAPI spec. When a developer changes a response type or adds a required field, the documentation updates automatically on the next deployment — no separate documentation step required.

Postman: Interactive API Testing

Postman collections organize every endpoint, authentication mechanism, and test case in a shareable format. A well-structured collection serves two purposes: it documents the API through working examples, and it provides a manual testing interface for developers who are integrating against your API.

Building a Production-Quality Collection

A complete Postman collection should include:

  • Environment variables separating development, staging, and production URLs
  • Authentication setup using pre-request scripts to fetch and store JWT tokens automatically
  • Test scripts on each request verifying status codes, response schemas, and business logic
  • Example responses for both success and error cases

The most valuable part of a mature Postman collection is not the manual test capability — it is the ability to run the entire collection in CI/CD using Newman:

newman run api-collection.json \
  --environment staging.json \
  --reporters cli,junit \
  --reporter-junit-export results/newman.xml

This turns the documentation collection into an automated integration test suite. Every deployment to staging runs the full collection and fails the pipeline if any request behaves unexpectedly.

Automated Testing: Jest and Supertest

For continuous integration, programmatic testing with Jest and Supertest provides more control than collection runners. Tests run against the actual application instance, validate edge cases, and produce structured output for CI dashboards.

Example: Comprehensive Endpoint Testing

import request from 'supertest';
import { app } from '../app';
import { createTestDatabase, seedCustomers, cleanDatabase } from '../test-helpers';

describe('POST /v1/customers', () => {
  beforeAll(() => createTestDatabase());
  afterEach(() => cleanDatabase());

  it('creates a customer with valid data and returns 201', async () => {
    const response = await request(app)
      .post('/v1/customers')
      .set('Authorization', `Bearer ${testToken}`)
      .send({ email: 'new@example.com', name: 'Test User' })
      .expect(201);

    expect(response.body).toMatchObject({
      id: expect.stringMatching(/^[0-9a-f-]{36}$/),
      email: 'new@example.com',
      status: 'active',
    });
  });

  it('returns 400 when email is missing', async () => {
    await request(app)
      .post('/v1/customers')
      .set('Authorization', `Bearer ${testToken}`)
      .send({ name: 'No Email' })
      .expect(400);
  });

  it('returns 409 when email is already registered', async () => {
    await seedCustomers([{ email: 'existing@example.com' }]);

    await request(app)
      .post('/v1/customers')
      .set('Authorization', `Bearer ${testToken}`)
      .send({ email: 'existing@example.com' })
      .expect(409);
  });

  it('returns 401 without authorization header', async () => {
    await request(app)
      .post('/v1/customers')
      .send({ email: 'test@example.com' })
      .expect(401);
  });
});

Each test case documents a specific API behavior while simultaneously verifying it. The test file becomes a machine-executable specification — more reliable than prose documentation because tests fail visibly when the behavior changes.

Contract Testing with Pact

In microservice architectures, multiple teams build services that depend on each other's APIs. When Team A's Customer API changes a response field, Team B's Billing Service breaks — often discovered in staging hours before a release. Contract testing prevents this class of failure.

How Pact Contract Testing Works

Pact operates on a producer/consumer model. The consumer team writes tests that define exactly what they need from the API — specific fields, status codes, response structures. These tests generate a "pact file" (a JSON contract). The producer team runs this contract against their API to verify they are fulfilling every consumer's requirements.

// Consumer side: Billing Service defines its contract with the Customer API
const { Pact } = require('@pact-foundation/pact');
const { like, term } = require('@pact-foundation/pact').Matchers;

const provider = new Pact({
  consumer: 'BillingService',
  provider: 'CustomerAPI',
  port: 8080,
  log: path.resolve(process.cwd(), 'logs', 'pact.log'),
  dir: path.resolve(process.cwd(), 'pacts'),
});

describe('Customer API contract from Billing Service', () => {
  before(() => provider.setup());
  after(() => provider.finalize());

  it('returns customer billing fields for a valid ID', async () => {
    await provider.addInteraction({
      state: 'customer 123 exists',
      uponReceiving: 'GET /customers/123',
      withRequest: {
        method: 'GET',
        path: '/customers/123',
        headers: { Authorization: like('Bearer token') },
      },
      willRespondWith: {
        status: 200,
        body: {
          id: like('123'),
          email: like('user@example.com'),
          billingPlan: term({ generate: 'professional', matcher: 'starter|professional|enterprise' }),
        },
      },
    });

    // Run the actual billing service code against this mock
    const result = await getCustomerBillingInfo('123');
    expect(result.billingPlan).toBeDefined();
  });
});

The pact file travels to the Customer API team's CI pipeline. Their tests run against it. If they rename billingPlan to subscription_tier without updating the contract, the pipeline fails before the change reaches production.

This workflow eliminates the "we broke the integration" conversation that happens in staging. Breaking changes surface in the team that introduces them, at the time they introduce them.

CI/CD Pipeline Integration

A complete API quality pipeline runs on every pull request. The sequence:

# .github/workflows/api-quality.yml
name: API Quality

on: [push, pull_request]

jobs:
  test-and-validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install dependencies
        run: npm ci

      - name: Validate OpenAPI spec
        run: npx @redocly/cli lint openapi.yaml

      - name: Run unit and integration tests
        run: npm test -- --coverage

      - name: Run contract tests (consumer)
        run: npm run test:pact:consumer

      - name: Publish pacts to Pact Broker
        run: npx pact-broker publish ./pacts --broker-base-url $PACT_BROKER_URL

      - name: Run Newman collection against staging
        run: |
          newman run api-collection.json \
            --environment staging.json \
            --reporter-junit-export results/newman.xml

      - name: Verify provider contracts
        run: npm run test:pact:provider

Each step catches a different failure mode:

  • Spec linting catches documentation drift before it ships
  • Integration tests catch logic errors in the implementation
  • Contract tests catch breaking changes before they reach dependent teams
  • Newman collection catches integration failures against a running environment

The total pipeline adds roughly four minutes to a build. The alternative — discovering integration failures in staging or production — costs significantly more.

API Changelog Management

Versioned APIs require a changelog that developers can trust. When your API releases version 1.3.0, integrators need to know what changed and what they need to update.

A well-structured changelog entry:

## [1.3.0] - 2026-04-01

### Added
- `GET /customers/{id}/activity` endpoint returning last 30 days of activity
- `billingPlan` field on Customer object (values: starter, professional, enterprise)
- Rate limiting headers: `X-RateLimit-Remaining`, `X-RateLimit-Reset`

### Changed
- `status` field now includes `suspended` as a valid value (previously undocumented)

### Deprecated
- `phone` field on Customer object — will be removed in 2.0.0
  - Migration: use the new `/customers/{id}/contacts` endpoint instead

### Fixed
- `created_at` timestamps now consistently return UTC (was server-local time in some regions)

The changelog should be part of the same repository as the spec. Tag releases in git. Send deprecation notices to API consumers with enough lead time to migrate — 90 days is the industry minimum for production APIs.

Developer Portal Design

The developer portal is the user interface for your API. Its quality directly predicts adoption rates.

Element Purpose Implementation
Interactive API Explorer Test endpoints without writing code Swagger UI, Redoc, or Stoplight Elements
Quick Start Guide First successful API call in under 5 minutes Curl examples with real credentials
Authentication Docs JWT, OAuth2, API Key flows Separate guide with diagrams
Error Reference All HTTP status codes and error payloads Searchable, linkable
Rate Limiting Docs Limits, headers, and retry guidance Include Retry-After header explanation
SDK Downloads Client libraries for major languages Generated from OpenAPI spec
Changelog Version history and migration guides Linked to git releases
Status Page Real-time uptime and incident history status.yourcompany.com

The most common mistake in developer portals is writing documentation from the API provider's perspective rather than the integrator's perspective. The integrator does not care about your internal architecture. They need to know: how do I authenticate, what do I call, what will I get back, and what happens when something goes wrong.

API Documentation Best Practices

Versioning Documentation

Keep documentation for each active API version separately accessible. When v1 is deprecated in favor of v2, the v1 docs should remain available with a clear deprecation notice and migration guide. Removing documentation for active API versions is the fastest way to generate support requests.

Consistency at Scale

Documentation inconsistency — user_name in one endpoint, userName in another — signals poor API design and erodes developer trust. An OpenAPI spec enforced in CI/CD prevents this class of drift. When the spec defines userId as the field name, every endpoint uses userId, because any deviation fails the automated validation step.

Testing Documentation Accuracy

The most reliable documentation is documentation that runs as tests. Every code example in your documentation should be executable. A dead code example — one that no longer works because the API changed — is worse than no example. It wastes developer time and erodes trust in all your other documentation.

Performance Information

Document rate limits, timeout behavior, and expected response times in the API documentation itself. Integrators need this information to design retry logic, set timeouts in their HTTP clients, and plan capacity. Undocumented rate limits are discovered in production, which is the worst time.

Conclusion

API documentation is not a one-time deliverable — it is a system. The combination of an OpenAPI specification checked into version control, automated generation from code annotations, Postman collections running in CI, programmatic tests covering edge cases, and contract tests verifying inter-service agreements creates a quality infrastructure that compounds over time.

Each layer catches a different failure mode. The spec catches structural drift. Integration tests catch logic errors. Contract tests catch breaking changes. The cumulative result is an API that external developers can adopt confidently, that internal teams can change safely, and that behaves predictably under the conditions that matter most: high traffic, partial failures, and the inevitable edge case that no one thought to document.

OpenAPI specifications, Swagger UI, Postman collections, Jest/Supertest integration tests, and Pact contract testing together form the foundation of enterprise-grade API quality. Teams that invest in this infrastructure early spend significantly less time on integration support and significantly more time shipping new capabilities.

Related Articles

August 11, 2026

MLOps Guide: Taking Machine Learning Models to Production [2026]

87% of machine learning models built by data science teams never reach production. The models work — they pass cross-validation, they score well on holdout sets, they demonstrate genuine predictive value. The problem is not the modeling. The problem is everything that happens between a notebook experiment and a reliable, monitored, production system. MLOps is the discipline that closes that gap. This guide covers the full MLOps stack: maturity levels, tooling choices (MLflow, DVC, Kubeflow

Read More: MLOps Guide: Taking Machine Learning Models to Production [2026]
August 10, 2026

LLM Fine-Tuning Guide: Custom Model Training with LoRA and QLoRA [2026]

General-purpose LLMs are impressive. They can write code, summarize documents, answer questions, and translate between languages with reasonable accuracy. But "reasonable" is not good enough when your application requires consistent output format, domain-specific terminology, a particular tone, or behavior that the base model was never trained to exhibit. That gap is where fine-tuning matters. Fine-tuning updates a model's weights on your specific data, changing how the model behaves — not

Read More: LLM Fine-Tuning Guide: Custom Model Training with LoRA and QLoRA [2026]
August 9, 2026

Computer Vision Applications: Object Detection, OCR, and Industrial AI [2026]

Computer vision has moved well past the research phase. The models are trained, the frameworks are mature, the hardware is accessible, and the use cases are generating measurable returns. What was a specialized capability requiring deep expertise in 2018 is now deployable infrastructure — if you know which component to reach for and where the real complexity lives. This guide covers computer vision applications across industrial, medical, logistics, and document processing domains. It expl

Read More: Computer Vision Applications: Object Detection, OCR, and Industrial AI [2026]