Writing API Documentation in Markdown
A practical guide to writing API docs that developers actually read: structure, auth, endpoint reference, examples, errors, and versioning — with Markdown patterns that scale.
API documentation is the contract between your backend and every developer who will ever integrate with it. Bad API docs cost millions of developer hours and drive integrators to competitors. Good API docs are the single highest-leverage thing a backend team can invest in after the API itself.
This post covers the structure, content, and Markdown patterns that make API docs genuinely useful.
The five questions every API doc must answer
When a developer lands on your API reference, they want to know:
- How do I authenticate?
- What endpoints exist?
- What parameters does each endpoint take?
- What does the response look like?
- What errors can I get, and what do they mean?
Everything else (SDKs, tutorials, guides) is secondary. Nail these five and you've built the foundation.
Recommended structure
A scaled API reference should look roughly like this:
docs/
├── README.md # Overview, start here
├── authentication.md # Auth + example token flow
├── rate-limits.md # Limits and headers
├── errors.md # Status codes and error format
├── versioning.md # How versions work, deprecation policy
├── changelog.md # API changelog
├── resources/
│ ├── users.md # Everything about /users
│ ├── orders.md # Everything about /orders
│ └── webhooks.md # Webhook delivery and retries
└── guides/
├── getting-started.md
├── pagination.md
└── idempotency.md
Start simple. For a small API, a single api.md might do. Split into files when a single page gets painful to scroll.
The overview page
Your README.md or overview should be ~200–400 words and answer: who, what, where, how.
# MDKit API The MDKit API lets you convert Markdown to HTML, HTML to Markdown, and generate PDF reports programmatically. ## Base URL
## Quick example
```bash
curl https://api.mdkit.io/v1/convert/markdown-to-html \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"markdown": "# Hello world"}'
{ "html": "<h1>Hello world</h1>", "wordCount": 2 }
What's next
The quick example is non-negotiable. Developers skip the prose and scan for `curl`.
## Documenting an endpoint
For each endpoint, adopt a consistent template. Here's a battle-tested pattern:
````markdown
## `POST /v1/convert/markdown-to-html`
Convert a Markdown string to HTML.
### Authentication
Requires a valid API token (see [Authentication](../authentication.md)).
### Request
| Header | Required | Description |
| :---------------- | :------: | :----------------------------------- |
| `Authorization` | Yes | `Bearer <token>` |
| `Content-Type` | Yes | `application/json` |
| `Idempotency-Key` | No | UUID to safely retry this request |
#### Body
```json
{
"markdown": "# Hello",
"options": {
"gfm": true,
"breaks": false
}
}
| Field | Type | Required | Description |
|---|---|---|---|
markdown | string | Yes | Source text. Max 1 MB. |
options.gfm | boolean | No | Enable GitHub Flavored Markdown. Default true. |
options.breaks | boolean | No | Render \n as <br>. Default false. |
Response
200 OK
{ "html": "<h1>Hello</h1>", "wordCount": 1, "renderTimeMs": 3 }
| Field | Type | Description |
|---|---|---|
html | string | Rendered HTML. |
wordCount | number | Words in the source (excluding code). |
renderTimeMs | number | Server-side render time in ms. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_markdown | Body missing or malformed. |
| 401 | unauthorized | Missing or invalid token. |
| 413 | payload_too_large | Markdown exceeded 1 MB. |
| 429 | rate_limit_exceeded | See Retry-After header. |
| 500 | internal_error | Something broke on our end. |
Examples
curl
curl https://api.mdkit.io/v1/convert/markdown-to-html \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"markdown": "# Hello"}'
JavaScript (fetch)
const res = await fetch("https://api.mdkit.io/v1/convert/markdown-to-html", { method: "POST", headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify({ markdown: "# Hello" }), }); const { html } = await res.json();
Python (requests)
import requests r = requests.post( "https://api.mdkit.io/v1/convert/markdown-to-html", headers={"Authorization": f"Bearer {token}"}, json={"markdown": "# Hello"}, ) html = r.json()["html"]
Notice what's present:
- HTTP method and path as the heading.
- One-line purpose.
- Auth requirement clearly stated.
- Request schema *and* a sample request.
- Response schema *and* a sample response.
- Errors with codes (strings, not just HTTP statuses).
- At least three example languages.
Copy this as your template and fill it in for every endpoint.
## Authentication page
```markdown
# Authentication
MDKit uses bearer tokens for all API requests.
## Get a token
1. Sign in at [mdkit.io/dashboard](https://mdkit.io/dashboard).
2. Go to **Settings → API tokens**.
3. Click **Create token** and copy the value.
Tokens are 64 characters, start with `mdk_`, and are shown **once**. We
hash them on our end; there's no way to retrieve a lost token. Create
a new one if lost.
## Sending a request
Include the token in an `Authorization` header:
```bash
curl https://api.mdkit.io/v1/convert/markdown-to-html \
-H "Authorization: Bearer mdk_abc123..."
```
## Scopes
| Scope | Capability |
| :----------- | :--------------------------------------- |
| `read` | Read your own account data |
| `convert` | Use conversion endpoints |
| `admin` | Manage tokens and settings |
## Token rotation
We recommend rotating tokens every 90 days. See the
[Rotation guide](./guides/rotate-tokens.md).
```
## Errors page
Centralize error handling in one page. Don't repeat error formats for every endpoint.
```markdown
# Errors
All errors return JSON with this shape:
```json
{
"error": {
"code": "invalid_markdown",
"message": "Request body missing 'markdown' field.",
"requestId": "req_abc123"
}
}
```
| Field | Description |
| :---------- | :------------------------------------------------- |
| `code` | Machine-readable error code (stable, safe to switch on). |
| `message` | Human-readable. Subject to change. |
| `requestId` | Include in support requests. |
## HTTP status codes
| Code | Meaning |
| :--- | :----------------------- |
| 200 | Success |
| 400 | Malformed request |
| 401 | Missing or invalid auth |
| 403 | Forbidden (wrong scope) |
| 404 | Resource not found |
| 409 | Conflict (e.g., duplicate) |
| 413 | Payload too large |
| 422 | Validation error |
| 429 | Rate limited |
| 500 | Internal server error |
| 503 | Temporarily unavailable |
```
## Rate limits page
```markdown
# Rate limits
Each token is limited to **100 requests per minute** by default. Larger
quotas are available on paid plans.
## Response headers
Every response includes:
| Header | Meaning |
| :---------------------- | :----------------------------------------- |
| `X-RateLimit-Limit` | Requests allowed per window |
| `X-RateLimit-Remaining` | Requests remaining in the current window |
| `X-RateLimit-Reset` | Unix timestamp when the window resets |
When you exceed the limit, you get a `429` with:
| Header | Meaning |
| :------------ | :------------------------------------------- |
| `Retry-After` | Seconds until you can retry |
## Handling rate limits
```javascript
async function callWithRetry(fn, maxRetries = 3) {
for (let i = 0; i <= maxRetries; i++) {
const res = await fn();
if (res.status !== 429) return res;
const wait = parseInt(res.headers.get("Retry-After") || "1", 10) * 1000;
await new Promise((r) => setTimeout(r, wait));
}
throw new Error("Rate limited after max retries");
}
```
```
## Versioning and deprecation
Be explicit. Don't surprise your users.
```markdown
# Versioning
The MDKit API is versioned in the URL: `/v1/...`, `/v2/...`.
## Active versions
| Version | Status | Released | EOL |
| :------ | :---------- | :---------- | :-------- |
| `v2` | Stable | 2026-01-15 | — |
| `v1` | Deprecated | 2025-03-10 | 2026-07-01 |
## Deprecation policy
- **Minimum 6 months** notice before a version is discontinued.
- Responses from deprecated endpoints include `Deprecation: true` and
`Sunset: <date>` headers.
- We send an email to all tokens that have used a deprecated endpoint
in the last 30 days, monthly until EOL.
```
## Documentation style rules that scale
- **One purpose per endpoint section.** Don't document 3 variants in one section. Create 3 sections.
- **Bold the word after a verb when describing behavior.** "The request **creates** a new user."
- **Avoid adjectives.** "Returns a user object" is better than "Returns a well-formed user object."
- **Show, then tell.** Code block first, explanation second.
- **Copy-paste-able.** Every code example should run as-is (replace token, ready).
- **Same schema, same table layout.** Consistency reduces cognitive load. Use the request/response table pattern from the template above everywhere.
- **Explicit types.** `string`, `number`, `boolean`, `object`, `array`, `null`. Not "text" or "some JSON."
- **Nullable fields.** Mark explicitly: `string | null`.
- **Default values in tables.** Include the default for every optional field.
## Tooling
### Source of truth
If you can afford it, maintain **OpenAPI** (or JSON Schema) as the single source of truth. Tools that consume it:
- **Redoc / ReDoc** — render OpenAPI as pretty HTML docs.
- **Stoplight** — interactive API docs with a try-it-out console.
- **Postman** — imports OpenAPI, generates collections.
- **SDK generators** — openapi-generator, NSwag.
Then *generate* Markdown reference docs from OpenAPI using tools like [widdershins](https://github.com/Mermade/widdershins) or [openapi-to-md](https://www.npmjs.com/package/openapi-to-md).
### Hosting
Options:
- **In-repo, rendered on GitHub** — zero setup. Works for most APIs.
- **Static site generator** — MkDocs, Docusaurus, Nextra. Nicer UX, search, theming.
- **Dedicated platforms** — ReadMe, GitBook, Mintlify. More features, paid.
### Keeping it in sync
The single biggest failure mode for API docs is drift. Strategies:
- **PR checklist item:** "Updated docs in `/docs` if the API changed."
- **CI linting:** fail the build if a new endpoint is added without a corresponding doc file.
- **Doc review in code review:** the reviewer checks docs along with code.
- **Automated generation** from OpenAPI — if your OpenAPI is the source of truth and drives CI, it can't drift.
## Real examples to study
If you want to see excellent API docs in the wild:
- [Stripe](https://stripe.com/docs/api) — the gold standard for clarity, examples, and navigation.
- [GitHub](https://docs.github.com/en/rest) — great structure, comprehensive.
- [Twilio](https://www.twilio.com/docs/usage/api) — clear language tabs.
- [Vercel](https://vercel.com/docs/rest-api) — minimal and fast.
Read 30 minutes of any of these and you'll see patterns worth stealing.
## Summary
- Answer the five questions (auth, endpoints, params, response, errors) before anything else.
- Use a consistent per-endpoint template. Copy the one above.
- Put errors and rate limits in centralized pages — don't repeat them.
- Keep docs in the repo, updated in the same PR as the code.
- Invest in OpenAPI as a source of truth if you have more than ~5 endpoints.
Need a starting template? Our [API Documentation template](/templates/api-documentation) gives you the skeleton to fill in.
Developer time is expensive. API docs that answer the basics without friction save hours per integration. That's the ROI.
Frequently Asked Questions
Should I write API docs in Markdown or OpenAPI/Swagger?+
Where should API docs live?+
How much detail should an API doc have?+
Should I include rate limits in the docs?+
How do I version API documentation?+
Keep reading
How to Write a Great GitHub README (With Template)
A complete guide to writing a README that gets stars, attracts contributors, and onboards users in seconds. Structure, tone, badges, screenshots, and a copy-paste template.
Using Markdown for Technical Writing
Why Markdown is the default format for technical writing in 2026, how to structure docs, and the patterns that keep large docs maintainable over time.
GitHub Flavored Markdown (GFM) Explained
What GitHub Flavored Markdown adds on top of standard Markdown: tables, task lists, autolinks, strikethrough, emoji, and platform-specific features. With examples you can use today.