20.07.2026 • 21 min read

API Documentation from OpenAPI | Building Spec-Driven Reference Sites Like bKash Developer

Cover Image

Introduction 🎯

Every API needs documentation. The question is how you build it — and keep it from rotting.

Hand-written docs look great on day one. By day ninety, the POST /users endpoint accepts a displayName field but the docs still say name. The response example shows a 201 with { "id": 1 } but the real API returns { "userId": "uuid-here" }. Someone on the team quips “the docs are aspirational” and everyone laughs nervously.

Spec-driven documentation solves this. You write one canonical OpenAPI specification (OAS) — a JSON/YAML file that describes every endpoint, parameter, response, and error code — and let a documentation tool render it as a beautiful reference site. The spec is the source of truth. When the API changes, you update the spec, and the docs update automatically.

In this article, we’ll study the bKash Developer Reference as our design target — its sidebar navigation, endpoint-per-page layout, parameter tables, sample requests with tabbed code examples, and response payloads — and build equivalent documentation from an OpenAPI spec using the best open-source tools available.

# A fragment of an OpenAPI spec — this IS the documentation source
paths:
  /tokenized/checkout/create:
    post:
      summary: Create a payment
      description: Initiates a tokenized checkout payment request.
      parameters:
        - name: X-App-Key
          in: header
          required: true
          schema: { type: string }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                mode:
                  type: string
                  example: "0001"
                amount:
                  type: string
                  example: "500"

No separate docs site. No copy-paste. No async drift. The spec is the documentation. 🚀


Part 1: Why Spec-Driven Documentation?

1.1 The Hand-Written Docs Trap

Most teams start with good intentions. They write markdown, host it on a wiki or a static site, and manually update it when the API changes. Here’s what happens next:

Pain PointHand-WrittenSpec-Driven
SyncDocs lag behind code by days/weeksSpec IS the source of truth
AccuracyCopy-paste errors accumulateAuto-generated from spec
Effort per endpointWrite docs separatelyAdd description to spec
Request/response examplesManually craftedGenerated from spec schemas
Schema driftUndetected until a customer complainsCI validates spec against implementation
Multi-language examplesWrite each one manuallyAuto-generated (curl, JS, Python, etc.)
VersioningSeparate doc copies per versionVersioned alongside the spec file

1.2 The bKash Reference Style as a Target

The bKash Developer Reference (powered by ReadMe) exemplifies the standard that API consumers expect:

┌──────────────────────────────────────────────┐
│  Navigation                  │  Endpoint      │
│  ┌──────────────────┐       │  Content        │
│  │ Quick Start      │       │                │
│  │ ▼ Tokenized      │       │  Description   │
│  │   ├ Grant Token  │       │  Request URL   │
│  │   ├ Create Pmt   │       │                │
│  │   ├ Execute Pmt  │       │  Headers Table │
│  │   └ Refund       │       │                │
│  │ ▼ Checkout URL   │       │  Params Table  │
│  │ ▼ Auth & Capture │       │                │
│  │ ▼ Refund         │       │  Sample Req    │
│  │ ▼ Webhooks       │       │  ┌─────┬─────┐ │
│  │   Error Codes    │       │  │JSON │cURL │ │
│  └──────────────────┘       │  └─────┴─────┘ │
│                              │                │
│                              │  Sample Resp   │
│                              │                │
│                              │  Error Codes   │
└──────────────────────────────┴────────────────┘

Key design elements we want to replicate:

  • Left sidebar with grouped, hierarchical navigation
  • Right content: description, URL, headers table, params table, request samples (tabbed), response samples, error codes
  • Parameter tables with Name, Type, Required, Description columns
  • Schema-aware — types and formats come from the spec, not manual markup
  • Responsive — readable on desktop and mobile

Part 2: The OpenAPI Specification — Your Documentation Source

Before generating docs, you need a well-structured OpenAPI spec. A complete spec has four top-level sections that map directly to documentation pages.

2.1 Anatomy of an OpenAPI Spec

openapi: 3.0.3
info:
  title: bKash Tokenized Checkout API
  description: API specification for bKash Tokenized Checkout integration.
  version: 1.2.0-beta

servers:
  - url: https://sandbox.bkash.com
    description: Sandbox environment
  - url: https://api.bkash.com
    description: Production environment

paths:
  /tokenized/checkout/token/grant:
    post:
      summary: Grant an access token
      description: Obtain an access token for API authentication.
      tags: [Authentication]
      parameters: [...]
      requestBody: { ... }
      responses:
        '200':
          description: Token granted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  id_token:
                    type: string
                    description: Access token for subsequent API calls.
                  expires_in:
                    type: integer
                    description: Token lifetime in seconds.

components:
  schemas:
    PaymentRequest:
      type: object
      properties:
        amount:
          type: string
          description: Payment amount
        currency:
          type: string
          enum: [BDT]
    ErrorResponse:
      type: object
      properties:
        errorCode:
          type: string
        errorMessage:
          type: string

Each section serves a documentation purpose:

Spec SectionDocs Page Element
infoPage title, description, version badge
serversBase URL selector / environment tabs
pathsOne page per endpoint group (by tag)
paths.*.summaryEndpoint title in sidebar
paths.*.parametersRequest parameters table
paths.*.requestBodyRequest body schema
paths.*.responsesResponse schemas + status codes
components.schemasReusable type definitions
tagsSidebar grouping + navigation order

2.2 Tag Grouping Drives Navigation Structure

The tags array and the tags on each path determine how your sidebar looks:

# Tag definitions (appear in sidebar in this order)
tags:
  - name: Authentication
    description: Token management endpoints
  - name: Payments
    description: Payment creation and execution
  - name: Refunds
    description: Refund operations

# Each path references a tag
paths:
  /tokenized/checkout/token/grant:
    post:
      tags: [Authentication]
      summary: Grant Token
  /tokenized/checkout/create:
    post:
      tags: [Payments]
      summary: Create Payment
  /tokenized/checkout/execute:
    post:
      tags: [Payments]
      summary: Execute Payment

This produces a sidebar exactly like bKash’s:

▼ Authentication
    Grant Token
    Refresh Token
▼ Payments
    Create Payment
    Execute Payment
▼ Refunds
    Refund Transaction
    Refund Status

Part 3: Tool Comparison — Choosing Your Docs Engine

Several open-source tools render OpenAPI specs into HTML. Here’s how they compare against the bKash reference style:

FeatureRedoc (Redocly)Swagger UIStoplight ElementsRapiDocScalar
Sidebar nav✅ Tree by tags⚠️ Flat list✅ Tree by tags✅ Tree by tags✅ Tree by tags
Parameter tables✅ Rich✅ Tables✅ Rich✅ Tables✅ Rich
Schema rendering✅ Deeply nested⚠️ Basic✅ Collapsible✅ Collapsible✅ Collapsible
Try-it console❌ (Redoc only)✅ Built-in✅ Built-in✅ Built-in✅ Built-in
Tabbed examples✅ Yes✅ Yes✅ Yes⚠️ Limited✅ Yes
Response samples✅ Auto from schema✅ Auto✅ Auto✅ Auto✅ Auto
Dark mode✅ Built-in⚠️ Themed✅ Built-in✅ Built-in✅ Built-in
Code sample gen⚠️ Basic (curl)✅ Multi-lang
Search⚠️ Plugin✅ Built-in✅ Built-in✅ Built-in
React component✅ Redoc/RedocStandalone✅ SwaggerUI✅ APIReference✅ RapiDoc (WC)✅ Scalar (WC)
CI/CD friendly✅ CLI + bundle✅ npm✅ npm✅ npm✅ npm
LicenseMITApache 2.0Apache 2.0MITMIT
Style closest to bKash?⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

3.1 Redoc — Best for Static Reference Pages

Redoc generates a clean, two-panel layout (sidebar + content) that closely mirrors the bKash reference style. It’s the most popular choice for standalone API reference sites.

# Install Redocly CLI
npm install -g @redocly/cli

# Generate a static HTML page from your spec
redocly build-docs openapi.yaml -o docs/index.html

The output is a single self-contained HTML file. No server, no build step, no JavaScript framework. Drop it behind any web server and it works.

3.2 Swagger UI — Best for Try-It Playgrounds

Swagger UI adds an interactive “Try it out” button on every endpoint — users can send real requests from the browser. This is ideal for sandbox environments but less clean as a reference-only docs site.

3.3 Stoplight Elements — Best for the bKash Look

Stoplight Elements provides a React component called APIReference that feels closest to the bKash/ReadMe aesthetic — clean parameter tables, collapsible schemas, and a sidebar that groups by tags.

import { APIReference } from '@stoplight/elements'
import spec from './openapi.json'

function ApiDocs() {
  return (
    <APIReference
      apiDescriptionDocument={spec}
      layout="sidebar"
      router="history"
    />
  )
}

3.4 Scalar — Best Modern Alternative

Scalar offers a polished, modern docs experience with dark mode, multi-language code samples (curl, JS, Python, Go, etc.), and a clean sidebar layout. It’s the newest contender and gaining rapid adoption.

npm install @scalar/api-reference
import { ApiReference } from '@scalar/api-reference'

function App() {
  return <ApiReference configuration={{ spec: { url: '/openapi.yaml' } }} />
}

Part 4: Building a Docs Site — Step by Step

Let’s build a complete documentation site using Redoc (for the static reference) and enhance it with Select Dropdown for environment switching.

4.1 Project Structure

api-docs/
├── spec/
   ├── openapi.yaml              # Main spec (references fragments)
   ├── fragments/
   ├── info.yaml
   ├── servers.yaml
   ├── paths/
   ├── auth.yaml
   ├── payments.yaml
   └── refunds.yaml
   └── components/
       ├── schemas.yaml
       └── responses.yaml
   └── examples/
       ├── payment-request.json
       └── payment-response.json
├── src/
   ├── index.html
   ├── styles/
   └── theme.css              # Custom theming
   └── scripts/
       └── env-selector.js        # Server URL switcher
├── redocly.yaml                   # Redocly config
├── package.json
└── .github/
    └── workflows/
        └── deploy-docs.yml

4.2 Define Your Spec with Tag Grouping

# spec/openapi.yaml
openapi: 3.0.3
info:
  title: Merchant Payment API
  description: |
    # Merchant Payment API
    
    Complete API reference for processing tokenized payments, 
    managing refunds, and handling webhook notifications.
    
    ### Base URLs
    | Environment | URL |
    |---|---|
    | Sandbox | `https://sandbox-api.merchant.com` |
    | Production | `https://api.merchant.com` |
  version: 1.0.0

servers:
  - url: https://sandbox-api.merchant.com
    description: Sandbox
  - url: https://api.merchant.com
    description: Production

tags:
  - name: Authentication
    description: Obtain and refresh API tokens
    x-displayName: Authentication
  - name: Payments
    description: Create, execute, and query payments
    x-displayName: Payments
  - name: Refunds
    description: Process and check refunds
    x-displayName: Refunds
  - name: Webhooks
    description: Receive real-time payment notifications
    x-displayName: Webhooks

paths:
  /token/grant:
    post:
      tags: [Authentication]
      summary: Grant Token
      description: |
        Obtain an access token for authenticating subsequent API calls.
        
        The token expires in 3600 seconds. Use the Refresh Token endpoint
        before expiry to get a new token without re-authenticating.
      operationId: grantToken
      parameters:
        - name: username
          in: header
          required: true
          schema:
            type: string
          description: Merchant account username (provided during onboarding)
        - name: password
          in: header
          required: true
          schema:
            type: string
          description: Merchant account password
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [app_key, app_secret]
              properties:
                app_key:
                  type: string
                  description: Application key shared during onboarding
                  example: test_app_key
                app_secret:
                  type: string
                  description: Application secret shared during onboarding
                  example: test_app_secret
      responses:
        '200':
          description: Token granted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  id_token:
                    type: string
                    description: Access token for subsequent API calls
                    example: eyJhbGciOiJIUzI1NiIs...
                  expires_in:
                    type: integer
                    description: Token lifetime in seconds
                    example: 3600
                  token_type:
                    type: string
                    example: Bearer
                  refresh_token:
                    type: string
                    description: Token for refreshing before expiry
                    example: eyJhbGciOiJIUzI1NiIs...
        '401':
          $ref: '#/components/responses/Unauthorized'

  /payment/create:
    post:
      tags: [Payments]
      summary: Create Payment
      description: |
        Initiate a payment request. Returns a `bkashURL` where the customer
        must be redirected to complete authentication.
        
        The payment ID expires after 24 hours if not executed.
      operationId: createPayment
      parameters:
        - name: X-App-Key
          in: header
          required: true
          schema:
            type: string
          description: Application key shared during onboarding
        - name: Authorization
          in: header
          required: true
          schema:
            type: string
          description: Bearer token from Grant Token API
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePaymentRequest'
      responses:
        '200':
          description: Payment initiated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreatePaymentResponse'
        '400':
          $ref: '#/components/responses/BadRequest'

components:
  schemas:
    CreatePaymentRequest:
      type: object
      required: [mode, payerReference, callbackURL, amount, currency, intent, merchantInvoiceNumber]
      properties:
        mode:
          type: string
          description: Payment mode
          enum: ['0001', '0011']
          example: '0001'
        payerReference:
          type: string
          description: Customer wallet number or reference
          maxLength: 255
          example: '01712345678'
        callbackURL:
          type: string
          format: uri
          description: Base URL for success/failure/cancel callbacks
          example: https://merchant.com/callback
        amount:
          type: string
          description: Payment amount
          example: '1500'
        currency:
          type: string
          enum: [BDT]
          default: BDT
        intent:
          type: string
          enum: [sale, authorization]
          description: 'sale for immediate capture, authorization for hold'
        merchantInvoiceNumber:
          type: string
          maxLength: 255
          description: Unique merchant-side invoice number
          example: INV-20260720-001

    CreatePaymentResponse:
      type: object
      properties:
        statusCode:
          type: string
          example: '0000'
        statusMessage:
          type: string
          example: Successful
        paymentID:
          type: string
          description: bKash-generated payment ID (expires in 24h)
          example: TR0011dQPHnuY1720518383420
        transactionStatus:
          type: string
          enum: [Initiated, Completed, Failed]
        bkashURL:
          type: string
          format: uri
          description: Redirect the customer to this URL for PIN entry
        amount:
          type: string
        currency:
          type: string
          enum: [BDT]
        merchantInvoiceNumber:
          type: string

    ErrorResponse:
      type: object
      properties:
        errorCode:
          type: string
          description: Numeric error code
          example: '2001'
        errorMessage:
          type: string
          description: Human-readable error description
          example: Invalid App Key

  responses:
    Unauthorized:
      description: Authentication failed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    BadRequest:
      description: Invalid request parameters
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'

4.3 Generate the Docs with Redoc

# Option A: Single HTML file (simplest)
npx @redocly/cli build-docs spec/openapi.yaml \
  -o docs/index.html

# Option B: With custom theme
npx @redocly/cli build-docs spec/openapi.yaml \
  -o docs/index.html \
  --theme.openapi.logo.url /logo.svg \
  --theme.openapi.colors.primary.main '#1a56db'

4.4 Custom Theme (Redocly)

# redocly.yaml
theme:
  openapi:
    logo:
      url: https://merchant.com/logo.svg
      altText: Merchant API Docs
    colors:
      primary:
        main: '#0F172A'  # Dark navy — like bKash's theme
      success:
        main: '#10B981'
      warning:
        main: '#F59E0B'
      error:
        main: '#EF4444'
    typography:
      fontFamily: 'Inter, system-ui, sans-serif'
      headings:
        fontFamily: 'Inter, system-ui, sans-serif'
        fontWeight: '600'
      code:
        fontFamily: '"JetBrains Mono", "Fira Code", monospace'
        fontSize: '13px'
    sidebar:
      backgroundColor: '#F8FAFC'
      activeTextColor: '#0F172A'
      textColor: '#475569'
    rightPanel:
      backgroundColor: '#0F172A'  # Dark panel for request/response samples

4.5 Environment Selector

Add a dropdown to switch between base URLs — a feature every consumer-grade API reference needs:

<!-- src/index.html (wrapping Redoc output) -->
<!DOCTYPE html>
<html>
<head>
  <title>Merchant Payment API Reference</title>
  <style>
    /* Match Redoc's theme */
    :root {
      --env-selector-bg: #F8FAFC;
      --env-selector-border: #E2E8F0;
    }
    #env-selector {
      position: fixed;
      top: 16px;
      right: 24px;
      z-index: 100;
      padding: 6px 12px;
      border: 1px solid var(--env-selector-border);
      border-radius: 6px;
      background: var(--env-selector-bg);
      font-size: 13px;
      font-family: 'Inter', system-ui, sans-serif;
    }
  </style>
</head>
<body>
  <select id="env-selector">
    <option value="https://sandbox-api.merchant.com">Sandbox</option>
    <option value="https://api.merchant.com">Production</option>
  </select>

  <div id="redoc-container"></div>

  <script>
    // Redoc renders here
    // Environment selector updates base URL
    
    const envSelector = document.getElementById('env-selector')
    
    // Store preference
    envSelector.addEventListener('change', () => {
      localStorage.setItem('api-env', envSelector.value)
    })
    
    // Restore preference
    const saved = localStorage.getItem('api-env')
    if (saved) envSelector.value = saved
  </script>
</body>
</html>

Part 5: Advanced Layouts — Picking Your Stack

5.1 Single Static HTML (Redoc)

Best for: Pure reference docs, no interactivity needed.

npx @redocly/cli build-docs spec.yaml -o docs/index.html
# Deploy docs/ to any static host (Netlify, Vercel, GitHub Pages, S3, etc.)

Pros: One file, any host, zero maintenance. Cons: No try-it console, no search (without plugin), no theming beyond Redocly’s options.

5.2 React SPA (Stoplight Elements)

Best for: Full control, custom pages, authentication flows, try-it console.

npm create vite@latest api-docs-site -- --template react-ts
npm install @stoplight/elements
// src/App.tsx
import { APIReference } from '@stoplight/elements'
import '@stoplight/elements/styles.min.css'

const specUrl = import.meta.env.PROD
  ? '/api/v1/openapi.yaml'    // Serve from your API server
  : '/openapi.yaml'            // Local dev file

function App() {
  return (
    <div className="api-docs">
      <APIReference
        apiDescriptionDocument={specUrl}
        layout="sidebar"
        router="history"
        basePath="/docs"
      />
    </div>
  )
}

export default App
npm run build
# Deploy dist/ to any static host

Pros: Full React ecosystem, custom pages, search built-in, try-it console. Cons: Heavier bundle, requires a build step.

5.3 Static Site Generator (Docusaurus + Redoc)

Best for: Documentation sites with guides + reference, multiple spec versions.

npx create-docusaurus@latest docs-site classic
npm install docusaurus-plugin-redoc
// docusaurus.config.ts
export default {
  plugins: [
    [
      'docusaurus-plugin-redoc',
      {
        specPath: 'spec/openapi.yaml',
        routePath: '/api/',
        enableSearch: true,
        theme: {
          colors: { primary: { main: '#0F172A' } },
        },
      },
    ],
  ],
}

Now you have:

  • /docs/ — guides and tutorials (markdown)
  • /api/ — spec-driven reference (Redoc)

Pros: Best of both worlds — guides + reference, versioning, search across all content. Cons: More setup, Docusaurus learning curve.

5.4 Vue/Nuxt Integration (Scalar)

npm create nuxt@latest api-docs
npm install @scalar/nuxt
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@scalar/nuxt'],
  scalar: {
    spec: {
      url: '/openapi.yaml',
    },
    theme: 'purple',
  },
})

Part 6: CI/CD Pipeline

6.1 Validate Spec on Every Commit

# .github/workflows/spec-validation.yml
name: Validate OpenAPI Spec

on:
  pull_request:
    paths:
      - 'spec/**/*.yaml'
      - 'spec/**/*.json'

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

      - name: Install Redocly CLI
        run: npm install -g @redocly/cli

      - name: Lint spec
        run: redocly lint spec/openapi.yaml

      - name: Validate spec
        run: redocly validate spec/openapi.yaml

6.2 Build and Deploy Docs

# .github/workflows/deploy-docs.yml
name: Deploy API Docs

on:
  push:
    branches: [main]
    paths:
      - 'spec/**'

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build docs
        run: |
          npm install -g @redocly/cli
          redocly build-docs spec/openapi.yaml \
            --theme.openapi.colors.primary.main '#0F172A' \
            -o docs/index.html

      - name: Deploy to GitHub Pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./docs
          publish_branch: gh-pages

6.3 Spec ↔ Code Contract Testing

To ensure your spec stays in sync with your actual API, add contract testing:

// tests/spec-contract.test.ts
import { test, expect } from 'vitest'
import axios from 'axios'
import fs from 'fs'
import { parse } from 'yaml'

const spec = parse(fs.readFileSync('spec/openapi.yaml', 'utf-8'))

test('all endpoints in spec are reachable', async () => {
  for (const [path, methods] of Object.entries(spec.paths)) {
    for (const method of Object.keys(methods)) {
      if (['get', 'post', 'put', 'patch', 'delete'].includes(method)) {
        const url = `http://localhost:3000${path}`
        const res = await axios({ method, url, validateStatus: () => true })
        
        // Spec says 200/201 → server should not return 404/500
        expect(res.status).not.toBe(404)
        expect(res.status).not.toBe(500)
      }
    }
  }
})

Part 7: Structuring Multi-Product Docs (Like bKash)

bKash has multiple API products (Tokenized Checkout, Checkout URL, Auth & Capture, Disbursement). Their sidebar groups these clearly.

7.1 Multi-Spec Structure

api-docs/
├── specs/
   ├── tokenized-checkout/
   ├── openapi.yaml
   └── examples/
   ├── checkout-url/
   ├── openapi.yaml
   └── examples/
   ├── auth-capture/
   ├── openapi.yaml
   └── examples/
   └── disbursement/
       ├── openapi.yaml
       └── examples/
├── docs/
   ├── index.html              # Landing page
   ├── tokenized-checkout/     # Built from specs/tokenized-checkout
   ├── checkout-url/
   ├── auth-capture/
   └── disbursement/
└── scripts/
    └── build-all.js

7.2 Landing Page

<!-- docs/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Merchant API Documentation</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body {
      font-family: 'Inter', system-ui, sans-serif;
      background: #F8FAFC;
      color: #0F172A;
    }
    .container { max-width: 800px; margin: 80px auto; padding: 0 24px; }
    h1 { font-size: 2rem; margin-bottom: 8px; }
    p.subtitle { color: #64748B; margin-bottom: 40px; }
    .card {
      background: white;
      border: 1px solid #E2E8F0;
      border-radius: 12px;
      padding: 24px;
      margin-bottom: 16px;
      transition: border-color 0.2s, box-shadow 0.2s;
    }
    .card:hover {
      border-color: #94A3B8;
      box-shadow: 0 4px 12px rgba(0,0,0,0.08);
    }
    .card h2 { font-size: 1.125rem; margin-bottom: 4px; }
    .card p { color: #64748B; font-size: 0.875rem; }
    a { text-decoration: none; color: inherit; display: block; }
    .badge {
      display: inline-block;
      padding: 2px 8px;
      border-radius: 4px;
      font-size: 0.75rem;
      font-weight: 600;
      background: #DBEAFE;
      color: #1D4ED8;
      margin-top: 8px;
    }
  </style>
</head>
<body>
  <div class="container">
    <h1>Merchant Payment API</h1>
    <p class="subtitle">
      Complete API reference for integrating payment services.
      Select a product below to view its API specification.
    </p>

    <a href="/tokenized-checkout/">
      <div class="card">
        <h2>Tokenized Checkout</h2>
        <p>Create agreements, process payments, and handle refunds with tokenized checkout — ideal for recurring payments.</p>
        <span class="badge">v1.2.0-beta</span>
      </div>
    </a>

    <a href="/checkout-url/">
      <div class="card">
        <h2>Checkout (URL Based)</h2>
        <p>Generate checkout URLs for one-time payments. Customers complete payment on bKash's hosted page.</p>
        <span class="badge">v1.1.0</span>
      </div>
    </a>

    <a href="/auth-capture/">
      <div class="card">
        <h2>Auth & Capture</h2>
        <p>Authorize a payment amount, then capture it later — useful for escrow and pre-authorization flows.</p>
        <span class="badge">v1.0.0</span>
      </div>
    </a>
  </div>
</body>
</html>

7.3 Build Script

// scripts/build-all.js
import { execSync } from 'child_process'
import { readdirSync, cpSync, mkdirSync } from 'fs'

const specDirs = readdirSync('specs/', { withFileTypes: true })
  .filter(d => d.isDirectory())

for (const dir of specDirs) {
  const name = dir.name
  const specPath = `specs/${name}/openapi.yaml`
  const outputDir = `docs/${name}`

  mkdirSync(outputDir, { recursive: true })

  execSync(
    `npx @redocly/cli build-docs ${specPath} -o ${outputDir}/index.html`,
    { stdio: 'inherit' },
  )
  console.log(`✓ Built ${name} → ${outputDir}/index.html`)
}

Part 8: Design Best Practices (The bKash Way)

8.1 Parameter Table Design

bKash’s tables follow a consistent pattern: Property, Type, Description for responses; Property, Presence, Type, Description for request parameters. Your OpenAPI spec drives this:

parameters:
  - name: authorization
    in: header
    required: true
    description: Bearer token from Grant Token API
    schema:
      type: string
      example: "Bearer eyJhbGciOi..."

Redoc renders this as:

PropertyTypeRequiredDescription
authorizationstring✅ YesBearer token from Grant Token API

8.2 Code Sample Tabs

Include multiple client examples in your spec descriptions or use Redoc’s x-codeSamples extension:

paths:
  /payment/create:
    post:
      x-codeSamples:
        - lang: cURL
          source: |
            curl -X POST https://sandbox-api.merchant.com/payment/create \
              -H 'Authorization: Bearer {{token}}' \
              -H 'Content-Type: application/json' \
              -d '{"amount": "500", "currency": "BDT"}'
        - lang: JavaScript
          source: |
            const response = await axios.post(
              'https://sandbox-api.merchant.com/payment/create',
              { amount: '500', currency: 'BDT' },
              { headers: { Authorization: `Bearer ${token}` } }
            )
        - lang: Python
          source: |
            import requests
            response = requests.post(
                'https://sandbox-api.merchant.com/payment/create',
                json={'amount': '500', 'currency': 'BDT'},
                headers={'Authorization': f'Bearer {token}'}
            )

8.3 Response Visualization

bKash shows sample request and sample response as formatted JSON code blocks. Redoc generates these automatically from the schema:

responses:
  '200':
    description: Payment initiated
    content:
      application/json:
        example:
          statusCode: '0000'
          statusMessage: Successful
          paymentID: TR0011dQPHnuY1720518383420
          transactionStatus: Initiated
          bkashURL: https://sandbox.bkash.com/pay?paymentID=TR0011...

8.4 Error Code Reference

A dedicated error codes page (like bKash’s) is essential. You can generate it from a custom OpenAPI extension or a standalone markdown file:

# In your spec, use x-errors for custom error code tables
x-error-codes:
  - code: '2001'
    message: Invalid App Key
    description: The provided app_key does not match any registered application.
  - code: '2023'
    message: Insufficient Balance
    description: The merchant account does not have sufficient balance.
  - code: '2057'
    message: Not a bKash Account
    description: The provided wallet number is not a valid bKash account.

Then render this as a standalone page alongside your spec-driven reference.


Part 9: Comparison Summary

ApproachSetup EffortbKash-like StyleTry-it ConsoleSearchBundle SizeBest For
Redoc single HTML🟢 5 min⭐⭐⭐⭐⭐⚠️ Plugin~2 MBPure reference sites
Swagger UI🟢 10 min⭐⭐⭐~3 MBInteractive sandboxes
Stoplight Elements + React🟡 30 min⭐⭐⭐⭐~200 KB gzipFull-featured docs
Scalar + Vue/Nuxt🟡 20 min⭐⭐⭐⭐⭐~150 KB gzipModern docs sites
Docusaurus + Redoc🟠 1 hr⭐⭐⭐⭐⭐~500 KBGuides + reference
ReadMe (hosted)🟢 15 min⭐⭐⭐⭐⭐HostedTeams with budget (paid)

Part 10: Best Practices

10.1 Spec Hygiene

  • One spec file per API product — bKash has separate specs for Tokenized Checkout, Checkout URL, Auth & Capture, and Disbursement
  • Reuse components$ref shared schemas instead of copying them
  • operationId must be unique — tools use it for URL routing
  • Every parameter needs a description — this is what shows up in the docs table
  • Provide examplesexample / examples on every property makes the reference useful
  • Use enum for constrained values — Redoc renders these as badges
  • Tag every operation — tags drive the sidebar navigation

10.2 Documentation UX

  • Group by use case, not by HTTP method — bKash groups by product flow, not GET vs POST
  • Provide at least cURL + one language example per endpoint
  • Add an environment selector — users shouldn’t edit URLs by hand
  • Show schema examples inline — a collapsed JSON example is worth 1000 words of descriptions
  • Include error codes — every API consumer needs this page
  • Link between guides and reference — bKash’s sidebar mixes “Quick Start” guides with API specs

10.3 CI/CD

  • Validate spec on every PRredocly lint catches missing descriptions, invalid references, broken schemas
  • Auto-deploy on merge to main — documentation should ship as fast as code
  • Version the spec alongside the code — the OpenAPI file lives in the same repo as the implementation
  • Contract-test the spec against the implementation — ensure what’s documented actually matches the running API

Conclusion

Spec-driven documentation transforms API docs from a maintenance burden into a natural output of the development process. Write the OpenAPI spec once, validate it in CI, generate beautiful reference pages with any of the tools we’ve covered, and deploy automatically.

The bKash Developer Reference style — clean sidebar navigation, parameter tables, tabbed code samples, response payloads — is achievable with free, open-source tools. You don’t need ReadMe’s paid plan to match the quality; Redoc + a theming pass gets you 90% of the way there, and Stoplight Elements or Scalar can close the remaining gap.

What to do next:

  1. Start with a single well-structured OpenAPI spec for one API product
  2. Generate docs with redocly build-docs and review the output
  3. Add x-codeSamples for real-world code examples
  4. Set up CI validation: redocly lint on every PR
  5. Auto-deploy to GitHub Pages / Netlify on merge to main

The best API documentation isn’t the one with the fanciest design. It’s the one that’s accurate, up-to-date, and costs nothing to maintain. Spec-driven docs are all three. 🚀


Further Reading