Career

Technical Writing for Devs: API & Architecture Docs

Master technical writing for developers. Learn to document APIs effectively with OpenAPI and capture architecture decisions using ADRs. Enhance collaboration and maintainability.

Khader Vali July 25, 2026 17 min read

Technical Writing for Developers: Documenting APIs and Architecture Decisions

As a senior engineer, I’ve seen firsthand how high-quality documentation can make or break a project, a team, and even an entire product. Conversely, I’ve also navigated the treacherous waters of undocumented APIs and forgotten architectural choices. It’s a painful experience that wastes countless hours, introduces bugs, and grinds innovation to a halt.

Too often, “technical writing” is seen as a task relegated to a specialized team, or worse, an afterthought. But for developers, especially those building complex systems with APIs and intricate architectures, this mindset is a significant disservice. In reality, technical writing is not just a skill for professional writers; it’s an indispensable part of the software development lifecycle that every engineer should embrace and master.

This article isn’t about turning you into a professional technical writer overnight. It’s about empowering you, the developer, to effectively document the two most critical aspects of your work: your APIs and your architecture decisions. We’ll explore why this is non-negotiable, what to document, how to do it efficiently with practical tools and examples, and how to integrate it seamlessly into your daily workflow. My goal is to equip you with the knowledge to create documentation that genuinely serves your team, your users, and your future self.

The “Why”: Beyond Good Practices – The Critical Necessity of Developer Documentation

Let’s be blunt: documentation is not a luxury; it’s a fundamental requirement for sustainable software development. For APIs and architectural decisions, the “why” becomes even more pronounced. Here’s why you, as a developer, must prioritize this aspect of your work:

  • Reducing the “Bus Factor”: Every team has critical knowledge held by a few key individuals. What happens if they’re unavailable or move on? Comprehensive documentation ensures that vital information about your APIs and architectural choices isn’t lost, reducing reliance on tribal knowledge and mitigating project risks.
  • Accelerated Onboarding: New team members spend less time asking repetitive questions and more time contributing. Well-documented APIs allow them to quickly understand how services interact, while clear architecture documentation provides the necessary context to grasp the system’s design philosophy and evolution.
  • Enhanced Cross-Team Collaboration: In distributed systems or large organizations, different teams often own different services. Clear API documentation acts as a contract, enabling seamless integration without constant back-and-forth. Architecture documentation fosters a shared understanding of the overall system, preventing conflicting design choices.
  • Improved Maintainability and Future-Proofing: Software evolves. When it’s time to refactor, debug, or extend a system, good documentation provides invaluable historical context. Why was this decision made? What were the alternatives? What are the known limitations? This saves immense time and prevents re-treading old ground.
  • Simplified Debugging and Troubleshooting: When things go wrong (and they always do), detailed API specifications and architectural context help pinpoint issues faster. Knowing the expected request/response formats or the rationale behind a particular component choice can quickly narrow down the problem space.
  • Superior Developer Experience (DX) for API Consumers: Whether your API is internal or external, its usability is directly tied to the quality of its documentation. A poorly documented API is a frustrating API. A well-documented API, with clear examples and error handling, attracts users and fosters adoption. This directly translates to business value.

Think of documentation as an investment. It takes time upfront, but it pays dividends many times over throughout the lifetime of your software. It’s an investment in efficiency, stability, and collective intelligence.

<

Technical Writing for Devs: API & Architecture Docs
Generated Image

>

Documenting APIs: The Developer’s Playbook

APIs are the communication backbone of modern software. Whether it’s a RESTful API for a microservice, a GraphQL endpoint, or even an internal RPC interface, how it communicates and what it expects needs to be crystal clear. As developers, we are the architects and implementers of these contracts; therefore, we are the primary owners of their documentation.

What to Document for APIs

When documenting an API, think from the perspective of someone who knows nothing about your service but needs to integrate with it. What information would they need to succeed? Here’s a comprehensive list:

  1. Overview & Introduction:
    • Purpose of the API: What problem does it solve?
    • Base URL/Endpoint.
    • High-level concepts and domain models.
    • Getting Started guide (e.g., how to obtain an API key).
  2. Authentication and Authorization:
    • Security scheme(s) used (e.g., OAuth 2.0, API Key, JWT).
    • How to obtain credentials.
    • Where to include credentials in requests (headers, query params).
    • Scope/permission requirements for different operations.
  3. Endpoints and Operations: For each endpoint (e.g., /users, /products/{id}):
    • Path and HTTP Method: GET /users, POST /products, PUT /products/{id}, DELETE /products/{id}.
    • Brief Description: What does this operation do?
    • Request Parameters:
      • Path Parameters: (e.g., id in /products/{id}) – Type, description, example.
      • Query Parameters: (e.g., ?limit=10&offset=0) – Name, type, description, default value, constraints (min/max, regex), example.
      • Header Parameters: (e.g., Authorization, Content-Type) – Name, type, description, example.
      • Request Body: Schema (JSON, XML, etc.), data types for each field, required/optional status, constraints, detailed examples.
    • Response Payloads: For each possible HTTP status code (200 OK, 201 Created, 400 Bad Request, 401 Unauthorized, 404 Not Found, 500 Internal Server Error, etc.):
      • Status Code: (e.g., 200 OK, 404 Not Found).
      • Description: What does this response signify?
      • Response Body: Schema (JSON, XML), data types, detailed examples.
  4. Error Handling:
    • Common error codes and their meanings.
    • Structure of error responses.
    • Strategies for handling specific errors.
  5. Pagination, Filtering, Sorting:
    • How to paginate results (e.g., limit, offset, page tokens).
    • Available filter parameters and their syntax.
    • How to specify sorting order.
  6. Rate Limiting:
    • Thresholds (requests per second/minute/hour).
    • Headers that indicate current limits and remaining requests.
    • How to handle rate limit exceeded errors.
  7. Versioning Strategy:
    • How API versions are managed (e.g., path, header, query parameter).
    • Lifecycle of versions (deprecation policy).
  8. Webhooks/Callbacks (if applicable):
    • How to register webhooks.
    • Payload structure of outgoing webhooks.
    • Security considerations for webhook endpoints.
  9. Use Cases/Workflow Examples: Practical scenarios demonstrating how to combine multiple API calls to achieve a specific outcome.

Tools and Formats for API Documentation

While you could write API docs in a simple Markdown file, specialized tools offer immense advantages:

1. OpenAPI Specification (OAS) / Swagger

This is the gold standard for RESTful API documentation. OpenAPI defines a standard, language-agnostic interface description for REST APIs, allowing both humans and computers to discover and understand the capabilities of a service without access to source code, documentation, or network traffic inspection. Tools like Swagger UI can then render this specification into beautiful, interactive documentation portals.

You can write OpenAPI specs in YAML or JSON. Here’s a snippet demonstrating a basic endpoint definition in YAML:


openapi: 3.0.0
info:
  title: User Management API
  version: 1.0.0
  description: API for managing user profiles.
servers:
  - url: https://api.example.com/v1
paths:
  /users:
    get:
      summary: Get a list of users
      operationId: getUsers
      parameters:
        - name: limit
          in: query
          description: Maximum number of users to return
          required: false
          schema:
            type: integer
            format: int32
            minimum: 1
            maximum: 100
            default: 20
      responses:
        '200':
          description: A list of users
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'
              examples:
                success:
                  value:
                    - id: "user-123"
                      name: "Alice"
                      email: "alice@example.com"
                    - id: "user-456"
                      name: "Bob"
                      email: "bob@example.com"
        '401':
          description: Unauthorized
        '500':
          description: Internal server error
    post:
      summary: Create a new user
      operationId: createUser
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewUser'
            examples:
              newUser:
                value:
                  name: "Charlie"
                  email: "charlie@example.com"
      responses:
        '201':
          description: User created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          description: Invalid input
components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the user
        name:
          type: string
          description: User's full name
        email:
          type: string
          format: email
          description: User's email address
      required:
        - id
        - name
        - email
    NewUser:
      type: object
      properties:
        name:
          type: string
        email:
          type: string
      required:
        - name
        - email

This YAML defines two endpoints for a User API, including parameters, request bodies, and response schemas. The real power comes when you use tools to render this. You can choose a design-first approach (write the OpenAPI spec first, then generate code) or a code-first approach (generate the spec from annotations in your code). For larger teams and public APIs, design-first often leads to better API design.

2. Markdown/AsciiDoc

While OpenAPI is excellent for the API contract, you’ll still need narrative documentation for things like high-level overviews, “getting started” guides, integration tutorials, and specific use cases. Markdown (or AsciiDoc for more advanced features) is perfect for this. Keep these files alongside your code in a docs/api directory.

3. Postman/Insomnia Collections

These tools allow you to create collections of API requests, complete with variables, environments, and tests. They can serve as executable documentation, allowing consumers to quickly try out your API. Many teams even publish these collections alongside their OpenAPI specs.

Best Practices for API Documentation

  • Be Clear, Concise, and Consistent: Avoid jargon where possible. Use consistent naming conventions, formatting, and terminology across all your documentation.
  • Examples, Examples, Examples: Nothing helps a developer more than working examples. Provide example requests (curl, Python, JavaScript) and example response payloads for both success and error scenarios.

Here’s a simple cURL example for our User API:


curl -X POST \
  https://api.example.com/v1/users \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Charlie Puth",
    "email": "charlie.puth@example.com"
  }'

And a Python example:


import requests
import json

api_base_url = "https://api.example.com/v1"
headers = {"Content-Type": "application/json"}

new_user_data = {
    "name": "Charlie Puth",
    "email": "charlie.puth@example.com"
}

try:
    response = requests.post(
        f"{api_base_url}/users",
        headers=headers,
        data=json.dumps(new_user_data)
    )
    response.raise_for_status() # Raises HTTPError for bad responses (4xx or 5xx)
    print("User created successfully:")
    print(json.dumps(response.json(), indent=2))
except requests.exceptions.HTTPError as err:
    print(f"HTTP error occurred: {err}")
    print(f"Response: {response.text}")
except Exception as err:
    print(f"An error occurred: {err}")
  • Keep it Up-to-Date: Outdated documentation is worse than no documentation. Integrate documentation generation/validation into your CI/CD pipeline. Use linters for OpenAPI specs.
  • Consider Your Audience: Is this for internal consumption, external partners, or the general public? Tailor your language, depth, and examples accordingly.
  • Interactive Documentation: Tools like Swagger UI, Redoc, or Postman’s generated web docs allow users to explore and even make API calls directly from the browser, significantly enhancing the DX.

Documenting Architecture Decisions: The AD(R) Template

Software architecture isn’t just about diagrams and high-level components; it’s about the significant choices that shape the system. Why did we choose Microservices over a Monolith? Why Kafka for messaging? Why PostgreSQL over MongoDB? These are critical questions whose answers often get lost in Slack threads, meeting notes, or individual memories. This is where Architecture Decision Records (ADRs) come in.

What is an Architecture Decision Record (ADR)?

An Architecture Decision Record (ADR) is a document that captures a significant architectural decision, its context, the options considered, and the consequences. ADRs are lightweight, simple, and version-controlled, living alongside your code. They serve as a historical log of your system’s evolution, providing invaluable context for future development, maintenance, and onboarding.

The “why” for ADRs is compelling:

  • Knowledge Preservation: Prevents “why did we do it this way?” questions years down the line.
  • Alignment and Consensus: Forces teams to explicitly state decisions and their rationale, fostering agreement.
  • Context for Future Decisions: Provides a foundation for evaluating new architectural proposals against past ones.
  • Onboarding Aid: New developers can quickly understand the foundational choices of the system.

Structure of an ADR

While there are various templates, a common and effective structure for an ADR includes:

  1. Title: A concise, descriptive title (e.g., “Use PostgreSQL for User Data”).
  2. Date: The date the decision was made/recorded.
  3. Status: Proposed, Accepted, Rejected, Superseded (by another ADR).
  4. Context:
    • What is the problem we are trying to solve?
    • What forces or constraints are influencing this decision?
    • What are the current pain points or opportunities?
  5. Decision:
    • The specific architectural choice made.
    • A clear, unambiguous statement of the solution.
  6. Alternatives Considered:
    • What other options were evaluated?
    • Why were they rejected? (Pros and cons for each alternative).
  7. Consequences:
    • Positive: What are the benefits of this decision?
    • Negative: What are the drawbacks, risks, or trade-offs?
    • Neutral: Any other impacts.
  8. Diagrams (in words): For complex decisions, describing system components and their interactions textually is crucial when visual diagrams aren’t embedded directly.

Architecture Description with Diagrams in Words (Example Scenario)

Let’s consider a real-world scenario: Deciding on the messaging system for an e-commerce platform that needs to handle order processing, inventory updates, and notification services.

ADR Title: ADR 005 – Choose Message Broker for Asynchronous Communication

Status: Accepted

Date: 2023-10-26

Context:
Our e-commerce platform currently relies on synchronous HTTP calls between microservices for critical operations like order creation, inventory deduction, and email notifications. This approach has led to several issues:

  • Tight Coupling: Services are directly dependent on each other’s availability, leading to cascading failures.
  • Performance Bottlenecks: Long-running operations (e.g., payment processing, external API calls) block the response cycle, impacting user experience.
  • Scalability Challenges: Individual services are difficult to scale independently based on their specific loads.
  • Data Consistency Issues: Retries and error handling are complex to implement consistently across multiple services.

We need a robust, scalable, and asynchronous communication mechanism to decouple services, improve resilience, and support event-driven architecture patterns.

Decision:
We will adopt Apache Kafka as our primary message broker for asynchronous communication between microservices. Kafka topics will be used for events related to order lifecycle, inventory changes, and user notifications.

Alternatives Considered:

  1. RabbitMQ:
    • Pros: Mature, easy to get started with, supports various messaging patterns (queues, topics), good for transient message queues.
    • Cons: Less suitable for high-throughput, persistent event streaming. Message retention is typically short. Scaling can be more complex for large-scale event logs.
  2. Amazon SQS/SNS:
    • Pros: Fully managed, highly scalable, integrates well within AWS ecosystem, cost-effective for simple queueing/pub-sub.
    • Cons: SQS is primarily a queueing service (point-to-point), not an event streaming platform. SNS is pub-sub but lacks the robust consumer group management and long-term retention of Kafka. Less cloud-agnostic.

Consequences:

  • Positive:
    • Decoupling: Services become independent, reducing cascading failures.
    • Improved Resilience: Consumers can process events at their own pace, and messages are persisted, allowing recovery from failures.
    • Scalability: Kafka is designed for high throughput and can scale horizontally to handle large volumes of events.
    • Event Sourcing Potential: Lay groundwork for richer event-driven patterns and potential event sourcing.
    • Auditability: Kafka’s distributed log provides a persistent record of all system events.
  • Negative:
    • Increased Operational Overhead: Kafka is a complex distributed system to manage and monitor, requiring specialized knowledge. (Mitigation: Use managed Kafka service like Confluent Cloud or AWS MSK).
    • Learning Curve: Team members will need to learn Kafka concepts (producers, consumers, topics, partitions, consumer groups).
    • Eventual Consistency: Introducing asynchronous communication means operations will be eventually consistent, requiring careful design of idempotency and failure handling in consumers.
    • Debugging Complexity: Tracing issues across asynchronous message flows can be more challenging than synchronous requests.

Architecture Diagram (Textual Description):

The system will now involve a central Kafka cluster. Microservices will act as either producers or consumers of events on specific Kafka topics. The flow for a typical order process would be:

  1. The Order Service (producer) receives a new order request via its API.
  2. After initial validation, the Order Service publishes an OrderCreated event to the orders-topic.
  3. The Inventory Service (consumer) subscribes to the orders-topic. Upon receiving an OrderCreated event, it attempts to deduct inventory. It then publishes either an InventoryDeducted or InventoryFailed event to an inventory-updates-topic.
  4. The Payment Service (consumer) also subscribes to the orders-topic. Upon receiving an OrderCreated event, it initiates payment processing. It then publishes either a PaymentProcessed or PaymentFailed event to a payments-topic.
  5. The Notification Service (consumer) subscribes to multiple topics (orders-topic, inventory-updates-topic, payments-topic) to send relevant emails/SMS messages to the user based on the progression of the order. For example, it might send “Order Confirmed” upon OrderCreated, “Payment Successful” upon PaymentProcessed, and “Inventory Low” upon InventoryFailed.
  6. A Dead Letter Queue (DLQ) topic will be configured for each critical consumer to capture messages that cannot be processed successfully after multiple retries, allowing for manual inspection and reprocessing.

This setup clearly separates concerns and enables services to operate independently, reacting to events rather than tightly coupled requests.

<

Technical Writing for Devs: API & Architecture Docs
Generated Image

>

Tools and Formats for ADRs

The beauty of ADRs is their simplicity:

  • Markdown Files: The most common approach. Store them in a docs/adr directory at the root of your repository. This allows them to be version-controlled alongside your code.
  • adr-tools: A command-line utility that helps manage ADRs, creating new ones from templates and managing their status.
  • Wiki Pages: For teams already heavily invested in a wiki (Confluence, Notion), these can work, but ensure they are still linked from the codebase and ideally follow a templated structure.

A simple Markdown template for an ADR:


# ADR [Number] - [Decision Title]

## Date
YYYY-MM-DD

## Status
[Proposed | Accepted | Rejected | Superseded by ADR NNN]

## Context
Describe the problem or situation that requires a decision.
What forces are at play? What are the current pain points or opportunities?
Why is this decision being made now?

## Decision
State the specific architectural choice made.
Be clear and unambiguous.

## Alternatives Considered
List and briefly describe other options that were evaluated.
For each alternative, summarize its pros and cons, and explain why it was not chosen over the selected decision.

### Alternative 1: [Name]
*   **Pros:**
    *   ...
*   **Cons:**
    *   ...
*   **Reason for Rejection:** ...

### Alternative 2: [Name]
*   **Pros:**
    *   ...
*   **Cons:**
    *   ...
*   **Reason for Rejection:** ...

## Consequences
Describe the positive and negative impacts of this decision.

### Positive
*   ...
*   ...

### Negative
*   ...
*   ...

## Architecture Diagram (Textual Description)
(Optional) If applicable, provide a high-level textual description of the architectural components and their interactions resulting from this decision.

Integrating Documentation into the Development Workflow

The best documentation is the documentation that gets written, maintained, and used. This requires integrating it directly into your daily development workflow, rather than treating it as a separate, onerous task.

1. Docs-as-Code

This philosophy treats documentation like any other part of your codebase:

  • Version Control (Git): Store all your documentation (OpenAPI specs, Markdown files for ADRs, tutorials) in the same Git repository as the code it describes. This ensures documentation versions align with code versions.
  • Review Process (PRs for Docs): Just like code, documentation should be reviewed. Open Pull Requests for documentation changes. This catches errors, ensures clarity, and spreads knowledge.
  • Automated Checks:
    • Linters: Use linters for Markdown (e.g., markdownlint) and OpenAPI (e.g., spectral) to enforce style guides and catch common errors.
    • Broken Link Checkers: Integrate tools to automatically find and report broken links in your documentation.
    • Schema Validation: For OpenAPI, ensure your spec is valid against the OpenAPI schema.
  • CI/CD for Deployment: Automate the generation and deployment of your documentation. When a PR with a new API endpoint or an updated ADR is merged, your CI/CD pipeline should automatically generate updated Swagger UI pages or publish the new ADR to your internal knowledge base. This guarantees documentation is always current.

Example CI/CD step (conceptual) for generating API docs:


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

on:
push:
branches:
- main
paths:
- 'api-spec/**.yaml'
- 'docs/**'

jobs:
build-and-deploy-docs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '16'

- name: Install Redoc CLI
run: npm install -g redoc-cli

- name: Validate OpenAPI spec
run: spectral lint api-spec/openapi.yaml

Written by

Khader Vali

Senior Software Engineer specializing in cloud architecture, real-time systems, and enterprise-scale applications.

Share this article

Related Articles

Technical Writing: APIs & Architecture for Devs

Jun 25, 2026 · 18 min read

Open Source Contribution Guide: Start and Sustain

Jun 30, 2026 · 14 min read

Write Engineering Blogs: Establish Technical Authority

Jul 23, 2026 · 9 min read