How to write content
Where files live, how to import from Postman, how to override generated endpoints.
Content folders
Four dirs under content/. Each has one job.
| Folder | Source of truth | Edit by hand? |
|---|---|---|
content/docs/{locale}/ | Guides, narrative pages (this file). Sidebar = meta.json order. | Yes |
content/generated/{locale}/ | Endpoint specs imported from Postman. <slug>.generated.json. | No. Script overwrites. |
content/archive/{locale}/ | Endpoints removed from Postman. Marked deprecated: true. | No |
content/manual/{locale}/ | Per-endpoint overrides + extras. <slug>.mdx. Slug matches generated file. | Yes |
content/meta/ | Category list (categories.json). Not localized. | Yes |
Slug rule: endpointId(method, route) → method-route-kebab. Example: DELETE /prices/{price_id} → delete-prices-price-id. See lib/parser/ids.ts.
Locale rule: locale is always the first segment — en/ for the default too, never a .th filename suffix. A missing th file falls back to en at load time.
Frontmatter
Guide page (content/docs/{locale}/*.mdx)
---
title: Expand
description: Short one-liner.
order: 30 # sidebar sort, lower = top
category: Guides # optional grouping
---Page also must appear in the meta.json pages array of its locale dir, else sidebar hide.
Manual override (content/manual/{locale}/<slug>.mdx)
---
id: delete-prices-price-id # MUST match generated slug
title: Delete price # overrides generated name
description: ""
notes: []
warnings: []
aliases: []
examples: []
---Optional fields (override generated):
request:
headers:
- { key: Authorization, value: "Bearer {{token}}", description: "..." }
query:
- { key: dry_run, value: "true" }
body:
mode: raw
language: json
raw: |
{ "reason": "duplicate" }
response:
- name: "200 OK"
code: 200
language: json
body: |
{ "deleted": true }
headers: []Postman vars
Edit lib/constants/postman-vars.ts. Add key → value.
export const POSTMAN_VARS: Record<string, string> = {
base_url: 'http://localhost:5002',
api_version: 'api/v1',
};Rules:
{{key}}in url/headers/body → replaced if key in map.- Unknown key → kept verbatim (e.g.
{{price_id}}). Path params stay as placeholders. - Substituted once by
lib/render/snippets/index.ts, then shared by every language snippet (cURL, JavaScript, Node axios, Python, Go, PHP, Java, C#, Ruby) rendered byExampleRequest.
Import from Postman
Script writes generated/ and archive/ only. manual/ is hand-written and merged at read time. Archive branch dimmed = deprecated, still renders but excluded from sidebar.
docs/postman/Wallet.json is generated, not hand-edited. It is built from the code contract
(backend/wallet/app/testdata/api_contract.golden, produced by DUMP=1 go test ./app -run TestAPIContractGolden) plus docs/postman/overlay/wallet.json, which holds the prose, folder names,
example values and saved responses. make postman-check fails if the committed collection does not match
the code — see docs/architecture/config-invariants.md.
To change an endpoint's docs, edit the overlay (or the Go handler), then:
python3 scripts/postman_build.py --service walletThen import into the docs site:
cd frontend/docs
bun scripts/import-postman.ts # write changes
bun scripts/import-postman.ts --dry-run # preview only
bun scripts/import-postman.ts --source path/to/other.jsonBehavior:
- New endpoint →
createdincontent/generated/en/, plus an untranslated copy in every other locale dir. postmanHashdiffer →updated(overwritesenonly; translated copies are left alone).- Hash same →
skipped. - ID gone from collection → every locale moved to
content/archive/{locale}/, markeddeprecated.
Manual files never touched. Safe to re-run.
Scaffold one manual file
bun scripts/scaffold-manual.ts DELETE /prices/{price_id} # en (default)
bun scripts/scaffold-manual.ts DELETE /prices/{price_id} th # th overrideCreates content/manual/en/delete-prices-price-id.mdx with id frontmatter pre-filled. Errors if file exists.
Override generated content
Edit content/manual/{locale}/<slug>.mdx. Merge rules (see lib/content/load-merged.ts):
Override header/query value
Match by key. Only listed keys override. Others untouched.
request:
headers:
- key: Authorization
value: "Bearer prod-token-here"
description: "Use rotated key from vault"Generated header keeps fields you omit. Set value only → description stays from generated.
Override request body
Whole body object replaced (not field-level merge inside body):
request:
body:
language: json
raw: |
{ "reason": "fraud", "notify": true }Override responses
response array fully replaces generated array. Want both? Copy generated entries + add yours.
response:
- name: "200 OK"
code: 200
language: json
body: |
{ "id": "price_123", "deleted": true }
- name: "404 Not Found"
code: 404
language: json
body: |
{ "error": "price_not_found" }Override title/description
title: "Cancel scheduled price change"
description: "Soft-delete a future-dated price."Extend generated content
Sections rendered on endpoint page but not in Postman:
notes:
- "Idempotent. Safe to retry."
- "Quota: 100/min per service account."
warnings:
- "Cascade deletes active subscriptions."
aliases:
- "remove-price"MDX body below frontmatter renders as Overview section above Request block:
---
id: delete-prices-price-id
title: Delete price
---
## When to use
Only call after subscription migration completes. Prefer `PATCH /prices/{id}` with `archived: true` for soft cases.Add category
Edit content/meta/categories.json. id matches folderPath segment from Postman.
{
"categories": [
{ "id": "wallet", "title": "Wallet", "order": 2 }
]
}Sidebar
Endpoint sidebar = auto from folderPath in generated files. Reorder = re-organize Postman folders + re-import. Guide pages = listed in content/docs/meta.json pages.
Full component list: MDX blocks reference.