Skip to content

Commit dfdb983

Browse files
docs(api): sync API reference with the b3 router (#153)
Signed-off-by: Arnob kumar saha <arnob@appscode.com>
1 parent 1530f5d commit dfdb983

16 files changed

Lines changed: 2092 additions & 86 deletions

File tree

‎.claude/commands/api-docs-sync.md‎

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
---
2+
description: Scan the b3 API server against docs/platform/api and update the docs to match
3+
argument-hint: "[path to b3 checkout] [--report-only]"
4+
allowed-tools: Bash, Read, Edit, Write, Grep, Glob
5+
---
6+
7+
# Sync `docs/platform/api` with the b3 API server
8+
9+
`$ARGUMENTS`
10+
11+
`b3` is the Go API server behind these docs. This command finds every place the
12+
two have drifted and fixes the docs. Default b3 checkout:
13+
`../b3` relative to this repo (i.e. `go.bytebuilders.dev/b3`). If `--report-only`
14+
is passed, produce the ranked findings list and stop — change nothing.
15+
16+
Docs never drive code: **b3 is always the source of truth.** Never edit b3 to
17+
match the docs, and never invent an endpoint the router does not register.
18+
19+
## 1. Extract the route table
20+
21+
```bash
22+
B3=${1:-../b3}
23+
python3 hack/api-doc-sync/extract_routes.py "$B3/routers/api/v1" > /tmp/routes.txt
24+
python3 hack/api-doc-sync/compare.py /tmp/routes.txt docs/platform/api \
25+
docs/platform/api/openapi.yaml > /tmp/api-diff.txt
26+
```
27+
28+
`extract_routes.py` walks the registration call graph from `RegisterRoutes` and
29+
`RegisterMarketplaceServiceRoutes`, so a helper called inside
30+
`m.Group("/user", …)` correctly gets the `/user` prefix. Read its docstring
31+
before trusting an odd-looking path.
32+
33+
**Baseline:** the last full run extracted **547 routes** (545 unique
34+
method+path) and ended with complete md and openapi.yaml coverage.
35+
36+
**Sanity gate:** if the route count dropped sharply versus the baseline, or many
37+
paths look truncated, the extractor has stopped understanding a new registration
38+
style (a new `m.Combo` chain shape, a `m.Group` whose path is a constant, a
39+
helper invoked as `pkg.Register(m)` rather than `register(m)`). Fix the
40+
extractor first — a silently short route table produces confidently wrong docs.
41+
42+
## 2. Triage the diff
43+
44+
Work the sections in this order; the first three are actionable, the last is
45+
advisory.
46+
47+
1. **in openapi.yaml but NOT in code** — a documented path that no longer
48+
exists, or a *wrong* path. Before deleting anything, grep the code for the
49+
handler name: a "missing" path is usually a real endpoint under a different
50+
prefix (a helper moved inside another `m.Group`), and the fix is to rename the
51+
path, not drop it.
52+
2. **in CODE but NOT in md** — undocumented endpoints. Write them up (step 3).
53+
3. **in CODE but NOT in openapi.yaml** — add the path/operation to the spec.
54+
4. **in md but NOT in code** — advisory. Overview tables put several verbs on one
55+
line (`| GET/POST/DELETE |`) and curl examples contain concrete values, so
56+
most entries here are noise. Only chase one if it names a plausible endpoint
57+
that `grep` cannot find in b3 at all.
58+
59+
Known-good false positive — leave it alone:
60+
61+
- `ANY /*` — the `/api/v1` catch-all 404 handler (`miscellaneous.go`). It is not
62+
an endpoint and is deliberately absent from the docs and the spec. As of the
63+
last run it is the **only** entry in both code→md and code→openapi.
64+
65+
`compare.py` already resolves the doc conventions this repo uses: absolute
66+
headings, headings relative to a page's declared root ("All routes on this page
67+
are rooted at `/api/v1/user/contracts`"), several verbs sharing one path
68+
(`PUT · PATCH /x`, `| POST/GET/DELETE |`), `/api/v1`-prefixed table entries, and
69+
abbreviated `### GET .../kubeDb/views/x` headings (matched as a path suffix). If a
70+
new page invents another convention, teach the tool rather than rewriting the
71+
page — but a page whose relative headings state no root cannot be matched, so
72+
that page does need a root sentence.
73+
74+
For each surviving finding, verify against the source before writing a word:
75+
open the `file:line` the extractor printed, read the handler, and derive the
76+
request/response shape from the bound payload type (`binding.Json(X{})` /
77+
`bind(X{})`) and what the handler actually writes (`ctx.JSON(...)`). Cite what
78+
you read. If a shape cannot be determined from the code, say so on the page
79+
instead of inventing fields.
80+
81+
## 3. Update the docs
82+
83+
- Put each endpoint on the page for its API group — the group table in
84+
`docs/platform/api/README.md` maps base paths to pages. Match the
85+
surrounding page's existing section style exactly (heading form, auth line,
86+
path/query parameter tables, JSON examples, `Errors:` line, `curl` block).
87+
- Respect each page's declared path root: if the page says paths are relative to
88+
`/api/v1/user/contracts`, write `### GET /{id}`, not the absolute path.
89+
- Middleware → prose mapping, as the existing pages use it: `reqToken()` →
90+
"token required"; `reqSiteAdmin()` / `authzCheck(...:site_admin)` → site admin;
91+
`reqOrgFromQuery()` → resolves the org from `?org=`; `reqClusterAssignment()`
92+
→ owner+cluster resolved and a Kubernetes client built; `authzCheck(X, "perm")`
93+
→ name the permission string.
94+
- Note availability when the registration is conditional in
95+
`routers/api/v1/api.go`: `setting.AppsCodeHosted` → "AppsCode-hosted only";
96+
`setting.IsBillingEnabled()` → "billing-enabled deployments only".
97+
- Only add a `> **Verified:** …` note if you actually ran the request against a
98+
live deployment in this session. Never copy one from another endpoint.
99+
- Update `docs/platform/api/openapi.yaml` alongside the md: add/rename the path,
100+
reuse existing `components/schemas` and `parameters` rather than duplicating,
101+
and tag the operation with the same tag its group's siblings use.
102+
- If the API-group set itself changed (a whole new `register*APIs` function),
103+
add a row to the group table and a section page — do not bury a new group
104+
inside an unrelated page.
105+
106+
## 4. Regenerate and verify
107+
108+
```bash
109+
python3 hack/api-doc-sync/refresh_reference.py # re-inlines the spec into reference/api.html
110+
python3 -c "import yaml;yaml.safe_load(open('docs/platform/api/openapi.yaml'))"
111+
liche -p -h -l -s <each changed md file> # exactly as CI runs it
112+
python3 hack/api-doc-sync/compare.py /tmp/routes.txt docs/platform/api \
113+
docs/platform/api/openapi.yaml | head -40
114+
```
115+
116+
The `liche` invocation must keep `-s`; this repo's links intentionally carry one
117+
extra `../` (see `CLAUDE.md`). Never "fix" a relative link to match the on-disk
118+
path: a sibling page in the same directory is `../sibling.md`, and a page in
119+
another directory is `../../other-dir/page.md`.
120+
121+
`docs/platform/api/README.md` fails this check by design — its links use
122+
non-standard paths, which is why CI's liche fork passes `-i '^README\.md$'`. Add
123+
that flag when scanning recursively; the locally installed `liche` may not have
124+
it, in which case just skip `README.md`.
125+
126+
Re-run `compare.py` at the end and confirm the code→md and code→openapi lists
127+
contain nothing but the known false positives above.
128+
129+
## 5. Report
130+
131+
Give a ranked summary: wrong paths first, then missing endpoints, then spec-only
132+
changes. For anything you chose not to change, say why in one line. Note the
133+
route count so the next run can compare.

‎docs/platform/api/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ The server supports several authentication mechanisms:
8080
| Session cookie | Web console | Cookie-based sign-in; CSRF-protected |
8181
| Personal access token / Bearer token | API clients, CLI | `Authorization: token <t>`, `?token=`, `?access_token=` |
8282
| Basic auth | Token management endpoints | With optional OTP (2FA) |
83-
| OAuth2 / OIDC | SSO; the KubeDB Platform API Server is both provider and consumer | `/login/oauth/*`, `/.well-known/openid-configuration` |
83+
| OAuth2 / OIDC | SSO; the KubeDB Platform API Server is both provider and consumer | `/accounts/login/oauth/*`, `/accounts/.well-known/openid-configuration` |
8484
| LDAP / PAM | Enterprise sign-in sources | Configured by site admins |
8585
| 2FA / WebAuthn | User accounts | TOTP, scratch tokens, security keys |
8686
| License-based auth | Member clusters | Clusters authenticate with issued licenses / cluster tokens |

‎docs/platform/api/billing-dashboard/usage-reports.md‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,73 @@ Common path parameters for the `summary` routes:
4545
| `year` | string | Four-digit report year (e.g. `2026`). |
4646
| `month` | string | Report month (e.g. `06` or `June`, per the generated-months list). |
4747

48+
## Monthly summary
49+
50+
### GET /dashboard/monthly-summary/{resourceType}
51+
52+
Returns the per-month usage totals for one product, newest month first, with
53+
month-over-month change percentages.
54+
55+
- **Auth:** token; site-admin (`view_usage_analytics:site_admin`). Requires `?org=`.
56+
- **Path parameters:**
57+
58+
| Name | Type | Description |
59+
|---|---|---|
60+
| `resourceType` | string | Product alias: `kubedb`, `stash`, `vault`, or `voyager`. |
61+
62+
- **Query parameters:**
63+
64+
| Name | Type | Required | Description |
65+
|---|---|---|---|
66+
| `org` | string | yes | Organization slug. |
67+
| `period_start` | string | no | First month to include, `YYYY-MM`. Defaults to five months before the current month (a six-month window). |
68+
| `period_end` | string | no | Last month to include, `YYYY-MM`. Only read when `period_start` is given; defaults to the current month. |
69+
70+
When an explicit period is given it is clamped to the months that actually have a
71+
generated summary; if it overlaps none, the response comes back with an empty
72+
`months` array. The requested range must not exceed **24 months**.
73+
74+
- **Response:** `200 OK` — a `MonthlySummaryResponse`:
75+
76+
```json
77+
{
78+
"resourceType": "kubedb",
79+
"months": [
80+
{
81+
"year": 2026,
82+
"month": 6,
83+
"cpuCoreMonth": 12.5,
84+
"memoryGiBMonth": 48.25,
85+
"instances": 7,
86+
"cpuChangeFromPrev": 4.2,
87+
"memoryChangeFromPrev": -1.8
88+
},
89+
{
90+
"year": 2026,
91+
"month": 5,
92+
"cpuCoreMonth": 12.0,
93+
"memoryGiBMonth": 49.1,
94+
"instances": 7
95+
}
96+
]
97+
}
98+
```
99+
100+
| Field | Type | Description |
101+
|---|---|---|
102+
| `resourceType` | string | Echo of the path parameter. |
103+
| `months` | array | Monthly rows, most recent first. |
104+
| `months[].year` | integer | Calendar year. |
105+
| `months[].month` | integer | Calendar month, `1`–`12`. |
106+
| `months[].cpuCoreMonth` | number | CPU core-months consumed. |
107+
| `months[].memoryGiBMonth` | number | Memory GiB-months consumed. |
108+
| `months[].instances` | integer | Instance count for the month. |
109+
| `months[].cpuChangeFromPrev` | number | Percentage change vs. the previous (older) month; omitted on the oldest row. |
110+
| `months[].memoryChangeFromPrev` | number | Percentage change vs. the previous (older) month; omitted on the oldest row. |
111+
112+
Errors: `400` for an unknown `resourceType`, a `period_start`/`period_end` that is not
113+
`YYYY-MM`, a `period_start` after `period_end`, or a range longer than 24 months.
114+
48115
## Generated months
49116

50117
### GET /dashboard/summary/generated-months

‎docs/platform/api/billing-dashboard/user-dashboard.md‎

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,20 @@ section_menu_id: api
1212

1313
# User Billing Dashboard
1414

15-
Owner-scoped endpoints under `/api/v1/dashboard/clusters` that back the self-service
15+
Owner-scoped endpoints under `/api/v1/user/dashboard/clusters` that back the self-service
1616
billing dashboard an organization sees for its **own** clusters: active clusters,
1717
their licenses and licensed products, and per-cluster / per-license / per-resource
1818
event counts.
1919

2020
All paths on this page are relative to `/api/v1`. Every endpoint requires
2121
`Authorization: token <YOUR_TOKEN>`, resolves the owner from the `org` query
22-
parameter (`?org=<org-slug>`), and requires the `view:contracts` permission on that
23-
organization (this is the org's "hosted mode / view contracts" grant — not
22+
parameter (`?org=<org-slug>`), and requires the `view:licensed_clusters` permission on that
23+
organization (the org's "hosted mode / view licensed clusters" grant — not
2424
site-admin). This group is available only on **billing-enabled deployments**.
2525

26-
> **Verified:** every endpoint on this page returned `404 Not Found` against
27-
> `appscode` on `<akp-host>` on 2026-07-14 — this deployment is not billing-enabled,
28-
> so the `/dashboard/clusters/*` routes are not registered. (Sanity: `GET /version`
29-
> and `GET /user` returned `200` with the same token.)
26+
> **Note.** These paths were previously documented without the `/user` prefix.
27+
> `registerBillingDashboardUserAPIs` registers `/dashboard/clusters` from inside the
28+
> `/user` group, so the served prefix is `/api/v1/user/dashboard/clusters`.
3029
3130
Shared conventions on this page:
3231

@@ -37,12 +36,12 @@ Shared conventions on this page:
3736

3837
## Clusters
3938

40-
### GET /dashboard/clusters/active
39+
### GET /user/dashboard/clusters/active
4140

4241
Lists clusters that reported within the `limit` window for the owner resolved from
4342
the query.
4443

45-
- **Auth:** token; owner-scoped (`view:contracts`). Requires `?org=`.
44+
- **Auth:** token; owner-scoped (`view:licensed_clusters`). Requires `?org=`.
4645
- **Query parameters:**
4746

4847
| Name | Type | Required | Description |
@@ -66,16 +65,16 @@ the query.
6665

6766
```
6867
curl -H "Authorization: token $AKP_TOKEN" \
69-
"https://<akp-host>/api/v1/dashboard/clusters/active?org=appscode"
68+
"https://<akp-host>/api/v1/user/dashboard/clusters/active?org=appscode"
7069
```
7170

7271
> **Verified:** returned `404` against `appscode` — billing not enabled on this deployment.
7372
74-
### GET /dashboard/clusters/{cid}
73+
### GET /user/dashboard/clusters/{cid}
7574

7675
Returns cluster information for a cluster owned by the request owner.
7776

78-
- **Auth:** token; owner-scoped (`view:contracts`). Requires `?org=`.
77+
- **Auth:** token; owner-scoped (`view:licensed_clusters`). Requires `?org=`.
7978
- **Path parameters:**
8079

8180
| Name | Type | Description |
@@ -89,11 +88,11 @@ Returns cluster information for a cluster owned by the request owner.
8988

9089
> **Verified:** returned `404` against `appscode` — billing not enabled.
9190
92-
### GET /dashboard/clusters/{cid}/events-count
91+
### GET /user/dashboard/clusters/{cid}/events-count
9392

9493
Returns today's event count for the cluster.
9594

96-
- **Auth:** token; owner-scoped (`view:contracts`). Requires `?org=`.
95+
- **Auth:** token; owner-scoped (`view:licensed_clusters`). Requires `?org=`.
9796
- **Path parameters:** `cid` (string) — cluster UID.
9897
- **Response:** `200 OK` — an `EventsCounterResponse`.
9998

@@ -113,11 +112,11 @@ the license/resource-scoped counter endpoints below; `error` is set instead of
113112
114113
## Licenses
115114

116-
### GET /dashboard/clusters/{cid}/licenses/
115+
### GET /user/dashboard/clusters/{cid}/licenses/
117116

118117
Lists licensed plans associated with the request owner and cluster.
119118

120-
- **Auth:** token; owner-scoped (`view:contracts`). Requires `?org=`.
119+
- **Auth:** token; owner-scoped (`view:licensed_clusters`). Requires `?org=`.
121120
- **Path parameters:** `cid` (string) — cluster UID.
122121
- **Response:** `200 OK` — an array of `LicensedPlan` (see the Admin Billing
123122
Dashboard page for the full shape).
@@ -138,11 +137,11 @@ Lists licensed plans associated with the request owner and cluster.
138137

139138
> **Verified:** returned `404` against `appscode` — billing not enabled.
140139
141-
### GET /dashboard/clusters/{cid}/licenses/{lid}
140+
### GET /user/dashboard/clusters/{cid}/licenses/{lid}
142141

143142
Returns a licensed plan (API form) associated with the request owner.
144143

145-
- **Auth:** token; owner-scoped (`view:contracts`). Requires `?org=`.
144+
- **Auth:** token; owner-scoped (`view:licensed_clusters`). Requires `?org=`.
146145
- **Path parameters:** `cid` (string), `lid` (string) — license ID.
147146
- **Response:** `200 OK` — a `LicensePlanApiForm`.
148147

@@ -166,22 +165,22 @@ Returns a licensed plan (API form) associated with the request owner.
166165
167166
## Event counts and events
168167

169-
### GET /dashboard/clusters/{cid}/licenses/{lid}/products/{product}/events-count
168+
### GET /user/dashboard/clusters/{cid}/licenses/{lid}/products/{product}/events-count
170169

171170
Returns today's event count for a license/product.
172171

173-
- **Auth:** token; owner-scoped (`view:contracts`). Requires `?org=`.
172+
- **Auth:** token; owner-scoped (`view:licensed_clusters`). Requires `?org=`.
174173
- **Path parameters:** `cid` (string), `lid` (string), `product` (string).
175174
- **Response:** `200 OK` — an `EventsCounterResponse` (with `product` and
176175
`licenseID` populated).
177176

178177
> **Verified:** returned `404` against `appscode` — billing not enabled.
179178
180-
### GET /dashboard/clusters/{cid}/licenses/{lid}/products/{product}/groups/{group}/resources/{resource}/{rid}/events-count
179+
### GET /user/dashboard/clusters/{cid}/licenses/{lid}/products/{product}/groups/{group}/resources/{resource}/{rid}/events-count
181180

182181
Returns today's event count for a specific resource object.
183182

184-
- **Auth:** token; owner-scoped (`view:contracts`). Requires `?org=`.
183+
- **Auth:** token; owner-scoped (`view:licensed_clusters`). Requires `?org=`.
185184
- **Path parameters:**
186185

187186
| Name | Type | Description |
@@ -211,11 +210,11 @@ Returns today's event count for a specific resource object.
211210

212211
> **Verified:** returned `404` against `appscode` — billing not enabled.
213212
214-
### GET /dashboard/clusters/{cid}/licenses/{lid}/products/{product}/events/
213+
### GET /user/dashboard/clusters/{cid}/licenses/{lid}/products/{product}/events/
215214

216215
Returns the tabular event list for the caller's cluster/license/product.
217216

218-
- **Auth:** token; owner-scoped (`view:contracts`). Requires `?org=`.
217+
- **Auth:** token; owner-scoped (`view:licensed_clusters` plus `view:event_resources`). Requires `?org=`.
219218
- **Path parameters:** `cid` (string), `lid` (string), `product` (string).
220219
- **Query parameters:**
221220

@@ -240,11 +239,11 @@ Returns the tabular event list for the caller's cluster/license/product.
240239

241240
> **Verified:** returned `404` against `appscode` — billing not enabled.
242241
243-
### GET /dashboard/clusters/{cid}/licenses/{lid}/products/{product}/events/raw-event
242+
### GET /user/dashboard/clusters/{cid}/licenses/{lid}/products/{product}/events/raw-event
244243

245244
Returns the raw badger value for a specific event key/version.
246245

247-
- **Auth:** token; owner-scoped (`view:contracts`). Requires `?org=`.
246+
- **Auth:** token; owner-scoped (`view:licensed_clusters` plus `view:event_resources`). Requires `?org=`.
248247
- **Path parameters:** `cid` (string), `lid` (string), `product` (string).
249248
- **Query parameters:**
250249

0 commit comments

Comments
 (0)