Compare commits

..

101 Commits

Author SHA1 Message Date
0xWheatyz f841c2216b fix(scripts): unique PAT names + show HTTP error body in token helper
Validate Flux manifests / kustomize-build (pull_request) Successful in 21s
Re-running the helper hit Gitea 400 'token name has been used' because the
token name was only date-stamped. Add time+pid to make names unique, surface
the real HTTP status/body, and continue past a single token failure.
2026-06-20 18:04:16 -04:00
0xWheatyz 98807884b1 fix(ci): authenticate registry push with package-scoped REGISTRY_TOKEN
Validate Flux manifests / kustomize-build (pull_request) Successful in 22s
The auto GITEA_TOKEN has no package-registry scope, so docker login to
gitea.leeworks.dev returned 'unauthorized'. Use a dedicated PAT (REGISTRY_TOKEN,
write:package) with the token-owner username. Helper script now mints both
SIBLING_REPOS_TOKEN and REGISTRY_TOKEN from one password prompt.
2026-06-20 17:53:28 -04:00
0xWheatyz 3cb8f648a1 fix(ci): push docs-site image to gitea.leeworks.dev registry
Validate Flux manifests / kustomize-build (pull_request) Successful in 25s
registry.leeworks.dev serves Traefik's default self-signed cert (TLS verify
fails). Gitea's built-in container registry at gitea.leeworks.dev has a valid
Let's Encrypt cert, so push there instead.
2026-06-20 17:49:43 -04:00
0xWheatyz 3f648cd5a0 fix(ci): install docker CLI in build-docs before registry steps
Validate Flux manifests / kustomize-build (pull_request) Successful in 22s
2026-06-20 17:41:09 -04:00
0xWheatyz a374419400 fix(docs-site): pin @astrojs/sitemap to ~3.2.1 for Astro 4 compat
Validate Flux manifests / kustomize-build (pull_request) Successful in 21s
sitemap 3.7.3 reads opts.routes from astro:build:done, which only exists in
Astro 5; on Astro 4.16 it is undefined and crashes the build with
'Cannot read properties of undefined (reading reduce)'. Pin to the 3.2.x line
and lock it so the docs-site build completes.
2026-06-20 17:35:59 -04:00
0xWheatyz d3f7222db1 build(docs-site): add package-lock.json so 'npm ci' works in CI
Validate Flux manifests / kustomize-build (pull_request) Successful in 21s
2026-06-20 17:31:55 -04:00
0xWheatyz 58e7a8e319 fix(ci): install kustomize via pinned release tarball with retries
Validate Flux manifests / kustomize-build (pull_request) Successful in 22s
2026-06-20 17:29:28 -04:00
0xWheatyz 1949873757 docs(deploy): add Flux/Talos deploy guide + sibling-repos PAT helper script
Validate Flux manifests / kustomize-build (pull_request) Failing after 26s
2026-06-20 17:25:38 -04:00
0xWheatyz ff7c9cfb7e fix(ci): repair failing Gitea Actions 2026-06-20 17:20:26 -04:00
AI-Manager 0cf5780eb8 Merge pull request 'docs: update STATUS.md for 2026-06-15 triage cycle' (#242) from feature/triage-2026-06-15-cycle into main
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 40s
Validate Flux manifests / kustomize-build (push) Failing after 17s
2026-06-15 10:10:05 +00:00
AI-Agent 3c2c39cb08 docs: update STATUS.md for 2026-06-15 triage cycle
Validate Flux manifests / kustomize-build (pull_request) Failing after 21s
2026-06-15 10:04:46 +00:00
AI-Manager 8c8f751a14 Merge pull request 'docs: update STATUS.md for 2026-06-08 triage cycle #240' (#240) from feature/triage-2026-06-08-cycle-240 into main
Validate Flux manifests / kustomize-build (push) Failing after 9s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 37s
2026-06-08 15:07:17 +00:00
AI-Agent 113f09a867 docs: update STATUS.md for 2026-06-08 triage cycle #240
Validate Flux manifests / kustomize-build (pull_request) Failing after 26s
All 28 open agent-ready issues triaged and commented.
All confirmed blocked on operator critical path:
- #47: create 0xWheatyz/api-company (HTTP 404)
- #218: merge 0xWheatyz/Talos PR #14 (Flux activation)
- #33/#106/#150: configure DNS for all 8 subdomains

kustomize build flux/ = PASS
0 open PRs, 0 new issues, 0 issues closed.
2026-06-08 15:06:52 +00:00
AI-Manager 86d45ab9a6 Merge pull request 'docs: fix stale TBD Grafana dashboard reference in metrics-standard.md (#238)' (#239) from feature/fix-metrics-standard-grafana-ref-238 into main
Validate Flux manifests / kustomize-build (push) Failing after 4s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 42s
2026-06-08 10:02:04 +00:00
AI-Agent 2c4fc14278 docs: fix stale TBD Grafana dashboard reference in metrics-standard.md
Validate Flux manifests / kustomize-build (pull_request) Failing after 26s
Replace the non-existent docs/grafana-api-dashboard.json reference with
pointers to the actual Flux ConfigMap dashboard locations:
- flux/monitoring/grafana-dashboard-apis.yaml (consolidated 4-API dashboard)
- flux/monitoring/vin-decoder-dashboard.yaml (VIN Decoder-specific dashboard)

Note that dashboards are auto-loaded via grafana_dashboard: "1" label.
Remove the (TBD) annotation — dashboards are already committed.

Closes leeworks-agents/api-company#238
2026-06-08 10:01:33 +00:00
AI-Manager c6165c1e08 Merge pull request 'docs: update STATUS.md for 2026-06-08 triage cycle #237' (#237) from feature/triage-2026-06-08-cycle into main
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 41s
Validate Flux manifests / kustomize-build (push) Failing after 30s
2026-06-08 05:03:09 +00:00
AI-Agent b88ac14e58 docs: update STATUS.md for 2026-06-08 triage cycle #237
Validate Flux manifests / kustomize-build (pull_request) Failing after 25s
2026-06-08 05:02:23 +00:00
AI-Manager 8fdd2a1edf Merge pull request 'docs: fix STATUS.md Current Blockers — replace closed #174 with open #231' (#236) from feature/fix-status-md-blocker-235 into main
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 47s
Validate Flux manifests / kustomize-build (push) Failing after 22s
2026-06-07 20:03:12 +00:00
AI-Agent c8271db1ec docs: fix STATUS.md Current Blockers — replace closed #174 with open #231
Validate Flux manifests / kustomize-build (pull_request) Failing after 9s
Issue #174 (VIN Decoder metrics instrumentation) is closed .
Replace stale reference in Current Blockers with open issue #231
(Verify VIN Decoder metrics endpoint and ServiceMonitor scrape config
in live cluster), which correctly tracks the outstanding verification
work. Also bump the 'as of' date to 2026-06-07.

kustomize build flux/ = PASS

Closes leeworks-agents/api-company#235
2026-06-07 20:02:42 +00:00
AI-Manager 7adcc9a524 Merge pull request 'fix: update ROADMAP.md stale issue reference #174#231 (issue #233)' (#234) from feature/fix-roadmap-issue-233 into main
Validate Flux manifests / kustomize-build (push) Failing after 17s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 41s
2026-06-07 15:01:55 +00:00
AI-Agent 25045d4ff1 fix: update ROADMAP.md stale issue reference from #174 to #231
Validate Flux manifests / kustomize-build (pull_request) Failing after 20s
Issue #174 (Add VIN Decoder metrics instrumentation) is closed .
The live-cluster verification of VIN Decoder metrics/ServiceMonitor
is now tracked by #231 (open).

Closes leeworks-agents/api-company#233
2026-06-07 15:01:30 +00:00
AI-Manager 7a45bfc369 Merge pull request 'docs: update STATUS.md for 2026-06-07 triage cycle #232' (#232) from feature/triage-2026-06-07-cycle232 into main
Validate Flux manifests / kustomize-build (push) Failing after 21s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 43s
2026-06-07 10:03:01 +00:00
AI-Agent 273a41e1fe docs: update STATUS.md for 2026-06-07 triage cycle #232
Validate Flux manifests / kustomize-build (pull_request) Failing after 23s
- 28 agent-ready issues reviewed (27 previously known + new #231)
- Issue #231 triaged: @qa-engineer — VIN Decoder metrics live-cluster
  verification; BLOCKED on #218 (Flux not active)
- kustomize build flux/ PASS
- 0 open PRs
2026-06-07 10:02:32 +00:00
AI-Manager 23dfec20a1 Merge pull request 'fix: add named port 'http' to vin-decoder Service and container for ServiceMonitor scraping' (#230) from feature/fix-vin-decoder-servicemonitor-port into main
Validate Flux manifests / kustomize-build (push) Failing after 21s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 37s
2026-06-07 05:06:43 +00:00
AI-Agent cc7172d563 fix: add named port 'http' to vin-decoder Service and container for ServiceMonitor compatibility
Validate Flux manifests / kustomize-build (pull_request) Failing after 20s
The ServiceMonitor at flux/vin-decoder/servicemonitor.yaml references port
by name ('http'), but the Service had anonymous port 80 -> 3000 and the
container had no named port. Prometheus ServiceMonitor port matching requires
the port name to match between the Service and the ServiceMonitor spec.

Adds:
- name: http to the containerPort (port 3000)
- name: http to the Service port (port 80 -> targetPort 3000)

This allows the kube-prometheus-stack ServiceMonitor to correctly scrape
/metrics from vin-decoder pods.

Closes leeworks-agents/api-company#174
2026-06-07 05:06:10 +00:00
AI-Manager 695dac91c4 Merge pull request 'docs: update STATUS.md for 2026-06-07 triage cycle' (#229) from feature/triage-2026-06-07 into main
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 34s
Validate Flux manifests / kustomize-build (push) Failing after 24s
2026-06-07 00:05:47 +00:00
AI-Agent b2d33a56c4 docs: update STATUS.md for 2026-06-07 triage cycle
Validate Flux manifests / kustomize-build (pull_request) Failing after 25s
- Updated last-updated date to 2026-06-07
- Added Completed This Cycle section for 2026-06-07 triage
- All 27 agent-ready issues triaged and commented
- 0 new issues, 0 closed, 0 PRs, kustomize build PASS
- Critical path unchanged: #47, #218, #33/#106/#150
2026-06-07 00:05:23 +00:00
AI-Agent 8c2cce576e chore: sprint planning cycle #227 — STATUS.md updated (2026-06-06)
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 59s
Validate Flux manifests / kustomize-build (push) Failing after 38s
Full ROADMAP cross-reference: all 18 unchecked items grounded in open issues, zero gaps.
Staleness check: oldest issue #3/#4 is 19 days old (< 30-day threshold), zero closures.
Label audit: all labels correct from prior cycle, zero changes.
kustomize build flux/ = PASS.
2026-06-06 20:25:07 +00:00
AI-Manager 9c2f228f0c Merge pull request 'docs: update ROADMAP.md Flux wiring entry to reference active tracker #218' (#228) from feature/issue-227-roadmap-flux-ref into main
Validate Flux manifests / kustomize-build (push) Failing after 13s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 41s
2026-06-06 20:02:10 +00:00
AI-Agent 1f8014113f docs: update ROADMAP.md Flux wiring ref from closed #2/#187 to active #218
Validate Flux manifests / kustomize-build (pull_request) Failing after 22s
Replace closed issue references (#2, #187) on the Phase 0 Flux
GitRepository entry with the current active tracker #218, which awaits
operator merge of 0xWheatyz/Talos PR #14 to activate Flux GitOps.

Closes leeworks-agents/api-company#227
2026-06-06 20:01:49 +00:00
AI-Manager 9470207b07 Merge pull request 'docs: update STATUS.md for 2026-06-06 triage cycle (#226)' (#226) from feature/issue-226-triage-cycle-2026-06-06 into main
Validate Flux manifests / kustomize-build (push) Failing after 21s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 37s
2026-06-06 15:07:01 +00:00
AI-Agent 04a6185824 docs: update STATUS.md for 2026-06-06 triage cycle (#226)
Validate Flux manifests / kustomize-build (pull_request) Failing after 25s
- Triaged all 27 open agent-ready issues; all confirmed blocked on operator
  tasks (create 0xWheatyz/api-company #47, merge Talos PR #14 #218, DNS #33)
- 0 new issues, 0 closures, 0 PRs, kustomize build flux/ = PASS
- Added Completed This Cycle entry for Triage Cycle #226
2026-06-06 15:06:34 +00:00
AI-Manager cde5122702 Merge pull request 'docs: update secrets-checklist.md item #4 for Alertmanager dual consumer (#224)' (#225) from feature/issue-224-secrets-checklist-alertmanager-dual-consumer into main
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 44s
Validate Flux manifests / kustomize-build (push) Failing after 21s
2026-06-06 10:02:53 +00:00
AI-Agent c7b44b9da6 docs: update secrets-checklist.md item #4 for Alertmanager dual consumer
Validate Flux manifests / kustomize-build (pull_request) Failing after 33s
gatus-slack-webhook (monitoring) is now consumed by two components:
1. Gatus (flux/monitoring/gatus-helmrelease.yaml) - public status-page alerts
2. Alertmanager (flux/monitoring/helmrelease.yaml via alertmanagerSpec.secrets)
   - SLO / firing-alert Slack notifications (enabled by PR #212 / issue #210)

Updates:
- Checklist item #4: mention both Gatus AND Alertmanager Slack SLO alerts
- Secret Details §4: add Consumers row + dual-consumer note explaining
  alertmanagerSpec.secrets mounts this secret and the slack_api_url_file path
- Dependency order: note Alertmanager is also unblocked by item #4

Closes leeworks-agents/api-company#224
2026-06-06 10:02:04 +00:00
AI-Manager 691fe45c13 Merge pull request 'docs: update STATUS.md for 2026-06-06 sprint planning cycle (#221)' (#223) from feature/issue-221-status-update-2026-06-06 into main
Validate Flux manifests / kustomize-build (push) Failing after 7s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 54s
2026-06-06 05:03:20 +00:00
AI-Agent 14433e7741 docs: update STATUS.md for 2026-06-06 sprint planning cycle (#221)
Validate Flux manifests / kustomize-build (pull_request) Failing after 22s
- Set Completed This Cycle to Sprint Planning Cycle #221
- Record 0 new issues, 0 closures, 3 label changes (blocked on #106/#150, P1→P2 on #203)
- Update Current Blockers date to 2026-06-06
- kustomize build flux/ = PASS

Closes leeworks-agents/api-company#221
2026-06-06 05:02:53 +00:00
AI-Manager a5aeaf64df Merge pull request 'docs: update STATUS.md blockers — replace closed #187/#90 with open #218' (#220) from feature/issue-219-status-update-blockers into main
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 46s
Validate Flux manifests / kustomize-build (push) Failing after 16s
2026-06-06 00:04:09 +00:00
AI-Agent 58c6e28ea5 docs: update STATUS.md blockers — replace closed #187/#90 with #218, update Critical Path
Validate Flux manifests / kustomize-build (pull_request) Failing after 51s
Closes leeworks-agents/api-company#219

- Current Blockers item 8: removed P1 #187 and P1 #90 (both closed);
  replaced with P1 #218 (Operator: merge 0xWheatyz/Talos upstream PR #14
  to activate Flux GitOps for api-company)
- Critical Path item 2: updated from #90 reference to #218
- Added Completed This Cycle entry for #219
- kustomize build flux/ = PASS
2026-06-06 00:03:40 +00:00
AI-Manager 321d3e6bf4 Merge pull request 'docs: ROADMAP.md Phase 4 Alertmanager annotation + STATUS.md cycle #215 update' (#216) from feature/issue-214-215-roadmap-status-update into main
Validate Flux manifests / kustomize-build (push) Failing after 21s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 3m12s
Validate Flux manifests / kustomize-build (pull_request) Failing after 16s
2026-06-05 15:03:21 +00:00
AI-Agent 14c051fe8f docs: annotate ROADMAP.md Phase 4 Alertmanager + update STATUS.md for cycle #215
Validate Flux manifests / kustomize-build (pull_request) Failing after 25s
- ROADMAP.md: add checked bullet for Alertmanager + Slack receiver (issue #210)
  under Phase 4 Monitoring after VIN Decoder Gatus health check entry.
  Closes leeworks-agents/api-company#214.

- STATUS.md: add Completed This Cycle section for Sprint Planning Cycle #215.
  Documents 1 new issue (#214), 0 closures, 0 label changes, critical path
  unchanged. kustomize build flux/ = PASS.
  Closes leeworks-agents/api-company#215.
2026-06-05 15:02:54 +00:00
AI-Manager 7cb427f4ea Merge pull request 'docs: update STATUS.md for 2026-06-05 sprint planning cycle (#211)' (#213) from feature/issue-211-status-update-2026-06-05 into main
Validate Flux manifests / kustomize-build (push) Failing after 17s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 36s
2026-06-05 10:05:27 +00:00
AI-Agent c3ee09b52c docs: update STATUS.md for 2026-06-05 sprint planning cycle (#211)
Validate Flux manifests / kustomize-build (pull_request) Failing after 16s
- Record sprint planning cycle #211 outcomes in Completed This Cycle
- 1 new issue implemented: #210 (Alertmanager + Slack receiver), PR #212 opened
- 0 issues closed (oldest open issue #2, 18 days — under 30-day threshold)
- 0 label changes (all labels confirmed correct)
- ROADMAP cross-reference: 18 unchecked items grounded; 1 gap found + filed (#210)
- Infrastructure section updated with Alertmanager status
- kustomize build flux/ = PASS

Closes leeworks-agents/api-company#211
2026-06-05 10:05:05 +00:00
AI-Manager e3aa6b5fa1 Merge pull request 'feat: enable Alertmanager + Slack receiver in kube-prometheus-stack HelmRelease' (#212) from feature/issue-210-enable-alertmanager into main
Validate Flux manifests / kustomize-build (push) Failing after 22s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 46s
2026-06-05 10:04:56 +00:00
AI-Agent 9bc28c2b46 feat: enable Alertmanager with Slack receiver in kube-prometheus-stack HelmRelease
Validate Flux manifests / kustomize-build (pull_request) Failing after 17s
- Set alertmanager.enabled: true (was false)
- Add Slack receiver config pointing to gatus-slack-webhook secret URL
- Add alertmanagerSpec.secrets to mount gatus-slack-webhook into Alertmanager pod
- Route: group by alertname+namespace, 30s wait, 5m interval, 4h repeat
- Channel: #api-alerts with summary title and description text

Closes leeworks-agents/api-company#210
2026-06-05 10:02:29 +00:00
AI-Manager a52bea2552 Merge pull request 'docs: add vin-decoder to pre-launch checklist quick-command snippets' (#209) from feature/fix-checklist-vin-decoder-snippets into main
Validate Flux manifests / kustomize-build (push) Failing after 13s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 50s
2026-06-05 05:02:34 +00:00
AI-Agent e3247de665 docs: add vin-decoder to pre-launch checklist quick-command snippets
Validate Flux manifests / kustomize-build (pull_request) Failing after 24s
Fix three omissions in the Quick Commands Reference section:
1. Pod-health loop: add vin-decoder to namespace list
2. DNS check loop: add vin subdomain (8th subdomain, not 7)
3. Health smoke-test loop: add vin.leeworks.dev/v1/health

Closes leeworks-agents/api-company#208
2026-06-05 05:02:07 +00:00
AI-Manager 8981122af8 Merge pull request 'docs: update STATUS.md for 2026-06-05 sprint planning cycle (#206)' (#207) from feature/issue-206-status-update-sprint-2026-06-04 into main
Validate Flux manifests / kustomize-build (push) Failing after 17s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 50s
2026-06-05 00:02:33 +00:00
AI-Agent be423719e1 docs: update STATUS.md for 2026-06-05 sprint planning cycle
Validate Flux manifests / kustomize-build (pull_request) Failing after 23s
Closes leeworks-agents/api-company#206

- Last-updated date set to 2026-06-05
- Completed This Cycle section records 2026-06-04 sprint planning outcomes:
  - Issue #204 (smoke-tests) reopened and relabeled blocked
  - ROADMAP cross-reference: all 18 unchecked items grounded (zero gaps)
  - Staleness check: oldest issue #2 is 18 days old (under 30-day threshold)
  - Label audit: no changes required
- Critical path confirmed: operator must create 0xWheatyz/api-company (#47),
  merge upstream Talos PR #14 (#187), and configure DNS (#33, #106, #150)
2026-06-05 00:02:12 +00:00
AI-Manager 1c04b047e8 Merge pull request 'docs: STATUS.md triage cycle #204 — all 29 agent-ready issues reviewed' (#205) from feature/status-update-204 into main
Validate Flux manifests / kustomize-build (push) Failing after 13s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 53s
2026-06-04 20:06:48 +00:00
AI-Agent 57a1f67802 docs: STATUS.md triage cycle #204 — all 29 agent-ready issues reviewed
Validate Flux manifests / kustomize-build (pull_request) Failing after 21s
Closes leeworks-agents/api-company#204 (smoke tests blocked — DNS not configured)

Summary:
- 29 issues triaged; all are blocked on operator actions (Flux wiring, DNS,
  secrets, RapidAPI listings, PayPal) or infrastructure not yet reachable
- kustomize build flux/ PASS — no regressions
- 0 open PRs
- Critical path: operator merge of 0xWheatyz/Talos PR #14 (#187) + create
  0xWheatyz/api-company repo (#47)
2026-06-04 20:06:21 +00:00
AI-Manager e4bf3ee12b Merge pull request 'docs: STATUS.md sprint cycle #200/#201 — Flux wiring infra update and sprint summary' (#202) from feature/status-update-cycle-200-201 into main
Validate Flux manifests / kustomize-build (push) Failing after 18s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 43s
2026-06-04 15:03:03 +00:00
AI-Agent fb68a12b86 docs: STATUS.md sprint cycle #200/#201 — Flux wiring infra update and sprint summary
Validate Flux manifests / kustomize-build (pull_request) Failing after 21s
- Infrastructure: update Flux wiring row — Fork PR #399 MERGED to leeworks-agents/Talos;
  upstream PR #14 open on 0xWheatyz/Talos awaiting operator merge
- Current Blockers #187/#90: add explicit reference to upstream PR #14 as critical gate
- Add Completed This Cycle entry for sprint cycle #200/#201

Closes leeworks-agents/api-company#200
Closes leeworks-agents/api-company#201
2026-06-04 15:02:33 +00:00
AI-Manager 5dd9f4dbc6 Merge pull request 'docs: update STATUS.md for 2026-06-04 sprint planning cycle' (#199) from feature/status-update-198 into main
Validate Flux manifests / kustomize-build (push) Failing after 25s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 52s
2026-06-04 10:04:51 +00:00
AI-Agent 01ec869a35 docs: update STATUS.md for 2026-06-04 sprint planning cycle
Validate Flux manifests / kustomize-build (pull_request) Failing after 23s
- Last-updated date: 2026-06-04
- Completed This Cycle section: 0 new code issues, 0 closed, 0 label changes
- Current Blockers: critical path operator-gated; Talos #187 unblocks #2/#3/#4
- kustomize build flux/ = PASS

Closes leeworks-agents/api-company#198
2026-06-04 10:04:24 +00:00
AI-Manager cfb8acea58 Merge pull request 'docs: update STATUS.md for 2026-06-04 sprint planning cycle (closes #196)' (#197) from feature/status-update-196 into main
Validate Flux manifests / kustomize-build (push) Failing after 14s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 46s
2026-06-04 05:02:04 +00:00
AI-Agent 3a56a9f4f3 docs: update STATUS.md for 2026-06-04 sprint planning cycle (closes #196)
Validate Flux manifests / kustomize-build (pull_request) Failing after 26s
2026-06-04 05:01:45 +00:00
AI-Manager cf33100c4b Merge pull request 'docs: annotate ROADMAP.md with #187/#166/#167 refs, update STATUS.md sprint 2026-06-03 (closes #194, #193)' (#195) from feature/sprint-annotations-193-194 into main
Validate Flux manifests / kustomize-build (push) Failing after 18s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 44s
2026-06-04 00:04:13 +00:00
AI-Agent ca91ebeae9 docs: annotate ROADMAP.md with #187/#166/#167 refs, update STATUS.md for 2026-06-03 sprint (closes #194, closes #193)
Validate Flux manifests / kustomize-build (pull_request) Failing after 26s
- ROADMAP.md Phase 0: add Talos PR implementation reference (issue #187) alongside issue #2
- ROADMAP.md Phase 6: add issue #166 (pre-launch checklist) and #167 (launch announcement)
- STATUS.md: new Completed This Cycle section for sprint planning cycle #193/#194
- STATUS.md Current Blockers: add #80 (gitea-image-automation-token) as item 10
- kustomize build flux/ = PASS

Closes leeworks-agents/api-company#194
Closes leeworks-agents/api-company#193
2026-06-04 00:03:35 +00:00
AI-Manager 8ffcdf53db Merge pull request 'docs: audit ZIP Enrichment and Holidays APIs, update STATUS.md (closes #190, #191)' (#192) from feature/audit-zip-holidays-190-191 into main
Validate Flux manifests / kustomize-build (push) Failing after 8s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 46s
2026-06-03 20:04:37 +00:00
AI-Agent dac39c8fa7 docs: audit ZIP Enrichment and Holidays spec/code, update STATUS.md (closes #190, #191)
Validate Flux manifests / kustomize-build (pull_request) Failing after 25s
- openapi.yaml confirmed present in leeworks-agents/zip-enrichment (57 KB, OpenAPI 3.0.3)
- openapi.yaml confirmed present in leeworks-agents/holidays (13 KB, OpenAPI 3.0.3)
- src/server.js entry points confirmed in both repos
- Route handlers confirmed for all paths in both specs
- X-RapidAPI-Proxy-Secret preHandler middleware confirmed in both repos
- /metrics Prometheus endpoint confirmed in both repos
- STATUS.md ZIP Enrichment Spec/Code updated [~] -> [x]
- STATUS.md Holidays Spec/Code updated [~] -> [x]

Closes leeworks-agents/api-company#190
Closes leeworks-agents/api-company#191
2026-06-03 20:04:00 +00:00
AI-Manager 5fccccb0ed Merge pull request 'docs: update STATUS.md for 2026-06-03 sprint planning cycle (closes #188, #186)' (#189) from feature/sprint-2026-06-03-status-update into main
Validate Flux manifests / kustomize-build (push) Failing after 22s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 46s
2026-06-03 15:07:39 +00:00
AI-Agent d8cc0402a3 docs: update STATUS.md for 2026-06-03 sprint planning cycle
Validate Flux manifests / kustomize-build (pull_request) Failing after 25s
- Set last updated date to 2026-06-03
- Update Air Quality row: Spec [x] and Code [x] (both confirmed present)
- Add sprint cycle summary: #186 audit, #187 Talos PR, #188 STATUS update
- Add #187 to Current Blockers before #90 (same unblock target)
- Update labels: #178 and #174 confirmed blocked

Closes leeworks-agents/api-company#188
Closes leeworks-agents/api-company#186
2026-06-03 15:07:09 +00:00
AI-Manager 1982e74aaf Merge pull request 'docs: mark Phase 4 metrics items as done in ROADMAP.md (closes #184)' (#185) from feature/roadmap-phase4-metrics-done-184 into main
Validate Flux manifests / kustomize-build (push) Failing after 8s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 39s
2026-06-03 10:01:57 +00:00
AI-Agent 64f1d1bd12 docs: mark Phase 4 metrics items as done in ROADMAP.md (closes leeworks-agents/api-company#184)
Validate Flux manifests / kustomize-build (pull_request) Failing after 25s
- Check off issue #27 (Instrument all APIs with api_requests_total,
  api_response_duration_seconds, api_data_freshness_seconds)
- Check off issue #180 (Add Prometheus metrics endpoint to holidays service)

Both tracking issues are closed; ROADMAP.md now reflects their completed status.
2026-06-03 10:01:37 +00:00
AI-Manager daa3866926 Merge pull request 'docs: annotate ROADMAP.md + update STATUS.md for 2026-06-03 sprint (closes #182, #181)' (#183) from feature/roadmap-status-sprint-2026-06-03-182-181 into main
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 45s
Validate Flux manifests / kustomize-build (push) Failing after 17s
2026-06-03 05:05:38 +00:00
AI-Agent 2208c61ff1 docs: annotate ROADMAP.md and STATUS.md for 2026-06-03 sprint (closes leeworks-agents/api-company#182, leeworks-agents/api-company#181)
Validate Flux manifests / kustomize-build (pull_request) Failing after 26s
2026-06-03 05:05:13 +00:00
AI-Manager 99ffdf48c4 Merge pull request 'docs: annotate ROADMAP.md Phase 6 unchecked items + update STATUS.md (#175, #176)' (#177) from feature/roadmap-phase5-6-annotations into main
Validate Flux manifests / kustomize-build (push) Failing after 9s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 46s
2026-06-02 20:03:43 +00:00
AI-Agent 2eadeeb4ee docs: annotate ROADMAP.md Phase 6 unchecked items + update STATUS.md sprint cycle
Validate Flux manifests / kustomize-build (pull_request) Failing after 24s
Closes leeworks-agents/api-company#176
Closes leeworks-agents/api-company#175

ROADMAP.md Phase 6 changes:
- DNS line: add '— issues #33, #106, #150'
- PayPal line: add '— issue #19'
- Paid tiers line: add '— issues #44 (original 3), #151 (VIN Decoder)'

STATUS.md changes:
- Last updated: 2026-06-02 (sprint planning cycle #175/#176)
- New 'Completed This Cycle' section with sprint summary
- Current Blockers date updated to 2026-06-02
- Added blockers #173 (ESO source secrets) and #174 (VIN metrics)

kustomize build flux/ = PASS
2026-06-02 20:03:12 +00:00
AI-Manager a30834db51 Merge pull request 'docs: add issue cross-references to ROADMAP.md + update STATUS.md (closes #171, #170)' (#172) from feature/roadmap-status-crossrefs-171-170 into main
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 43s
Validate Flux manifests / kustomize-build (push) Failing after 30s
2026-06-02 10:03:22 +00:00
AI-Agent b4460ad818 docs: add issue cross-references to ROADMAP.md and update STATUS.md
Validate Flux manifests / kustomize-build (pull_request) Failing after 26s
- Phase 0 unchecked items now link to #2, #3, #4
- Phase 4 unchecked items now link to #7, #8, #27
- Phase 5 unchecked item now links to #30
- STATUS.md Last updated set to 2026-06-02
- STATUS.md new Completed This Cycle entry for 2026-06-02 Sprint Planning Cycle

Closes leeworks-agents/api-company#171
Closes leeworks-agents/api-company#170
kustomize build flux/ = PASS
2026-06-02 10:02:53 +00:00
AI-Manager e228f61c21 Merge pull request 'docs: remove closed issues #120 and #121 from STATUS.md blockers' (#169) from feature/168-status-remove-closed-120-121 into main
Validate Flux manifests / kustomize-build (push) Failing after 17s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 54s
2026-06-02 05:04:03 +00:00
AI-Agent 80f4d5be22 docs: remove closed issues #120 and #121 from STATUS.md blockers (closes leeworks-agents/api-company#168)
Validate Flux manifests / kustomize-build (pull_request) Failing after 26s
2026-06-02 05:03:33 +00:00
AI-Manager ee224fba3d Merge pull request 'docs: update ROADMAP.md + STATUS.md after VIN Decoder #120/#121 confirmed closed (closes #163, #164)' (#165) from feature/163-164-roadmap-status-vin-decoder-complete into main
Validate Flux manifests / kustomize-build (push) Failing after 21s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 37s
2026-06-02 00:03:01 +00:00
AI-Agent 8f26dff2f6 docs: update ROADMAP.md and STATUS.md after VIN Decoder #120/#121 confirmed closed
Validate Flux manifests / kustomize-build (pull_request) Failing after 21s
- ROADMAP.md Phase 2: mark VIN Decoder data layer [x] (issue #120 — closed )
- ROADMAP.md Phase 3: mark VIN Decoder Fastify server [x] (issue #121 — closed )
- STATUS.md Current Blockers: remove #120 and #121 (both confirmed closed)
- STATUS.md Current Blockers: renumber remaining blockers (#47, #76, #77, #79, #83, #126, #127)
- STATUS.md Blockers — VIN Decoder: remove #120 and #121
- STATUS.md: add Sprint Planning Cycle completed section for 2026-06-01
- STATUS.md: add Sprint Summary for 2026-06-01 sprint planning cycle
- STATUS.md: update top actionable agent task to #163
- STATUS.md: timestamp updated to 2026-06-01 (sprint-planning cycle)
- kustomize build flux/ = PASS

Closes leeworks-agents/api-company#163
Closes leeworks-agents/api-company#164
2026-06-02 00:02:30 +00:00
AI-Manager a2f45be7f1 Merge pull request 'docs: mark VIN Decoder Code [x] in STATUS.md' (#162) from feature/status-vin-decoder-code-done into main
Validate Flux manifests / kustomize-build (push) Failing after 20s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 43s
2026-06-01 15:05:17 +00:00
AI-Agent 3c4c3aa1ab docs: mark VIN Decoder Code [x] in STATUS.md (closes leeworks-agents/api-company#157)
Validate Flux manifests / kustomize-build (pull_request) Failing after 25s
leeworks-agents/vin-decoder data layer (#120) and Fastify server (#121)
are confirmed merged. Previous PR #160 closed due to merge conflicts and
ROADMAP.md regressions (incorrectly unchecked completed items).

kustomize build flux/ = PASS
2026-06-01 15:04:52 +00:00
AI-Manager 34d12a696a Merge pull request 'docs: update ROADMAP.md checkboxes and STATUS.md sprint state (closes #156, closes #158)' (#159) from feature/156-158-roadmap-status-update into main
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 38s
Validate Flux manifests / kustomize-build (push) Failing after 24s
2026-06-01 05:05:37 +00:00
AI-Agent 52c0baedf5 docs: update ROADMAP.md checkboxes and STATUS.md sprint state (closes #156, closes #158)
Validate Flux manifests / kustomize-build (pull_request) Failing after 25s
ROADMAP.md (#156):
- Phase 0: mark cluster audit done (issue #36 )
- Phase 4: mark VIN Decoder metrics + Grafana dashboard done (issue #129 )
- Phase 5: mark docs-site skeleton (#28 ), CI pipeline (#29 #38 #85 ),
  and SEO blog posts (#39 #124 ) as complete
- Phase 6: mark legal docs (ToS, Privacy Policy, AUP) done (#40 #89 )

STATUS.md (#158):
- Update last-updated date to 2026-06-01
- Add Current Blockers section (as of 2026-06-01) with dependency-ordered P1 list
- Add Sprint Summary section: issues closed (#145-#154), opened (#150 #151 #156 #157 #158)
- Update VIN Decoder blockers: #129 marked closed, #126 added
- Add new Completed This Cycle entry for 2026-06-01

kustomize build flux/ = PASS
2026-06-01 05:05:07 +00:00
AI-Manager e0c70b861a Merge pull request '[Phase 4] feat: extend monitoring to include VIN Decoder API' (#152) from feature/129-vin-decoder-metrics into main
Validate Flux manifests / kustomize-build (push) Failing after 8s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 44s
2026-06-01 00:05:51 +00:00
AI-Manager 10323a2874 Merge pull request '[Phase 0] docs: add VIN Decoder namespace steps to operator-runbook.md' (#155) from feature/154-runbook-vin-decoder into main
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 39s
Validate Flux manifests / kustomize-build (push) Failing after 21s
2026-06-01 00:05:35 +00:00
AI-Agent edcef75418 docs: add VIN Decoder namespace steps to operator runbook
Validate Flux manifests / kustomize-build (pull_request) Failing after 25s
Adds a dedicated Phase 4-A section to docs/operator-runbook.md covering
all four operator manual tasks needed to bring up the VIN Decoder namespace:

- Step 4A-1: Create vin-decoder namespace
- Step 4A-2: gitea-registry imagePullSecret in vin-decoder (issue #127)
- Step 4A-3: rapidapi-proxy-secret placeholder in vin-decoder (issue #128)
- Step 4A-4: GITEA_TOKEN Actions secret in vin-decoder repo (issue #126)
- Step 4A-5: RAPIDAPI_VIN_API_ID + RAPIDAPI_VIN_VERSION_ID Actions secrets (issue #139)

Also updates:
- Phase 3 Step 3-A: adds leeworks-agents/vin-decoder to the GITEA_TOKEN repo list
- Phase 4 DNS table: adds vin.leeworks.dev as the 8th subdomain (issue #150)
- Phase 5 rapidapi-proxy-secret block: adds vin-decoder command
- Phase 5: adds VIN Decoder pricing tier table (issue #151)
- Dependency Summary: reflects VIN Decoder additions throughout

Commands are consistent with docs/secrets-checklist.md items #11-#13.
kustomize build flux/ still passes.

Closes leeworks-agents/api-company#154
2026-06-01 00:05:01 +00:00
AI-Engineer 1bbcb8e28a feat: extend monitoring to include VIN Decoder API
Validate Flux manifests / kustomize-build (pull_request) Failing after 17s
- flux/monitoring/prometheusrule-apis.yaml: extend all 5 SLO alert
  rules to include vin-decoder in the job regex filter
- flux/monitoring/grafana-dashboard-apis.yaml: update all PromQL
  expressions to include vin-decoder in the api regex filter;
  update dashboard description
- flux/monitoring/vin-decoder-dashboard.yaml: new dedicated Grafana
  dashboard for VIN Decoder showing request rate by route, 5xx error
  rate, P50/P95/P99 latency by route, and data freshness gauge
- flux/monitoring/kustomization.yaml: add vin-decoder-dashboard.yaml
- flux/vin-decoder/servicemonitor.yaml: Prometheus ServiceMonitor
  for auto-discovery of /metrics on the vin-decoder service
- flux/vin-decoder/kustomization.yaml: add servicemonitor.yaml

Closes leeworks-agents/api-company#129
2026-05-31 20:11:03 +00:00
AI-Manager cd41724f39 Merge pull request '[Phase 5] Add vin-decoder openapi.yaml to build-docs.yaml CI spec-aggregation step' (#149) from feature/148-add-vin-decoder-to-build-docs into main
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 35s
Validate Flux manifests / kustomize-build (push) Failing after 8s
2026-05-31 15:03:21 +00:00
agent-company 254ebf28e7 ci: add vin-decoder spec to build-docs.yaml aggregation step
Validate Flux manifests / kustomize-build (pull_request) Failing after 24s
Add checkout step for leeworks-agents/vin-decoder and copy
vin-decoder/openapi.yaml into docs-site/public/specs/vin-decoder.yaml
so the vin-decoder.astro Redoc page renders correctly.

Closes leeworks-agents/api-company#148
2026-05-31 15:02:54 +00:00
AI-Manager b7c4309941 Merge pull request 'docs: update ROADMAP.md and STATUS.md for VIN Decoder sprint wave (#117–#141)' (#147) from feature/145-146-vin-decoder-docs into main
Validate Flux manifests / kustomize-build (push) Failing after 4s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 42s
2026-05-31 10:06:45 +00:00
agent-company d1bcab16e7 docs: update ROADMAP.md and STATUS.md for VIN Decoder sprint wave (#117–#141)
Validate Flux manifests / kustomize-build (pull_request) Failing after 22s
- ROADMAP.md: add VIN Decoder note at top; reference vin-decoder repo in
  Phase 1; add NHTSA vPIC data source note in Phase 2; add Fastify server
  note in Phase 3; add metrics + Gatus items in Phase 4; add docs/blog
  items in Phase 5; add DNS + RapidAPI listing items in Phase 6; update
  revenue target table to reflect 4 APIs
- STATUS.md: add VIN Decoder row to APIs table; document VIN Decoder wave
  #117–#141 with per-issue status; add VIN Decoder P1 blockers (#120, #121,
  #127); record this cycle in Completed This Cycle section

Closes leeworks-agents/api-company#145
Closes leeworks-agents/api-company#146
2026-05-31 10:06:20 +00:00
AI-Manager 4c1a577eab Merge pull request 'feat: update pricing page to include VIN Decoder as 4th API (#141)' (#144) from feature/141-pricing-vin-decoder into main
Validate Flux manifests / kustomize-build (push) Failing after 8s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 43s
2026-05-31 05:07:28 +00:00
AI-Manager 9ca29694ab Merge pull request 'feat: add Flux deployment manifests for VIN Decoder service (#140)' (#143) from feature/140-flux-vin-decoder into main
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 32s
Validate Flux manifests / kustomize-build (push) Failing after 16s
2026-05-31 05:07:20 +00:00
AI-Manager 5a7c4342fa Merge pull request 'feat: extend publish-openapi pipeline to include VIN Decoder API (#138)' (#142) from feature/138-publish-openapi-vin-decoder into main
Validate Flux manifests / kustomize-build (push) Failing after 8s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 43s
2026-05-31 05:07:15 +00:00
agent-company 5fc5013a51 feat: update pricing page to include VIN Decoder as 4th API (#141)
Validate Flux manifests / kustomize-build (pull_request) Failing after 8s
- Update meta description to include VIN Decoder
- Change hero text from 'all three APIs' to 'all four APIs'
- Add VIN Decoder column to the pricing table with same 4-tier rates
- Add link to /vin-decoder docs page in the CTA row

npm run build exits 0; /pricing route builds successfully.

Closes leeworks-agents/api-company#141
2026-05-31 05:06:26 +00:00
agent-company f722757417 feat: add Flux deployment manifests for VIN Decoder service (flux/vin-decoder/)
Validate Flux manifests / kustomize-build (pull_request) Failing after 25s
Create flux/vin-decoder/ with namespace, HelmRelease, ExternalSecret, and
kustomization files mirroring the existing API Flux manifest structure.
Register vin-decoder in the root flux/kustomization.yaml so Flux picks it up.
Image automation is handled by existing flux/image-automation/ manifests
(ImageRepository and ImagePolicy for vin-decoder already present).

kustomize build flux/ passes with no errors.

Closes leeworks-agents/api-company#140
2026-05-31 05:04:51 +00:00
agent-company 2d8ad295d4 feat: extend publish-openapi pipeline to include VIN Decoder API
Validate Flux manifests / kustomize-build (pull_request) Failing after 20s
Add vin-decoder as 4th entry to the APIS array in scripts/publish-openapi.js
and add RAPIDAPI_VIN_API_ID / RAPIDAPI_VIN_VERSION_ID secrets to the
publish-openapi.yaml CI workflow env block.

Closes leeworks-agents/api-company#138
2026-05-31 05:03:51 +00:00
AI-Manager 5bebb2d2da Merge pull request 'feat: extend Flux image automation, Gatus, and docs for VIN Decoder (#133, #134, #135, #136)' (#137) from feature/vin-decoder-flux-and-docs into main
Validate Flux manifests / kustomize-build (push) Failing after 15s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 52s
2026-05-31 00:04:50 +00:00
agent-company b8ba748db7 feat: extend Flux image automation, Gatus, and docs for VIN Decoder
Validate Flux manifests / kustomize-build (pull_request) Failing after 16s
Closes leeworks-agents/api-company#133
Closes leeworks-agents/api-company#134
Closes leeworks-agents/api-company#135
Closes leeworks-agents/api-company#136

- flux/image-automation/imagerepositories.yaml: add vin-decoder ImageRepository
  pointing at registry.leeworks.dev/vin-decoder/api (#133)
- flux/image-automation/imagepolicies.yaml: add vin-decoder ImagePolicy
  with semver range >=0.1.0 (#133)
- flux/monitoring/gatus-helmrelease.yaml: add VIN Decoder API endpoint check
  targeting https://vin.leeworks.dev/v1/health with Slack alert (#134)
- docs/pre-launch-checklist.md: add vin-decoder to Infrastructure, DNS & TLS,
  Functional Verification, and Legal sections; update status page line to
  reference all four APIs (#135)
- docs/secrets-checklist.md: add items #11-13 for vin-decoder GITEA_TOKEN,
  gitea-registry, and rapidapi-proxy-secret; add detail sections for each (#136)

kustomize build flux/ passes with zero errors.
2026-05-31 00:04:20 +00:00
AI-Manager b810c17681 Merge pull request '[Phase 6] docs: add VIN Decoder RapidAPI listing copy and vin.leeworks.dev DNS entry' (#132) from feature/issue-130-131-vin-decoder-docs into main
Validate Flux manifests / kustomize-build (push) Failing after 8s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 36s
2026-05-30 20:04:20 +00:00
agent-company fc079d67c9 docs: add VIN Decoder listing copy and dns.md entry
Validate Flux manifests / kustomize-build (pull_request) Failing after 24s
Closes leeworks-agents/api-company#131
Closes leeworks-agents/api-company#130

- docs/rapidapi-listings.md: add Section 4 — VIN Decoder API with
  title, tagline, short/long description, tier table (Free/Basic/Pro/Ultra),
  endpoint descriptions, and keywords. Mirrors style of existing three
  API sections. Tiers match the spec from issue #131.
- docs/dns.md: add vin.leeworks.dev as 8th subdomain in records table,
  verification dig command, action-required count (7→8), and status
  checklist entry. Updated last-updated date to 2026-05-30.
2026-05-30 20:03:48 +00:00
AI-Manager 635b7b1b10 Merge pull request '[Phase 5] docs-site: VIN Decoder Redoc page, index card, and SEO blog post' (#125) from feature/issue-123-124-vin-decoder-docs-blog into main
Validate Flux manifests / kustomize-build (push) Failing after 16s
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 42s
2026-05-30 15:07:10 +00:00
agent-company 39501f39bc [Phase 5] docs-site: add VIN Decoder Redoc page and SEO blog post
Validate Flux manifests / kustomize-build (pull_request) Failing after 21s
- Add docs-site/src/pages/vin-decoder.astro: Redoc reference page
  rendering /specs/vin-decoder.yaml (matches pattern of existing API pages)
- Copy apis/vin-decoder/openapi.yaml to docs-site/public/specs/vin-decoder.yaml
  so Redoc can serve the spec
- Add VIN Decoder card to index.astro hero grid (4th API)
- Add docs-site/src/pages/blog/vin-decoder.mdx: 1,500+ word SEO blog post
  'How to Decode a VIN Number with Node.js Using Free NHTSA Data'
  with meta description, working code examples, VIN structure explanation,
  NHTSA vPIC background, and use-case context
- npm run build exits 0; /vin-decoder and /blog/vin-decoder routes present

Closes leeworks-agents/api-company#123
Closes leeworks-agents/api-company#124
2026-05-30 15:06:32 +00:00
AI-Manager 10971e9d3d Merge pull request '[Phase 7] docs: launch-announcement.md — copy for all 5 marketing channels' (#119) from feature/issue-115-launch-announcement into main
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 38s
Validate Flux manifests / kustomize-build (push) Failing after 17s
2026-05-30 10:11:24 +00:00
AI-Manager a0371009f4 Merge pull request '[Phase 1-3] VIN Decoder: feasibility research and OpenAPI spec draft' (#118) from feature/issue-117-vin-decoder-spec into main
Build Docs Site / Aggregate OpenAPI Specs (push) Failing after 31s
Validate Flux manifests / kustomize-build (push) Failing after 19s
Validate and Publish OpenAPI Specs / Lint and publish specs (push) Successful in 51s
2026-05-30 10:10:44 +00:00
agent-company 76abe394a1 [Phase 7] docs: add launch-announcement.md with copy for all 5 marketing channels
Validate Flux manifests / kustomize-build (pull_request) Failing after 25s
All 5 sections written and copy-paste ready (pending RapidAPI URLs from #44):

1. Hacker News Show HN post (150-300 word body)
2. Reddit posts for r/webdev, r/SideProject, r/learnprogramming (with code examples)
3. Indie Hackers milestone post (MRR /bin/bash, stack, lessons learned)
4. Product Hunt listing: tagline (58 chars), description (211 chars), maker note, gallery placeholders
5. Email waitlist message: subject + 3-4 paragraph body with quick-start curl example

All sections marked DRAFT until issue #44 RapidAPI URLs are available.
Pre-publish checklist included at bottom of document.

Closes leeworks-agents/api-company#115
2026-05-30 10:09:40 +00:00
36 changed files with 9951 additions and 94 deletions
+45 -7
View File
@@ -21,33 +21,47 @@ jobs:
with:
path: api-company
# NOTE: cross-repo checkouts use SIBLING_REPOS_TOKEN, NOT the auto-injected
# GITEA_TOKEN. Gitea's automatic GITEA_TOKEN is scoped to THIS repo only,
# so checking out other repos fails with
# "Determining the default branch → not found". GITEA_TOKEN is also a
# reserved secret name that cannot be overridden, hence a separate secret.
# SIBLING_REPOS_TOKEN must be a PAT with read access to the API repos.
- name: Checkout zip-enrichment
uses: actions/checkout@v4
with:
repository: leeworks-agents/zip-enrichment
token: ${{ secrets.GITEA_TOKEN }}
token: ${{ secrets.SIBLING_REPOS_TOKEN }}
path: zip-enrichment
- name: Checkout holidays
uses: actions/checkout@v4
with:
repository: leeworks-agents/holidays
token: ${{ secrets.GITEA_TOKEN }}
token: ${{ secrets.SIBLING_REPOS_TOKEN }}
path: holidays
- name: Checkout air-quality
uses: actions/checkout@v4
with:
repository: leeworks-agents/air-quality
token: ${{ secrets.GITEA_TOKEN }}
token: ${{ secrets.SIBLING_REPOS_TOKEN }}
path: air-quality
- name: Checkout vin-decoder
uses: actions/checkout@v4
with:
repository: leeworks-agents/vin-decoder
token: ${{ secrets.SIBLING_REPOS_TOKEN }}
path: vin-decoder
- name: Copy openapi.yaml specs into docs-site
run: |
mkdir -p api-company/docs-site/public/specs
cp zip-enrichment/openapi.yaml api-company/docs-site/public/specs/zip-enrichment.yaml
cp holidays/openapi.yaml api-company/docs-site/public/specs/holidays.yaml
cp air-quality/openapi.yaml api-company/docs-site/public/specs/air-quality.yaml
cp vin-decoder/openapi.yaml api-company/docs-site/public/specs/vin-decoder.yaml
echo "Specs copied:"
ls -la api-company/docs-site/public/specs/
@@ -64,15 +78,39 @@ jobs:
working-directory: api-company/docs-site
run: npm run build
- name: Log in to container registry
- name: Install Docker CLI
# The job container (node:20) ships no `docker` binary. The runner has a
# dind daemon (dind.enabled in the runner HelmRelease), reachable via
# DOCKER_HOST, so we only need the client. Install the static binary.
env:
DOCKER_CLI_VERSION: "27.3.1"
run: |
echo "${{ secrets.GITEA_TOKEN }}" | docker login registry.leeworks.dev \
-u ${{ gitea.actor }} --password-stdin
set -euxo pipefail
curl -fSL --retry 5 --retry-delay 3 --retry-all-errors \
-o /tmp/docker.tgz \
"https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_CLI_VERSION}.tgz"
tar -xzf /tmp/docker.tgz -C /tmp
install -m 0755 /tmp/docker/docker /usr/local/bin/docker
docker version --format '{{.Client.Version}}'
docker info >/dev/null # confirms the dind daemon is reachable
- name: Log in to container registry
# Use Gitea's built-in container registry (gitea.leeworks.dev), which
# has a valid Let's Encrypt cert. The standalone registry.leeworks.dev
# serves Traefik's default self-signed cert and fails TLS verification.
#
# Auth uses REGISTRY_TOKEN, NOT the auto GITEA_TOKEN: the auto token has
# no package-registry scope, and the registry requires the username to
# match the token owner. REGISTRY_TOKEN must be a PAT (owner: 0xWheatyz)
# with write:package + read:package scope.
run: |
echo "${{ secrets.REGISTRY_TOKEN }}" | docker login gitea.leeworks.dev \
-u 0xWheatyz --password-stdin
- name: Build and push docs-site image
working-directory: api-company/docs-site
run: |
IMAGE="registry.leeworks.dev/leeworks-agents/docs-site"
IMAGE="gitea.leeworks.dev/leeworks-agents/docs-site"
SHA="${{ gitea.sha }}"
docker build -t "$IMAGE:$SHA" -t "$IMAGE:latest" .
docker push "$IMAGE:$SHA"
+4 -1
View File
@@ -2,7 +2,8 @@
# Requires secrets (configured once RapidAPI listings are live):
# RAPIDAPI_PLATFORM_KEY, RAPIDAPI_ZIP_API_ID, RAPIDAPI_ZIP_VERSION_ID,
# RAPIDAPI_HOLIDAYS_API_ID, RAPIDAPI_HOLIDAYS_VERSION_ID,
# RAPIDAPI_AQI_API_ID, RAPIDAPI_AQI_VERSION_ID
# RAPIDAPI_AQI_API_ID, RAPIDAPI_AQI_VERSION_ID,
# RAPIDAPI_VIN_API_ID, RAPIDAPI_VIN_VERSION_ID
#
# If RAPIDAPI_PLATFORM_KEY is not set the publish step exits 0 with a skip message.
@@ -55,4 +56,6 @@ jobs:
RAPIDAPI_HOLIDAYS_VERSION_ID: ${{ secrets.RAPIDAPI_HOLIDAYS_VERSION_ID }}
RAPIDAPI_AQI_API_ID: ${{ secrets.RAPIDAPI_AQI_API_ID }}
RAPIDAPI_AQI_VERSION_ID: ${{ secrets.RAPIDAPI_AQI_VERSION_ID }}
RAPIDAPI_VIN_API_ID: ${{ secrets.RAPIDAPI_VIN_API_ID }}
RAPIDAPI_VIN_VERSION_ID: ${{ secrets.RAPIDAPI_VIN_VERSION_ID }}
run: node scripts/publish-openapi.js
+12 -2
View File
@@ -13,9 +13,19 @@ jobs:
- uses: actions/checkout@v4
- name: Install kustomize
# The act runner runs as root (no `sudo`). Download a pinned release
# tarball directly with retries instead of piping the upstream installer
# script through bash — the installer makes extra GitHub API calls that
# are rate-limited/unreliable on this runner, and `curl -s` hid the
# error (a silent exit 6 = could not resolve host).
env:
KUSTOMIZE_VERSION: "5.4.3"
run: |
curl -sL "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
sudo mv kustomize /usr/local/bin/
set -euxo pipefail
url="https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv${KUSTOMIZE_VERSION}/kustomize_v${KUSTOMIZE_VERSION}_linux_amd64.tar.gz"
curl -fSL --retry 5 --retry-delay 3 --retry-all-errors -o /tmp/kustomize.tar.gz "$url"
tar -xzf /tmp/kustomize.tar.gz -C /usr/local/bin kustomize
kustomize version
- name: kustomize build flux/
run: kustomize build flux/ > /dev/null
+37 -19
View File
@@ -2,40 +2,58 @@
Cross-repo milestones. Per-API phases live in each API repo's ROADMAP.md.
> **As of 2026-05-30, a 4th API (VIN Decoder) has been added. Per-API phases also live in `leeworks-agents/vin-decoder`.**
## Phase 0 — Orientation (one-time)
- [ ] Audit cluster: nodes, namespaces, ingress, Flux state → `docs/cluster-audit.md`
- [ ] Add Flux GitRepository + Kustomization watching `leeworks-agents/api-company`
- [ ] Deploy `gitea-act-runner` in cluster via Flux HelmRelease (`flux/gitea-runner/`)
- [ ] Stand up `registry.leeworks.dev` (container registry) — required before any API CI works
- [x] Audit cluster: nodes, namespaces, ingress, Flux state → `docs/cluster-audit.md` (issue #36 — closed ✅)
- [ ] Add Flux GitRepository + Kustomization watching `leeworks-agents/api-company` — issue #218 (fork PR #399 merged to leeworks-agents/Talos; awaiting operator merge of 0xWheatyz/Talos PR #14)
- [ ] Deploy `gitea-act-runner` in cluster via Flux HelmRelease (`flux/gitea-runner/`) — issue #3
- [ ] Stand up `registry.leeworks.dev` (container registry) — required before any API CI works — issue #4
- [ ] Validate ESO HelmRelease deployment and ExternalSecret sync for all 4 API namespaces — issue #178
- [ ] Extend full-stack Flux validation checklist to cover VIN Decoder — issue #179 (extends #66)
## Phase 1 — API Contracts
Each API repo writes its `openapi.yaml` first and lints it. Spec is the source of truth.
- See: `leeworks-agents/zip-enrichment`, `holidays`, `air-quality`
- See: `leeworks-agents/zip-enrichment`, `holidays`, `air-quality`, `vin-decoder`
## Phase 2 — Data
Per-API: pull free public data sources, cache locally (SQLite or in-memory), schedule monthly re-seed.
- [x] VIN Decoder: NHTSA vPIC as data source — SQLite cache seeded on demand (issue #120 — closed ✅)
## Phase 3 — Servers
Per-API: implement Fastify/FastAPI service matching `openapi.yaml` exactly. RapidAPI proxy-secret middleware on every route.
- [x] VIN Decoder: Fastify server implementation (issue #121 — closed ✅)
## Phase 4 — Monitoring
- [ ] Prometheus + Grafana via Flux HelmRelease (`flux/monitoring/`)
- [ ] Gatus public status page at `status.leeworks.dev`
- [ ] Instrument every API with `api_requests_total`, `api_response_duration_seconds`, `api_data_freshness_seconds`
- [ ] Prometheus + Grafana via Flux HelmRelease (`flux/monitoring/`) — issue #7
- [ ] Gatus public status page at `status.leeworks.dev` — issue #8
- [x] Instrument every API with `api_requests_total`, `api_response_duration_seconds`, `api_data_freshness_seconds` (issue #27 — closed ✅)
- [x] Add Prometheus metrics endpoint to holidays service (issue #180 — closed ✅)
- [ ] Verify VIN Decoder metrics endpoint and ServiceMonitor scrape config — issue #231
- [x] VIN Decoder metrics instrumentation + Grafana dashboard (issue #129 — closed ✅)
- [x] VIN Decoder Gatus health check (issue #134 — closed ✅)
- [x] Enable Alertmanager in kube-prometheus-stack + configure Slack receiver for SLO alerts (issue #210 — closed ✅)
## Phase 5 — Documentation site
- [ ] `docs-site/` Astro + Redoc skeleton
- [ ] Gitea Actions pipeline that copies per-API `openapi.yaml` files at build time
- [ ] Deploy to `docs.leeworks.dev` via Flux
- [ ] One SEO blog post per API minimum (see master prompt §Phase 5)
- [x] `docs-site/` Astro + Redoc skeleton (issue #28 — closed ✅)
- [x] Gitea Actions pipeline that copies per-API `openapi.yaml` files at build time (issues #29 ✅, #38 ✅, #85 ✅)
- [ ] Deploy to `docs.leeworks.dev` via Flux — issue #30
- [x] One SEO blog post per API minimum (issues #39 ✅ — ZIP/Holidays/AirQuality, #124 ✅ — VIN Decoder)
- [x] VIN Decoder docs page (issue #123 — closed ✅)
- [x] VIN Decoder SEO blog post (issue #124 — closed ✅)
## Phase 6 — Pre-launch
- [ ] `docs/legal/terms-of-service.md`
- [ ] `docs/legal/privacy-policy.md`
- [ ] `docs/legal/acceptable-use-policy.md`
- [ ] DNS: `zip.leeworks.dev`, `holidays.leeworks.dev`, `aqi.leeworks.dev`, `docs.`, `status.`, `registry.`
- [ ] PayPal linked to RapidAPI (manual, on 18th birthday)
- [ ] Paid tiers enabled on RapidAPI listings (manual)
- [x] `docs/legal/terms-of-service.md` (issue #40 — closed ✅)
- [x] `docs/legal/privacy-policy.md` (issue #40 — closed ✅)
- [x] `docs/legal/acceptable-use-policy.md` (issues #40 ✅, #89 ✅)
- [ ] DNS: `zip.leeworks.dev`, `holidays.leeworks.dev`, `aqi.leeworks.dev`, `docs.`, `status.`, `registry.` — issues #33, #106, #150
- [x] VIN Decoder DNS `vin.leeworks.dev` (issue #130 — closed ✅)
- [x] VIN Decoder RapidAPI listing (issue #131 — closed ✅ as tracker; operator action required)
- [ ] PayPal linked to RapidAPI (manual, on 18th birthday) — issue #19
- [ ] Paid tiers enabled on RapidAPI listings (manual) — issues #44 (original 3), #151 (VIN Decoder)
- [ ] Execute pre-launch checklist before go-live — issue #166
- [ ] Publish launch announcement (HN, Reddit, Twitter, Dev.to) — issue #167
## Revenue target
@@ -47,4 +65,4 @@ $100/mo **net** ≈ $136/mo gross after RapidAPI 25% + PayPal ~2%.
| 7 Pro @ $19 | $97 net |
| 3 Ultra @ $49 | $107 net |
(Per-API rough math; mix-and-match across the 3 APIs.)
(Per-API rough math; mix-and-match across all 4 APIs — ZIP Enrichment, Holidays, Air Quality, VIN Decoder.)
+295 -28
View File
@@ -1,29 +1,314 @@
# Company Status
_Last updated: 2026-05-30 (sprint planning cycle)_
_Last updated: 2026-06-15 (triage cycle)_
## Completed This Cycle (2026-06-15 — Triage Cycle)
- **Triage** — All 38 open `agent-ready` issues reviewed; all confirmed still blocked on the same critical-path operator tasks: #47 (create `0xWheatyz/api-company`), #218 (merge Talos PR #14), #33/#106/#150 (DNS for all 8 subdomains).
- **No agent-implementable work found** — every open issue is either a manual operator task or gated on Flux/cluster activation; kubectl unreachable from agent environment.
- **0 new issues, 0 closures, 0 label changes, 0 open PRs.**
- **`kustomize build flux/` = PASS** (1648 lines, no manifest regressions).
- **Critical path unchanged:** (1) Operator creates `0xWheatyz/api-company` (#47), (2) Operator merges `0xWheatyz/Talos` PR #14 (#218), (3) Operator configures DNS for all 8 subdomains (#33, #106, #150).
## APIs
| API | Spec | Code | Deployed | Listed on RapidAPI | Paying Users | MRR |
|----------------|------|------|----------|--------------------|--------------|-----|
| ZIP Enrichment | [~] | [~] | [ ] | [ ] | 0 | $0 |
| Holidays | [~] | [~] | [ ] | [ ] | 0 | $0 |
| Air Quality | [ ] | [ ] | [ ] | [ ] | 0 | $0 |
| ZIP Enrichment | [x] | [x] | [ ] | [ ] | 0 | $0 |
| Holidays | [x] | [x] | [ ] | [ ] | 0 | $0 |
| Air Quality | [x] | [x] | [ ] | [ ] | 0 | $0 |
| VIN Decoder | [x] | [x] | [ ] | [ ] | 0 | $0 |
Legend: [x]=done, [~]=in-progress, [ ]=not started
## Infrastructure
- Cluster nodes: 3 control plane (10.0.1.3, .4, .5) + workers (testing1)
- **Flux wiring (api-company):** Manifests staged at `flux/api-company-source/`PENDING Talos merge (issue #2)
- **Flux wiring (api-company):** `flux/api-company-source/`Fork PR #399 **MERGED** to leeworks-agents/Talos; upstream PR #14 open on 0xWheatyz/Talos — awaiting operator merge to activate Flux (issue #2)
- **Gitea Actions runner:** Flux manifest committed at `flux/gitea-runner/` — PENDING runner token secret + Talos wiring (issue #3)
- **Container registry:** Gitea built-in registry selected; docs/registry.md committed — PENDING Gitea packages enabled (issue #4)
- **Prometheus + Grafana:** Flux HelmRelease at `flux/monitoring/` — PENDING Flux wiring + Grafana secret (issue #7)
- **Gatus status page:** Flux HelmRelease at `flux/monitoring/gatus-helmrelease.yaml` — PENDING Flux wiring (issue #8)
- **API service manifests:** `flux/zip-enrichment/`, `flux/holidays/`, `flux/air-quality/` scaffolded (PR #48, closes #46)
- **Image automation:** `flux/image-automation/` — ImageRepository + ImagePolicy + ImageUpdateAutomation for all three APIs
- **VIN Decoder manifests:** `flux/vin-decoder/` — Flux HelmRelease + Kustomization committed (issue #140)
- **Alertmanager:** `flux/monitoring/helmrelease.yaml` — enabled with Slack receiver config (`gatus-slack-webhook` secret); PR #212 open (issue #210)
## Completed This Cycle (2026-05-30 — Sprint Cycle)
## Completed Previous Cycle (2026-06-08 — Triage Cycle #240)
- **Triage** — All 28 open `agent-ready` issues reviewed and commented with current status (2026-06-08).
- **All issues confirmed blocked** on the same critical-path operator tasks: #47 (create `0xWheatyz/api-company`), #218 (merge Talos PR #14), #33/#106/#150 (DNS).
- **No agent-implementable work found** — all open issues are either manual operator tasks or gated on Flux/cluster activation; kubectl unreachable from agent environment.
- **28 issues triaged** — status comments posted on all open `agent-ready` issues.
- **0 new issues created** — no gaps found; all ROADMAP items remain grounded in open issues.
- **0 issues closed** — no stale issues; oldest open issue is #3 from 2026-05-18 (21 days < 30-day threshold).
- **0 label changes** — all existing labels correct.
- **0 open PRs** — nothing to review or merge.
- **`kustomize build flux/` = PASS** — no manifest regressions.
- **Critical path unchanged:** (1) Operator creates `0xWheatyz/api-company` (#47), (2) Operator merges `0xWheatyz/Talos` PR #14 (#218), (3) Operator configures DNS for all 8 subdomains (#33, #106, #150).
## Completed Previous Cycle (2026-06-07 — Triage Cycle #232)
- **Triage** — All 28 open `agent-ready` issues reviewed and commented with current status (2026-06-07).
- **All issues confirmed blocked** on the same critical-path operator tasks: #47 (create `0xWheatyz/api-company`), #218 (merge Talos PR #14), #33/#106/#150 (DNS).
- **No agent-implementable work found** — all open issues are either manual operator tasks or gated on Flux/cluster activation; kubectl unreachable from agent environment.
- **1 new issue triaged** — #231 commented with @qa-engineer assessment; BLOCKED on #218.
- **0 new issues created** — no gaps found; all ROADMAP items remain grounded in open issues.
- **0 issues closed** — no stale issues.
- **0 label changes** — all existing labels correct.
- **0 open PRs** — nothing to review or merge.
- **`kustomize build flux/` = PASS** — no manifest regressions.
- **Critical path unchanged:** (1) Operator creates `0xWheatyz/api-company` (#47), (2) Operator merges `0xWheatyz/Talos` PR #14 (#218), (3) Operator configures DNS for all 8 subdomains (#33, #106, #150).
## Completed Previous Cycle (2026-06-06 — Sprint Planning Cycle #227)
- **Sprint Planning** — Full ROADMAP cross-reference + backlog triage completed (2026-06-06).
- **ROADMAP cross-reference:** All 18 unchecked items confirmed grounded in open issues — **zero gaps**.
- Phase 0: #3, #4, #66, #76#83, #126#128, #173, #178, #179, #203, #218 — all tracked.
- Phase 4: #7, #8, #174 — all tracked.
- Phase 5: #30 — tracked.
- Phase 6: #19, #33, #44, #106, #116, #139, #150, #151, #161, #166, #167 — all tracked.
- **Staleness check:** Oldest open issue is #3 / #4 from 2026-05-18 (19 days) — **below 30-day threshold; zero closures**.
- **Label audit:** All priority/complexity labels confirmed correct from cycle #221 audit — **zero changes**.
- **0 new issues created** — no roadmap gaps found.
- **0 issues closed** — no stale issues.
- **0 label changes** — all existing labels correct.
- **`kustomize build flux/` = PASS** — no manifest regressions.
- **Critical path unchanged:** (1) Operator creates `0xWheatyz/api-company` (#47), (2) Operator merges `0xWheatyz/Talos` PR #14 (#218), (3) Operator configures DNS for all 8 subdomains (#33, #106, #150).
## Completed Previous Cycle (2026-06-06 — Triage Cycle #226)
- **Triage** — All 28 open `agent-ready` issues reviewed and commented with current status (2026-06-06).
- **All issues confirmed blocked** on the same critical-path operator tasks: #47 (create `0xWheatyz/api-company`), #218 (merge Talos PR #14), #33/#106/#150 (DNS).
- **No agent-implementable work found** — all open issues are either manual operator tasks or gated on Flux/cluster activation.
- **0 new issues created** — no gaps found; all ROADMAP items are grounded in open issues.
- **0 issues closed** — no issues are stale or resolvable by agent action.
- **0 PRs open** — nothing to review or merge.
- **`kustomize build flux/` = PASS** — no manifest regressions.
- **Current critical path unchanged:** (1) Operator creates `0xWheatyz/api-company` (#47), (2) Operator merges `0xWheatyz/Talos` PR #14 (#218), (3) Operator configures DNS for all 8 subdomains (#33, #106, #150).
## Completed Previous Cycle (2026-06-06 — Sprint Planning Cycle #221)
- **#221** — STATUS.md updated for 2026-06-06 sprint planning cycle (this entry). Last-updated date confirmed 2026-06-06.
- **0 new issues created** — all ROADMAP unchecked items confirmed grounded in open issues (35 open issues, zero gaps).
- **0 issues closed** — oldest open issue is #3 from 2026-05-18 (19 days < 30-day stale threshold).
- **3 label changes:**
- #106 (grafana.leeworks.dev DNS): added `blocked` label (missing despite dependency on #47)
- #150 (vin.leeworks.dev DNS): added `blocked` label (missing despite dependency on #47)
- #203 (ClusterIssuer verification): downgraded P1 → P2 (gated on DNS #33, not a first-order blocker)
- **Critical path unchanged:** Operator must (1) create `0xWheatyz/api-company` (#47), (2) merge upstream Talos PR #14 (#218) to activate Flux, (3) configure DNS (#33, #106, #150).
- **`kustomize build flux/` = PASS** — no manifest regressions.
## Completed Previous Cycle (2026-06-06 — Docs Cycle #219)
- **#219** — STATUS.md Current Blockers updated: removed closed issues #187 and #90; replaced with open issue #218 (Operator: merge 0xWheatyz/Talos upstream PR #14 to activate Flux GitOps for api-company). Critical Path item 2 updated to reference #218. `kustomize build flux/` = PASS.
## Completed Previous Cycle (2026-06-05 — Sprint Planning Cycle #215)
- **#215** — STATUS.md updated for 2026-06-05 sprint planning cycle (this entry). Last-updated date confirmed 2026-06-05.
- **1 new issue created:** #214 (ROADMAP.md Phase 4 Alertmanager annotation — documentation gap filed).
- **0 issues closed** — no stale issues; oldest open issue is #2 from 2026-05-18 (18 days < 30-day threshold).
- **0 label/priority changes** — all existing labels confirmed correct.
- **ROADMAP cross-reference:** all unchecked items confirmed grounded in open issues; 1 new documentation gap found and filed (#214 — Alertmanager / Slack receiver missing from ROADMAP.md Phase 4).
- **Critical path unchanged:** Operator must (1) create `0xWheatyz/api-company` (#47), (2) merge upstream Talos PR #14 (#187) to activate Flux, (3) configure DNS (#33, #106, #150).
- **`kustomize build flux/` = PASS** — no regressions.
- **0 open PRs** — nothing to review or merge.
## Completed Previous Cycle (2026-06-05 — Sprint Planning Cycle #211)
- **#211** — STATUS.md updated for 2026-06-05 sprint planning cycle (this entry). Last-updated date confirmed 2026-06-05.
- **1 new issue implemented:** #210 (Enable Alertmanager + Slack receiver — gap identified by roadmap cross-reference); PR #212 opened to `leeworks-agents/api-company` with `kustomize build flux/` = PASS.
- **0 issues closed** — oldest open issue is #2 from 2026-05-18 (18 days old — under 30-day stale threshold).
- **0 label changes** — all priority/complexity labels confirmed correct.
- **ROADMAP cross-reference:** 18 unchecked items confirmed grounded in open issues; 1 new gap found and filed (#210 — Alertmanager was missing from roadmap coverage).
- **Current Blockers unchanged:** Operator must (1) create `0xWheatyz/api-company` (#47), (2) merge upstream Talos PR #14 (#187) to activate Flux, (3) configure DNS (#33, #106, #150).
- **`kustomize build flux/` = PASS** — no regressions.
- **1 open PR** — PR #212 (Alertmanager enable + Slack receiver): `kustomize build flux/` PASS; pending merge.
## Completed Previous Cycle (2026-06-05 — Sprint Planning Cycle #206)
- **#206** — STATUS.md updated for 2026-06-05 sprint planning cycle (this entry). Last-updated date set to 2026-06-05.
- **Completed outcomes from 2026-06-04 sprint planning cycle recorded:**
- Issue #204 (smoke-tests): reopened and relabeled `blocked` — was prematurely closed while prerequisites (DNS #33/#150, Flux #2/#187) are still open.
- ROADMAP cross-reference: all 18 unchecked items confirmed grounded in open issues (zero gaps).
- Staleness check: oldest open issue is #2 from 2026-05-18 (18 days, under 30-day threshold — no closures).
- Label audit: no label changes required — all existing priority/complexity labels are correct.
- **Critical path confirmed:** Operator must (1) create `0xWheatyz/api-company` (#47), (2) merge upstream Talos PR #14 (#187) to activate Flux, (3) configure DNS (#33, #106, #150).
- **`kustomize build flux/` = PASS** — no regressions.
- **0 open PRs** — nothing to review or merge.
## Completed Previous Cycle (2026-06-04 — Triage Cycle #204)
- **Triage** — All 29 open `agent-ready` issues reviewed and commented with current status (2026-06-04).
- **#204** (smoke tests): ❌ BLOCKED — all HTTP endpoints timeout; DNS not configured; kubectl unreachable.
- **#203** (ClusterIssuer): ❌ BLOCKED — kubectl not accessible from agent environment.
- **#187, #90, #2** (Flux wiring): Waiting on operator to merge upstream Talos PR #14.
- **#179, #178, #66** (validation checklists): Blocked on Flux active + cluster access.
- **#174** (VIN metrics): `kustomize build flux/` PASS; blocked on cluster deploy.
- **#173, #81, #128, #126, #127, #83, #80, #79, #77, #76, #74, #73, #70** (secrets): Manual operator tasks, all documented.
- **#167, #166** (launch/pre-launch): Blocked on DNS + Flux + RapidAPI + PayPal.
- **#161, #151, #150, #139, #116, #106, #47, #44, #33, #30, #19, #8, #7, #4, #3**: Manual operator tasks or blocked on operator actions.
- **`kustomize build flux/` = PASS** — no regressions.
- **0 PRs open** — nothing to review or merge.
- **Critical path unchanged:** Operator must (1) create `0xWheatyz/api-company` (#47), (2) merge upstream Talos PR #14 (#187) to activate Flux.
## Completed Previous Cycle (2026-06-04 — Sprint Planning Cycle #200/#201)
- **#200** — STATUS.md Infrastructure section updated: Flux wiring row for `flux/api-company-source/` updated to reflect Fork PR #399 **MERGED** to leeworks-agents/Talos; upstream PR #14 open on 0xWheatyz/Talos awaiting operator merge. Current Blockers updated to note upstream PR #14 as critical gate.
- **#201** — Sprint planning STATUS.md update for 2026-06-04 cycle. Issues #200 and #201 created this cycle. ROADMAP cross-reference: all 18 unchecked items confirmed grounded in open issues (no gaps). Staleness check: oldest open issue is #2 from 2026-05-18 (17 days, not stale — threshold 30 days). Priority review: all labels confirmed appropriate. Critical path: operator-gated on 0xWheatyz/Talos PR #14 merge.
- **`kustomize build flux/` = PASS** — no regressions confirmed.
## Completed Previous Cycle (2026-06-04 — Sprint Planning Cycle #198)
- **#198** — STATUS.md updated for 2026-06-04 sprint planning cycle. Last updated date set to 2026-06-04.
- **Sprint Summary:** 0 new code issues created (all ROADMAP items grounded — all 18 unchecked items have corresponding open issues). 0 issues closed (no stale; oldest is #2 at 17 days). 0 label changes. Critical path is entirely operator-gated: Talos merge of #187 unblocks the #2/#3/#4 chain.
- **`kustomize build flux/` = PASS** — no regressions confirmed.
## Completed Previous Cycle (2026-06-04 — Sprint Planning Cycle #196)
- **#196** — STATUS.md updated for 2026-06-04 sprint planning cycle. Last updated date set to 2026-06-04.
- **Sprint Summary:** ROADMAP cross-reference complete — all 18 unchecked items have corresponding open issues (no gaps). 1 new issue created: #196 (this STATUS.md update). 0 issues closed (no stale issues; oldest open issue is #2 from 2026-05-18, only 17 days old). 0 label changes (all priority/complexity labels correct). Critical path remains entirely operator-gated.
- **`kustomize build flux/` = PASS** — no regressions confirmed.
## Completed Previous Cycle (2026-06-03 — Sprint Planning Cycle #193/#194)
- **#194** — ROADMAP.md annotated with missing issue cross-references: Phase 0 issue #2 entry updated to reference implementation issue #187; Phase 6 entries added for issue #166 (pre-launch checklist) and #167 (launch announcement). Closes leeworks-agents/api-company#194.
- **#193** — STATUS.md updated for 2026-06-03 sprint planning cycle. Current Blockers confirmed accurate; #80 (gitea-image-automation-token) listed. Closes leeworks-agents/api-company#193.
- **Sprint Summary:** 2 new documentation issues created (#193 STATUS.md update, #194 ROADMAP annotation). 0 issues closed. 0 stale. Critical path entirely operator-gated.
- **`kustomize build flux/` = PASS** — no regressions confirmed.
## Completed Previous Cycle (2026-06-03 — Audit Cycle #190/#191)
- **#190** — ZIP Enrichment API audit complete: `openapi.yaml` present at repo root (57 KB, OpenAPI 3.0.3); server entry point `src/server.js`; all routes confirmed implemented; `X-RapidAPI-Proxy-Secret` preHandler middleware on all routes except `/health`; `/metrics` Prometheus endpoint present; STATUS.md ZIP Enrichment Spec and Code rows updated to `[x]`. Closes leeworks-agents/api-company#190.
- **#191** — Holidays API audit complete: `openapi.yaml` present at repo root (13 KB, OpenAPI 3.0.3); server entry point `src/server.js`; all routes confirmed implemented; `X-RapidAPI-Proxy-Secret` preHandler middleware on all routes except `/health` and `/metrics`; `/metrics` Prometheus endpoint present (issue #180 confirmed done); STATUS.md Holidays Spec and Code rows updated to `[x]`. Closes leeworks-agents/api-company#191.
- **`kustomize build flux/` = PASS** — no regressions confirmed.
## Completed Previous Cycle (2026-06-03 — Sprint Planning Cycle)
- **#188** — STATUS.md updated for 2026-06-03 sprint planning cycle (this entry). Last updated date set to 2026-06-03.
- **#187** — Talos PR for api-company Flux source manifests: PR #399 on `leeworks-agents/Talos` confirmed valid; upstream deploy PR created targeting `0xWheatyz/Talos`.
- **#186** — Air Quality API audit complete: `openapi.yaml` ✅ exists at repo root in `leeworks-agents/air-quality`; server implementation ✅ present (`src/server.ts`, `src/app.ts`, `src/routes/`); STATUS.md Air Quality row updated to `[x]/[x]`.
- **#178** relabeled — added `blocked` label (depends on #173 + Flux active).
- **#174** relabeled — added `blocked` label (depends on #18 deploy + #7 monitoring).
- **Current Blockers updated:** #187 added before #90 (same unblock target, more actionable).
- **`kustomize build flux/` = PASS** — no regressions confirmed.
## Completed Previous Cycle (2026-06-02 — Sprint Planning Cycle #175/#176)
- **#176** — ROADMAP.md Phase 6 unchecked items annotated with issue cross-references: DNS — issues #33, #106, #150; PayPal — issue #19; Paid tiers — issues #44 (original 3), #151 (VIN Decoder). No checked items changed.
- **#175** — STATUS.md updated with 2026-06-02 sprint planning cycle results (this entry). Last updated date set to 2026-06-02.
- **Sprint Summary:** 2 new documentation issues created (#175 STATUS.md update, #176 ROADMAP Phase 5/6 annotation). 0 code issues created (all ROADMAP items already covered by existing issues). 0 issues closed (none stale). 0 reprioritizations.
- **Current Blockers updated:** #173 (ESO source secrets in `external-secrets` ns) and #174 (VIN Decoder metrics instrumentation) added.
- **`kustomize build flux/` = PASS** — no regressions confirmed.
## Completed Previous Cycle (2026-06-02 — Sprint Planning Cycle)
- **#171** — ROADMAP.md Phase 0 unchecked items annotated with issue #2, #3, #4; Phase 4 unchecked items annotated with issue #7, #8, #27; Phase 5 unchecked item annotated with issue #30. No checked-off items changed.
- **#170** — STATUS.md updated with 2026-06-02 sprint planning cycle results (this entry). Last updated date set to 2026-06-02.
- **Sprint Summary:** 2 new issues created (#171, #170), 0 issues closed this cycle, 0 stale found.
- **`kustomize build flux/` = PASS** — no regressions confirmed.
## Completed Previous Cycle (2026-06-02 — Triage Cycle #168)
- **#168** — STATUS.md cleanup: removed all stale “open, in progress” references to #120 and #121 (both confirmed closed ✅ in leeworks-agents/vin-decoder). Historical sections corrected. VIN Decoder Code column confirmed `[x]`.
- **`kustomize build flux/` = PASS** — no regressions.
## Completed Previous Cycle (2026-06-01 — Sprint Planning Cycle)
- **#163** — ROADMAP.md Phase 2 and Phase 3 VIN Decoder bullets marked `[x]` with closed ✅ references for issues #120 and #121.
- **#164** — STATUS.md Current Blockers updated: removed #120 and #121 (both confirmed closed ✅). Top actionable agent task updated to #163.
- **`kustomize build flux/` = PASS** — no regressions.
## Completed Previous Cycle (2026-06-01 — Triage Cycle)
- **#158** — STATUS.md updated with 2026-06-01 current blockers and sprint summary.
- **#156** — ROADMAP.md checkboxes updated: Phase 4 VIN Decoder metrics, Phase 5 docs-site/pipeline/blog, Phase 6 legal docs all checked off.
- **VIN Decoder Code `[x]`** — leeworks-agents/vin-decoder data layer (#120) and Fastify server (#121) confirmed merged; STATUS.md Code column updated.
- **PR #160 closed** — had merge conflicts and ROADMAP.md regressions (unchecked completed items). Clean fix applied here.
- **`kustomize build flux/` = PASS** — no regressions.
## Current Blockers (as of 2026-06-07)
In dependency order — operator tasks unless noted:
1. **P1 #47** — Create `0xWheatyz/api-company` upstream repo (operator) — **highest priority, unblocks everything**
2. **P1 #76** — Create `gitea-leeworks-agents-token` secret in `flux-system` (operator)
3. **P1 #77** — Create `gitea-runner-token` secret in `gitea-runner` namespace (operator)
4. **P1 #79** — Create `gitea-registry` imagePullSecrets in all API namespaces (operator)
5. **P1 #83** — Configure `GITEA_TOKEN` Actions secret in zip-enrichment, holidays, air-quality repos (operator)
6. **P1 #126** — Configure `GITEA_TOKEN` Actions secret in vin-decoder repo (operator — depends on #122 ✅)
7. **P1 #127** — Create `gitea-registry` imagePullSecret in `vin-decoder` namespace (operator)
8. **P1 #218** — Operator: merge 0xWheatyz/Talos upstream PR #14 to activate Flux GitOps for api-company — Fork PR #399 already MERGED to leeworks-agents/Talos; upstream PR #14 is the critical gate; unblocks #2, #3, #4, #66 chain (replaces closed #187 and #90)
10. **P2 #80** — Create `gitea-image-automation-token` secret in `flux-system` namespace (operator) — enables Flux image automation
11. **P2 #173** — Create ESO source secrets in `external-secrets` namespace for RapidAPI proxy-secret syncing (operator) — blocks ESO sync
12. **P2 #231** — Verify VIN Decoder metrics endpoint and ServiceMonitor scrape config in live cluster (depends on #218 — Flux must be active first)
13. **P2 #178** — Validate ESO HelmRelease deployment and ExternalSecret sync for all 4 API namespaces (depends on #173 + Flux active)
14. **P2 #179** — Execute extended full-stack Flux validation checklist including VIN Decoder (depends on #126, #127, #128)
## Sprint Summary (2026-06-01 — Sprint Planning Cycle)
- **Issues closed this cycle:** 0
- **New issues created this cycle:** 1 (#163 — ROADMAP/STATUS cleanup after #120/#121)
- **Stale issues found:** 0
- **Top actionable agent task:** #163 (pure doc update, immediately actionable)
- **Critical path:** Entirely operator-gated after #163 is merged (see Current Blockers)
## Sprint Summary (2026-06-01)
- **Issues closed since last STATUS update (#146):** #145#154 — VIN Decoder sprint docs + infra (✅ all closed)
- **Issues opened this sprint:** #150 (vin DNS reminder), #151 (VIN Decoder RapidAPI tiers), #155 (if applicable), #156 (ROADMAP.md update), #157 (full-stack validation), #158 (STATUS.md update), #163 (ROADMAP/STATUS cleanup post-#120/#121), #164 (STATUS.md sprint planning update)
- **Top actionable agent task:** #163 (ROADMAP/STATUS doc cleanup for #120/#121 — pure doc update, immediately actionable)
## Completed Previous Cycle (2026-05-31 — Sprint Cycle)
- **#146** — STATUS.md updated to reflect VIN Decoder sprint wave (#117#141): APIs table row added, completed wave documented, blockers updated.
- **#145** — ROADMAP.md updated to reference VIN Decoder as 4th API across all phases (Phase 16). `kustomize build flux/` = PASS.
- **PR #147 opened** — docs: update ROADMAP.md and STATUS.md for VIN Decoder sprint wave (closes #145, #146).
## Completed Previous Cycle (2026-05-30 — VIN Decoder Sprint Wave)
### VIN Decoder issues #117#141 — status as of 2026-05-31:
- **#117** — VIN Decoder spec + feasibility — **closed ✅** (`apis/vin-decoder/openapi.yaml` merged)
- **#120** — VIN Decoder data layer (SQLite/NHTSA vPIC) — **closed ✅** (leeworks-agents/vin-decoder)
- **#121** — VIN Decoder Fastify server — **closed ✅** (leeworks-agents/vin-decoder)
- **#122** — Create `leeworks-agents/vin-decoder` repo — **closed ✅**
- **#123** — VIN Decoder docs page — **closed ✅**
- **#124** — VIN Decoder SEO blog post — **closed ✅**
- **#130** — VIN Decoder DNS `vin.leeworks.dev`**closed ✅**
- **#131** — VIN Decoder RapidAPI listing — **closed ✅** (tracker; operator action required)
- **#133** — VIN Decoder pricing page integration — **closed ✅**
- **#134** — VIN Decoder Gatus health check — **closed ✅**
- **#135** — VIN Decoder image automation — **closed ✅**
- **#136** — VIN Decoder ExternalSecret manifest — **closed ✅**
- **#138** — Extend publish-openapi pipeline to include VIN Decoder — **closed ✅** (PR #142 merged)
- **#139** — Operator: add RAPIDAPI_VIN_API_ID + RAPIDAPI_VIN_VERSION_ID secrets — **open** (operator task, blocked on RapidAPI listing)
- **#140** — VIN Decoder Flux manifests — **closed ✅** (PR #143 merged)
- **#141** — Pricing page updated for VIN Decoder — **closed ✅** (PR #144 merged)
## Critical Path (operator must complete in order)
See **Current Blockers** section above for the full ordered list as of 2026-06-01.
1. **Create `0xWheatyz/api-company` repo** (#47) — **highest priority, unblocks everything**
2. **Merge upstream Talos PR #14 to activate Flux for api-company** (#218) — manifests already committed; operator must merge 0xWheatyz/Talos PR #14
3. **Create `gitea-leeworks-agents-token` secret** in `flux-system` (#76)
4. **Create `gitea-runner-token` secret** in `gitea-runner` (#77)
5. **Enable Gitea packages + DNS for `registry.leeworks.dev`** (#4)
6. **All other secrets** (#70, #73, #74, #79, #80, #81, #83) follow in order
## Blockers — VIN Decoder critical path
- **P1: #127** — Create `gitea-registry` imagePullSecret in `vin-decoder` namespace — operator manual task
- **P2: #126** — Configure `GITEA_TOKEN` secret in `vin-decoder` repo — operator manual task
- **P2: #128** — Create `rapidapi-proxy-secret` in `vin-decoder` namespace — operator manual task
- ~~**P2: #129** — VIN Decoder Prometheus metrics instrumentation~~ — **closed ✅** (PR merged)
- **P3: #139** — Add RAPIDAPI_VIN_* Actions secrets — operator task, depends on RapidAPI listing (#131)
## New Issues This Sprint (2026-05-30, #120#146)
- **#120** — VIN Decoder data layer — **closed ✅** (leeworks-agents/vin-decoder)
- **#121** — VIN Decoder Fastify server — **closed ✅** (leeworks-agents/vin-decoder)
- **#126** — Operator: GITEA_TOKEN secret in vin-decoder repo — manual operator task
- **#127** — Operator: gitea-registry imagePullSecret in vin-decoder namespace — manual operator task, P1 blocker
- **#128** — Operator: rapidapi-proxy-secret in vin-decoder namespace — manual operator task
- **#129** — VIN Decoder Prometheus metrics — **open, P2, depends on #121**
- **#139** — Operator: RAPIDAPI_VIN_* secrets — **open, P3, operator task**
- **#145** — Update ROADMAP.md for VIN Decoder — **DONE (this cycle)**
- **#146** — Update STATUS.md for VIN Decoder sprint wave — **DONE (this cycle)**
## Completed Previous Cycle (2026-05-30 — Sprint Cycle)
- **#108** — STATUS.md updated with 2026-05-30 sprint plan and critical path.
- **#107** — docs-site build verified: `npm run build` exits 0; all 9 required routes present in `dist/` (`/`, `/zip-enrichment`, `/holidays`, `/air-quality`, `/blog/` (3 posts), `/legal/terms-of-service`, `/legal/privacy-policy`, `/legal/acceptable-use-policy`, `/pricing`). Unblocks #30.
- **#107** — docs-site build verified: `npm run build` exits 0; all 9 required routes present in `dist/`.
- **PR #105 MERGED** — `scripts/publish-openapi.js` + `.gitea/workflows/publish-openapi.yaml` (closes #100).
- **PR #103 MERGED** — `docs-site/src/pages/pricing.astro` + nav link (closes #101).
- **PR #104 MERGED** — `research/RESEARCH_LOG.md` first session: VIN Decoder next candidate (closes #102).
@@ -31,26 +316,6 @@ Legend: [x]=done, [~]=in-progress, [ ]=not started
- **`0xWheatyz/api-company`** — still does not exist; fork sync skipped (blocker #47).
- **`kustomize build flux/` = PASS** — no regressions.
## Critical Path (operator must complete in order)
1. **Create `0xWheatyz/api-company` repo** (#47) — **highest priority, unblocks everything**
2. **Add api-company Flux source to Talos** (#90) — needs Talos PR, reference manifests at `flux/api-company-source/`
3. **Create `gitea-leeworks-agents-token` secret** in `flux-system` (#76)
4. **Create `gitea-runner-token` secret** in `gitea-runner` (#77)
5. **Enable Gitea packages + DNS for `registry.leeworks.dev`** (#4)
6. **All other secrets** (#70, #73, #74, #79, #80, #81, #83) follow in order
## New Issues This Sprint (2026-05-29, #100#108)
- **#100** — `scripts/publish-openapi.js` + CI workflow — **DONE (PR #105 merged)**
- **#101** — Pricing page at `docs.leeworks.dev/pricing/`**DONE (PR #103 merged)**
- **#102** — Seed `research/RESEARCH_LOG.md`**DONE (PR #104 merged)**
- **#106** — Add `grafana.leeworks.dev` as 7th subdomain to DNS task (#33) — tracker only; docs already updated (#67)
- **#107** — docs-site build verification — **DONE (build passes, all 9 routes confirmed)**
- **#108** — STATUS.md update — **DONE (this update)**
## Backlog
- **28 open issues total**; all remaining blocked on operator Phase 0 actions above.
- **Agent work that can proceed without operator:** all completed this cycle.
## Completed Previous Cycle (2026-05-28 — Cycle-2 Triage)
- **#83** — Triaged: `GITEA_TOKEN` Actions secrets needed in `zip-enrichment`, `holidays`, `air-quality` repos. Manual operator task; reuse token from #74 if it has `write:package` scope. Step-by-step instructions posted.
- **No open PRs** — queue empty.
@@ -130,6 +395,7 @@ All flux manifests validate successfully. Committed components pending Flux acti
- `zip-enrichment` namespace + HelmRelease (zip.leeworks.dev) + rapidapi-proxy-secret placeholder
- `holidays` namespace + HelmRelease (holidays.leeworks.dev) + rapidapi-proxy-secret placeholder
- `air-quality` namespace + HelmRelease (aqi.leeworks.dev) + rapidapi-proxy-secret placeholder
- `vin-decoder` namespace + HelmRelease (vin.leeworks.dev) + Flux manifests
- `image-automation` ImageRepository + ImagePolicy + ImageUpdateAutomation for all three APIs
## Blockers (human operator action required)
@@ -138,6 +404,7 @@ All flux manifests validate successfully. Committed components pending Flux acti
3. **Create `gitea-leeworks-agents-token` secret** in `flux-system` namespace (HTTPS token for Gitea)
4. **Create `gitea-runner-token` secret** in `gitea-runner` namespace (Gitea Admin -> Actions -> Runners -> New Runner) (#3)
5. **Enable Gitea packages** (for container registry at `registry.leeworks.dev`) (#4)
6. **RapidAPI + PayPal setup** — manual, gated on operator turning 18 (#19, #44)
6. **Create `gitea-registry` imagePullSecret in `vin-decoder` namespace** (#127) — VIN Decoder P1
7. **RapidAPI + PayPal setup** — manual, gated on operator turning 18 (#19, #44)
> Full ordered runbook with copy-paste commands: `docs/operator-runbook.md`
+7567
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -8,9 +8,9 @@
"preview": "astro preview"
},
"dependencies": {
"astro": "^4.8.0",
"@astrojs/mdx": "^3.0.0",
"@astrojs/sitemap": "^3.1.0",
"@astrojs/sitemap": "~3.2.1",
"astro": "^4.8.0",
"redoc": "^2.1.5"
},
"devDependencies": {
+405
View File
@@ -0,0 +1,405 @@
openapi: 3.1.0
info:
title: VIN Decoder API
version: 1.0.0
description: |
Decode any 17-character Vehicle Identification Number (VIN) into structured
vehicle data including make, model, year, trim, engine, body style, and more.
Powered by the NHTSA vPIC public-domain database.
**Data source:** NHTSA Product Information Catalog and Vehicle Listing (vPIC)
public API - US federal government data, public domain (17 U.S.C. 105).
**Coverage:** Model years 1981-present, all major manufacturers registered
with NHTSA (domestic and import).
**Caching:** Decoded results are cached for 90 days; cache status is
indicated by the X-Cache response header.
contact:
name: leeworks.dev API Support
url: https://docs.leeworks.dev
license:
name: MIT
url: https://opensource.org/licenses/MIT
servers:
- url: https://vin.leeworks.dev/v1
description: Production
security:
- RapidApiProxy: []
tags:
- name: decode
description: VIN decoding endpoints
- name: health
description: Service health and observability
paths:
/decode:
get:
operationId: decodeVin
summary: Decode a single VIN
description: |
Decodes a 17-character VIN and returns structured vehicle attributes.
Results are cached for 90 days; a cache hit is indicated by
X-Cache: HIT in the response headers.
tags:
- decode
parameters:
- name: vin
in: query
required: true
description: 17-character Vehicle Identification Number (uppercase, no I/O/Q).
schema:
type: string
minLength: 17
maxLength: 17
pattern: "^[A-HJ-NPR-Z0-9]{17}$"
example: 1HGCM82633A004352
- name: raw
in: query
required: false
description: If true, include raw NHTSA vPIC fields in the response.
schema:
type: boolean
default: false
responses:
"200":
description: VIN successfully decoded
headers:
X-Cache:
schema:
type: string
enum: [HIT, MISS]
description: Whether the result was served from cache
X-Data-Source:
schema:
type: string
description: Upstream data source identifier
X-Request-Id:
schema:
type: string
description: Unique request identifier
content:
application/json:
schema:
$ref: "#/components/schemas/VinDecodeResult"
"400":
description: Invalid VIN format or missing parameter
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"403":
description: Missing or invalid RapidAPI proxy secret
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"429":
description: Rate limit exceeded for your plan
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"500":
description: Internal server error or upstream NHTSA API failure
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/batch:
post:
operationId: decodeVinBatch
summary: Decode up to 50 VINs in a single request
description: |
Accepts a JSON body with an array of VINs (1-50) and returns a decoded
result for each. Each VIN is processed independently; partial failures
return an error object in that position rather than failing the whole batch.
tags:
- decode
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- vins
properties:
vins:
type: array
minItems: 1
maxItems: 50
items:
type: string
minLength: 17
maxLength: 17
pattern: "^[A-HJ-NPR-Z0-9]{17}$"
description: Array of 17-character VINs to decode
example:
vins:
- 1HGCM82633A004352
- WBABW33486PX01612
responses:
"200":
description: Batch decode results (one entry per input VIN, in order)
headers:
X-Request-Id:
schema:
type: string
content:
application/json:
schema:
type: object
properties:
results:
type: array
items:
oneOf:
- $ref: "#/components/schemas/VinDecodeResult"
- $ref: "#/components/schemas/VinDecodeError"
count:
type: integer
description: Total number of VINs processed
cached_count:
type: integer
description: Number of results served from cache
error_count:
type: integer
description: Number of VINs that could not be decoded
"400":
description: Invalid request body
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"403":
description: Missing or invalid RapidAPI proxy secret
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"429":
description: Rate limit exceeded
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"500":
description: Internal server error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/health:
get:
operationId: healthCheck
summary: Service health check
description: |
Returns health status of the VIN Decoder service including cache
statistics and NHTSA API reachability. Does not require X-RapidAPI-Proxy-Secret.
tags:
- health
security: []
responses:
"200":
description: Service is healthy
content:
application/json:
schema:
$ref: "#/components/schemas/HealthResponse"
"503":
description: Service is degraded (upstream unreachable or DB error)
content:
application/json:
schema:
$ref: "#/components/schemas/HealthResponse"
components:
securitySchemes:
RapidApiProxy:
type: apiKey
in: header
name: X-RapidAPI-Proxy-Secret
description: |
RapidAPI proxy secret injected automatically by RapidAPI on every
subscriber request. Direct callers must include this header manually.
schemas:
VinDecodeResult:
type: object
required:
- vin
- error_code
properties:
vin:
type: string
description: The input VIN (uppercased)
example: 1HGCM82633A004352
make:
type: ["string", "null"]
description: Vehicle manufacturer brand
example: HONDA
model:
type: ["string", "null"]
description: Vehicle model name
example: Accord
model_year:
type: ["string", "null"]
description: Model year as a 4-digit string
example: "2003"
trim:
type: ["string", "null"]
description: Trim level (e.g. EX, LX, Sport)
example: EX
series:
type: ["string", "null"]
description: Series designation if applicable
body_class:
type: ["string", "null"]
description: Body style classification
example: Sedan/Saloon
drive_type:
type: ["string", "null"]
description: Drive configuration
example: FWD/Front-Wheel Drive
engine_displacement_cc:
type: ["number", "null"]
description: Engine displacement in cubic centimetres
example: 2354
engine_displacement_l:
type: ["number", "null"]
description: Engine displacement in litres
example: 2.4
engine_cylinders:
type: ["integer", "null"]
description: Number of engine cylinders
example: 4
fuel_type_primary:
type: ["string", "null"]
description: Primary fuel type
example: Gasoline
transmission_style:
type: ["string", "null"]
description: Transmission type (Automatic, Manual, CVT, etc.)
example: Automatic
transmission_speeds:
type: ["string", "null"]
description: Number of transmission speeds as string
example: "5"
plant_city:
type: ["string", "null"]
description: Assembly plant city
example: MARYSVILLE
plant_state:
type: ["string", "null"]
description: Assembly plant state/province
example: OHIO
plant_country:
type: ["string", "null"]
description: Assembly plant country
example: UNITED STATES (USA)
manufacturer_name:
type: ["string", "null"]
description: Full legal name of the manufacturer
example: HONDA OF AMERICA MFG., INC.
vehicle_type:
type: ["string", "null"]
description: NHTSA vehicle type classification
example: PASSENGER CAR
error_code:
type: string
description: NHTSA decode error code. "0" means successful decode.
example: "0"
error_text:
type: ["string", "null"]
description: Human-readable decode error (null when error_code is "0")
cached:
type: boolean
description: Whether this result was served from the local cache
example: true
VinDecodeError:
type: object
required:
- vin
- error
- message
properties:
vin:
type: string
description: The VIN that could not be decoded
error:
type: string
description: Error code
example: INVALID_VIN
message:
type: string
description: Human-readable error description
example: VIN must be exactly 17 alphanumeric characters
Error:
type: object
required:
- error
- message
- status
properties:
error:
type: string
description: Machine-readable error code
example: BAD_REQUEST
message:
type: string
description: Human-readable error description
example: "Query parameter 'vin' is required"
status:
type: integer
description: HTTP status code
example: 400
HealthResponse:
type: object
required:
- status
- version
properties:
status:
type: string
enum: [ok, degraded]
description: Overall service health
version:
type: string
description: Service version
example: "1.0.0"
uptime_seconds:
type: integer
description: Seconds since the service started
example: 86400
cache:
type: object
properties:
entries:
type: integer
description: Number of cached VIN records
hit_rate_24h:
type: number
description: Cache hit rate over the last 24 hours (0.0-1.0)
size_mb:
type: number
description: SQLite cache file size in megabytes
upstream:
type: object
properties:
nhtsa_vpic:
type: string
enum: [reachable, unreachable]
description: NHTSA vPIC API reachability
last_check:
type: string
format: date-time
description: ISO-8601 timestamp of last upstream health check
+340
View File
@@ -0,0 +1,340 @@
---
title: "How to Decode a VIN Number with Node.js Using Free NHTSA Data"
description: "Learn how to decode any 17-character Vehicle Identification Number (VIN) with Node.js using the free NHTSA vPIC database — or skip the plumbing and call the leeworks.dev VIN Decoder API directly."
date: "2026-05-30"
author: "leeworks.dev"
tags: ["vin-decoder", "nodejs", "automotive", "api", "tutorial"]
---
import Base from '../../layouts/Base.astro';
<Base title="How to Decode a VIN Number with Node.js Using Free NHTSA Data" description="Learn how to decode any 17-character Vehicle Identification Number (VIN) with Node.js using the free NHTSA vPIC database — or skip the plumbing and call the leeworks.dev VIN Decoder API directly.">
<article style="max-width: 800px; margin: 0 auto; padding: 2rem; line-height: 1.75;">
<script type="application/ld+json" set:html={JSON.stringify({
"@context": "https://schema.org",
"@type": "Article",
"headline": "How to Decode a VIN Number with Node.js Using Free NHTSA Data",
"datePublished": "2026-05-30",
"author": { "@type": "Organization", "name": "leeworks.dev" },
"publisher": { "@type": "Organization", "name": "leeworks.dev", "url": "https://docs.leeworks.dev" }
})} />
# How to Decode a VIN Number with Node.js Using Free NHTSA Data
Every vehicle sold in the United States since 1981 carries a unique 17-character fingerprint stamped into the chassis: the **Vehicle Identification Number**, or VIN. Decode it and you unlock make, model, year, trim level, engine type, body class, transmission, plant of manufacture, and more — without paying Carfax $40 per report.
In this tutorial you'll learn how VINs are structured, how to query the free NHTSA vPIC database directly in Node.js, and how to call the **leeworks.dev VIN Decoder API** for a production-ready solution that handles caching, error handling, and batch decoding out of the box.
---
## What Is a VIN?
A VIN is a 17-character alphanumeric string divided into three logical sections:
| Section | Characters | Name | What It Encodes |
|---------|-----------|------|-----------------|
| **WMI** | 13 | World Manufacturer Identifier | Country of origin + manufacturer |
| **VDS** | 49 | Vehicle Descriptor Section | Model, body style, engine type, check digit |
| **VIS** | 1017 | Vehicle Identifier Section | Model year, plant, sequential serial number |
### Breaking down a real VIN
Take `1HGCM82633A004352` — a 2003 Honda Accord EX:
- `1HG` → Manufactured in the USA by Honda
- `CM826` → Accord EX 4-door sedan, 2.4L i-VTEC engine (position 9 = check digit `3`)
- `3` → Model year 2003 (position 10)
- `A` → Marysville, Ohio assembly plant (position 11)
- `004352` → Sequential production number
VIN characters deliberately exclude `I`, `O`, and `Q` to avoid confusion with `1`, `0`, and `0` respectively — something to remember when validating user input.
---
## Why NHTSA vPIC?
The **NHTSA Product Information Catalog and Vehicle Listing (vPIC)** is a US federal government database maintained by the National Highway Traffic Safety Administration. It covers:
- All model years 1981 to present
- Every manufacturer registered with NHTSA (domestic and imported)
- 70+ decoded attributes per VIN including engine displacement, fuel type, GVWR, and more
- **No API key, no rate limits** (beyond fair-use throttling), **public domain** under 17 U.S.C. 105
The base endpoint is:
```
https://vpic.nhtsa.dot.gov/api/vehicles/DecodeVinValues/{vin}?format=json
```
---
## Calling NHTSA vPIC Directly in Node.js
Here's a minimal Node.js script using the built-in `fetch` API (Node 18+):
```js
// decode-vin.js
const VIN = process.argv[2] ?? '1HGCM82633A004352';
async function decodeVin(vin) {
// Validate: 17 chars, no I/O/Q
if (!/^[A-HJ-NPR-Z0-9]{17}$/.test(vin)) {
throw new Error(`Invalid VIN format: ${vin}`);
}
const url = `https://vpic.nhtsa.dot.gov/api/vehicles/DecodeVinValues/${vin}?format=json`;
const res = await fetch(url);
if (!res.ok) {
throw new Error(`NHTSA returned HTTP ${res.status}`);
}
const json = await res.json();
const r = json.Results[0];
return {
vin: r.VIN,
make: r.Make,
model: r.Model,
modelYear: r.ModelYear,
trim: r.Trim,
series: r.Series,
bodyClass: r.BodyClass,
driveType: r.DriveType,
engineDisplacementL: r.DisplacementL,
engineCylinders: r.EngineCylinders,
fuelTypePrimary: r.FuelTypePrimary,
transmissionStyle: r.TransmissionStyle,
manufacturerName: r.Manufacturer,
plantCity: r.PlantCity,
plantState: r.PlantState,
plantCountry: r.PlantCountry,
errorCode: r.ErrorCode,
errorText: r.ErrorText,
};
}
decodeVin(VIN)
.then(data => console.log(JSON.stringify(data, null, 2)))
.catch(err => { console.error(err.message); process.exit(1); });
```
Run it:
```bash
node decode-vin.js 1HGCM82633A004352
```
Expected output (abridged):
```json
{
"vin": "1HGCM82633A004352",
"make": "HONDA",
"model": "Accord",
"modelYear": "2003",
"trim": "EX",
"bodyClass": "Sedan/Saloon",
"driveType": "FWD/Front-Wheel Drive",
"engineDisplacementL": "2.4",
"engineCylinders": "4",
"fuelTypePrimary": "Gasoline",
"transmissionStyle": "Automatic",
"manufacturerName": "HONDA OF AMERICA MFG., INC.",
"plantCity": "MARYSVILLE",
"plantState": "OHIO",
"plantCountry": "UNITED STATES (USA)"
}
```
---
## The Problem with Rolling Your Own
Calling NHTSA directly works great for a quick script. But for a production application, you'll quickly run into friction:
1. **No caching** — every request hits the NHTSA servers. At scale, this is slow (NHTSA p99 ≈ 800ms) and risks being throttled.
2. **Raw NHTSA response** — the flat key/value array has 80+ fields, many empty; you need to map and filter these yourself.
3. **No batch support** — decoding 50 VINs means 50 sequential round-trips.
4. **No SLA** — the NHTSA API is a government service; it has no uptime guarantee.
5. **Header boilerplate** — proxy-secret validation, request IDs, CORS headers — you write it every time.
---
## Using the leeworks.dev VIN Decoder API
The **leeworks.dev VIN Decoder API** wraps NHTSA vPIC with a 90-day SQLite cache, pre-mapped response schema, and batch endpoint — all available on RapidAPI.
### Single VIN decode
```js
// Using the leeworks.dev VIN Decoder API
const VIN = '1HGCM82633A004352';
const API_KEY = process.env.RAPIDAPI_KEY; // Your RapidAPI key
const res = await fetch(`https://vin.leeworks.dev/v1/decode?vin=${VIN}`, {
headers: {
'X-RapidAPI-Key': API_KEY,
'X-RapidAPI-Host': 'vin.leeworks.dev',
},
});
const data = await res.json();
console.log(`${data.make} ${data.model} (${data.model_year})`);
// → HONDA Accord (2003)
// Check cache status
const cacheStatus = res.headers.get('X-Cache'); // "HIT" or "MISS"
console.log(`Cache: ${cacheStatus}`);
```
### Batch decode (up to 50 VINs)
```js
const vins = [
'1HGCM82633A004352', // 2003 Honda Accord
'1FTFW1ET5DFA18803', // 2013 Ford F-150
'WBA3A5G59DNP26082', // 2013 BMW 3 Series
];
const res = await fetch('https://vin.leeworks.dev/v1/batch', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-RapidAPI-Key': API_KEY,
'X-RapidAPI-Host': 'vin.leeworks.dev',
},
body: JSON.stringify({ vins }),
});
const { results, count, cached_count } = await res.json();
console.log(`Decoded ${count} VINs, ${cached_count} from cache`);
results.forEach(r => {
if (r.error) {
console.log(`${r.vin}: ERROR — ${r.error}`);
} else {
console.log(`${r.vin}: ${r.make} ${r.model} ${r.model_year}`);
}
});
```
### Health check
```js
// No auth required on /health
const health = await fetch('https://vin.leeworks.dev/v1/health').then(r => r.json());
console.log(`Status: ${health.status}, Cache: ${health.cache.total_entries} entries`);
```
---
## Real-World Use Cases
### Automotive apps and dealership software
Show instant vehicle details when a user types a VIN at checkout or trade-in. Cache the result — the same VIN is often looked up dozens of times across different users.
```js
async function enrichListing(listingVin) {
const vehicle = await decodeVinCached(listingVin);
return {
title: `${vehicle.model_year} ${vehicle.make} ${vehicle.model} ${vehicle.trim}`,
engine: `${vehicle.engine_displacement_l}L ${vehicle.engine_cylinders}-cyl ${vehicle.fuel_type_primary}`,
drivetrain: vehicle.drive_type,
body: vehicle.body_class,
};
}
```
### Insurance tech and underwriting
Premium calculators, claims systems, and underwriting platforms need reliable vehicle specs. A VIN decode call returns body class (sedan vs. SUV vs. pickup) and engine details in under 50ms with a cache hit — fast enough for real-time quote generation.
### Fleet management platforms
Decode entire fleets in a single batch call. The `/v1/batch` endpoint processes up to 50 VINs per request, making it practical to seed a database of 10,000 fleet vehicles with 200 API calls rather than 10,000 sequential hits.
### Used car marketplaces
User-generated listings often contain VIN typos or incorrect specs. Validate and auto-fill vehicle details server-side on listing creation:
```js
app.post('/listings', async (req, res) => {
const { vin, ...listing } = req.body;
// Validate + enrich
const vehicle = await vinApi.decode(vin);
if (vehicle.error_code !== '0') {
return res.status(422).json({ error: 'Invalid or unrecognised VIN' });
}
const enriched = { ...listing, vin, make: vehicle.make, model: vehicle.model, year: vehicle.model_year };
await db.listings.create(enriched);
res.status(201).json(enriched);
});
```
---
## VIN Validation
Before calling any API, validate the VIN client-side to save an unnecessary round-trip:
```js
function isValidVin(vin) {
// 17 chars, alphanumeric excluding I, O, Q
if (!/^[A-HJ-NPR-Z0-9]{17}$/.test(vin)) return false;
// Optional: verify check digit (position 9)
const weights = [8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2];
const transliteration = { A:1,B:2,C:3,D:4,E:5,F:6,G:7,H:8,
J:1,K:2,L:3,M:4,N:5,P:7,R:9,S:2,T:3,U:4,V:5,W:6,X:7,Y:8,Z:9 };
const vals = vin.toUpperCase().split('').map(c =>
/\d/.test(c) ? parseInt(c) : transliteration[c]
);
const sum = vals.reduce((acc, v, i) => acc + v * weights[i], 0);
const check = sum % 11;
const expected = check === 10 ? 'X' : String(check);
return vin[8].toUpperCase() === expected;
}
console.log(isValidVin('1HGCM82633A004352')); // true
console.log(isValidVin('1HGCM82633A00435X')); // false (bad check digit)
```
---
## About the Data Source
The NHTSA vPIC database is maintained by the US Department of Transportation under its statutory mandate (49 U.S.C. § 30111). Manufacturers are legally required to register VIN patterns with NHTSA, so coverage is comprehensive for vehicles sold in the US market.
Key facts:
- **Coverage**: Model years 1981present; 1980 and earlier VINs were not standardised and are not covered
- **Accuracy**: Authoritative for the original vehicle specification; does not reflect modifications, title brands, or recall status
- **Update frequency**: NHTSA updates the database when new model variants are registered, typically months before vehicles reach dealerships
- **Licence**: US federal government work, public domain under 17 U.S.C. 105 — free to use commercially with no attribution requirement
---
## Get Started
The leeworks.dev VIN Decoder API is available on RapidAPI with a free tier (100 requests/month, no credit card required):
👉 **[VIN Decoder API on RapidAPI](https://rapidapi.com/leeworks/api/vin-decoder)**
Full API reference, including request/response schemas and error codes:
👉 **[API Documentation](/vin-decoder)**
---
*Built with ❤️ by [leeworks.dev](https://docs.leeworks.dev) — production-ready data APIs powered by free public-domain data sources.*
</article>
</Base>
+10 -1
View File
@@ -20,7 +20,7 @@ import Base from '../layouts/Base.astro';
<div class="hero">
<h1>Simple. Reliable. APIs.</h1>
<p>Production-ready data APIs for ZIP enrichment, public holidays, and air quality. Available on RapidAPI.</p>
<p>Production-ready data APIs for ZIP enrichment, public holidays, air quality, and VIN decoding. Available on RapidAPI.</p>
<a href="https://rapidapi.com/leeworks" class="cta" target="_blank" rel="noopener">Get API Key on RapidAPI</a>
</div>
@@ -52,6 +52,15 @@ import Base from '../layouts/Base.astro';
<a href="https://rapidapi.com/leeworks/api/air-quality" target="_blank" rel="noopener">RapidAPI</a>
</div>
</div>
<div class="api-card">
<span class="badge wip">In Development</span>
<h2>VIN Decoder API</h2>
<p>Decode any 17-character VIN into make, model, year, trim, engine, body class, and more. Powered by the NHTSA vPIC public-domain database.</p>
<div class="links">
<a href="/vin-decoder">Docs</a>
<a href="https://rapidapi.com/leeworks/api/vin-decoder" target="_blank" rel="noopener">RapidAPI</a>
</div>
</div>
</div>
<footer style="text-align: center; padding: 2rem; border-top: 1px solid #2d3748; margin-top: 3rem; color: #718096; font-size: 0.875rem;">
+8 -2
View File
@@ -1,7 +1,7 @@
---
import Base from '../layouts/Base.astro';
---
<Base title="API Pricing" description="API Pricing — leeworks.dev — compare plans for ZIP Enrichment, Holidays, and Air Quality APIs">
<Base title="API Pricing" description="API Pricing — leeworks.dev — compare plans for ZIP Enrichment, Holidays, Air Quality, and VIN Decoder APIs">
<style>
.pricing-hero { padding: 4rem 2rem 2rem; text-align: center; }
.pricing-hero h1 { font-size: 2.5rem; font-weight: 800; background: linear-gradient(135deg, #90cdf4, #667eea); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 1rem; }
@@ -26,7 +26,7 @@ import Base from '../layouts/Base.astro';
<div class="pricing-hero">
<h1>API Pricing</h1>
<p>Simple, transparent pricing for all three APIs. Start free — upgrade when you need more.</p>
<p>Simple, transparent pricing for all four APIs. Start free — upgrade when you need more.</p>
</div>
<div class="pricing-wrap">
@@ -39,6 +39,7 @@ import Base from '../layouts/Base.astro';
<th scope="col">ZIP Enrichment</th>
<th scope="col">Holidays</th>
<th scope="col">Air Quality</th>
<th scope="col">VIN Decoder</th>
</tr>
</thead>
<tbody>
@@ -48,6 +49,7 @@ import Base from '../layouts/Base.astro';
<td>100 req/mo &middot; 5 req/min</td>
<td>100 req/mo &middot; 5 req/min</td>
<td>100 req/mo &middot; 5 req/min</td>
<td>100 req/mo &middot; 5 req/min</td>
</tr>
<tr>
<td class="tier-name">Basic</td>
@@ -55,6 +57,7 @@ import Base from '../layouts/Base.astro';
<td>5,000 req/mo &middot; 60 req/min</td>
<td>5,000 req/mo &middot; 60 req/min</td>
<td>5,000 req/mo &middot; 60 req/min</td>
<td>5,000 req/mo &middot; 60 req/min</td>
</tr>
<tr>
<td class="tier-name">Pro</td>
@@ -62,6 +65,7 @@ import Base from '../layouts/Base.astro';
<td>25,000 req/mo &middot; 200 req/min</td>
<td>20,000 req/mo &middot; 200 req/min</td>
<td>20,000 req/mo &middot; 200 req/min</td>
<td>20,000 req/mo &middot; 200 req/min</td>
</tr>
<tr>
<td class="tier-name">Ultra</td>
@@ -69,6 +73,7 @@ import Base from '../layouts/Base.astro';
<td>100,000 req/mo &middot; 500 req/min</td>
<td>100,000 req/mo &middot; 500 req/min</td>
<td>100,000 req/mo &middot; 500 req/min</td>
<td>100,000 req/mo &middot; 500 req/min</td>
</tr>
</tbody>
</table>
@@ -77,6 +82,7 @@ import Base from '../layouts/Base.astro';
<a href="#" class="cta-btn">ZIP Enrichment on RapidAPI</a>
<a href="#" class="cta-btn">Holidays on RapidAPI</a>
<a href="#" class="cta-btn">Air Quality on RapidAPI</a>
<a href="/vin-decoder" class="cta-btn">VIN Decoder Docs</a>
</div>
<p class="disclaimer">Subscriptions managed via RapidAPI marketplace. Prices shown in USD. Rate limits apply per API key.</p>
+27
View File
@@ -0,0 +1,27 @@
---
import Base from '../layouts/Base.astro';
const apiName = 'vin-decoder';
const title = 'VIN Decoder API';
const description = 'Decode any 17-character VIN into make, model, year, trim, engine, body class, and more. Powered by the NHTSA vPIC public-domain database.';
---
<Base title={title} description={description}>
<style>
#redoc-container { background: #fff; }
</style>
<div id="redoc-container"></div>
<script is:inline define:vars={{ specUrl: `/specs/${apiName}.yaml` }}>
// Load Redoc from CDN
var script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js';
script.onload = function () {
Redoc.init(specUrl, {
theme: {
colors: { primary: { main: '#667eea' } },
typography: { fontFamily: 'system-ui, sans-serif' },
},
}, document.getElementById('redoc-container'));
};
document.head.appendChild(script);
</script>
</Base>
+120
View File
@@ -0,0 +1,120 @@
# Deploying the APIs (Flux GitOps → Talos)
> Handoff doc. The APIs are **not** deployed by Gitea Actions — they are deployed
> by **Flux** running on the Talos Kubernetes cluster. The Gitea Actions in this
> repo only build the docs-site image, validate Flux manifests, and publish
> OpenAPI specs to RapidAPI.
## How a deploy actually happens
```
API repo (e.g. leeworks-agents/zip-enrichment)
└─ its own CI builds & pushes registry.leeworks.dev/zip-enrichment/server:<tag>
└─ Flux image-automation (flux/image-automation/) rewrites the
{"$imagepolicy": "flux-system:<api>"} marker in flux/<api>/helmrelease.yaml
└─ Flux GitRepository "api-company" (polls main every 5m)
└─ HelmRelease per API (flux/<api>/helmrelease.yaml, bedag/raw chart)
└─ Deployment rolls out in the cluster namespace
```
Each API has its own directory under `flux/`:
| API | Namespace | HelmRelease path | Image |
|---|---|---|---|
| zip-enrichment | `zip-enrichment` | `flux/zip-enrichment/helmrelease.yaml` | `registry.leeworks.dev/zip-enrichment/server` |
| holidays | `holidays` | `flux/holidays/helmrelease.yaml` | `registry.leeworks.dev/holidays/server` |
| air-quality | `air-quality` | `flux/air-quality/helmrelease.yaml` | `registry.leeworks.dev/air-quality/server` |
| vin-decoder | `vin-decoder` | `flux/vin-decoder/helmrelease.yaml` | `registry.leeworks.dev/vin-decoder/server` |
(Confirm each path's exact image with `grep -r imagepolicy flux/`.)
## Prerequisite: the manifests must be live in the cluster's Flux source
`flux/api-company-source/gitrepository.yaml` is **reference only**. The
*authoritative* copy must be committed to **`0xWheatyz/Talos`** at:
```
testing1/first-cluster/cluster/flux/api-company/
```
If that path does not point Flux at this repo's `flux/` directory, Flux never
sees these HelmReleases and nothing deploys. Verify the Talos repo references
this repo's `main` branch and that a Flux `Kustomization` includes the
`api-company` path.
Per-API the cluster also needs (already templated under `flux/<api>/`):
- `namespace.yaml` — the target namespace
- the `gitea-registry` imagePullSecret in that namespace
- `externalsecret.yaml` — pulls API keys (e.g. RapidAPI) via external-secrets
- `servicemonitor.yaml` — Prometheus scraping (optional for deploy)
## One-time local setup (machine with cluster access)
You need tools that are **not** installed on the dev machine yet:
```bash
# Talos kubeconfig — export from the Talos controlplane, e.g.:
# talosctl kubeconfig ~/.kube/talos-leeworks
export KUBECONFIG=~/.kube/talos-leeworks
kubectl cluster-info # must succeed before continuing
# Flux CLI
brew install fluxcd/tap/flux
# Helm (optional, for debugging charts)
brew install helm
flux check # confirm Flux is installed & healthy in-cluster
```
## Deploy / sync all APIs
```bash
export KUBECONFIG=~/.kube/talos-leeworks
# 1. Pull the latest main into the cluster's Git source
flux reconcile source git api-company -n flux-system
# 2. Apply the manifests (name may differ — check: flux get kustomizations -A)
flux reconcile kustomization api-company -n flux-system
# 3. Reconcile each API's HelmRelease
flux reconcile helmrelease zip-enrichment -n zip-enrichment
flux reconcile helmrelease holidays -n holidays
flux reconcile helmrelease air-quality -n air-quality
flux reconcile helmrelease vin-decoder -n vin-decoder
# 4. Verify everything is Ready
flux get helmreleases -A
kubectl get pods -A | grep -E 'zip-enrichment|holidays|air-quality|vin-decoder'
```
## Troubleshooting
```bash
# Why is a release not Ready?
flux get helmrelease <name> -n <ns>
kubectl describe helmrelease <name> -n <ns>
# Is image automation picking up new tags?
flux get image policy -n flux-system
flux get image update -A
# Pod won't start (image pull / secret issues)
kubectl describe pod <pod> -n <ns>
kubectl get events -n <ns> --sort-by=.lastTimestamp | tail -20
```
## Force a fresh deploy of a single API
```bash
flux suspend helmrelease <name> -n <ns>
flux resume helmrelease <name> -n <ns> # triggers a fresh reconcile
# or restart the workload directly:
kubectl rollout restart deployment/<name> -n <ns>
```
## Related docs in this repo
- `docs/operator-runbook.md` — day-2 operations
- `docs/registry.md` — container registry (`registry.leeworks.dev`) setup
- `docs/secrets-checklist.md` — required cluster secrets
- `flux/image-automation/` — automatic image tag bumping
+6 -3
View File
@@ -1,6 +1,6 @@
# DNS Configuration
**Last updated:** 2026-05-24
**Last updated:** 2026-05-30
**Status:** Planned (Phase 6 pre-launch)
---
@@ -28,6 +28,7 @@ kubectl get svc -n ingress-nginx ingress-nginx-controller -o jsonpath='{.status.
| `status.leeworks.dev` | A | `<cluster-ingress-ip>` | Gatus status page | Yes (cert-manager) |
| `registry.leeworks.dev` | A | `<cluster-ingress-ip>` | Container registry (Gitea) | Yes (cert-manager) |
| `grafana.leeworks.dev` | A | `<cluster-ingress-ip>` | Grafana (internal/restricted) | Yes (cert-manager) |
| `vin.leeworks.dev` | A | `<cluster-ingress-ip>` | VIN Decoder API | Yes (cert-manager) |
---
@@ -103,6 +104,7 @@ dig docs.leeworks.dev +short
dig status.leeworks.dev +short
dig registry.leeworks.dev +short
dig grafana.leeworks.dev +short
dig vin.leeworks.dev +short
# Check TLS certificates (once services are deployed)
curl -v https://zip.leeworks.dev/health 2>&1 | grep -E "SSL|certificate|issuer"
@@ -126,7 +128,7 @@ The following actions require human operator access to the DNS provider:
1. Log into the DNS provider managing `leeworks.dev`
2. Find the cluster ingress IP: `kubectl get svc -n ingress-nginx ingress-nginx-controller`
3. Create/update the 7 A records listed in the table above
3. Create/update the 8 A records listed in the table above
4. Verify propagation: `dig +trace zip.leeworks.dev`
DNS propagation typically takes 560 minutes.
@@ -143,4 +145,5 @@ DNS propagation typically takes 560 minutes.
- [ ] `status.leeworks.dev` → DNS record created
- [ ] `registry.leeworks.dev` → DNS record created
- [ ] `grafana.leeworks.dev` → DNS record created
- [ ] TLS certificates issued and valid for all 7 subdomains
- [ ] `vin.leeworks.dev` → DNS record created
- [ ] TLS certificates issued and valid for all 8 subdomains
+359
View File
@@ -0,0 +1,359 @@
# Launch Announcement Copy
Ready-to-copy marketing text for leeworks.dev API launch day.
**Status key:**
- **DRAFT** — copy is written and ready; needs final RapidAPI URLs inserted once issue #44 is complete
- **READY** — all placeholders filled; copy-paste ready to publish
All sections are currently **DRAFT** pending RapidAPI listing URLs from issue #44.
---
## Placeholder Reference
When issue #44 is complete, replace these placeholders throughout this document:
| Placeholder | Replace with |
|---|---|
| `[RAPIDAPI_ZIP_URL]` | RapidAPI listing URL for ZIP Code Enrichment API |
| `[RAPIDAPI_HOLIDAYS_URL]` | RapidAPI listing URL for Holidays API |
| `[RAPIDAPI_AQI_URL]` | RapidAPI listing URL for Air Quality Index API |
| `[RAPIDAPI_PROFILE_URL]` | Your RapidAPI provider profile URL |
---
## 1. Hacker News — Show HN Post [DRAFT]
**Title:**
```
Show HN: I built 3 free-data APIs on Kubernetes — ZIP enrichment, public holidays, air quality
```
**Body (paste into the "text" field):**
```
Three small APIs I've been building over the past few months, deployed via Flux GitOps on a self-hosted Kubernetes cluster.
**What they do:**
1. ZIP Code Enrichment API — turn any US ZIP code into city, state, county, timezone, area codes, and coordinates. Backed by USPS/Census public data, refreshed monthly. [RAPIDAPI_ZIP_URL]
2. Public Holidays API — query official public holidays for any country and year. 90+ countries, ISO 3166 codes. Backed by Nager.Date / public government calendars. [RAPIDAPI_HOLIDAYS_URL]
3. Air Quality Index API — current and historical AQI by city or coordinates. PM2.5, PM10, O3, NO2, SO2, CO. Backed by OpenAQ public dataset. [RAPIDAPI_AQI_URL]
**Tech stack:** Fastify (Node.js), SQLite (data cache), Flux GitOps on Talos Linux, cert-manager + ingress-nginx, Prometheus + Grafana for metrics, Gatus for status page.
**Business model:** Free tier (100 req/mo) + paid tiers ($9/$19/$49/mo) on RapidAPI. All three APIs use only public-domain data sources with no redistribution restrictions, so operating costs are cluster hosting only.
**Why I built this:** I wanted to learn GitOps/Kubernetes end-to-end, build something that generates real revenue, and ship entirely on open data. The stack is overkill for 3 simple APIs — but that's the point.
Code is private (it's a product), but happy to answer questions about the architecture.
```
---
## 2. Reddit Posts [DRAFT]
### r/webdev
**Title:**
```
I built a ZIP code enrichment API on public Census data — city, state, county, timezone from a single lookup
```
**Body:**
```
Been working on a simple utility API for the past few months. ZIP Code Enrichment takes any US ZIP code and returns:
- City name + state (abbreviation and full name)
- County + FIPS code
- Timezone (IANA name + UTC offset)
- Area codes
- Latitude/longitude centroid
- ZIP type (standard, PO Box, military, unique)
**The data source** is a monthly-refreshed dataset from USPS/Census Bureau — entirely public domain, no scraping.
**Code example:**
```javascript
const response = await fetch('https://zip.leeworks.dev/v1/lookup?zip=10001', {
headers: { 'X-RapidAPI-Proxy-Secret': process.env.RAPIDAPI_KEY }
});
const data = await response.json();
// { zip: "10001", city: "New York", state: "NY", county: "New York County",
// timezone: "America/New_York", lat: 40.7484, lon: -73.9967, ... }
```
Free tier is 100 requests/month. Paid plans start at $9/mo for 10,000 req/mo.
RapidAPI listing: [RAPIDAPI_ZIP_URL]
Happy to answer any questions about the stack (Fastify + SQLite + Kubernetes/Flux).
```
---
### r/SideProject
**Title:**
```
Launched 3 data APIs on RapidAPI — ZIP enrichment, public holidays, air quality. $0 → targeting $100/mo MRR
```
**Body:**
```
Finally shipped the thing I've been building on weekends for the past few months.
**What I built:**
Three utility APIs on RapidAPI, all backed by free public-domain data:
1. **ZIP Code Enrichment** — city/state/county/timezone from a ZIP code ([RAPIDAPI_ZIP_URL])
2. **Public Holidays** — official holidays for 90+ countries ([RAPIDAPI_HOLIDAYS_URL])
3. **Air Quality Index** — current + historical AQI by city or coordinates ([RAPIDAPI_AQI_URL])
**Stack:** Fastify + SQLite + Kubernetes (Talos Linux) + Flux GitOps + Prometheus/Grafana
**Business model:**
- Free tier: 100 req/month (marketing + trial)
- Basic: $9/mo — 10,000 req/mo
- Pro: $19/mo — 50,000 req/mo
- Ultra: $49/mo — 250,000 req/mo
**Data cost: $0.** All three APIs use US government / OpenAQ public datasets with no licensing fees.
**Revenue so far:** $0 (launched today). Target: $100/mo net within 90 days, which is roughly 12 Basic subscribers across all three APIs.
The whole build — from first commit to Kubernetes deployment — is documented in a private research log. Happy to share architecture details.
What would you do differently for the pricing?
```
---
### r/learnprogramming
**Title:**
```
I used free US government data to build a ZIP code API — here's how the data pipeline works
```
**Body:**
```
A walkthrough of the data layer behind the ZIP Code Enrichment API I just launched.
**The problem:** ZIP codes change. Cities merge. New ZIPs are added. Any ZIP lookup service needs to stay fresh.
**The solution:** A monthly seed script that:
1. Downloads the latest US ZIP code dataset from USPS/Census Bureau (public domain)
2. Parses and normalizes ~43,000 records
3. Inserts into SQLite with upsert logic (new ZIPs added, old ones retired)
4. Runs automatically via a Kubernetes CronJob on the 1st of each month
**The API itself** is a Fastify (Node.js) server that queries SQLite. Cold query: ~5ms. The whole thing runs in a 128MB container.
**Code snippet** (the seed script core logic):
```javascript
// Fetch and parse Census ZIP dataset
const stream = await fetch(CENSUS_ZIP_URL);
const records = await parseCSV(stream.body);
// Upsert into SQLite
const stmt = db.prepare(`
INSERT INTO zips (zip, city, state, county, lat, lon, timezone, type)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(zip) DO UPDATE SET
city=excluded.city, state=excluded.state,
updated_at=CURRENT_TIMESTAMP
`);
for (const record of records) {
stmt.run([record.zip, record.city, record.state, record.county,
record.lat, record.lon, record.timezone, record.type]);
}
```
The API is live on RapidAPI with a free tier: [RAPIDAPI_ZIP_URL]
Happy to answer questions about SQLite performance, the seed pipeline, or the Kubernetes/Flux deployment.
```
---
## 3. Indie Hackers Milestone Post [DRAFT]
**Title:**
```
Launched 3 data APIs on RapidAPI: $0 MRR, targeting $100/mo in 90 days
```
**Body:**
```
### What I built
Three utility APIs backed entirely by free public-domain data:
- **ZIP Code Enrichment** — city/state/county/timezone/coordinates from any US ZIP ([RAPIDAPI_ZIP_URL])
- **Public Holidays** — official holidays for 90+ countries + year ([RAPIDAPI_HOLIDAYS_URL])
- **Air Quality Index** — current + historical AQI by city or coordinates ([RAPIDAPI_AQI_URL])
### Revenue: $0 → targeting $100/mo
The $100/mo target is ~12 Basic subscribers ($9/mo) across all three APIs after RapidAPI's 25% cut. Stretch: 4 Pro subscribers ($19/mo each).
### Stack
- **API servers:** Fastify (Node.js) + SQLite for data cache
- **Infrastructure:** Talos Linux Kubernetes cluster (self-hosted, single-node)
- **GitOps:** Flux CD — everything is declared in YAML, zero manual kubectl
- **Observability:** Prometheus + Grafana, Gatus status page at status.leeworks.dev
- **Data sources:** USPS/Census (ZIP), Nager.Date (Holidays), OpenAQ (AQI) — all public domain, $0 licensing cost
### What I learned
1. **GitOps is excellent for solo projects.** Flux means my cluster is always in sync with git. I've done zero manual deploys.
2. **SQLite is underrated for read-heavy APIs.** Sub-5ms query times for cached lookups, zero infrastructure overhead vs PostgreSQL.
3. **Public-domain data has a moat.** Anyone can build this, but most people don't bother. The data is stable, legal, and free forever.
4. **Kubernetes is overkill — and that's fine.** I did it to learn. I now know Talos, Flux, cert-manager, ingress-nginx, Prometheus, Grafana from first principles.
### What's next
- Monitor first 30 days for subscriber growth
- Build VIN Decoder as API #4 (NHTSA vPIC data, also public domain)
- Add batch endpoints to ZIP and Holidays
Would love feedback on pricing — is $9/mo entry too high or too low for a utility API with a free tier?
```
---
## 4. Product Hunt Listing [DRAFT]
### Tagline (60 chars max)
```
3 utility APIs on public data — ZIP, Holidays, Air Quality
```
*(58 characters ✓)*
### Description (260 chars max)
```
Look up ZIP codes, public holidays for 90+ countries, and air quality index data — all via clean REST APIs backed by free government datasets. Free tier included. No API keys to generate — available on RapidAPI.
```
*(211 characters ✓)*
### First Comment (Maker Note)
```
Hey Product Hunt! 👋
I'm the developer behind leeworks.dev — three utility APIs I've been building over the past few months:
**ZIP Code Enrichment** [RAPIDAPI_ZIP_URL]
Turn any US ZIP code into city, state, county, timezone, area codes, and GPS coordinates. 43,000+ ZIP codes, refreshed monthly from USPS/Census Bureau public data.
**Public Holidays API** [RAPIDAPI_HOLIDAYS_URL]
Query official public holidays for any country and year. 90+ countries, ISO 3166 codes, backed by government calendar data. Great for payroll software, scheduling tools, and calendar apps.
**Air Quality Index API** [RAPIDAPI_AQI_URL]
Current and historical AQI readings by city or coordinates. PM2.5, PM10, O3, NO2, SO2, CO — backed by the OpenAQ public dataset covering thousands of monitoring stations worldwide.
**What makes these different:**
- All data is 100% public domain — no licensing fees, no terms restrictions
- Free tier (100 req/mo) to try before you buy
- Paid plans start at $9/mo for 10,000 requests/month
- Running on Kubernetes with Prometheus monitoring and a public status page at status.leeworks.dev
Happy to answer questions about the data sources, the tech stack (Fastify + SQLite + Flux GitOps), or the pricing model. Thanks for checking it out!
```
### Gallery / Screenshot URL Placeholders
```
1. docs-site homepage: https://docs.leeworks.dev (screenshot)
2. Grafana dashboard: https://grafana.leeworks.dev (screenshot)
3. status.leeworks.dev (screenshot)
4. Example API response (ZIP lookup): code screenshot
5. RapidAPI listing page: [RAPIDAPI_PROFILE_URL] (screenshot)
```
---
## 5. Email Waitlist Message [DRAFT]
**Subject line:**
```
leeworks.dev APIs are live — here's your free tier access
```
**Body:**
```
Hi there,
The three APIs I've been building are now live on RapidAPI. Here's what's available and how to get started:
---
**ZIP Code Enrichment API**
Turn any US ZIP code into city, state, county, timezone, area codes, and GPS coordinates — in a single API call.
→ [RAPIDAPI_ZIP_URL]
**Public Holidays API**
Query official public holidays for any country and year. 90+ countries, ISO 3166 codes.
→ [RAPIDAPI_HOLIDAYS_URL]
**Air Quality Index API**
Current and historical AQI by city or coordinates. PM2.5, PM10, O3, NO2, SO2, CO.
→ [RAPIDAPI_AQI_URL]
---
**How to try for free:**
1. Click any link above
2. Subscribe to the **Free tier** (100 requests/month, no credit card needed)
3. Copy your RapidAPI key from the dashboard
4. Make your first request — full docs at https://docs.leeworks.dev
---
**Quick start (ZIP enrichment):**
```bash
curl "https://zip.leeworks.dev/v1/lookup?zip=90210" \
-H "X-RapidAPI-Key: YOUR_KEY" \
-H "X-RapidAPI-Host: zip-enrichment.p.rapidapi.com"
```
---
**Want more than 100 requests/month?**
Paid plans start at $9/mo for 10,000 requests. See the full pricing table on each API's RapidAPI listing page.
Questions? Reply to this email or open an issue at https://docs.leeworks.dev/support.
Thanks for your interest,
Wyatt
leeworks.dev
---
*You're receiving this because you signed up for early access. To unsubscribe, reply with "unsubscribe".*
```
---
## Checklist Before Publishing
Before changing any section from DRAFT to READY:
- [ ] Issue #44 complete — RapidAPI listing URLs obtained
- [ ] Replace all `[RAPIDAPI_ZIP_URL]` placeholders
- [ ] Replace all `[RAPIDAPI_HOLIDAYS_URL]` placeholders
- [ ] Replace all `[RAPIDAPI_AQI_URL]` placeholders
- [ ] Replace all `[RAPIDAPI_PROFILE_URL]` placeholders
- [ ] Verify code examples work against live endpoints
- [ ] Confirm free tier limit is accurate (currently documented as 100 req/mo)
- [ ] Confirm all three APIs pass pre-launch-checklist.md
- [ ] Product Hunt gallery screenshots captured
- [ ] Email list exported from whatever signup form was used
Once all items above are checked, update the Status key at the top of this document from DRAFT to READY for each section.
+5 -1
View File
@@ -286,7 +286,11 @@ annotations:
## Grafana Dashboard
A reference dashboard JSON is available at `docs/grafana-api-dashboard.json` (TBD — will be committed once Grafana is deployed per issue #7).
Grafana dashboards are committed as Flux ConfigMaps and are auto-loaded by Grafana via the `grafana_dashboard: "1"` label:
- `flux/monitoring/grafana-dashboard-apis.yaml` — consolidated 4-API dashboard (all APIs)
- `flux/monitoring/vin-decoder-dashboard.yaml` — VIN Decoder-specific dashboard
Dashboards will load automatically once Grafana is deployed (issue #7) and Flux is active (issue #218).
Key panels to include:
1. Request rate by API and status (`rate(api_requests_total[5m])`)
+106 -7
View File
@@ -2,7 +2,7 @@
**Audience:** Human operator (0xWheatyz)
**Purpose:** Ordered, copy-paste-ready guide to bring the full `api-company` stack live.
**Last updated:** 2026-05-26
**Last updated:** 2026-06-01
**Closes:** leeworks-agents/api-company#50
---
@@ -165,6 +165,7 @@ Repos to configure:
- `leeworks-agents/zip-enrichment`
- `leeworks-agents/holidays`
- `leeworks-agents/air-quality`
- `leeworks-agents/vin-decoder` (see also Phase 4-A Step 4)
**For each repo:** Repo → Settings → Actions → Secrets → Add Secret
- **Name:** `GITEA_TOKEN`
@@ -186,9 +187,9 @@ kubectl create secret generic gitea-image-automation-token \
---
## Phase 4 — DNS for API services (issue #33)
## Phase 4 — DNS for API services (issues #33, #106, #150)
Add DNS A records for all seven leeworks.dev subdomains (all point to the same
Add DNS A records for **all eight** leeworks.dev subdomains (all point to the same
cluster ingress IP):
| Hostname | Target |
@@ -196,6 +197,7 @@ cluster ingress IP):
| `zip.leeworks.dev` | `<cluster ingress IP>` |
| `holidays.leeworks.dev` | `<cluster ingress IP>` |
| `aqi.leeworks.dev` | `<cluster ingress IP>` |
| `vin.leeworks.dev` | `<cluster ingress IP>` |
| `docs.leeworks.dev` | `<cluster ingress IP>` |
| `grafana.leeworks.dev` | `<cluster ingress IP>` |
| `status.leeworks.dev` | `<cluster ingress IP>` |
@@ -204,7 +206,7 @@ cluster ingress IP):
Verify DNS propagation:
```bash
for host in zip holidays aqi docs grafana status registry; do
for host in zip holidays aqi vin docs grafana status registry; do
echo -n "${host}.leeworks.dev: "
dig ${host}.leeworks.dev +short
done
@@ -215,6 +217,76 @@ propagates (typically minutes, up to 48 h).
---
---
## Phase 4-A — VIN Decoder namespace setup (issues #126, #127, #128, #139)
Before VIN Decoder pods can start, the following manual steps are required.
Do these alongside Phase 3 (they are independent of the DNS batch):
### Step 4A-1 — Create `vin-decoder` namespace
```bash
kubectl create namespace vin-decoder --dry-run=client -o yaml | kubectl apply -f -
```
### Step 4A-2 — Create `gitea-registry` imagePullSecret in `vin-decoder` namespace (issue #127)
Reuse the same Gitea token with `read:packages` scope from secrets checklist item #8.
```bash
kubectl create secret docker-registry gitea-registry \
--namespace=vin-decoder \
--docker-server=registry.leeworks.dev \
--docker-username=leeworks-agents \
--docker-password=<GITEA_TOKEN_WITH_READ_PACKAGES> \
--docker-email=agent@leeworks.dev
```
Verify:
```bash
kubectl get secret gitea-registry -n vin-decoder -o jsonpath='{.type}'
# Expected: kubernetes.io/dockerconfigjson
```
### Step 4A-3 — Create `rapidapi-proxy-secret` in `vin-decoder` namespace (issue #128)
**Now (placeholder — unblocks deploy testing):**
```bash
kubectl create secret generic rapidapi-proxy-secret \
--namespace=vin-decoder \
--from-literal=X-RapidAPI-Proxy-Secret=PLACEHOLDER_REPLACE_AFTER_RAPIDAPI_LISTING
```
**After VIN Decoder is listed on RapidAPI (Phase 5), update with real secret:**
```bash
kubectl create secret generic rapidapi-proxy-secret \
-n vin-decoder \
--from-literal=X-RapidAPI-Proxy-Secret=<VIN_RAPIDAPI_PROXY_SECRET> \
--save-config --dry-run=client -o yaml | kubectl apply -f -
```
### Step 4A-4 — Add `GITEA_TOKEN` Actions secret to `leeworks-agents/vin-decoder` repo (issue #126)
Gitea → `leeworks-agents/vin-decoder` → Settings → Secrets and Variables → Actions
- **Name:** `GITEA_TOKEN`
- **Value:** Gitea personal access token with `write:packages` scope
Verify it appears in the repo's Actions Secrets list before the next push to `main`.
### Step 4A-5 — Add RapidAPI VIN secrets to `leeworks-agents/api-company` Actions (issue #139)
> **Blocked** — requires VIN Decoder to be listed on RapidAPI first (issue #131 tracker; operator action required).
Once the VIN Decoder listing is live:
Gitea → `leeworks-agents/api-company` → Settings → Secrets and Variables → Actions
| Secret name | Where to find it |
|---------------------------|------------------|
| `RAPIDAPI_VIN_API_ID` | RapidAPI dashboard → VIN Decoder listing → Overview |
| `RAPIDAPI_VIN_VERSION_ID` | RapidAPI dashboard → VIN Decoder listing → Versions tab |
## Phase 5 — RapidAPI + PayPal (issue #44, #19)
> **Blocked on operator being 18+ for PayPal.** Complete when eligible.
@@ -241,8 +313,25 @@ kubectl create secret generic rapidapi-proxy-secret \
kubectl create secret generic rapidapi-proxy-secret \
-n air-quality \
--from-literal=X-RapidAPI-Proxy-Secret=<VALUE>
# vin-decoder (updates the placeholder secret from Phase 4-A Step 3)
kubectl create secret generic rapidapi-proxy-secret \
-n vin-decoder \
--from-literal=X-RapidAPI-Proxy-Secret=<VIN_VALUE> \
--save-config --dry-run=client -o yaml | kubectl apply -f -
```
Also configure VIN Decoder pricing tiers on RapidAPI (issue #151):
| Tier | Monthly Price | Request Limit | Rate Limit |
|-------|---------------|----------------|-------------|
| Free | $0 | 100 req/mo | 5 req/min |
| Basic | $9 | 5,000 req/mo | 60 req/min |
| Pro | $19 | 20,000 req/mo | 200 req/min |
| Ultra | $49 | 100,000 req/mo | 500 req/min |
Confirm VIN Decoder revenue flows through the same PayPal account as the other 3 APIs.
---
## Quick Verification Checklist
@@ -287,12 +376,22 @@ Phase 2: Service secrets (runner-token, grafana-admin, gatus-webhook, registry)
Phase 3: CI secrets + image-automation token
└─► images build, push, and auto-update → API services deploy
(includes leeworks-agents/vin-decoder GITEA_TOKEN — Step 3-A)
Phase 4: DNS records
Phase 4: DNS records (8 subdomains including vin.leeworks.dev)
└─► HTTPS certs issued → public URLs go live
Phase 5: RapidAPI + PayPal
└─► revenue enabled
Phase 4-A: VIN Decoder namespace setup (#126, #127, #128)
4A-1: vin-decoder namespace created
4A-2: gitea-registry imagePullSecret in vin-decoder (#127)
4A-3: rapidapi-proxy-secret placeholder in vin-decoder (#128)
4A-4: GITEA_TOKEN Actions secret in vin-decoder repo (#126)
4A-5: RAPIDAPI_VIN_API_ID + RAPIDAPI_VIN_VERSION_ID Actions secrets (#139)
[blocked on VIN Decoder RapidAPI listing]
└─► VIN Decoder pods start; CI can push images
Phase 5: RapidAPI + PayPal (all 4 APIs including VIN Decoder; issue #151)
└─► revenue enabled for all 4 APIs
```
---
+13 -6
View File
@@ -11,6 +11,7 @@ Use this checklist as the final go-live gate — run through every item the day
- [ ] `zip-enrichment` pod `READY=1/1` (`kubectl get pods -n zip-enrichment`)
- [ ] `holidays` pod `READY=1/1` (`kubectl get pods -n holidays`)
- [ ] `air-quality` pod `READY=1/1` (`kubectl get pods -n air-quality`)
- [ ] `vin-decoder` pod `READY=1/1` (`kubectl get pods -n vin-decoder`)
- [ ] `docs-site` pod Running and READY (`kubectl get pods -n docs-site`)
- [ ] Prometheus scraping all three API services (check Prometheus Targets UI)
- [ ] Grafana dashboard accessible at `grafana.leeworks.dev`
@@ -25,8 +26,9 @@ Use this checklist as the final go-live gate — run through every item the day
- [ ] `docs.leeworks.dev` → cluster ingress IP
- [ ] `status.leeworks.dev` → cluster ingress IP
- [ ] `registry.leeworks.dev` → cluster ingress IP
- [ ] `vin.leeworks.dev` → cluster ingress IP (`dig vin.leeworks.dev +short` + `curl -I https://vin.leeworks.dev`)
- [ ] `grafana.leeworks.dev` → cluster ingress IP
- [ ] TLS certificates issued for all 7 subdomains (`kubectl get certificates -A`)
- [ ] TLS certificates issued for all 8 subdomains (`kubectl get certificates -A`)
---
@@ -38,9 +40,12 @@ Use this checklist as the final go-live gate — run through every item the day
- [ ] `GET /zip/{zip}` returns correct data for a sample ZIP code (e.g. `curl https://zip.leeworks.dev/zip/10001`)
- [ ] `GET /holidays/{year}` returns correct data (e.g. `curl https://holidays.leeworks.dev/holidays/2026`)
- [ ] `GET /aqi/{city}` returns correct data (e.g. `curl https://aqi.leeworks.dev/aqi/New%20York`)
- [ ] `GET /v1/health` returns HTTP 200 on **vin-decoder** (`curl https://vin.leeworks.dev/v1/health`)
- [ ] `GET /v1/decode?vin=1HGCM82633A004352` returns correct make/model/year data
- [ ] Request **without** `X-RapidAPI-Proxy-Secret` returns HTTP 403 on **vin-decoder** (`curl https://vin.leeworks.dev/v1/decode?vin=1HGCM82633A004352`)
- [ ] Request **without** `X-RapidAPI-Proxy-Secret` returns HTTP 403 on all three APIs
- [ ] `docs.leeworks.dev/pricing` loads correctly
- [ ] `status.leeworks.dev` shows all three APIs as **UP**
- [ ] `status.leeworks.dev` shows all **four** APIs as **UP**
---
@@ -50,6 +55,8 @@ Use this checklist as the final go-live gate — run through every item the day
- [ ] `docs/legal/privacy-policy.md` committed and reachable at `docs.leeworks.dev/legal/privacy-policy`
- [ ] `docs/legal/acceptable-use-policy.md` committed and reachable at `docs.leeworks.dev/legal/acceptable-use-policy`
- [ ] All three APIs listed on RapidAPI with **Free + 3 paid tiers** (leeworks-agents/api-company#44)
- [ ] VIN Decoder listed on RapidAPI with Free + 3 paid tiers (leeworks-agents/api-company#131)
- [ ] `rapidapi-proxy-secret` updated with real RapidAPI value in `vin-decoder` namespace (leeworks-agents/api-company#128)
- [ ] PayPal linked to RapidAPI (leeworks-agents/api-company#19)
- [ ] `rapidapi-proxy-secret` updated with **real** RapidAPI values in all 3 namespaces (leeworks-agents/api-company#81)
@@ -70,7 +77,7 @@ Use this checklist as the final go-live gate — run through every item the day
flux get all
# Check API pod health
for ns in zip-enrichment holidays air-quality docs-site; do
for ns in zip-enrichment holidays air-quality vin-decoder docs-site; do
echo "=== $ns ==="
kubectl get pods -n $ns
done
@@ -78,13 +85,13 @@ done
# Verify TLS certs
kubectl get certificates -A
# Check all 7 DNS subdomains
for sub in zip holidays aqi docs status registry grafana; do
# Check all 8 DNS subdomains
for sub in zip holidays aqi vin docs status registry grafana; do
echo "$sub.leeworks.dev -> $(dig $sub.leeworks.dev +short)"
done
# Smoke test health endpoints
for svc in "zip.leeworks.dev/health" "holidays.leeworks.dev/health" "aqi.leeworks.dev/health"; do
for svc in "zip.leeworks.dev/health" "holidays.leeworks.dev/health" "aqi.leeworks.dev/health" "vin.leeworks.dev/v1/health"; do
echo "$svc: $(curl -s -o /dev/null -w '%{http_code}' https://$svc)"
done
+71
View File
@@ -193,6 +193,77 @@ air quality, AQI, PM2.5, PM10, air pollution, smog, ozone, nitrogen dioxide, env
---
---
## 4. VIN Decoder API
### API Name
VIN Decoder API
### Tagline
Decode any vehicle VIN into make, model, year, engine, and trim — powered by NHTSA vPIC.
### Short Description (≤ 300 chars)
Decode any 17-character Vehicle Identification Number into structured vehicle data: make, model, year, trim, body style, engine specs, transmission, and assembly plant. Backed by the NHTSA vPIC public database with 90-day result caching.
### Long Description
Unlock the full story behind any Vehicle Identification Number with a single API call.
**What you get per VIN:**
- Make, model, model year, and trim level
- Body class (Sedan, SUV, Pickup, etc.) and drive type (FWD, RWD, AWD, 4WD)
- Engine displacement (CC and litres) and cylinder count
- Primary fuel type (Gasoline, Diesel, Electric, Hybrid, etc.)
- Transmission style (Automatic, Manual, CVT) and speed count
- Assembly plant city, state, and country
- Full manufacturer name and NHTSA vehicle type classification
- NHTSA decode error code and text for non-standard VINs
**Data source:** NHTSA Product Information Catalog and Vehicle Listing (vPIC) — US federal government public-domain data, always current. No licensing fees.
**Coverage:** Model years 1981present. All major domestic and import manufacturers registered with NHTSA.
**Caching:** Decoded VINs are cached for 90 days in a local SQLite store. The `X-Cache: HIT/MISS` response header tells you whether the result came from cache or a live NHTSA lookup.
**Use cases:**
- Used-car marketplaces — enrich listings with decoded specs at scale
- Insurance platforms — auto-populate vehicle details from VIN at quote time
- Fleet management — maintain structured vehicle inventories without manual entry
- Automotive valuation tools — feed year/make/model/trim into pricing algorithms
- Recall & warranty systems — match VINs to manufacturer service campaigns
- Registration & titling apps — validate and enrich VIN data in DMV workflows
**Endpoints:**
- `GET /v1/decode` — decode a single VIN
- `POST /v1/batch` — decode up to 50 VINs in one request
- `GET /v1/health` — service health check (no auth required)
### Category
Data / Automotive / Transportation
### Plan Table
| Tier | Price/month | Requests/month | Rate limit |
|------|-------------|----------------|------------|
| Free | $0 | 100 req/mo | 5 req/min |
| Basic | $9 | 5,000 req/mo | 60 req/min |
| Pro | $19 | 20,000 req/mo | 200 req/min |
| Ultra | $49 | 100,000 req/mo | 500 req/min |
### Endpoint Descriptions
| Endpoint | Description |
|----------|-------------|
| `GET /v1/decode?vin={vin}` | Decodes a single 17-character VIN. Returns structured vehicle attributes including make, model, year, engine, body, drivetrain, and plant info. Optional `?raw=true` includes the full NHTSA vPIC response. |
| `POST /v1/batch` | Accepts a JSON body with a `vins` array (150 VINs). Returns one decoded result (or error) per VIN in input order, plus aggregate counts for `cached_count` and `error_count`. |
| `GET /v1/health` | Returns service status, uptime, cache stats (entries, hit rate, size), and NHTSA upstream reachability. No `X-RapidAPI-Proxy-Secret` required. |
### Keywords
VIN decoder, vehicle identification number, car lookup, NHTSA, make model year, automotive API, vehicle data, VIN lookup, auto specs, fleet management
---
## Tagline Length Validation
Run to confirm all taglines are ≤ 120 characters:
+72 -4
View File
@@ -12,13 +12,16 @@ Follow this list top-to-bottom; each step unblocks the next.
- [ ] 1. `gitea-leeworks-agents-token` (flux-system) — unblocks Flux GitRepository auth
- [ ] 2. `gitea-runner-token` (gitea-runner) — unblocks Gitea Actions runner registration
- [ ] 3. `grafana-admin` (monitoring) — unblocks Grafana login
- [ ] 4. `gatus-slack-webhook` (monitoring) — unblocks Gatus alert notifications
- [ ] 4. `gatus-slack-webhook` (monitoring) — unblocks Gatus alert notifications AND Alertmanager Slack SLO alerts
- [ ] 5. `GITEA_TOKEN` in each API repo's Actions Secrets — unblocks CI image push
- [ ] 6. Gitea packages enabled + DNS record for `registry.leeworks.dev` — unblocks image push to registry
- [ ] 7. Add api-company Flux source + kustomization to 0xWheatyz/Talos — unblocks all GitOps reconciliation
- [ ] 8. `gitea-registry` (zip-enrichment, holidays, air-quality, docs-site) — imagePullSecret for pods pulling from `registry.leeworks.dev`
- [ ] 9. `gitea-image-automation-token` (flux-system) — write-scoped token for Flux ImageUpdateAutomation to push image-tag commits
- [ ] 10. `rapidapi-proxy-secret` (zip-enrichment, holidays, air-quality) — RapidAPI Proxy Secret for server-side request validation
- [ ] 11. `GITEA_TOKEN` Actions secret in `leeworks-agents/vin-decoder` repo — enables CI image push for VIN Decoder (leeworks-agents/api-company#126)
- [ ] 12. `gitea-registry` imagePullSecret in `vin-decoder` namespace — enables pod image pulls from `registry.leeworks.dev` (leeworks-agents/api-company#127)
- [ ] 13. `rapidapi-proxy-secret` in `vin-decoder` namespace — enables RapidAPI proxy-secret header validation (leeworks-agents/api-company#128)
---
@@ -89,9 +92,12 @@ Grafana will be accessible at `https://grafana.leeworks.dev` (login: `admin` / `
|-----------|-------|
| Name | `gatus-slack-webhook` |
| Namespace | `monitoring` |
| Purpose | Gatus posts downtime alerts to a Slack channel via incoming webhook |
| Purpose | Gatus posts downtime alerts to a Slack channel via incoming webhook. Also mounted by Alertmanager (`alertmanagerSpec.secrets`) to send SLO / firing-alert Slack notifications. |
| Source | Slack → Your workspace → Apps → Incoming Webhooks → Add to Slack → copy webhook URL |
| Unblocks | Issue #8 (Gatus status page at `status.leeworks.dev`) |
| Unblocks | Issue #8 (Gatus status page at `status.leeworks.dev`); Alertmanager Slack receiver (issue #210) |
| Consumers | **Two components** share this secret: (1) **Gatus** (`flux/monitoring/gatus-helmrelease.yaml`) — public status-page alerts; (2) **Alertmanager** (`flux/monitoring/helmrelease.yaml` via `alertmanagerSpec.secrets`) — SLO / firing-alert Slack notifications |
> **Note (dual consumer):** Creating this one secret (`gatus-slack-webhook` in the `monitoring` namespace) unblocks **both** Gatus alert delivery AND Alertmanager's Slack receiver. The Alertmanager configuration references this secret at `global.slack_api_url_file: /etc/alertmanager/secrets/gatus-slack-webhook/url` (enabled in PR #212 / issue #210).
```bash
kubectl create secret generic gatus-slack-webhook \
@@ -194,6 +200,68 @@ done
```
### 11. `GITEA_TOKEN` Actions secret in `leeworks-agents/vin-decoder`
| Field | Value |
|----------|-------|
| Name | `GITEA_TOKEN` |
| Scope | Gitea Actions Secret — set in repo Settings |
| Purpose | CI workflow pushes container image to `registry.leeworks.dev/vin-decoder/api:<sha>` |
| Source | Gitea token with `write:packages` scope (reuse from item #5 if it has `write:packages`) |
| Tracked | leeworks-agents/api-company#126 |
| Unblocks | CI image push for VIN Decoder |
Path: **Gitea → leeworks-agents/vin-decoder → Settings → Secrets → `GITEA_TOKEN`**
---
### 12. `gitea-registry` imagePullSecret in `vin-decoder` namespace
| Field | Value |
|-----------|-------|
| Name | `gitea-registry` |
| Namespace | `vin-decoder` |
| Type | `kubernetes.io/dockerconfigjson` |
| Purpose | Allows VIN Decoder pods to pull images from `registry.leeworks.dev` without ImagePullBackOff |
| Tracked | leeworks-agents/api-company#127 |
```bash
kubectl create namespace vin-decoder --dry-run=client -o yaml | kubectl apply -f -
kubectl create secret docker-registry gitea-registry \
--namespace=vin-decoder \
--docker-server=registry.leeworks.dev \
--docker-username=leeworks-agents \
--docker-password=<GITEA_TOKEN_WITH_READ_PACKAGES> \
--docker-email=agent@leeworks.dev
```
---
### 13. `rapidapi-proxy-secret` in `vin-decoder` namespace
| Field | Value |
|-----------|-------|
| Name | `rapidapi-proxy-secret` |
| Namespace | `vin-decoder` |
| Purpose | VIN Decoder validates `X-RapidAPI-Proxy-Secret` header; returns HTTP 403 if missing/wrong |
| Source | RapidAPI dashboard → VIN Decoder listing → Settings → Security → Proxy Secret |
| Tracked | leeworks-agents/api-company#128 |
```bash
# Placeholder (unblocks deploy testing):
kubectl create secret generic rapidapi-proxy-secret \
--namespace=vin-decoder \
--from-literal=X-RapidAPI-Proxy-Secret=PLACEHOLDER_REPLACE_AFTER_RAPIDAPI_LISTING
# Update with real value after RapidAPI listing (#131) is live:
kubectl create secret generic rapidapi-proxy-secret \
-n vin-decoder \
--from-literal=X-RapidAPI-Proxy-Secret=<VIN_RAPIDAPI_PROXY_SECRET> \
--save-config --dry-run=client -o yaml | kubectl apply -f -
```
---
## Dependency Order
```
@@ -201,7 +269,7 @@ done
1 (gitea-leeworks-token) → Flux can pull this repo over HTTPS
2 (gitea-runner-token) → runner online → CI runs
3 (grafana-admin) → Grafana login works
4 (gatus-slack-webhook) → Gatus alerting works
4 (gatus-slack-webhook) → Gatus alerting works + Alertmanager Slack SLO alerts
5 + 6 (GITEA_TOKEN + registry packages) → CI pushes images → API services deploy
8 (gitea-registry) → pods can pull images from registry.leeworks.dev → services start
9 (gitea-image-automation-token) → Flux ImageUpdateAutomation pushes tag-update commits
+12
View File
@@ -40,6 +40,18 @@ spec:
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: vin-decoder
namespace: flux-system
spec:
imageRepositoryRef:
name: vin-decoder
policy:
semver:
range: ">=0.1.0"
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: docs-site
namespace: flux-system
@@ -35,6 +35,17 @@ spec:
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: vin-decoder
namespace: flux-system
spec:
image: registry.leeworks.dev/vin-decoder/api
interval: 5m
secretRef:
name: gitea-leeworks-agents-token
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: docs-site
namespace: flux-system
+1
View File
@@ -9,4 +9,5 @@ resources:
- zip-enrichment
- holidays
- air-quality
- vin-decoder
- image-automation
+9
View File
@@ -68,6 +68,15 @@ spec:
description: "Air Quality API is down"
send-on-resolved: true
- name: VIN Decoder API
url: https://vin.leeworks.dev/v1/health
interval: 1m
conditions:
- "[STATUS] == 200"
- "[RESPONSE_TIME] < 1000"
alerts:
- type: slack
- name: Docs Site
url: https://docs.leeworks.dev
interval: 5m
+1 -1
View File
@@ -9,7 +9,7 @@ data:
api-dashboard.json: |
{
"annotations": { "list": [] },
"description": "Request rate, latency, error rate, and data freshness for zip-enrichment, holidays, and air-quality APIs",
"description": "Request rate, latency, error rate, and data freshness for zip-enrichment, holidays, air-quality, and vin-decoder APIs",
"editable": true,
"graphTooltip": 1,
"panels": [
+19 -1
View File
@@ -85,7 +85,25 @@ spec:
action: replace
target_label: kubernetes_pod_name
alertmanager:
enabled: false # Enable when alert routing is configured
enabled: true
config:
global:
slack_api_url_file: /etc/alertmanager/secrets/gatus-slack-webhook/url
route:
receiver: slack-api-alerts
group_by: [alertname, namespace]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receivers:
- name: slack-api-alerts
slack_configs:
- channel: '#api-alerts'
title: '{{ .CommonAnnotations.summary }}'
text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'
alertmanagerSpec:
secrets:
- gatus-slack-webhook
kubeStateMetrics:
enabled: true
nodeExporter:
+1
View File
@@ -8,3 +8,4 @@ resources:
- gatus-helmrelease.yaml
- grafana-dashboard-apis.yaml
- prometheusrule-apis.yaml
- vin-decoder-dashboard.yaml
+9 -9
View File
@@ -18,9 +18,9 @@ spec:
- alert: APIHighErrorRate
expr: |
(
sum by (job) (rate(api_requests_total{status=~"5..", job=~"zip|holidays|air-quality"}[5m]))
sum by (job) (rate(api_requests_total{status=~"5..", job=~"zip|holidays|air-quality|vin-decoder"}[5m]))
/
sum by (job) (rate(api_requests_total{job=~"zip|holidays|air-quality"}[5m]))
sum by (job) (rate(api_requests_total{job=~"zip|holidays|air-quality|vin-decoder"}[5m]))
) > 0.05
for: 5m
labels:
@@ -36,9 +36,9 @@ spec:
- alert: APIHighErrorRate
expr: |
(
sum by (job) (rate(api_requests_total{status=~"5..", job=~"zip|holidays|air-quality"}[5m]))
sum by (job) (rate(api_requests_total{status=~"5..", job=~"zip|holidays|air-quality|vin-decoder"}[5m]))
/
sum by (job) (rate(api_requests_total{job=~"zip|holidays|air-quality"}[5m]))
sum by (job) (rate(api_requests_total{job=~"zip|holidays|air-quality|vin-decoder"}[5m]))
) > 0.20
for: 5m
labels:
@@ -55,7 +55,7 @@ spec:
expr: |
histogram_quantile(
0.95,
sum by (job, le) (rate(api_response_duration_seconds_bucket{job=~"zip|holidays|air-quality"}[5m]))
sum by (job, le) (rate(api_response_duration_seconds_bucket{job=~"zip|holidays|air-quality|vin-decoder"}[5m]))
) > 2
for: 5m
labels:
@@ -70,7 +70,7 @@ spec:
# -------------------------------------------------------------------
- alert: APIDataStale
expr: |
api_data_freshness_seconds{job=~"zip|holidays|air-quality"} > 2592000
api_data_freshness_seconds{job=~"zip|holidays|air-quality|vin-decoder"} > 2592000
for: 30m
labels:
severity: warning
@@ -84,7 +84,7 @@ spec:
# -------------------------------------------------------------------
- alert: APIDataCriticallyStale
expr: |
api_data_freshness_seconds{job=~"zip|holidays|air-quality"} > 5184000
api_data_freshness_seconds{job=~"zip|holidays|air-quality|vin-decoder"} > 5184000
for: 1h
labels:
severity: critical
@@ -98,9 +98,9 @@ spec:
# -------------------------------------------------------------------
- alert: APIDown
expr: |
absent(up{job=~"zip|holidays|air-quality"} == 1)
absent(up{job=~"zip|holidays|air-quality|vin-decoder"} == 1)
or
up{job=~"zip|holidays|air-quality"} == 0
up{job=~"zip|holidays|air-quality|vin-decoder"} == 0
for: 2m
labels:
severity: critical
+161
View File
@@ -0,0 +1,161 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-dashboard-vin-decoder
namespace: monitoring
labels:
grafana_dashboard: "1"
data:
vin-decoder-dashboard.json: |
{
"annotations": { "list": [] },
"description": "Request rate, latency, error rate, and cache hit ratio for the VIN Decoder API",
"editable": true,
"graphTooltip": 1,
"panels": [
{
"collapsed": false,
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 },
"id": 1,
"title": "VIN Decoder — Request Rate",
"type": "row"
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "palette-classic" },
"custom": { "axisLabel": "requests/sec", "drawStyle": "line", "fillOpacity": 10, "lineWidth": 1, "showPoints": "never" },
"unit": "reqps"
},
"overrides": []
},
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 1 },
"id": 2,
"options": {
"legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom" },
"tooltip": { "mode": "multi" }
},
"targets": [
{
"expr": "sum by (route) (rate(api_requests_total{api=\"vin-decoder\"}[5m]))",
"legendFormat": "{{route}}",
"refId": "A"
}
],
"title": "Request Rate by Route",
"type": "timeseries"
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "palette-classic" },
"custom": { "axisLabel": "", "drawStyle": "line", "fillOpacity": 10, "lineWidth": 1, "showPoints": "never" },
"unit": "percentunit"
},
"overrides": []
},
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 1 },
"id": 3,
"options": {
"legend": { "calcs": ["mean", "last"], "displayMode": "table", "placement": "bottom" },
"tooltip": { "mode": "multi" }
},
"targets": [
{
"expr": "sum(rate(api_requests_total{api=\"vin-decoder\",status_code=~\"5..\"}[5m])) / sum(rate(api_requests_total{api=\"vin-decoder\"}[5m]))",
"legendFormat": "5xx error rate",
"refId": "A"
}
],
"title": "5xx Error Rate",
"type": "timeseries"
},
{
"collapsed": false,
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 9 },
"id": 10,
"title": "VIN Decoder — Latency",
"type": "row"
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "palette-classic" },
"custom": { "axisLabel": "seconds", "drawStyle": "line", "fillOpacity": 5, "lineWidth": 1, "showPoints": "never" },
"unit": "s"
},
"overrides": []
},
"gridPos": { "h": 8, "w": 24, "x": 0, "y": 10 },
"id": 4,
"options": {
"legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom" },
"tooltip": { "mode": "multi" }
},
"targets": [
{
"expr": "histogram_quantile(0.50, sum by (route, le) (rate(api_response_duration_seconds_bucket{api=\"vin-decoder\"}[5m])))",
"legendFormat": "P50 {{route}}",
"refId": "A"
},
{
"expr": "histogram_quantile(0.95, sum by (route, le) (rate(api_response_duration_seconds_bucket{api=\"vin-decoder\"}[5m])))",
"legendFormat": "P95 {{route}}",
"refId": "B"
},
{
"expr": "histogram_quantile(0.99, sum by (route, le) (rate(api_response_duration_seconds_bucket{api=\"vin-decoder\"}[5m])))",
"legendFormat": "P99 {{route}}",
"refId": "C"
}
],
"title": "Response Latency P50 / P95 / P99 by Route",
"type": "timeseries"
},
{
"collapsed": false,
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 18 },
"id": 20,
"title": "VIN Decoder — Cache & Data Freshness",
"type": "row"
},
{
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": {
"defaults": {
"color": { "mode": "thresholds" },
"thresholds": {
"steps": [
{ "color": "green", "value": null },
{ "color": "yellow", "value": 2592000 },
{ "color": "red", "value": 5184000 }
]
},
"unit": "s"
},
"overrides": []
},
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 19 },
"id": 5,
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "orientation": "auto", "textMode": "auto", "colorMode": "background" },
"targets": [
{
"expr": "api_data_freshness_seconds{api=\"vin-decoder\"}",
"legendFormat": "data freshness",
"refId": "A"
}
],
"title": "Data Freshness (seconds since last NHTSA fetch)",
"type": "stat"
}
],
"refresh": "1m",
"schemaVersion": 38,
"tags": ["vin-decoder", "api-company"],
"title": "VIN Decoder API",
"uid": "vin-decoder-api",
"version": 1
}
+18
View File
@@ -0,0 +1,18 @@
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: rapidapi-proxy-secret
namespace: vin-decoder
spec:
refreshInterval: 1h
secretStoreRef:
name: kubernetes-provider
kind: ClusterSecretStore
target:
name: rapidapi-proxy-secret
creationPolicy: Owner
data:
- secretKey: X-RapidAPI-Proxy-Secret
remoteRef:
key: rapidapi-vin-decoder-source
property: X-RapidAPI-Proxy-Secret
+103
View File
@@ -0,0 +1,103 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: vin-decoder
namespace: vin-decoder
spec:
interval: 10m
chart:
spec:
chart: raw
version: ">=0.2.0"
sourceRef:
kind: HelmRepository
name: bedag
namespace: flux-system
interval: 60m
values:
resources:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: vin-decoder
namespace: vin-decoder
spec:
replicas: 1
selector:
matchLabels:
app: vin-decoder
template:
metadata:
labels:
app: vin-decoder
spec:
imagePullSecrets:
- name: gitea-registry
containers:
- name: vin-decoder
image: registry.leeworks.dev/vin-decoder/api:latest # {"$imagepolicy": "flux-system:vin-decoder"}
ports:
- name: http
containerPort: 3000
env:
- name: RAPIDAPI_PROXY_SECRET
valueFrom:
secretKeyRef:
name: rapidapi-proxy-secret
key: X-RapidAPI-Proxy-Secret
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
livenessProbe:
httpGet:
path: /v1/health
port: 3000
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /v1/health
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
- apiVersion: v1
kind: Service
metadata:
name: vin-decoder
namespace: vin-decoder
spec:
selector:
app: vin-decoder
ports:
- name: http
port: 80
targetPort: 3000
- apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vin-decoder
namespace: vin-decoder
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- vin.leeworks.dev
secretName: vin-decoder-tls
rules:
- host: vin.leeworks.dev
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: vin-decoder
port:
number: 80
+7
View File
@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- externalsecret.yaml
- helmrelease.yaml
- servicemonitor.yaml
+4
View File
@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: vin-decoder
+15
View File
@@ -0,0 +1,15 @@
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: vin-decoder
namespace: vin-decoder
labels:
release: kube-prometheus-stack
spec:
selector:
matchLabels:
app: vin-decoder
endpoints:
- port: http
path: /metrics
interval: 30s
+8
View File
@@ -11,6 +11,8 @@
* RAPIDAPI_HOLIDAYS_VERSION_ID
* RAPIDAPI_AQI_API_ID
* RAPIDAPI_AQI_VERSION_ID
* RAPIDAPI_VIN_API_ID
* RAPIDAPI_VIN_VERSION_ID
*
* Usage: node scripts/publish-openapi.js
*/
@@ -45,6 +47,12 @@ const APIS = [
apiId: process.env.RAPIDAPI_AQI_API_ID,
versionId: process.env.RAPIDAPI_AQI_VERSION_ID,
},
{
name: 'vin-decoder',
specPath: path.join(__dirname, '..', 'apis', 'vin-decoder', 'openapi.yaml'),
apiId: process.env.RAPIDAPI_VIN_API_ID,
versionId: process.env.RAPIDAPI_VIN_VERSION_ID,
},
];
/**
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env bash
# Create the Gitea PATs the build-docs workflow needs and store them as action
# secrets on leeworks-agents/api-company:
# - SIBLING_REPOS_TOKEN : read:repository (clone the sibling API repos)
# - REGISTRY_TOKEN : write:package + read:package
# (push the docs-site image to gitea.leeworks.dev)
#
# Why this script exists:
# - The auto-injected GITEA_TOKEN is scoped to THIS repo only (can't read
# sibling repos) and has no package-registry scope (can't push images).
# GITEA_TOKEN is also a reserved secret name that cannot be overridden.
# - `tea` cannot CREATE a PAT (no such command), and Gitea's token-creation
# API requires BASIC AUTH (your password) — a token cannot mint a token.
# - `tea` CAN set the action secrets using its existing login.
#
# So: this prompts for your password ONCE, mints both PATs via the API, and
# pipes each straight into `tea`. Token values are never written to disk.
#
# Usage: bash scripts/setup-sibling-repos-token.sh
set -euo pipefail
GITEA_URL="https://gitea.leeworks.dev"
GITEA_USER="0xWheatyz"
REPO="leeworks-agents/api-company"
# Unique per run (date + seconds + pid) so re-runs never collide with an
# existing PAT name — Gitea returns 400 "token name has been used" otherwise.
STAMP="$(date +%Y%m%d-%H%M%S)-$$"
command -v curl >/dev/null || { echo "curl required"; exit 1; }
command -v tea >/dev/null || { echo "tea required"; exit 1; }
command -v python3 >/dev/null || { echo "python3 required"; exit 1; }
echo "Gitea user: $GITEA_USER ($GITEA_URL)"
read -r -s -p "Gitea password (for $GITEA_USER): " GITEA_PASS
echo
failures=0
# mint_token <token-name> <json-scopes-array> <secret-name>
mint_token() {
local token_name="$1" scopes="$2" secret_name="$3" body code pat
# Capture body + HTTP status separately so 4xx errors show the real message.
body="$(curl -sS -o - -w $'\n%{http_code}' -X POST \
-u "${GITEA_USER}:${GITEA_PASS}" \
-H 'Content-Type: application/json' \
-d "{\"name\":\"${token_name}\",\"scopes\":${scopes}}" \
"${GITEA_URL}/api/v1/users/${GITEA_USER}/tokens")"
code="${body##*$'\n'}"
body="${body%$'\n'*}"
if [ "$code" -lt 200 ] || [ "$code" -ge 300 ]; then
echo "${secret_name}: token API returned HTTP ${code}: ${body}" >&2
echo " (401/403 = wrong password or 2FA; 400 = duplicate name or bad scope)" >&2
failures=$((failures+1)); return 1
fi
pat="$(printf '%s' "$body" | python3 -c 'import sys,json; print(json.load(sys.stdin)["sha1"])' 2>/dev/null || true)"
[ -n "$pat" ] || { echo "${secret_name}: could not parse token from: ${body}" >&2; failures=$((failures+1)); return 1; }
printf '%s' "$pat" | tea actions secrets create "$secret_name" --repo "$REPO" --stdin
echo "${secret_name} set (PAT '${token_name}')"
}
# Don't let one failure abort the rest.
mint_token "sibling-repos-readonly-${STAMP}" '["read:repository"]' "SIBLING_REPOS_TOKEN" || true
mint_token "docs-registry-${STAMP}" '["write:package","read:package"]' "REGISTRY_TOKEN" || true
unset GITEA_PASS
echo "Done (${failures} failure(s)). Verify: tea actions secrets list --repo ${REPO}"
echo "Then re-run build-docs (push to main, or: tea actions workflows dispatch build-docs.yaml)"
[ "$failures" -eq 0 ]