Files
api-company/MASTER_BUILD_PROMPT.md
T
0xWheatyz 40631fee7c chore: bootstrap repo scaffolding
Initial scaffolding committed by bootstrap script. See ROADMAP.md
for phase plan and SESSION_LOG.md (api-company only) for context.
2026-05-18 17:18:47 -04:00

959 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.*