H. API Development & Testing
Wasteology builds and consumes a growing number of HTTP APIs — the WDP customer API, CieTrade, QuickBooks, HubSpot, Prefect, n8n, and internal service endpoints — but has no standard for how those APIs are exercised, shared, or regression-tested. This chapter sets that standard: Bruno as the company API client, collections stored in-repo as version-controlled files, and API contract checks that run in CI like every other test.
H.1 — Current State
API testing today is ad hoc and split across tools. Two Postman collections are already committed as exported JSON — quickbooks-etl/qbo-api.postman_collection.json (20 requests) and prefect-azure-infrastructure/cietrade-api.postman_collection.json (13 requests) — plus a get_postman_tokens.py helper and Postman references in docs/hubspot-setup.md. At least one developer has moved to Bruno. There is no declared standard, no CI execution of any collection, and no rule for where collections live or how secrets are handled.
The committed Postman JSON is a tell: someone already felt the pull to get collections into the repo. But those files are lossy exports — the canonical collection still lives in Postman's cloud, so the in-repo copy drifts the moment anyone edits in the app. That is the exact "second source of truth outside git" anti-pattern the rest of this package works to eliminate.
H.2 — Recommended Standard: Bruno
Standardize on Bruno as the Wasteology API client. Bruno stores each request as a plain-text .bru file in a folder you commit to the repo — no cloud account, no export step, no drift.
| Factor | Bruno | Postman |
|---|---|---|
| Source of truth | .bru files in the API's repo — human-readable, diffable, PR-reviewable | Cloud workspace; committed JSON is a drifting export |
| Cost / access | Open-source, free, offline; no per-seat licensing; works for sandboxed contractors | Free tier limits collaborators/runs; team sync is paid |
| CI | bru CLI (@usebruno/cli) runs collections headlessly → contract tests as a pipeline step | Newman exists, but the canonical collection lives off-repo |
| Migration | Imports Postman collections directly | — |
| Templating | {{var}} variables + per-environment .bru files | {{var}} + cloud environments |
| Where Postman is stronger | — | Richer UI, mock servers, monitors, larger ecosystem |
This is not a preference call — it is the choice consistent with every other standard in this package: the source of truth lives in git, next to the code, reviewed via PR, and runnable in CI. Bruno gives API collections the same treatment as lat check, pytest, and docs-as-code.
Bruno is the recommended and (per this package's decision register) selected default. It is a reversible choice — collections are portable text, and Bruno imports/exports Postman format — so if a future need favors Postman's mock-server/monitor features for a specific product, that product can be documented as an exception rather than reversing the company default.
H.3 — In-Repo Collection Layout
Every repo that owns or consumes an API keeps its Bruno collection in that repo, versioned alongside the code it exercises.
<api-repo>/
└── bruno/
├── bruno.json # collection manifest (name, version, type)
├── environments/
│ ├── dev.bru # variable NAMES + non-secret defaults
│ └── prod.bru # variable NAMES only (values injected at run time)
├── Reference Data/ # folders → subdirectories
│ ├── Get Company Info.bru
│ └── ...
└── Write Operations/
└── ...
Rules:
- One collection per API, in the API's repo. The collection is reviewed in the same PR as the code that changes the endpoint.
- No secrets in
.brufiles or environment files. Environment files hold variable names with placeholders (token: {{QBO_ACCESS_TOKEN}}); real values come from the developer's local environment, Key Vault, or CI pipeline variables — never committed. This mirrors the secrets discipline in E — Access Governance and D — Agentic Workflows. - Collections are documentation. A well-named Bruno collection is the fastest way for a new developer (or contractor) to understand an API's surface — treat it as a first-class deliverable, not a scratchpad.
Directory name — bruno/ (tool-named, unambiguous) vs api-tests/ (tool-agnostic). Recommended: bruno/, since the tool is now a standard and the name signals what opens it. Revisit only if the tool ever changes.
H.4 — CI Contract Checks
Run the Bruno collection in CI so a breaking API change fails the build, the same way a failing unit test does.
# azure-pipelines.yml — API contract check stage
- script: |
npm install -g @usebruno/cli
bru run bruno --env dev --bail
displayName: "API contract checks (Bruno)"
env:
QBO_ACCESS_TOKEN: $(QBO_ACCESS_TOKEN) # injected secret, never in the .bru files
- Point contract checks at a dev or sandbox environment, never production (consistent with the contractor sandbox model in F and least-privilege in E).
- Use Bruno assertions/tests in the
.brufiles (status code, response shape) sobru runis a real gate, not just a smoke ping. - This makes API health a mechanical check — the same philosophy as
lat checkandpytestelsewhere in the standard.
Whether contract checks are blocking (fail the PR) or advisory (report only) at first. Recommended: advisory for the first sprint while collections gain assertions, then flip to blocking once each API's collection has meaningful tests.
H.5 — Migrating from Postman
The two existing Postman collections are the reference migration. A reusable converter lives at docs/strategy/standardization/tools/postman_to_bruno.py (Postman v2.1 JSON → Bruno .bru directory, stdlib-only).
Per collection:
- Run the converter:
python tools/postman_to_bruno.py <collection>.postman_collection.json <api-repo>/bruno. - Review the generated
.brufiles in a PR; confirm folders, methods, headers, query params, and auth converted correctly. - Move any secret variable values out of the collection into CI pipeline variables / Key Vault; the converter placeholder-izes anything that looks like a token.
- Add the
bru runCI stage (H.4). - Retire the Postman cloud copy and delete the committed
.postman_collection.jsonexport. Retireget_postman_tokens.py(its token flow becomes environment/CI variables).
In flight: qbo-api (quickbooks-etl) and cietrade-api (prefect-azure-infrastructure) are being converted as the first two migrations and the pattern for the rest. Both repos are GitHub-hosted, so the bruno/ directories are committed via their normal GitHub PR flow.
Timing of the Postman retirement. Recommended: run Bruno and Postman in parallel for one sprint per API (validate the converted collection against the live API), then delete the Postman export and cancel unused Postman seats.
H.6 — Where This Fits — "I'm Building a New API"
API testing is one stop on the new-API path, and the wg-navigator agent routes a developer through all of them:
- Scaffold the repo from
wg-template(A). - Build the API; add a
bruno/collection alongside the code (this chapter). - Wire the
bru runcontract check into CI (A branch policies + this chapter). - Request any DB/RBAC access the API needs (E).
- Publish the API reference to the internal docs site — or the CRM site if it is vendor-facing (C).
- If a contractor is building it, apply the external-team runbook and sandbox model (F).
This is exactly the kind of cross-cutting, "which processes apply to my scenario" question the navigator agent exists to answer — API development touches six of the eight standards at once.
Summary
| Decision | Recommendation | Status |
|---|---|---|
| API client | Bruno (git-native .bru, free, CLI in CI) | Selected default |
| Collection location | bruno/ directory in each API's repo | Adopt |
| Secrets | Never in .bru/env files; CI variables + Key Vault | Adopt immediately |
| CI | bru run contract check per API repo | Advisory first sprint → blocking |
| Migration | tools/postman_to_bruno.py; convert, validate, retire Postman | qbo-api + cietrade-api in flight |