>_ Analyst Engineering

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.

Cover for how to document an API, showing an OpenAPI snippet, an error catalogue table, and the seven documentation sections.

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 requirementsAPI contract (OpenAPI)API documentation
ReaderThe team building the APITools and developersConsumers integrating with it
WhenBefore and during the buildDuring the build, kept currentFrom first release, kept current
AnswersWhat must it do, and why?What exactly is the interface?How do I succeed with it?
FormatSpecification, stories, acceptance criteriaYAML or JSONRendered reference plus guides
Fails whenAmbiguous or untestableOut of sync with the codeCorrect 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.

SectionThe consumer’s questionWhat happens if it is missing
1. OverviewWhat does this API do, and what do its terms mean?Consumers misread statuses and misuse fields
2. Getting startedHow do I make one successful call right now?Every integration starts with a meeting
3. AuthenticationHow do I get and use credentials?Days lost on 401s and access requests
4. Endpoint referenceWhat exactly does each endpoint accept and return?Developers guess from examples
5. Flow guidesWhich calls, in which order, for my business journey?Correct endpoints wired into a wrong process
6. Error catalogueWhat went wrong, what do I do, can I retry?Duplicate payments or silent failures
7. Changelog and limitsWhat 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 201 response 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:

StatusMeaningFinal?
RCVDReceived, not yet validatedNo
ACCPAccepted: technical and business validation passedNo
ACSPSettlement in processNo
ACSCSettlement completed on the debtor sideYes
RJCTRejected, with a reason code explaining whyYes

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. supplierInvoice teaches more than example1.
  • Say what a status code means for this endpoint. A generic “Bad request” description wastes the field.
  • Use maxLength, pattern, and enum wherever 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?

HTTPCodeMeaningConsumer actionRetry?
400FIELD_REQUIREDA required field is missing; field names itFix the requestNo
400FIELD_TOO_LONGA field exceeds its maximum lengthFix the requestNo
400FIELD_FORMAT_INVALIDA field does not match its patternFix the requestNo
401UNAUTHENTICATEDToken missing, expired, or invalidObtain a new token, then retryAfter fixing
403INSUFFICIENT_SCOPEToken lacks payments:writeRequest the scopeNo
404PAYMENT_NOT_FOUNDNo payment with that id visible to youCheck the id and the environmentNo
409IDEMPOTENCY_KEY_REUSEDSame key, different bodyUse a new key for a new paymentNo
409PAYMENT_NOT_CANCELLABLECancellation requested after settlement startedUse the recall processNo
422IBAN_CHECKSUM_INVALIDIBAN fails checksum validationCorrect the IBAN with the payerNo
422AMOUNT_LIMIT_EXCEEDEDAmount above 100000.00Split, or use the high value channelNo
422SAME_DEBTOR_CREDITORDebtor and creditor IBAN are identicalFix the requestNo
429RATE_LIMITEDToo many requestsWait for Retry-After secondsYes
500INTERNAL_ERRORUnexpected server failureRetry with the same idempotency keyYes
503SERVICE_UNAVAILABLEPlanned or unplanned outageRetry with backoff and the same keyYes

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.

StatusReason codeMeaningConsumer action
RJCTAC01Account identifier incorrectCorrect the creditor details with the payer
RJCTAC04Account closedObtain new account details
RJCTAM04Insufficient fundsNotify 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

  1. POST /oauth/token with client credentials. Store access_token; it expires after expires_in seconds.
  2. POST /v1/payments with a new UUID in Idempotency-Key. On 201, store paymentId and endToEndId. On a network timeout, retry with the same key.
  3. Wait for a webhook: payment.settled or payment.rejected. Verify the signature header before trusting the body.
  4. If no webhook arrives within 10 minutes, GET /v1/payments/{paymentId} and act on the status.
  5. On ACSC, mark the invoice paid. On RJCT, read reasonCode and follow the rejection table.
  6. Once per day, GET /v1/payments?settledDate=YYYY-MM-DD to 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:

  1. Prerequisites. What they need before starting: a sandbox account, a client id.
  2. Get a token. One copy-pasteable curl command, with placeholders clearly marked.
  3. Make the first call. One curl command that creates something harmless in the sandbox.
  4. See the result. The expected response, trimmed, with the two fields that matter highlighted.
  5. 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

  1. What is an API and how it works
  2. Your first API collection in Bruno and Postman: requests, environments, variables, and secrets
  3. How to analyze an API: capability, data, behavior, limits, and change
  4. How to document an API (you are here)
  5. How to write API test cases: deriving a complete suite from one endpoint
  6. Chaining API requests with JavaScript: variables, scripts, polling, and a full Stripe flow
  7. API proof of concept and demos: POCs and demos that settle decisions
  8. 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.

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.