How to Document an API: What Analysts Write So Developers Integrate Without a Call
Written by Ahmed at Analyst Engineering, a Senior Technical Business Analyst with 10+ years in banking and payments delivery.
Key takeaways
- API documentation is written for the consumer who must integrate without a meeting: it combines the machine-readable contract with the narrative that explains business meaning, flows, errors, and change.
- Good API documentation has seven sections: overview, getting started, authentication, endpoint reference, flow guides, error catalogue, and changelog with limits.
- The analyst's highest-value contribution to an OpenAPI file is the description fields: what a status really guarantees, which rule a field enforces, and what the consumer must do next.
- An error catalogue must say whether each error is safe to retry; that single column prevents duplicate payments and pointless retry storms.
- Documentation stays true only if it is tested: lint the OpenAPI file with Spectral or Redocly, run the examples from a Bruno or Postman collection, and fail the build when either breaks.
API documentation is what lets a developer on another team integrate with your API without booking a call. It combines the machine-readable contract, usually an OpenAPI file, with the narrative a contract cannot carry: what each status really guarantees, which calls make up a business journey, what to do about each error, and how the API will change.
To document an API, write seven sections: an overview with business concepts, a getting started guide that reaches a first successful call in minutes, authentication, an endpoint reference generated from OpenAPI, flow guides for each business journey, an error catalogue with retry guidance, and a changelog with limits and versioning policy. Keep it in the repository next to the code, and test the examples so the documentation fails the build before it misleads a consumer.
APIs for Analysts, part 4 of 8. Previous: Part 3, how to analyze an API. Next: Part 5, how to write API test cases. All parts: series overview.
The running example is a payments API for EUR credit transfers, the same one used for test cases in Part 5, so you can see the documentation and the tests line up field by field. The full method, with templates for every section below, is in API Documentation from Scratch.
How is API documentation different from API requirements and the contract?
Three artifacts describe an API, and teams blur them constantly. Each has a different reader and a different moment.
| API requirements | API contract (OpenAPI) | API documentation | |
|---|---|---|---|
| Reader | The team building the API | Tools and developers | Consumers integrating with it |
| When | Before and during the build | During the build, kept current | From first release, kept current |
| Answers | What must it do, and why? | What exactly is the interface? | How do I succeed with it? |
| Format | Specification, stories, acceptance criteria | YAML or JSON | Rendered reference plus guides |
| Fails when | Ambiguous or untestable | Out of sync with the code | Correct but unusable |
Requirements come first, and writing them well is covered in how to write API requirements. The contract is the precise interface, and reading an API contract shows how to navigate one. Documentation is the layer that makes the contract usable by someone who was not in the room. That is why it is analyst territory: it is translation between business meaning and technical interface, which is the analyst’s whole job.
Look at the public APIs developers praise and the pattern holds. Stripe pairs every reference entry with runnable examples and guides organized around business tasks such as accepting a payment. GitHub publishes its full OpenAPI description in the github/rest-api-description repository and builds its reference from it, and Stripe does the same in stripe/openapi. Contract plus narrative, generated where possible, written by hand where it matters.
What sections does good API documentation need?
Seven. Skip one and a predictable support question appears in your inbox.
| Section | The consumer’s question | What happens if it is missing |
|---|---|---|
| 1. Overview | What does this API do, and what do its terms mean? | Consumers misread statuses and misuse fields |
| 2. Getting started | How do I make one successful call right now? | Every integration starts with a meeting |
| 3. Authentication | How do I get and use credentials? | Days lost on 401s and access requests |
| 4. Endpoint reference | What exactly does each endpoint accept and return? | Developers guess from examples |
| 5. Flow guides | Which calls, in which order, for my business journey? | Correct endpoints wired into a wrong process |
| 6. Error catalogue | What went wrong, what do I do, can I retry? | Duplicate payments or silent failures |
| 7. Changelog and limits | What changed, what will change, what are the limits? | Breaking changes discovered in production |
What goes in the overview?
One paragraph on what the API does and does not do, then a glossary of the business terms that appear in fields. For the payments API:
The Payments API initiates single EUR credit transfers and reports their progress. A payment is accepted synchronously and settled asynchronously: a
201response means the payment passed validation, not that money has moved. Final outcomes arrive by webhook or by polling. The API does not support batch payments, other currencies, or cancellation after settlement has started.
Then the glossary, which prevents the most expensive misunderstanding in payments, the meaning of a status:
| Status | Meaning | Final? |
|---|---|---|
RCVD | Received, not yet validated | No |
ACCP | Accepted: technical and business validation passed | No |
ACSP | Settlement in process | No |
ACSC | Settlement completed on the debtor side | Yes |
RJCT | Rejected, with a reason code explaining why | Yes |
The codes follow ISO 20022, and their exact meaning per scheme is laid out in ISO 20022 payment status codes. The documentation’s job is to state, for this API, which ones the consumer can see and which are final.
How do you document an endpoint in OpenAPI?
Let developers own the structure and let the analyst own the descriptions. Here is the create endpoint, with the business rules written where consumers will actually read them:
openapi: 3.1.0
info:
title: Payments API
version: 1.4.0
servers:
- url: https://sandbox.payments.example.com
description: Sandbox. No real money moves.
paths:
/v1/payments:
post:
operationId: createPayment
summary: Submit a EUR credit transfer
description: |
Validates and accepts one credit transfer. A 201 means the payment
passed validation and has status ACCP. It does NOT mean the funds
have settled; wait for a payment.settled or payment.rejected webhook,
or poll GET /v1/payments/{paymentId}.
parameters:
- name: Idempotency-Key
in: header
required: true
description: |
Client-generated UUID. Replaying the same key with the same body
within 24 hours returns the original response and creates nothing.
Reusing a key with a different body returns 409
IDEMPOTENCY_KEY_REUSED.
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentRequest'
examples:
supplierInvoice:
summary: Pay a supplier invoice
value:
endToEndId: E2E-INV-88213
instructedAmount: { currency: EUR, value: "1250.00" }
debtor: { name: ACME BV, iban: NL91ABNA0417164300 }
creditor: { name: Beta GmbH, iban: DE89370400440532013000 }
remittanceInformation: INVOICE 88213
responses:
'201':
description: Accepted for processing (status ACCP). Settlement is asynchronous.
content:
application/json:
schema:
$ref: '#/components/schemas/Payment'
'400':
description: The body is structurally invalid. Fix the request; do not retry unchanged.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'409':
description: Idempotency key reused with a different body.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'422':
description: Structurally valid but breaks a business rule. See the error catalogue.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
PaymentRequest:
type: object
required: [endToEndId, instructedAmount, debtor, creditor]
properties:
endToEndId:
type: string
minLength: 1
maxLength: 35
description: |
Your reference. Returned unchanged in every status update and
passed to the creditor's bank. Must be unique per debtor.
instructedAmount:
type: object
required: [currency, value]
properties:
currency:
type: string
enum: [EUR]
value:
type: string
pattern: '^[0-9]{1,6}\.[0-9]{2}$'
description: |
Decimal string with exactly two decimals, sent as a string to
avoid floating point rounding. Greater than 0.00 and at most
100000.00 per payment (422 AMOUNT_LIMIT_EXCEEDED above that).
debtor:
$ref: '#/components/schemas/Party'
creditor:
$ref: '#/components/schemas/Party'
remittanceInformation:
type: string
maxLength: 140
description: Free text shown on the creditor's statement.
Party:
type: object
required: [name, iban]
properties:
name:
type: string
minLength: 1
maxLength: 70
iban:
type: string
description: |
Validated by checksum. A structurally valid IBAN with a wrong
checksum returns 422 IBAN_CHECKSUM_INVALID with the field path.
Error:
type: object
required: [errors, traceId]
properties:
errors:
type: array
items:
type: object
required: [code, message]
properties:
code: { type: string, examples: [FIELD_TOO_LONG] }
field: { type: string, examples: [creditor.name] }
message: { type: string }
traceId:
type: string
description: Quote this value in every support request.
The Payment response schema and the security scheme are omitted for length; they follow the same pattern.
Read the descriptions again, because they are the point. “A 201 does NOT mean the funds have settled.” “Reusing a key with a different body returns 409.” “Must be unique per debtor.” None of that is expressible as schema, all of it decides whether an integration is correct, and all of it is knowledge the analyst holds. Put it in the file, not in a separate Word document that will be out of date by the next sprint.
A few OpenAPI habits that make documentation better:
- Give every request body a named, realistic example. Consumers copy examples before they read schemas.
supplierInvoiceteaches more thanexample1. - Say what a status code means for this endpoint. A generic “Bad request” description wastes the field.
- Use
maxLength,pattern, andenumwherever the rule is real. Tools, mocks, and test generators read them. - Describe money as a decimal string with its currency. Floating point amounts are a defect waiting to happen.
How do you write an API error catalogue?
As a table the consumer can build their error handling from, with one column most catalogues forget: can I retry?
| HTTP | Code | Meaning | Consumer action | Retry? |
|---|---|---|---|---|
| 400 | FIELD_REQUIRED | A required field is missing; field names it | Fix the request | No |
| 400 | FIELD_TOO_LONG | A field exceeds its maximum length | Fix the request | No |
| 400 | FIELD_FORMAT_INVALID | A field does not match its pattern | Fix the request | No |
| 401 | UNAUTHENTICATED | Token missing, expired, or invalid | Obtain a new token, then retry | After fixing |
| 403 | INSUFFICIENT_SCOPE | Token lacks payments:write | Request the scope | No |
| 404 | PAYMENT_NOT_FOUND | No payment with that id visible to you | Check the id and the environment | No |
| 409 | IDEMPOTENCY_KEY_REUSED | Same key, different body | Use a new key for a new payment | No |
| 409 | PAYMENT_NOT_CANCELLABLE | Cancellation requested after settlement started | Use the recall process | No |
| 422 | IBAN_CHECKSUM_INVALID | IBAN fails checksum validation | Correct the IBAN with the payer | No |
| 422 | AMOUNT_LIMIT_EXCEEDED | Amount above 100000.00 | Split, or use the high value channel | No |
| 422 | SAME_DEBTOR_CREDITOR | Debtor and creditor IBAN are identical | Fix the request | No |
| 429 | RATE_LIMITED | Too many requests | Wait for Retry-After seconds | Yes |
| 500 | INTERNAL_ERROR | Unexpected server failure | Retry with the same idempotency key | Yes |
| 503 | SERVICE_UNAVAILABLE | Planned or unplanned outage | Retry with backoff and the same key | Yes |
The retry column is where documentation prevents incidents. A consumer who retries a 500 with a new idempotency key can create a duplicate payment. A consumer who retries a 422 forever creates a retry storm that never succeeds. One column, two production incidents avoided.
Then document the second kind of failure separately: rejections that happen after acceptance and arrive asynchronously, as a status change with a reason code.
| Status | Reason code | Meaning | Consumer action |
|---|---|---|---|
RJCT | AC01 | Account identifier incorrect | Correct the creditor details with the payer |
RJCT | AC04 | Account closed | Obtain new account details |
RJCT | AM04 | Insufficient funds | Notify the debtor; retry only after funding |
A consumer handles a 422 in the code that sends the request and an RJCT in the code that receives webhooks. Mixing them in one table guarantees one of the two paths goes unbuilt. The complete code set is in ISO 20022 reason codes, and turning codes into customer messages is reason code mapping.
How do you document a flow, not just endpoints?
An endpoint reference lists the pieces; a flow guide shows how they fit a business journey. Write one guide per journey, as numbered steps with the call, the outcome, and the decision the consumer makes.
Flow guide: pay a supplier invoice and confirm settlement
POST /oauth/tokenwith client credentials. Storeaccess_token; it expires afterexpires_inseconds.POST /v1/paymentswith a new UUID inIdempotency-Key. On201, storepaymentIdandendToEndId. On a network timeout, retry with the same key.- Wait for a webhook:
payment.settledorpayment.rejected. Verify the signature header before trusting the body. - If no webhook arrives within 10 minutes,
GET /v1/payments/{paymentId}and act on the status. - On
ACSC, mark the invoice paid. OnRJCT, readreasonCodeand follow the rejection table. - Once per day,
GET /v1/payments?settledDate=YYYY-MM-DDto reconcile against your ledger.
Add a sequence diagram beside it. Consumers understand the asynchronous gap between steps 2 and 3 in one glance at a diagram, and in three paragraphs of prose otherwise. Drawing one well is covered in sequence diagrams for business analysts, and step 6 exists because of reconciliation design.
How do you write a getting started guide?
Aim for one outcome: a consumer’s first successful call within five minutes of opening the page. Structure it exactly like this:
- Prerequisites. What they need before starting: a sandbox account, a client id.
- Get a token. One copy-pasteable curl command, with placeholders clearly marked.
- Make the first call. One curl command that creates something harmless in the sandbox.
- See the result. The expected response, trimmed, with the two fields that matter highlighted.
- Next steps. Links to the flow guide and the error catalogue.
Then test it the only way that works: give it to someone who has never seen the API and time them without helping. Every question they ask is a defect in the guide. On one programme I watched a partner developer spend forty minutes on step 2 because the documented token URL pointed at production; that one line was the most expensive typo in the project.
Publish a matching collection too. A Bruno collection committed next to the OpenAPI file, or a Postman collection linked from the page, turns the getting started guide into one click. Bruno even stores Markdown documentation on each request in a docs field, so the collection explains itself. Building that collection is Part 2 of this series.
How do you keep API documentation true?
Treat it as code: store it in the repository, review it in pull requests, and test it in the pipeline.
Lint the contract. Both of these catch missing descriptions, undefined responses, and invalid schemas:
# Spectral, with a .spectral.yaml containing: extends: ["spectral:oas"]
npx @stoplight/spectral-cli lint openapi.yaml
# Redocly
npx @redocly/cli lint openapi.yaml
Render the reference from the contract, never by hand:
npx @redocly/cli build-docs openapi.yaml -o docs/index.html
Run the examples. Import the OpenAPI file into Bruno, add assertions to each documented example, and run it in CI. If the documented 201 example now returns 422, either the code or the documentation is wrong, and the build should say so before a consumer does. Contract-level checks that go further are covered in contract testing.
Mock before build. Serve the documented examples as a working fake API so consumers can start integrating early:
npx @stoplight/prism-cli mock openapi.yaml
That mock is also the fastest way to build a demo against an API that does not exist yet, which is where Part 7 picks up.
An API documentation review checklist
Before publishing, check the list below. To review the design itself before it is built, use the lenses in API design review.
- The overview says what the API does not do.
- Every status in the glossary says whether it is final.
- Getting started reaches a successful call in five minutes, tested on a newcomer.
- Every request body has a realistic, named example.
- Every field with a rule has
maxLength,pattern,enum, or a description stating it. - Every error has a code, a consumer action, and a retry answer.
- Asynchronous rejections are documented separately from synchronous errors.
- Each business journey has a flow guide with a diagram.
- Rate limits, pagination, and versioning policy are stated with numbers.
- The changelog lists every change with a date and whether it is breaking, using the classification in API versioning and breaking changes.
- The OpenAPI file lints clean and the examples run green in CI.
The APIs for Analysts series
- What is an API and how it works
- Your first API collection in Bruno and Postman: requests, environments, variables, and secrets
- How to analyze an API: capability, data, behavior, limits, and change
- How to document an API (you are here)
- How to write API test cases: deriving a complete suite from one endpoint
- Chaining API requests with JavaScript: variables, scripts, polling, and a full Stripe flow
- API proof of concept and demos: POCs and demos that settle decisions
- The analyst who can send a request: why it is an edge, and a 30-day plan
Beyond the core series, the APIs for Analysts learning path organizes companion articles by level: the API glossary and troubleshooting failed requests for beginners, webhooks and GraphQL at intermediate level, and API design review, versioning and breaking changes, API security testing, and API tests in CI for advanced analysts.
The takeaway
API documentation exists so a consumer can integrate without a meeting. It needs seven sections: overview, getting started, authentication, reference, flow guides, error catalogue, and changelog with limits. The analyst’s biggest contribution is meaning: status semantics, field rules, and consumer actions written directly into the OpenAPI descriptions, plus flow guides and an error catalogue that says what is safe to retry. Keep it in git, lint it, render it from the contract, and run its examples in CI, because documentation nobody tests drifts within a sprint.
The complete method, with a template for each section, is in API Documentation from Scratch. If you are documenting an API right now and want a reviewer who has shipped payment API documentation to partners, book a 1:1 Tech BA Coaching Call, or browse everything at The Tech BA Toolkit.
Ahmed is a Senior Technical Business Analyst with 10+ years in banking and payments. He builds practical guides and tools for analysts at The Tech BA Toolkit.
Tags: API Documentation, OpenAPI, Technical Writing, Payments, Business Analysis
About the author
Analyst Engineering is written by Ahmed, a Senior Technical Business Analyst with 10+ years of banking and payments delivery experience: ISO 20022 and SWIFT messaging, payments API integration, Kafka event validation, and production support. Every article comes from real delivery work, and each one is reviewed and updated as tools and standards change.
Related articles
- How to Write API Requirements That Developers Can Actually Build Write API requirements the right way: endpoint, method, request and response schema, status codes, error contracts, and testable acceptance criteria. With examples.
- Reading an API Contract: OpenAPI Without a Developer How an analyst reads an API contract: endpoints, methods, request and response schemas, status codes, and OpenAPI structure. Understand any API without asking a developer.
- How to Analyze an API: The Analyst's Method Before Anyone Writes Integration Code A method for analyzing an API before integration: capability mapping, field-level data mapping, failure behavior, limits, versioning, and a fit-gap worksheet.
- How to Write API Test Cases: 40 Tests Derived From One Endpoint How to write API test cases from the contract: a six-source derivation method, 40 worked cases for one payment endpoint, and data-driven automation in Bruno.
Free account
Practice on the Labs, keep your progress
A free account, no password: an email link signs you in. It saves your steps and self-assessments on the Labs, shows your missions on a dashboard, unlocks the solutions, and, if you tick the box, sends you new missions and articles when they ship.
Your email is used to sign you in. Nothing else, unless you ask. Privacy.