# MASTER BUILD PROMPT — Recursive API Company Bootstrap ## For Claude Code | Talos/Kubernetes + RapidAPI + Free Public Data --- ## CONTEXT & CONSTRAINTS You are building a production API business from scratch for a developer who: - Runs a multi-site Talos Kubernetes cluster (homelab + VPS tunnel for static IP) - Will list APIs on RapidAPI marketplace at age 18 (target launch date ~30 days out) - Target: $100/mo net profit with <40 hrs/mo maintenance - Has zero hosting budget (existing infra only) - PayPal account for RapidAPI payouts goes live on 18th birthday Stack: - Kubernetes with Nginx or Traefik ingress already running - VPS acts as a WireGuard/tunnel endpoint with static IP - Node.js or Python (use whichever fits the task better) - Gitea at `gitea.leeworks.dev` for source control — NOT GitHub - Gitea Actions for CI (same YAML syntax as GitHub Actions, runs on Gitea runners) - Flux CD handles ALL Kubernetes deployments — never run `helm upgrade` manually - Helm charts committed to git; Flux watches and reconciles automatically - OpenAPI spec (YAML) is the single source of truth for all documentation --- ## PRIME DIRECTIVE This is a **recursive build loop**. You will never stop unless explicitly told to. The loop is: ``` RESEARCH → PLAN → SPEC → BUILD → TEST → COMMIT → (Flux deploys) → DOCUMENT → RESEARCH → ... ``` When you exhaust your context window on building, switch to research. When you exhaust research, return to building. You never idle. You never ask "what should I do next?" — you decide and do it. --- ## PHASE 0 — ORIENTATION (run once at start, then never again) Before touching any code, do all of the following: 1. **Audit existing cluster state** - `kubectl get nodes` — document node count, roles, resource totals - `kubectl get namespaces` — identify existing workloads - `kubectl get ingress -A` — understand current ingress setup - `kubectl top nodes` — check available headroom - `flux get kustomizations` — understand what Flux is already managing - `flux get helmreleases -A` — list Helm releases under Flux control - Save findings to `./docs/cluster-audit.md` 2. **Check existing tooling** - `node --version` - `python3 --version` - `helm version` - `flux version` - `kubectl config current-context` - Confirm git remote points to `gitea.leeworks.dev` - Save to `./docs/tooling.md` 3. **Create project root and push to Gitea** ```bash mkdir -p api-company/{apis,helm,flux,docs,scripts,research,monitoring,openapi} cd api-company git init git remote add origin https://gitea.leeworks.dev/{your-username}/api-company.git git push -u origin main ``` 4. **Bootstrap Flux on this repo** (if not already watching it) ```bash flux create source git api-company \ --url=https://gitea.leeworks.dev/{your-username}/api-company \ --branch=main \ --interval=1m \ --secret-ref=gitea-credentials flux create kustomization api-company \ --source=GitRepository/api-company \ --path="./flux" \ --prune=true \ --interval=5m ``` All Flux manifests live in `./flux/`. Flux watches this path and reconciles HelmRelease, Kustomization, and ConfigMap changes automatically on every commit. 5. **Internalize the RapidAPI constraints** - 25% platform fee + ~2% PayPal fee = keep ~73.5 cents per dollar charged - Payout only via PayPal (18+ required) - Free tier required on every API - Recommended 4 tiers: Free / Basic / Pro / Ultra - Every plan needs a "Requests" object - Rate limits set per plan, enforced by RapidAPI gateway - OpenAPI spec upload uses RapidAPI Platform API (Bearer token auth) --- ## REPOSITORY STRUCTURE ``` api-company/ ├── apis/ │ ├── zip-enrichment/ │ │ ├── src/ ← application code │ │ ├── scripts/ ← data seeding scripts │ │ ├── Dockerfile │ │ └── openapi.yaml ← OpenAPI spec (source of truth) │ ├── holidays/ │ │ ├── src/ │ │ ├── Dockerfile │ │ └── openapi.yaml │ └── air-quality/ │ ├── src/ │ ├── Dockerfile │ └── openapi.yaml ├── helm/ │ ├── zip-enrichment/ ← Helm chart │ ├── holidays/ │ ├── air-quality/ │ └── docs-site/ ├── flux/ │ ├── sources/ │ │ └── gitea-api-company.yaml │ ├── zip-enrichment/ │ │ ├── helmrelease.yaml ← Flux watches this, deploys automatically │ │ └── namespace.yaml │ ├── holidays/ │ │ ├── helmrelease.yaml │ │ └── namespace.yaml │ ├── air-quality/ │ │ ├── helmrelease.yaml │ │ └── namespace.yaml │ ├── docs-site/ │ │ ├── helmrelease.yaml │ │ └── namespace.yaml │ └── monitoring/ │ ├── helmrelease.yaml │ └── namespace.yaml ├── docs-site/ ← Astro + Redoc site ├── monitoring/ ← Prometheus, Grafana, Gatus configs ├── docs/ ← Internal docs ├── research/ ├── scripts/ │ └── publish-openapi.js ← RapidAPI spec upload script ├── .gitea/ │ └── workflows/ │ ├── build-and-deploy.yaml │ ├── publish-openapi.yaml │ └── rebuild-docs.yaml ├── SESSION_LOG.md └── STATUS.md ``` --- ## THE OPENAPI-FIRST WORKFLOW The OpenAPI spec is written once and drives everything downstream: ``` apis/{name}/openapi.yaml │ ├──▶ Gitea Actions validates spec on every push (redocly lint) │ ├──▶ Gitea Actions uploads to RapidAPI Platform API │ (updates listing, endpoint docs, example responses automatically) │ ├──▶ Redoc container at docs.leeworks.dev/{name} renders it live │ (same spec, beautiful searchable docs site, self-hosted) │ └──▶ Postman collection auto-generated from spec (for testing) ``` ### OpenAPI spec template Create `./apis/{api-name}/openapi.yaml`. Example for ZIP enrichment: ```yaml openapi: 3.0.3 info: title: ZIP Code Enrichment # Do NOT write "API" in the title — RapidAPI appends it automatically description: | Enrich any US ZIP code with Census demographic data, geographic metadata, and USPS-verified information. Sourced from the US Census Bureau ACS and HUD-USPS crosswalk files. Updated monthly. ## Data Sources - US Census Bureau ACS 5-year estimates - HUD-USPS ZIP crosswalk files - IANA timezone boundary data ## Authentication All requests must come through the RapidAPI gateway. Direct access is blocked. version: "1.0.0" x-rapidapi-category: "Data" x-rapidapi-subcategory: "Geodata" servers: - url: https://zip.leeworks.dev paths: /lookup: get: summary: Enrich a single ZIP code description: Returns full demographic and geographic data for a US ZIP code. parameters: - name: zip in: query required: true schema: type: string pattern: '^\d{5}$' example: "01085" responses: "200": description: Successful enrichment content: application/json: schema: $ref: '#/components/schemas/ZipResult' example: zip: "01085" city: "Westfield" state: "MA" county: "Hampden" lat: 42.1501 lon: -72.7495 timezone: "America/New_York" population: 41301 median_income: 58200 median_age: 40.1 unemployment_rate: 4.2 metro_area: "Springfield, MA" housing_units: 17842 congressional_district: "MA-01" data_vintage: "ACS 2023 5-year" "400": description: Invalid ZIP code format "404": description: ZIP code not found /batch: post: summary: Enrich up to 100 ZIP codes in one request requestBody: required: true content: application/json: schema: type: object properties: zips: type: array items: type: string maxItems: 100 example: ["01085", "10001", "90210"] responses: "200": description: Array of enriched ZIP results /radius: get: summary: Find all ZIP codes within N miles of a given ZIP parameters: - name: zip in: query required: true schema: type: string - name: miles in: query required: true schema: type: number minimum: 1 maximum: 100 responses: "200": description: Array of nearby ZIP codes /health: get: summary: Health check and data freshness responses: "200": description: Service status components: schemas: ZipResult: type: object properties: zip: type: string city: type: string state: type: string county: type: string lat: type: number lon: type: number timezone: type: string population: type: integer median_income: type: number median_age: type: number unemployment_rate: type: number metro_area: type: string housing_units: type: integer congressional_district: type: string data_vintage: type: string ``` --- ## GITEA ACTIONS CI PIPELINES ### Pipeline 1 — Build image, update Flux manifest `.gitea/workflows/build-and-deploy.yaml`: ```yaml name: Build and Deploy on: push: branches: [main] paths: - 'apis/**' - 'helm/**' jobs: detect-changes: runs-on: ubuntu-latest outputs: apis: ${{ steps.filter.outputs.changes }} steps: - uses: actions/checkout@v4 - uses: dorny/paths-filter@v3 id: filter with: filters: | zip-enrichment: - 'apis/zip-enrichment/**' holidays: - 'apis/holidays/**' air-quality: - 'apis/air-quality/**' build: needs: detect-changes runs-on: ubuntu-latest strategy: matrix: api: ${{ fromJSON(needs.detect-changes.outputs.apis) }} steps: - uses: actions/checkout@v4 - name: Build and push Docker image run: | IMAGE="registry.leeworks.dev/${{ matrix.api }}:${{ gitea.sha }}" docker build -t $IMAGE ./apis/${{ matrix.api }} docker push $IMAGE - name: Update Flux HelmRelease image tag run: | sed -i "s|tag:.*|tag: ${{ gitea.sha }}|" \ flux/${{ matrix.api }}/helmrelease.yaml git config user.email "ci@leeworks.dev" git config user.name "Gitea Actions" git add flux/${{ matrix.api }}/helmrelease.yaml git commit -m "chore: update ${{ matrix.api }} to ${{ gitea.sha }}" git push # Flux detects commit → reconciles → rolling update → done # No helm upgrade needed. Ever. ``` ### Pipeline 2 — Validate and publish OpenAPI spec to RapidAPI `.gitea/workflows/publish-openapi.yaml`: ```yaml name: Publish OpenAPI to RapidAPI on: push: branches: [main] paths: - 'apis/*/openapi.yaml' jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 2 - uses: actions/setup-node@v4 with: node-version: '20' - name: Validate all OpenAPI specs run: npx @redocly/cli lint apis/*/openapi.yaml # Pipeline fails here if spec is invalid. Never publish broken docs. - name: Detect changed specs id: changed run: | CHANGED=$(git diff --name-only HEAD~1 HEAD | grep 'openapi.yaml' || true) echo "CHANGED_FILES=$CHANGED" >> $GITHUB_ENV - name: Publish to RapidAPI env: RAPIDAPI_KEY: ${{ secrets.RAPIDAPI_PLATFORM_KEY }} ZIP_API_ID: ${{ secrets.RAPIDAPI_ZIP_API_ID }} ZIP_VERSION_ID: ${{ secrets.RAPIDAPI_ZIP_VERSION_ID }} HOLIDAYS_API_ID: ${{ secrets.RAPIDAPI_HOLIDAYS_API_ID }} HOLIDAYS_VERSION_ID: ${{ secrets.RAPIDAPI_HOLIDAYS_VERSION_ID }} AQI_API_ID: ${{ secrets.RAPIDAPI_AQI_API_ID }} AQI_VERSION_ID: ${{ secrets.RAPIDAPI_AQI_VERSION_ID }} run: node scripts/publish-openapi.js ``` ### `./scripts/publish-openapi.js` ```javascript const fs = require('fs'); const path = require('path'); const FormData = require('form-data'); const fetch = require('node-fetch'); const API_MAP = { 'zip-enrichment': { apiId: process.env.ZIP_API_ID, versionId: process.env.ZIP_VERSION_ID, }, 'holidays': { apiId: process.env.HOLIDAYS_API_ID, versionId: process.env.HOLIDAYS_VERSION_ID, }, 'air-quality': { apiId: process.env.AQI_API_ID, versionId: process.env.AQI_VERSION_ID, }, }; async function publishSpec(apiName) { const { apiId, versionId } = API_MAP[apiName]; if (!apiId || !versionId) { console.log(`Skipping ${apiName} — RapidAPI ID not configured yet`); return; } const specPath = path.join('apis', apiName, 'openapi.yaml'); const specContent = fs.readFileSync(specPath, 'utf8'); const form = new FormData(); form.append('file', specContent, { filename: 'openapi.yaml', contentType: 'application/yaml', }); const url = `https://platformapi1.p.rapidapi.com/v1/apis/${apiId}/versions/${versionId}`; const response = await fetch(url, { method: 'PUT', headers: { 'x-rapidapi-host': 'platformapi1.rapidapi-x.rapidapi.com', 'x-rapidapi-key': process.env.RAPIDAPI_KEY, ...form.getHeaders(), }, body: form, }); if (!response.ok) { throw new Error(`Failed to publish ${apiName}: ${response.status} ${await response.text()}`); } console.log(`✓ Published ${apiName} spec to RapidAPI`); } const changedFiles = (process.env.CHANGED_FILES || '').split('\n').filter(Boolean); const apisToPublish = Object.keys(API_MAP).filter(api => changedFiles.length === 0 || changedFiles.some(f => f.includes(api)) ); Promise.all(apisToPublish.map(publishSpec)) .then(() => console.log('Done')) .catch(err => { console.error(err); process.exit(1); }); ``` ### Pipeline 3 — Rebuild docs site when specs change `.gitea/workflows/rebuild-docs.yaml`: ```yaml name: Rebuild Docs Site on: push: branches: [main] paths: - 'apis/*/openapi.yaml' - 'docs-site/**' jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Copy specs into docs site run: | mkdir -p docs-site/public/specs cp apis/zip-enrichment/openapi.yaml docs-site/public/specs/ cp apis/holidays/openapi.yaml docs-site/public/specs/ cp apis/air-quality/openapi.yaml docs-site/public/specs/ - name: Build Astro site run: | cd docs-site && npm ci && npm run build - name: Push docs image and update Flux run: | docker build -t registry.leeworks.dev/docs-site:${{ gitea.sha }} ./docs-site docker push registry.leeworks.dev/docs-site:${{ gitea.sha }} sed -i "s|tag:.*|tag: ${{ gitea.sha }}|" flux/docs-site/helmrelease.yaml git config user.email "ci@leeworks.dev" git config user.name "Gitea Actions" git add flux/docs-site/helmrelease.yaml git commit -m "chore: update docs-site to ${{ gitea.sha }}" git push ``` --- ## FLUX CD MANIFEST TEMPLATES ### `./flux/zip-enrichment/helmrelease.yaml` ```yaml apiVersion: helm.toolkit.fluxcd.io/v2beta1 kind: HelmRelease metadata: name: zip-enrichment namespace: zip-enrichment spec: interval: 5m chart: spec: chart: ./helm/zip-enrichment sourceRef: kind: GitRepository name: api-company namespace: flux-system values: replicaCount: 2 image: repository: registry.leeworks.dev/zip-enrichment tag: latest # Gitea Actions updates this on every push resources: requests: memory: "128Mi" cpu: "50m" limits: memory: "256Mi" cpu: "200m" ingress: enabled: true host: zip.leeworks.dev env: RAPIDAPI_PROXY_SECRET: valueFrom: secretKeyRef: name: rapidapi-secrets key: proxy-secret ``` Repeat for `holidays` (namespace: `holidays`) and `air-quality` (namespace: `air-quality`). --- ## PHASE 1 — FIRST API: US ZIP CODE ENRICHMENT ### Why this one first - Data source: US Census Bureau API (free, no key for basic use) - Secondary: HUD-USPS crosswalk files (free download) - High demand: shipping, marketing, real estate, fintech - Low maintenance: Census updates ~annually - Proven market: competitors charge $49–$299/mo ### Step 1.1 — Write the OpenAPI spec first Before any application code, create `./apis/zip-enrichment/openapi.yaml` using the template above. Validate locally: ```bash npx @redocly/cli lint apis/zip-enrichment/openapi.yaml ``` Commit and push. The spec is now the contract — implement to match it. ### Step 1.2 — Data acquisition & caching Build `./apis/zip-enrichment/scripts/seed-data.js`: Pull and cache: - Census Bureau ACS 5-year: `https://api.census.gov/data/2023/acs/acs5` (population, median income, median age, housing units, unemployment rate) - HUD-USPS crosswalk: ZIP to county, metro area, census tract - Timezone data from precomputed IANA/Census boundary CSV Store in SQLite at `./apis/zip-enrichment/data/zip.db`, indexed on ZIP for <5ms lookups. Monthly CronJob re-seeds from source. **Failure mode:** Census API down → return stale data with `X-Data-Freshness` header. Never return a 500 to a paying customer. ### Step 1.3 — API server `./apis/zip-enrichment/src/server.js` — Node.js + Fastify Endpoints must match `openapi.yaml` paths exactly: - `GET /lookup?zip=01085` - `POST /batch` — body `{ zips: [...] }`, max 100 - `GET /radius?zip=01085&miles=25` - `GET /health` RapidAPI proxy secret verification on every request: ```javascript const PROXY_SECRET = process.env.RAPIDAPI_PROXY_SECRET; fastify.addHook('preHandler', (req, reply, done) => { if (req.headers['x-rapidapi-proxy-secret'] !== PROXY_SECRET) { reply.code(403).send({ error: 'Direct access not permitted' }); return; } done(); }); ``` ### Step 1.4 — Deploy via Flux Create Helm chart at `./helm/zip-enrichment/` and Flux manifest at `./flux/zip-enrichment/helmrelease.yaml`. Commit and push. Flux reconciles within 5 minutes. Monitor: ```bash flux get helmreleases -n zip-enrichment --watch ``` Use a PersistentVolumeClaim for the SQLite file so it survives pod restarts. ### Step 1.5 — Pricing tiers | Plan | Price | Requests/mo | Overage | |-------|---------|-------------|---------------| | Free | $0 | 100 | Not available | | Basic | $9/mo | 5,000 | $0.003/req | | Pro | $19/mo | 25,000 | $0.002/req | | Ultra | $49/mo | 100,000 | $0.001/req | To net $100/mo: need ~$136/mo gross = 15 Basic OR 7 Pro OR 3 Ultra subscribers. --- ## PHASE 2 — SECOND API: PUBLIC HOLIDAYS & BUSINESS DAYS ### Data sources (free) - Nager.Date: `https://date.nager.at/api/v3/PublicHolidays/{year}/{countryCode}` - US Federal Reserve bank holidays (scrape federalreserve.gov) - NYSE trading days (public calendar) ### Endpoints ``` GET /holidays?country=US&year=2025 GET /is-holiday?country=US&date=2025-12-25 GET /business-days?country=US&start=2025-01-01&end=2025-01-31 GET /next-business-day?country=US&date=2025-12-24 GET /trading-days?exchange=NYSE&year=2025 ← Pro+ only ``` ### Deployment Same Gitea Actions + Flux pattern. Namespace: `holidays`. Data is tiny — rebuild from upstream on pod start, no PVC needed. ### Pricing | Free | Basic $5/mo | Pro $15/mo | Ultra $29/mo (unlimited) | --- ## PHASE 3 — THIRD API: AIR QUALITY + POLLEN ### Data sources (free) - OpenAQ: `https://api.openaq.org/v3/` - EPA AirNow: `https://www.airnowapi.org/` - Open-Meteo: `https://air-quality-api.open-meteo.com/` ### Endpoints ``` GET /aqi?lat=42.11&lon=-72.59 GET /aqi?zip=01085 GET /forecast?lat=42.11&lon=-72.59&days=3 GET /pollen?lat=42.11&lon=-72.59 GET /history?lat=42.11&lon=-72.59&start=2025-01-01&end=2025-01-07 ``` --- ## RECURSIVE RESEARCH PROTOCOL When finishing a build phase OR hitting a context limit, run this block: ``` SEARCH 1: "RapidAPI most subscribed APIs {current_year}" SEARCH 2: "developers complaining about {category} API {current_year} reddit" (rotate: geocoding, business data, weather, finance, legal) SEARCH 3: "free public dataset API {niche} no existing wrapper" SEARCH 4: site:rapidapi.com {category} (check subscriber counts) SEARCH 5: "is there an API for" site:reddit.com ``` Append findings to `./research/RESEARCH_LOG.md`: ```markdown ## Research Session {date} {time} ### Findings - [finding]: [URL] [demand: high/medium/low] ### New API Candidates 1. {name} — {description} — {data source} — {evidence} ### Decision Building next: {name} because {reason} ``` --- ## PHASE 4 — MONITORING & OBSERVABILITY Deploy via Flux HelmRelease once 2+ APIs are live: - Prometheus + Grafana (community Helm charts) - Instrument with `prom-client` (Node) or `prometheus-client` (Python) - Metrics per API: `api_requests_total`, `api_response_duration_seconds`, `api_data_freshness_seconds`, `api_active_subscribers` Deploy **Gatus** for public status page at `status.leeworks.dev`: ```yaml # monitoring/gatus/config.yaml endpoints: - name: ZIP Enrichment url: https://zip.leeworks.dev/health interval: 30s conditions: - "[STATUS] == 200" - "[RESPONSE_TIME] < 500" - name: Holidays API url: https://holidays.leeworks.dev/health interval: 30s conditions: - "[STATUS] == 200" ``` Gatus is self-hosted and Kubernetes-native — fits the existing stack. Customers trust APIs with a public status page. This is not optional. --- ## PHASE 5 — DOCUMENTATION SITE ``` Stack: Astro + Redoc, self-hosted at docs.leeworks.dev Deployed via: Flux HelmRelease (same as everything else) ``` Redoc renders each `openapi.yaml` as a polished, searchable reference page. Gitea Actions copies specs into the docs site at build time (Pipeline 3 above). Docs always match the live API because they come from the same file. ``` docs.leeworks.dev/ ← company landing page docs.leeworks.dev/zip-enrichment ← Redoc renders openapi.yaml docs.leeworks.dev/holidays docs.leeworks.dev/air-quality docs.leeworks.dev/blog/ ← SEO content (not optional) docs.leeworks.dev/pricing/ ← cross-API comparison ``` Blog posts for SEO — write one per API at minimum: - "How to get Census demographic data by ZIP code in Node.js" - "Checking if a date is a business day in 30+ countries with one API call" - "Building an air quality widget for your React app using free EPA data" Each post has a working code example using YOUR API and links to the RapidAPI listing. This is how developers find you on Google before they find you on RapidAPI. --- ## PHASE 6 — COMPANY INFRASTRUCTURE ### Gitea organization at `gitea.leeworks.dev` - Create org: `leeworks-apis` (or chosen company name) - Repos: one per API + `helm-charts` + `docs-site` + `flux-config` - All CI: Gitea Actions (`.gitea/workflows/*.yaml`) - All deploys: Flux watching `gitea.leeworks.dev` ### Gitea Actions runner (deploy in cluster if not running) ```yaml # flux/gitea-runner/helmrelease.yaml apiVersion: helm.toolkit.fluxcd.io/v2beta1 kind: HelmRelease metadata: name: gitea-runner namespace: gitea-runner spec: chart: spec: chart: gitea-act-runner sourceRef: kind: HelmRepository name: gitea values: config: runner: labels: - "ubuntu-latest:docker://node:20" ``` ### DNS (all routed through VPS tunnel) ``` zip.leeworks.dev → ZIP enrichment API holidays.leeworks.dev → Holidays API aqi.leeworks.dev → Air quality API docs.leeworks.dev → Documentation + Redoc status.leeworks.dev → Gatus status page gitea.leeworks.dev → Already running ✓ registry.leeworks.dev → Container registry ``` ### Legal (prepare before 18th birthday) `./docs/legal/terms-of-service.md`, `privacy-policy.md`, `acceptable-use-policy.md` --- ## PHASE 7 — LAUNCH SEQUENCE (18th birthday) ```bash # 1. Verify Flux is healthy flux get helmreleases -A # All should show READY=True RECONCILING=False # 2. Smoke test each API curl https://zip.leeworks.dev/health curl https://holidays.leeworks.dev/health curl https://aqi.leeworks.dev/health # 3. Verify OpenAPI specs are showing on RapidAPI # (check each listing's Endpoints tab — must match your openapi.yaml) # 4. Link PayPal to RapidAPI (manual) # rapidapi.com → avatar → Personal Payouts → Link PayPal # 5. Enable paid tiers on all RapidAPI listings (manual) # Monetize tab → enable Basic / Pro / Ultra plans # 6. Post announcements # - Hacker News: "Show HN: I built 3 APIs on free Census/EPA data" # - r/webdev, r/SideProject, r/learnprogramming # - Indie Hackers # - Product Hunt (schedule in advance) # 7. Email waitlist from 30-day build window ``` --- ## RECURSIVE LOOP RULES — READ EVERY SESSION 1. **Read `SESSION_LOG.md` and `STATUS.md` first.** Always. No exceptions. 2. **Read `./research/RESEARCH_LOG.md`** to understand decisions already made. 3. **Check cluster state before writing any deployment code:** ```bash flux get helmreleases -A kubectl get deployments -A ``` 4. **Never run `helm upgrade` manually.** Commit to git, let Flux reconcile. Manual helm commands cause drift and break the GitOps workflow. 5. **OpenAPI spec before code.** Write and validate the spec entry for any new endpoint before implementing the handler. Docs drift is a trust killer. 6. **Prioritize:** - Fix broken things first (`flux get all -A` for errors) - Deploy nearly-finished things second - Build new things third - Research when blocked or between phases 7. **End every session by appending to `SESSION_LOG.md`:** ```markdown ## Session {timestamp} ### Completed - ... ### In progress - ... ### Next session starts with - ... ``` 8. **The goal is $100/mo net.** If it doesn't move toward revenue, it's optional. 9. **When truly stuck**: run the research protocol. There is always another API. --- ## STATUS TRACKER — `./STATUS.md` ```markdown # Company Status ## APIs | API | Spec | Code | Deployed | Listed on RapidAPI | Paying Users | MRR | |----------------|------|------|----------|--------------------|--------------|-----| | ZIP Enrichment | [ ] | [ ] | [ ] | [ ] | 0 | $0 | | Holidays | [ ] | [ ] | [ ] | [ ] | 0 | $0 | | Air Quality | [ ] | [ ] | [ ] | [ ] | 0 | $0 | ## Infrastructure - Cluster nodes: - Flux healthy: Y/N - Gitea Actions runner: UP/DOWN - VPS tunnel: UP/DOWN - Site 3 expansion: IN PROGRESS / DONE ## Revenue - Gross MRR: $0 - Net MRR (after ~26.5% fees): $0 - Target: $100/mo net - Gap: $100 ## Next action {one sentence — exactly what to do first next session} ``` --- *This prompt is intentionally never complete. More APIs, more research, more revenue.* *The loop ends when you say it ends — not before.*