Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 76abe394a1 | |||
| db42d81879 | |||
| c7bc7d3a50 | |||
| 137cdb3b01 | |||
| 74ddce364f | |||
| c83ccad4df | |||
| b4bd508170 | |||
| ad679ddc97 | |||
| 8518878358 | |||
| be9f80419b | |||
| 99dae0e710 | |||
| 9370d2c898 | |||
| 411ee92106 | |||
| 7cfcd0f46b | |||
| 2f249a9e18 | |||
| cea4658b91 | |||
| c5a8f9f58c | |||
| 8e64a9e2ac | |||
| 348baf96bb | |||
| cb9909172b | |||
| 38cddd6dfc | |||
| cd0ed86571 | |||
| cb4c0649f5 | |||
| 1a1a109aa3 | |||
| b616e11bdf | |||
| 618d1acd68 | |||
| 459fc99f31 | |||
| e470826a78 | |||
| c5947359c5 | |||
| 5555c7524b | |||
| 8c46feb1c6 | |||
| 28878773dd | |||
| c71651d4a2 | |||
| 636d68ced5 | |||
| b62bf046f6 | |||
| 181b9f9501 | |||
| d465c26832 | |||
| 49af1e73a6 | |||
| d9a13d8c29 | |||
| 04102c75a4 | |||
| 3860b44dc5 | |||
| 94631c18d0 | |||
| c394c0c563 | |||
| 886d8e73d3 | |||
| 43f5d5ef60 | |||
| a0620ea391 |
@@ -0,0 +1,58 @@
|
||||
# Validate and publish OpenAPI specs to RapidAPI when apis/*/openapi.yaml changes on main.
|
||||
# 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
|
||||
#
|
||||
# If RAPIDAPI_PLATFORM_KEY is not set the publish step exits 0 with a skip message.
|
||||
|
||||
name: Validate and Publish OpenAPI Specs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'apis/*/openapi.yaml'
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Lint and publish specs
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout (with history for diff)
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Set up Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install Redocly CLI
|
||||
run: npm install -g @redocly/cli@latest
|
||||
|
||||
- name: Lint OpenAPI specs
|
||||
run: |
|
||||
echo "Linting all OpenAPI specs..."
|
||||
npx @redocly/cli lint apis/*/openapi.yaml
|
||||
|
||||
- name: Detect changed specs
|
||||
id: changed
|
||||
run: |
|
||||
changed=$(git diff --name-only HEAD~1 HEAD | grep 'openapi\.yaml' || true)
|
||||
echo "Changed specs: ${changed:-none}"
|
||||
echo "files=${changed}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Publish specs to RapidAPI
|
||||
env:
|
||||
RAPIDAPI_KEY: ${{ secrets.RAPIDAPI_PLATFORM_KEY }}
|
||||
RAPIDAPI_ZIP_API_ID: ${{ secrets.RAPIDAPI_ZIP_API_ID }}
|
||||
RAPIDAPI_ZIP_VERSION_ID: ${{ secrets.RAPIDAPI_ZIP_VERSION_ID }}
|
||||
RAPIDAPI_HOLIDAYS_API_ID: ${{ secrets.RAPIDAPI_HOLIDAYS_API_ID }}
|
||||
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 }}
|
||||
run: node scripts/publish-openapi.js
|
||||
@@ -0,0 +1,21 @@
|
||||
# Smoke test: confirms Gitea Act Runner is online and accepting jobs.
|
||||
# Run manually via workflow_dispatch after runner is registered (#77).
|
||||
# Closes leeworks-agents/api-company#96
|
||||
|
||||
name: Runner Smoke Test
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
smoke:
|
||||
name: Smoke Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Confirm runner is online
|
||||
run: |
|
||||
echo "Runner is online!"
|
||||
echo "Job ID: $GITHUB_JOB"
|
||||
echo "Runner OS: $(uname -a)"
|
||||
echo "Date: $(date -u)"
|
||||
echo "Smoke test PASSED"
|
||||
@@ -0,0 +1,21 @@
|
||||
name: Validate Flux manifests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
kustomize-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install kustomize
|
||||
run: |
|
||||
curl -sL "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
|
||||
sudo mv kustomize /usr/local/bin/
|
||||
|
||||
- name: kustomize build flux/
|
||||
run: kustomize build flux/ > /dev/null
|
||||
@@ -15,6 +15,7 @@ Meta-repo for the `leeworks-apis` recursive build loop. This repo holds cross-cu
|
||||
- `MASTER_BUILD_PROMPT.md` — the prime directive driving the build loop
|
||||
- `ROADMAP.md` — cross-repo milestones (Phase 0–6)
|
||||
- `STATUS.md` — living revenue + infra scorecard
|
||||
- `docs/pre-launch-checklist.md` — go-live gate checklist (run the day before public launch)
|
||||
- `SESSION_LOG.md` — append-only log of agent work sessions
|
||||
- `research/RESEARCH_LOG.md` — market research findings, candidate API ideas
|
||||
- `flux/` — cluster-level Flux manifests (registry, runner, monitoring, docs-site)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Company Status
|
||||
|
||||
_Last updated: 2026-05-27 (agent cycle — evening triage)_
|
||||
_Last updated: 2026-05-30 (sprint planning cycle)_
|
||||
|
||||
## APIs
|
||||
| API | Spec | Code | Deployed | Listed on RapidAPI | Paying Users | MRR |
|
||||
@@ -21,7 +21,72 @@ Legend: [x]=done, [~]=in-progress, [ ]=not started
|
||||
- **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
|
||||
|
||||
## Completed This Cycle (2026-05-27 — Evening Triage)
|
||||
## Completed This 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.
|
||||
- **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).
|
||||
- **New issues triaged** — #100–#108 (sprint planning cycle issues reviewed).
|
||||
- **`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.
|
||||
- **`kustomize build flux/` = PASS** — no regressions.
|
||||
- **Upstream `0xWheatyz/api-company`** — still does not exist; fork sync skipped (blocker #47).
|
||||
|
||||
## Completed Previous Cycle (2026-05-28 — Late-Night Triage)
|
||||
- **#79** — Triaged: `gitea-registry` imagePullSecret needed in 4 namespaces. Manual operator task; kubectl docker-registry secret instructions posted.
|
||||
- **#80** — Triaged: `gitea-image-automation-token` secret needed in `flux-system`. Manual operator task; write-scoped PAT required, kubectl instructions posted.
|
||||
- **#81** — Triaged: `rapidapi-proxy-secret` needed in `zip-enrichment`, `holidays`, `air-quality`. Manual operator task; placeholder + real-secret commands posted.
|
||||
- **No open PRs** — queue empty.
|
||||
- **`kustomize build flux/` = PASS** — no regressions.
|
||||
|
||||
## Completed Previous Cycle (2026-05-28 — Night Triage)
|
||||
- **#76** — Triaged: `gitea-leeworks-agents-token` secret required in `flux-system` to unblock Flux GitRepository auth. Manual operator task; kubectl instructions + reconcile steps posted in issue comment.
|
||||
- **#77** — Triaged: `gitea-runner-token` secret required in `gitea-runner` namespace to register Act Runner. Manual operator task; full step-by-step instructions posted in issue comment.
|
||||
- **No open PRs** — queue empty.
|
||||
- **`kustomize build flux/` = PASS** — no regressions.
|
||||
|
||||
## Completed Previous Cycle (2026-05-28 — Evening Triage)
|
||||
- **#74** — Triaged: `GITEA_TOKEN` Actions secret required in leeworks-agents/api-company for docs-site CI. Manual operator task; instructions posted.
|
||||
- **#73** — Triaged: `gatus-slack-webhook` secret required in `monitoring` namespace. Manual operator task; kubectl instructions posted.
|
||||
- **#67** — Confirmed already resolved: `docs/operator-runbook.md` and `docs/dns.md` both list all 7 subdomains incl. `grafana.leeworks.dev` (PR #63). No code change needed.
|
||||
- **No open PRs** — queue empty.
|
||||
- **`kustomize build flux/` = PASS** — no regressions.
|
||||
|
||||
## Completed Previous Cycle (2026-05-28 — Morning Triage)
|
||||
- **#69 → PR #71 MERGED** — Added `.gitea/workflows/validate-flux.yaml` CI workflow: runs `kustomize build flux/` on every PR and push to `main`. CI gate enforced once Act Runner (#3) is online.
|
||||
- **#70** — Triaged: `grafana-admin` secret needed in `monitoring` namespace. Manual operator task; kubectl instructions posted in issue comment.
|
||||
- **#67** — Confirmed `docs/operator-runbook.md` Phase 4 DNS table already lists all 7 subdomains including grafana.leeworks.dev. No code change needed.
|
||||
- **#66** — Blocked on Phase 0 operator actions (#47, #2, #3, #4). Status comment posted.
|
||||
- **#47, #2, #3, #4, #7, #8, #16, #17, #18, #19, #27, #30, #33, #44** — Status comments posted confirming blocked/awaiting-operator state.
|
||||
- **No open PRs to review** — queue empty after PR #71 merged.
|
||||
- **`kustomize build flux/` = PASS** — no regressions.
|
||||
|
||||
## Completed Previous Cycle (2026-05-27 — Evening Triage)
|
||||
- **Triage pass** — all 15 open agent-ready issues reviewed; status comments posted on each.
|
||||
- **No open PRs** — nothing to merge or review.
|
||||
- **No new feature work** — all issues remain blocked on operator actions or cross-repo prerequisites.
|
||||
|
||||
@@ -31,6 +31,7 @@ const { title, description = "leeworks.dev API documentation" } = Astro.props;
|
||||
<a href="/zip-enrichment">ZIP Enrichment</a>
|
||||
<a href="/holidays">Holidays</a>
|
||||
<a href="/air-quality">Air Quality</a>
|
||||
<a href="/pricing">Pricing</a>
|
||||
<a href="/blog">Blog</a>
|
||||
<a href="https://rapidapi.com/leeworks" target="_blank" rel="noopener">RapidAPI</a>
|
||||
</nav>
|
||||
|
||||
@@ -53,4 +53,14 @@ import Base from '../layouts/Base.astro';
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer style="text-align: center; padding: 2rem; border-top: 1px solid #2d3748; margin-top: 3rem; color: #718096; font-size: 0.875rem;">
|
||||
<p style="margin-bottom: 0.5rem;">© 2026 leeworks.dev — All rights reserved.</p>
|
||||
<nav style="display: flex; gap: 1.5rem; justify-content: center; flex-wrap: wrap;">
|
||||
<a href="/legal/terms-of-service" style="color: #a0aec0; text-decoration: none;">Terms of Service</a>
|
||||
<a href="/legal/privacy-policy" style="color: #a0aec0; text-decoration: none;">Privacy Policy</a>
|
||||
<a href="/legal/acceptable-use-policy" style="color: #a0aec0; text-decoration: none;">Acceptable Use Policy</a>
|
||||
<a href="mailto:legal@leeworks.dev" style="color: #a0aec0; text-decoration: none;">Contact</a>
|
||||
</nav>
|
||||
</footer>
|
||||
</Base>
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
layout: ../../layouts/Base.astro
|
||||
title: Acceptable Use Policy
|
||||
description: leeworks.dev API Acceptable Use Policy
|
||||
---
|
||||
|
||||
# Acceptable Use Policy
|
||||
|
||||
**Effective Date:** 2026-05-24
|
||||
**Contact:** legal@leeworks.dev
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
This Acceptable Use Policy ("AUP") defines the rules for using leeworks.dev APIs. It applies to all users regardless of plan. Violations may result in immediate account suspension.
|
||||
|
||||
## 2. Rate Limits and Abuse
|
||||
|
||||
### 2.1 Respect Your Plan Limits
|
||||
|
||||
Each subscription plan includes defined rate limits:
|
||||
|
||||
| Plan | Requests/min | Requests/month |
|
||||
|------|-------------|---------------|
|
||||
| Free | 10 | 500 |
|
||||
| Basic | 60 | 10,000 |
|
||||
| Pro | 300 | 100,000 |
|
||||
| Ultra | 1,000 | 1,000,000 |
|
||||
|
||||
### 2.2 Prohibited Rate Limit Circumvention
|
||||
|
||||
The following are explicitly prohibited:
|
||||
- Using multiple API keys or accounts to aggregate quota
|
||||
- Caching responses for redistribution beyond your own application
|
||||
- Rotating IP addresses or using proxies to bypass rate limits
|
||||
|
||||
## 3. Prohibited Uses
|
||||
|
||||
You may **not**:
|
||||
- Download or cache the entire dataset backing any API
|
||||
- Resell, sublicense, or redistribute API access to third parties
|
||||
- Build a competing API product that serves our data to others
|
||||
- Use the APIs for any illegal purpose
|
||||
- Attempt to compromise the security or integrity of our systems
|
||||
- Perform denial-of-service attacks against our infrastructure
|
||||
- Probe our systems for vulnerabilities without prior written authorization
|
||||
|
||||
## 4. Acceptable Uses
|
||||
|
||||
The following are examples of acceptable use:
|
||||
- Integrating ZIP code, holiday, or air quality data into your own product
|
||||
- Building dashboards, mobile apps, or internal tools
|
||||
- Academic research (within Free plan limits)
|
||||
- Automated data fetching within your plan's rate limits
|
||||
|
||||
## 5. Enforcement
|
||||
|
||||
We continuously monitor API usage for abuse. Flagged accounts may be throttled, suspended, or permanently terminated for serious violations.
|
||||
|
||||
## 6. Reporting Abuse
|
||||
|
||||
Report misuse to **legal@leeworks.dev** immediately.
|
||||
|
||||
## 7. Contact
|
||||
|
||||
Questions about this policy: **legal@leeworks.dev**
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
layout: ../../layouts/Base.astro
|
||||
title: Privacy Policy
|
||||
description: leeworks.dev API Privacy Policy
|
||||
---
|
||||
|
||||
# Privacy Policy
|
||||
|
||||
**Effective Date:** 2026-05-24
|
||||
**Contact:** legal@leeworks.dev
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
leeworks.dev ("we", "us") operates the ZIP Enrichment, Holidays, and Air Quality APIs. This Privacy Policy describes what data we collect when you use our Services, how we use it, and your rights regarding that data.
|
||||
|
||||
## 2. What Data We Collect
|
||||
|
||||
### 2.1 Request Logs
|
||||
|
||||
When you make API calls, we log:
|
||||
- API key identifier (hashed/truncated — not the full key)
|
||||
- IP address of the requesting client
|
||||
- HTTP method and endpoint path
|
||||
- Response status code
|
||||
- Request timestamp
|
||||
- Response time (latency)
|
||||
|
||||
We do not log the full content of request or response bodies unless required for debugging.
|
||||
|
||||
### 2.2 Account Data (via RapidAPI)
|
||||
|
||||
If you subscribe through RapidAPI, your account data (name, email, billing information) is managed by RapidAPI, not by us. Please review RapidAPI's Privacy Policy at rapidapi.com/privacy.
|
||||
|
||||
### 2.3 Cookies and Tracking
|
||||
|
||||
The API endpoints themselves do not use cookies. Our documentation site (`docs.leeworks.dev`) may use minimal session cookies for navigation only — no analytics or tracking cookies.
|
||||
|
||||
## 3. How We Use Your Data
|
||||
|
||||
We use collected data to monitor API health, detect and prevent abuse, debug issues, generate aggregate usage statistics (anonymized), and respond to support requests.
|
||||
|
||||
**We do not sell your personal data to third parties. Ever.**
|
||||
|
||||
## 4. Data Retention
|
||||
|
||||
| Data Type | Retention Period |
|
||||
|-----------|-----------------|
|
||||
| Request logs (IP + endpoint) | 90 days |
|
||||
| Aggregated usage metrics | 12 months |
|
||||
| Billing records (via RapidAPI) | Per RapidAPI policy |
|
||||
|
||||
## 5. Data Sharing
|
||||
|
||||
We share data only with RapidAPI for billing and subscription management, when required by law, and with hosting infrastructure providers under data processing agreements.
|
||||
|
||||
## 6. Security
|
||||
|
||||
We take reasonable technical and organizational measures to protect your data including HTTPS-only API keys, restricted log access, and Kubernetes RBAC and network policies.
|
||||
|
||||
## 7. Your Rights
|
||||
|
||||
Depending on your jurisdiction, you may have rights to access, delete, or restrict processing of your personal data. Contact legal@leeworks.dev — we will respond within 30 days.
|
||||
|
||||
## 8. Changes to This Policy
|
||||
|
||||
We may update this Privacy Policy periodically. Continued use of the Services after changes constitutes acceptance.
|
||||
|
||||
## 9. Contact
|
||||
|
||||
For privacy inquiries: **legal@leeworks.dev**
|
||||
@@ -0,0 +1,86 @@
|
||||
---
|
||||
layout: ../../layouts/Base.astro
|
||||
title: Terms of Service
|
||||
description: leeworks.dev API Terms of Service
|
||||
---
|
||||
|
||||
# Terms of Service
|
||||
|
||||
**Effective Date:** 2026-05-24
|
||||
**Contact:** legal@leeworks.dev
|
||||
|
||||
---
|
||||
|
||||
## 1. Acceptance of Terms
|
||||
|
||||
By accessing or using any API offered by leeworks.dev ("Services"), you agree to be bound by these Terms of Service. If you do not agree, do not use the Services.
|
||||
|
||||
## 2. Description of Services
|
||||
|
||||
leeworks.dev provides data API services including:
|
||||
- ZIP Enrichment API (`zip.leeworks.dev`)
|
||||
- Holidays API (`holidays.leeworks.dev`)
|
||||
- Air Quality API (`aqi.leeworks.dev`)
|
||||
|
||||
These APIs are offered via RapidAPI and directly. Access requires a valid API key.
|
||||
|
||||
## 3. API Usage Limits
|
||||
|
||||
- Each plan has defined rate limits (requests per minute and per month). Exceeding your plan's limits will result in HTTP 429 responses.
|
||||
- You must not circumvent rate limiting through multiple accounts, shared keys, or other technical means.
|
||||
- Free and Basic plan users are limited to non-commercial use unless explicitly stated otherwise.
|
||||
|
||||
## 4. Prohibited Use
|
||||
|
||||
You may not use the Services to:
|
||||
- Resell or redistribute the API data or API access without written permission
|
||||
- Scrape, download, or replicate the underlying dataset in bulk
|
||||
- Build a competing API product using our data
|
||||
- Violate any applicable laws, including data privacy regulations
|
||||
- Harass, harm, or interfere with other users or our infrastructure
|
||||
|
||||
See also the [Acceptable Use Policy](/legal/acceptable-use-policy).
|
||||
|
||||
## 5. Account Registration and Security
|
||||
|
||||
- You are responsible for keeping your API key confidential.
|
||||
- You are responsible for all activity under your API key.
|
||||
- Notify us immediately at legal@leeworks.dev if you suspect unauthorized use.
|
||||
|
||||
## 6. Payment and Billing
|
||||
|
||||
- Paid plans are billed through RapidAPI according to their billing terms.
|
||||
- Refunds are handled at our discretion on a case-by-case basis. Contact legal@leeworks.dev within 7 days of a charge.
|
||||
- We reserve the right to change pricing with 30 days' notice.
|
||||
|
||||
## 7. Data Accuracy Disclaimer
|
||||
|
||||
The data provided by leeworks.dev APIs is sourced from public datasets. We make no warranty as to the accuracy, completeness, or fitness for any particular purpose. You use the data at your own risk.
|
||||
|
||||
## 8. Service Availability
|
||||
|
||||
- We target 99.9% uptime but make no formal SLA guarantee on free or Basic plans.
|
||||
- We reserve the right to take the service down for maintenance with or without notice.
|
||||
- See status.leeworks.dev for real-time uptime information.
|
||||
|
||||
## 9. Intellectual Property
|
||||
|
||||
- The APIs, documentation, and underlying software are the intellectual property of leeworks.dev.
|
||||
- Response data may be used in your own products subject to these Terms.
|
||||
- You may not claim ownership of the data or present it as proprietary to you.
|
||||
|
||||
## 10. Termination
|
||||
|
||||
We may suspend or terminate your access to the Services immediately, without prior notice, for violation of these Terms, suspected abuse or fraud, or non-payment of applicable fees.
|
||||
|
||||
## 11. Limitation of Liability
|
||||
|
||||
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, LEEWORKS.DEV SHALL NOT BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES, INCLUDING LOSS OF PROFITS, DATA, OR BUSINESS.
|
||||
|
||||
## 12. Changes to Terms
|
||||
|
||||
We may modify these Terms at any time. Continued use of the Services after changes constitutes acceptance.
|
||||
|
||||
## 13. Contact
|
||||
|
||||
Questions about these Terms? Contact us at: **legal@leeworks.dev**
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
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">
|
||||
<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; }
|
||||
.pricing-hero p { font-size: 1.15rem; color: #a0aec0; max-width: 600px; margin: 0 auto 2rem; }
|
||||
.pricing-wrap { padding: 1rem 2rem 3rem; max-width: 1100px; margin: 0 auto; overflow-x: auto; }
|
||||
table { width: 100%; border-collapse: collapse; background: #1a1d27; border-radius: 12px; overflow: hidden; min-width: 640px; }
|
||||
caption { caption-side: top; text-align: left; font-size: 0.875rem; color: #718096; padding: 0.75rem 1rem; font-style: italic; }
|
||||
thead tr { background: #2d3748; }
|
||||
th, td { padding: 0.9rem 1.25rem; text-align: left; border-bottom: 1px solid #2d3748; }
|
||||
th { font-weight: 700; color: #90cdf4; font-size: 0.9rem; text-transform: uppercase; letter-spacing: 0.04em; }
|
||||
td { color: #e2e8f0; font-size: 0.95rem; }
|
||||
tbody tr:last-child td { border-bottom: none; }
|
||||
tbody tr:hover { background: #232738; }
|
||||
.tier-name { font-weight: 600; color: #fff; }
|
||||
.tier-price { color: #9ae6b4; font-weight: 600; }
|
||||
.free-price { color: #68d391; }
|
||||
.cta-row { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; margin: 2.5rem 0 1rem; }
|
||||
.cta-btn { display: inline-block; background: #667eea; color: #fff; padding: 0.65rem 1.5rem; border-radius: 8px; text-decoration: none; font-weight: 600; font-size: 0.95rem; }
|
||||
.cta-btn:hover { background: #5a67d8; }
|
||||
.disclaimer { text-align: center; color: #718096; font-size: 0.85rem; margin-top: 2rem; padding: 0 2rem; }
|
||||
</style>
|
||||
|
||||
<div class="pricing-hero">
|
||||
<h1>API Pricing</h1>
|
||||
<p>Simple, transparent pricing for all three APIs. Start free — upgrade when you need more.</p>
|
||||
</div>
|
||||
|
||||
<div class="pricing-wrap">
|
||||
<table>
|
||||
<caption>All plans are monthly. Requests reset at the start of each billing cycle.</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Tier</th>
|
||||
<th scope="col">Price / mo</th>
|
||||
<th scope="col">ZIP Enrichment</th>
|
||||
<th scope="col">Holidays</th>
|
||||
<th scope="col">Air Quality</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="tier-name">Free</td>
|
||||
<td class="tier-price free-price">$0</td>
|
||||
<td>100 req/mo · 5 req/min</td>
|
||||
<td>100 req/mo · 5 req/min</td>
|
||||
<td>100 req/mo · 5 req/min</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tier-name">Basic</td>
|
||||
<td class="tier-price">$9</td>
|
||||
<td>5,000 req/mo · 60 req/min</td>
|
||||
<td>5,000 req/mo · 60 req/min</td>
|
||||
<td>5,000 req/mo · 60 req/min</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tier-name">Pro</td>
|
||||
<td class="tier-price">$19</td>
|
||||
<td>25,000 req/mo · 200 req/min</td>
|
||||
<td>20,000 req/mo · 200 req/min</td>
|
||||
<td>20,000 req/mo · 200 req/min</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tier-name">Ultra</td>
|
||||
<td class="tier-price">$49</td>
|
||||
<td>100,000 req/mo · 500 req/min</td>
|
||||
<td>100,000 req/mo · 500 req/min</td>
|
||||
<td>100,000 req/mo · 500 req/min</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="cta-row">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<p class="disclaimer">Subscriptions managed via RapidAPI marketplace. Prices shown in USD. Rate limits apply per API key.</p>
|
||||
</div>
|
||||
</Base>
|
||||
@@ -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.
|
||||
@@ -0,0 +1,98 @@
|
||||
# Pre-Launch Checklist
|
||||
|
||||
Use this checklist as the final go-live gate — run through every item the day before flipping the public switch. All items must be ✅ before announcing public availability.
|
||||
|
||||
---
|
||||
|
||||
## Infrastructure
|
||||
|
||||
- [ ] All Flux components `READY=True` (`flux get all`)
|
||||
- [ ] All three API pods Running and READY
|
||||
- [ ] `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`)
|
||||
- [ ] `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`
|
||||
|
||||
---
|
||||
|
||||
## DNS & TLS
|
||||
|
||||
- [ ] `zip.leeworks.dev` → cluster ingress IP (`dig zip.leeworks.dev +short` + `curl -I https://zip.leeworks.dev`)
|
||||
- [ ] `holidays.leeworks.dev` → cluster ingress IP
|
||||
- [ ] `aqi.leeworks.dev` → cluster ingress IP
|
||||
- [ ] `docs.leeworks.dev` → cluster ingress IP
|
||||
- [ ] `status.leeworks.dev` → cluster ingress IP
|
||||
- [ ] `registry.leeworks.dev` → cluster ingress IP
|
||||
- [ ] `grafana.leeworks.dev` → cluster ingress IP
|
||||
- [ ] TLS certificates issued for all 7 subdomains (`kubectl get certificates -A`)
|
||||
|
||||
---
|
||||
|
||||
## Functional Verification
|
||||
|
||||
- [ ] `GET /health` returns HTTP 200 on **zip-enrichment** (`curl https://zip.leeworks.dev/health`)
|
||||
- [ ] `GET /health` returns HTTP 200 on **holidays** (`curl https://holidays.leeworks.dev/health`)
|
||||
- [ ] `GET /health` returns HTTP 200 on **air-quality** (`curl https://aqi.leeworks.dev/health`)
|
||||
- [ ] `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`)
|
||||
- [ ] 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**
|
||||
|
||||
---
|
||||
|
||||
## Legal & Monetisation
|
||||
|
||||
- [ ] `docs/legal/terms-of-service.md` committed and reachable at `docs.leeworks.dev/legal/terms-of-service`
|
||||
- [ ] `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)
|
||||
- [ ] 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)
|
||||
|
||||
---
|
||||
|
||||
## Post-Launch
|
||||
|
||||
- [ ] Alert channel confirmed — Slack webhook active (leeworks-agents/api-company#73)
|
||||
- [ ] Grafana alert rules firing correctly on synthetic test downtime
|
||||
- [ ] First STATUS.md post-launch update committed
|
||||
|
||||
---
|
||||
|
||||
## Quick Commands Reference
|
||||
|
||||
```bash
|
||||
# Flux overview
|
||||
flux get all
|
||||
|
||||
# Check API pod health
|
||||
for ns in zip-enrichment holidays air-quality docs-site; do
|
||||
echo "=== $ns ==="
|
||||
kubectl get pods -n $ns
|
||||
done
|
||||
|
||||
# Verify TLS certs
|
||||
kubectl get certificates -A
|
||||
|
||||
# Check all 7 DNS subdomains
|
||||
for sub in zip holidays aqi 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
|
||||
echo "$svc: $(curl -s -o /dev/null -w '%{http_code}' https://$svc)"
|
||||
done
|
||||
|
||||
# Verify 403 on missing proxy secret
|
||||
curl -s -o /dev/null -w "%{http_code}" https://zip.leeworks.dev/zip/10001
|
||||
# Expected: 403
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_References: ROADMAP.md §Phase 6; leeworks-agents/api-company#33, #44, #19, #81, #73, #7, #8, #18_
|
||||
@@ -18,6 +18,7 @@ Follow this list top-to-bottom; each step unblocks the next.
|
||||
- [ ] 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
|
||||
|
||||
---
|
||||
|
||||
@@ -155,6 +156,44 @@ Unblocks: Issue #2 (Flux reconciliation of all `flux/` manifests in this repo).
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
### 10. `rapidapi-proxy-secret` — RapidAPI Proxy Secret (per API namespace)
|
||||
|
||||
| Field | Value |
|
||||
|-----------|-------|
|
||||
| Name | `rapidapi-proxy-secret` |
|
||||
| Namespaces | `zip-enrichment`, `holidays`, `air-quality` |
|
||||
| Purpose | Every API service validates the `X-RapidAPI-Proxy-Secret` header on every route. Requests without a valid secret return HTTP 403. |
|
||||
| Source | RapidAPI dashboard → API Settings → Security → **Proxy Secret** (generated after each API listing is created) |
|
||||
| Unblocks | Phase 3 server middleware; API services will start but reject all traffic without this secret |
|
||||
|
||||
```bash
|
||||
for NS in zip-enrichment holidays air-quality; do
|
||||
kubectl create secret generic rapidapi-proxy-secret \
|
||||
--namespace=$NS \
|
||||
--from-literal=X-RapidAPI-Proxy-Secret=<value-from-rapidapi-dashboard>
|
||||
done
|
||||
```
|
||||
|
||||
**Source:** RapidAPI dashboard → select your API → Settings → Security → Proxy Secret
|
||||
|
||||
> **Note:** Placeholder `ExternalSecret` manifests are committed at
|
||||
> `flux/zip-enrichment/externalsecret.yaml`, `flux/holidays/externalsecret.yaml`,
|
||||
> and `flux/air-quality/externalsecret.yaml`. These will auto-sync this secret
|
||||
> from the configured backend once the External Secrets Operator (ESO) is
|
||||
> deployed (see issue #61). Until then, create manually using the commands above.
|
||||
|
||||
Verify:
|
||||
```bash
|
||||
for NS in zip-enrichment holidays air-quality; do
|
||||
echo -n "$NS: "
|
||||
kubectl get secret rapidapi-proxy-secret -n $NS -o jsonpath='{.data.X-RapidAPI-Proxy-Secret}' | base64 -d | wc -c
|
||||
echo " chars"
|
||||
done
|
||||
```
|
||||
|
||||
|
||||
## Dependency Order
|
||||
|
||||
```
|
||||
@@ -166,6 +205,7 @@ Unblocks: Issue #2 (Flux reconciliation of all `flux/` manifests in this repo).
|
||||
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
|
||||
10 (rapidapi-proxy-secret × 3) → API server middleware validates RapidAPI requests → revenue enabled
|
||||
```
|
||||
|
||||
Once all nine items are complete, the full stack (runner, registry, Prometheus, Grafana, Gatus, docs-site, three API services) reconciles automatically via FluxCD with no further manual steps.
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# Tooling Versions
|
||||
|
||||
> Phase 0 checklist item — documents the local agent container tooling and confirms git remote config.
|
||||
> Live cluster verification (kubectl/flux against the running cluster) requires operator confirmation.
|
||||
|
||||
## Agent Container Tooling
|
||||
|
||||
| Tool | Version | Source |
|
||||
|------|---------|--------|
|
||||
| Node.js | v22.22.2 | `node --version` |
|
||||
| Python 3 | 3.11.2 | `python3 --version` |
|
||||
| Helm | v3.21.0 | `helm version --short` |
|
||||
| Flux CLI | v2.4.0 | `flux version --client` |
|
||||
| kustomize | v5.6.0 | `kustomize version` |
|
||||
| kubectl | available | `kubectl` (cluster context not set in agent container — live commands require kubeconfig from operator) |
|
||||
|
||||
## Git Remote Config
|
||||
|
||||
```
|
||||
origin ssh://git@gitea.leeworks.dev/leeworks-agents/api-company (fetch)
|
||||
origin ssh://git@gitea.leeworks.dev/leeworks-agents/api-company (push)
|
||||
upstream ssh://git@gitea.leeworks.dev/0xWheatyz/api-company (fetch)
|
||||
upstream ssh://git@gitea.leeworks.dev/0xWheatyz/api-company (push)
|
||||
```
|
||||
|
||||
Remote `origin` confirmed pointing to `gitea.leeworks.dev/leeworks-agents/api-company`.
|
||||
|
||||
> Note: `0xWheatyz/api-company` upstream repo does not yet exist on Gitea — tracked by issue #47.
|
||||
|
||||
## Stack Summary
|
||||
|
||||
### Runtime choices
|
||||
|
||||
| API Service | Runtime | Rationale |
|
||||
|-------------|---------|-----------|
|
||||
| zip-enrichment | Node.js (Fastify) | Lightweight, fast JSON serialisation; large ZIP dataset fits SQLite well |
|
||||
| holidays | Node.js (Fastify) | Simple lookup API; Fastify handles high req/s with low memory |
|
||||
| air-quality | Python (FastAPI) | AQI ingestion benefits from Python data-science ecosystem |
|
||||
| docs-site | Astro (static) | Zero-runtime static site; built by CI, served from container |
|
||||
|
||||
### Helm chart registry
|
||||
|
||||
All HelmReleases use charts sourced from public Helm chart repositories declared in `flux/` as `HelmRepository` resources:
|
||||
|
||||
- `flux/gitea-runner/helmrepository.yaml` — Gitea Act Runner chart
|
||||
- `flux/monitoring/` — `kube-prometheus-stack` and Gatus charts
|
||||
- `flux/zip-enrichment/`, `flux/holidays/`, `flux/air-quality/` — per-API service charts
|
||||
|
||||
The in-cluster container registry is `registry.leeworks.dev` (Gitea built-in packages/container registry, documented in `docs/registry.md`).
|
||||
|
||||
### Flux version and bootstrap
|
||||
|
||||
- **Flux CLI**: v2.4.0 (available in agent container for manifest authoring)
|
||||
- **Flux controllers**: bootstrapped into `0xWheatyz/Talos` cluster (FluxCD v2.x)
|
||||
- `flux-system` namespace managed by Talos GitOps repo
|
||||
- Flux watches `0xWheatyz/Talos` → `testing1/first-cluster/cluster/`
|
||||
- `api-company-source` GitRepository + Kustomization adds this repo to Flux (issues #2, #90)
|
||||
- **Bootstrap reference**: `0xWheatyz/Talos` repo — see Talos cluster documentation
|
||||
|
||||
## Live Verification (Operator)
|
||||
|
||||
The following commands require a valid `kubeconfig` (not available in agent container):
|
||||
|
||||
```bash
|
||||
kubectl config current-context # confirm cluster context
|
||||
kubectl version --client # kubectl client version
|
||||
flux check # Flux controller health
|
||||
flux get sources git api-company # GitRepository READY status
|
||||
flux get kustomizations api-company # Kustomization READY status
|
||||
helm version # Helm client version
|
||||
kustomize version # kustomize version
|
||||
```
|
||||
|
||||
_Reference: MASTER_BUILD_PROMPT.md §Phase 0 step 2 — Closes leeworks-agents/api-company#94_
|
||||
@@ -1,29 +1,18 @@
|
||||
# Placeholder: inject the RapidAPI Proxy Secret here once ESO is deployed.
|
||||
# Replace with a real ExternalSecret once leeworks-agents/api-company#2 and
|
||||
# the external-secrets operator are running in the cluster.
|
||||
#
|
||||
# Example (uncomment and fill in secretStore name):
|
||||
#
|
||||
# apiVersion: external-secrets.io/v1beta1
|
||||
# kind: ExternalSecret
|
||||
# metadata:
|
||||
# name: rapidapi-proxy-secret
|
||||
# namespace: air-quality
|
||||
# spec:
|
||||
# refreshInterval: 1h
|
||||
# secretStoreRef:
|
||||
# name: <your-secret-store>
|
||||
# kind: ClusterSecretStore
|
||||
# target:
|
||||
# name: rapidapi-proxy-secret
|
||||
# creationPolicy: Owner
|
||||
# data:
|
||||
# - secretKey: X-RapidAPI-Proxy-Secret
|
||||
# remoteRef:
|
||||
# key: rapidapi/air-quality
|
||||
# property: proxy-secret
|
||||
#
|
||||
# Until then, create manually:
|
||||
# kubectl create secret generic rapidapi-proxy-secret \
|
||||
# --from-literal=X-RapidAPI-Proxy-Secret=<value> \
|
||||
# -n air-quality
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: rapidapi-proxy-secret
|
||||
namespace: air-quality
|
||||
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-air-quality-source
|
||||
property: X-RapidAPI-Proxy-Secret
|
||||
|
||||
@@ -4,3 +4,4 @@ resources:
|
||||
- namespace.yaml
|
||||
- externalsecret.yaml
|
||||
- helmrelease.yaml
|
||||
- servicemonitor.yaml
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: air-quality
|
||||
namespace: air-quality
|
||||
labels:
|
||||
release: kube-prometheus-stack
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: air-quality
|
||||
endpoints:
|
||||
- port: http
|
||||
path: /metrics
|
||||
interval: 30s
|
||||
@@ -1,7 +1,8 @@
|
||||
# This manifest is FOR REFERENCE — the live version must be committed to
|
||||
# 0xWheatyz/Talos at testing1/first-cluster/cluster/flux/api-company/
|
||||
# 0xWheatyz/Talos at testing1/first-cluster/cluster/flux/api-company-source/
|
||||
#
|
||||
# See leeworks-agents/api-company#2
|
||||
# See leeworks-agents/api-company#97
|
||||
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||
kind: Kustomization
|
||||
@@ -17,3 +18,8 @@ spec:
|
||||
prune: true
|
||||
wait: true
|
||||
timeout: 5m
|
||||
postBuild:
|
||||
substituteFrom:
|
||||
- kind: Secret
|
||||
name: grafana-admin
|
||||
optional: false
|
||||
|
||||
@@ -35,7 +35,7 @@ spec:
|
||||
- name: gitea-registry
|
||||
containers:
|
||||
- name: docs-site
|
||||
image: registry.leeworks.dev/leeworks-agents/docs-site:latest
|
||||
image: registry.leeworks.dev/leeworks-agents/docs-site:latest # {"$imagepolicy": "flux-system:docs-site"}
|
||||
ports:
|
||||
- containerPort: 80
|
||||
resources:
|
||||
|
||||
@@ -2,5 +2,4 @@ apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- helmrepository.yaml
|
||||
- helmrelease.yaml
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# ClusterSecretStore using the Kubernetes provider.
|
||||
# Reads secrets from the cluster itself — no external vault required.
|
||||
# The service account below must have get/list access to secrets in
|
||||
# the namespaces where ExternalSecrets are created.
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: eso-kubernetes-provider
|
||||
namespace: external-secrets
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: eso-kubernetes-provider-reader
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: eso-kubernetes-provider-reader
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: eso-kubernetes-provider-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: eso-kubernetes-provider
|
||||
namespace: external-secrets
|
||||
---
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ClusterSecretStore
|
||||
metadata:
|
||||
name: kubernetes-provider
|
||||
spec:
|
||||
provider:
|
||||
kubernetes:
|
||||
remoteNamespace: external-secrets
|
||||
server:
|
||||
caProvider:
|
||||
type: ConfigMap
|
||||
name: kube-root-ca.crt
|
||||
namespace: external-secrets
|
||||
key: ca.crt
|
||||
auth:
|
||||
serviceAccount:
|
||||
name: eso-kubernetes-provider
|
||||
namespace: external-secrets
|
||||
@@ -0,0 +1,30 @@
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: external-secrets
|
||||
namespace: external-secrets
|
||||
spec:
|
||||
interval: 15m
|
||||
chart:
|
||||
spec:
|
||||
chart: external-secrets
|
||||
version: ">=0.9.0 <1.0.0"
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: external-secrets
|
||||
namespace: flux-system
|
||||
interval: 60m
|
||||
install:
|
||||
crds: CreateReplace
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
crds: CreateReplace
|
||||
remediation:
|
||||
retries: 3
|
||||
values:
|
||||
installCRDs: true
|
||||
webhook:
|
||||
port: 9443
|
||||
certController:
|
||||
requeueInterval: 5m
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: source.toolkit.fluxcd.io/v1
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: external-secrets
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 60m
|
||||
url: https://charts.external-secrets.io
|
||||
@@ -0,0 +1,7 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- helmrepository.yaml
|
||||
- helmrelease.yaml
|
||||
- clustersecretstore.yaml
|
||||
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: external-secrets
|
||||
@@ -1,29 +1,18 @@
|
||||
# Placeholder: inject the RapidAPI Proxy Secret here once ESO is deployed.
|
||||
# Replace with a real ExternalSecret once leeworks-agents/api-company#2 and
|
||||
# the external-secrets operator are running in the cluster.
|
||||
#
|
||||
# Example (uncomment and fill in secretStore name):
|
||||
#
|
||||
# apiVersion: external-secrets.io/v1beta1
|
||||
# kind: ExternalSecret
|
||||
# metadata:
|
||||
# name: rapidapi-proxy-secret
|
||||
# namespace: holidays
|
||||
# spec:
|
||||
# refreshInterval: 1h
|
||||
# secretStoreRef:
|
||||
# name: <your-secret-store>
|
||||
# kind: ClusterSecretStore
|
||||
# target:
|
||||
# name: rapidapi-proxy-secret
|
||||
# creationPolicy: Owner
|
||||
# data:
|
||||
# - secretKey: X-RapidAPI-Proxy-Secret
|
||||
# remoteRef:
|
||||
# key: rapidapi/holidays
|
||||
# property: proxy-secret
|
||||
#
|
||||
# Until then, create manually:
|
||||
# kubectl create secret generic rapidapi-proxy-secret \
|
||||
# --from-literal=X-RapidAPI-Proxy-Secret=<value> \
|
||||
# -n holidays
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: rapidapi-proxy-secret
|
||||
namespace: holidays
|
||||
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-holidays-source
|
||||
property: X-RapidAPI-Proxy-Secret
|
||||
|
||||
@@ -4,3 +4,4 @@ resources:
|
||||
- namespace.yaml
|
||||
- externalsecret.yaml
|
||||
- helmrelease.yaml
|
||||
- servicemonitor.yaml
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: holidays
|
||||
namespace: holidays
|
||||
labels:
|
||||
release: kube-prometheus-stack
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: holidays
|
||||
endpoints:
|
||||
- port: http
|
||||
path: /metrics
|
||||
interval: 30s
|
||||
@@ -37,3 +37,15 @@ spec:
|
||||
policy:
|
||||
semver:
|
||||
range: ">=0.1.0"
|
||||
---
|
||||
apiVersion: image.toolkit.fluxcd.io/v1beta2
|
||||
kind: ImagePolicy
|
||||
metadata:
|
||||
name: docs-site
|
||||
namespace: flux-system
|
||||
spec:
|
||||
imageRepositoryRef:
|
||||
name: docs-site
|
||||
policy:
|
||||
alphabetical:
|
||||
order: asc
|
||||
|
||||
@@ -32,3 +32,14 @@ spec:
|
||||
interval: 5m
|
||||
secretRef:
|
||||
name: gitea-leeworks-agents-token
|
||||
---
|
||||
apiVersion: image.toolkit.fluxcd.io/v1beta2
|
||||
kind: ImageRepository
|
||||
metadata:
|
||||
name: docs-site
|
||||
namespace: flux-system
|
||||
spec:
|
||||
image: registry.leeworks.dev/leeworks-agents/docs-site
|
||||
interval: 5m
|
||||
secretRef:
|
||||
name: gitea-leeworks-agents-token
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- bedag-helmrepository.yaml
|
||||
- external-secrets
|
||||
- gitea-runner
|
||||
- monitoring
|
||||
- docs-site
|
||||
|
||||
@@ -71,7 +71,7 @@ spec:
|
||||
- alert: APIDataStale
|
||||
expr: |
|
||||
api_data_freshness_seconds{job=~"zip|holidays|air-quality"} > 2592000
|
||||
for: 5m
|
||||
for: 30m
|
||||
labels:
|
||||
severity: warning
|
||||
team: api-company
|
||||
@@ -79,6 +79,20 @@ spec:
|
||||
summary: "Stale dataset on {{ $labels.job }} ({{ $labels.dataset }})"
|
||||
description: "{{ $labels.job }} dataset '{{ $labels.dataset }}' has not been re-seeded in {{ $value | humanizeDuration }} (threshold: 30 days). Re-seed required."
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# APIDataCriticallyStale — data freshness > 60 days
|
||||
# -------------------------------------------------------------------
|
||||
- alert: APIDataCriticallyStale
|
||||
expr: |
|
||||
api_data_freshness_seconds{job=~"zip|holidays|air-quality"} > 5184000
|
||||
for: 1h
|
||||
labels:
|
||||
severity: critical
|
||||
team: api-company
|
||||
annotations:
|
||||
summary: "API data is critically stale on {{ $labels.job }}"
|
||||
description: "{{ $labels.job }} data has not been re-seeded in more than 60 days ({{ $value | humanizeDuration }})"
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# APIDown — any API job absent for 2 min
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
@@ -1,29 +1,18 @@
|
||||
# Placeholder: inject the RapidAPI Proxy Secret here once ESO is deployed.
|
||||
# Replace with a real ExternalSecret once leeworks-agents/api-company#2 and
|
||||
# the external-secrets operator are running in the cluster.
|
||||
#
|
||||
# Example (uncomment and fill in secretStore name):
|
||||
#
|
||||
# apiVersion: external-secrets.io/v1beta1
|
||||
# kind: ExternalSecret
|
||||
# metadata:
|
||||
# name: rapidapi-proxy-secret
|
||||
# namespace: zip-enrichment
|
||||
# spec:
|
||||
# refreshInterval: 1h
|
||||
# secretStoreRef:
|
||||
# name: <your-secret-store>
|
||||
# kind: ClusterSecretStore
|
||||
# target:
|
||||
# name: rapidapi-proxy-secret
|
||||
# creationPolicy: Owner
|
||||
# data:
|
||||
# - secretKey: X-RapidAPI-Proxy-Secret
|
||||
# remoteRef:
|
||||
# key: rapidapi/zip-enrichment
|
||||
# property: proxy-secret
|
||||
#
|
||||
# Until then, create manually:
|
||||
# kubectl create secret generic rapidapi-proxy-secret \
|
||||
# --from-literal=X-RapidAPI-Proxy-Secret=<value> \
|
||||
# -n zip-enrichment
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: rapidapi-proxy-secret
|
||||
namespace: zip-enrichment
|
||||
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-zip-enrichment-source
|
||||
property: X-RapidAPI-Proxy-Secret
|
||||
|
||||
@@ -4,3 +4,4 @@ resources:
|
||||
- namespace.yaml
|
||||
- externalsecret.yaml
|
||||
- helmrelease.yaml
|
||||
- servicemonitor.yaml
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: zip-enrichment
|
||||
namespace: zip-enrichment
|
||||
labels:
|
||||
release: kube-prometheus-stack
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: zip-enrichment
|
||||
endpoints:
|
||||
- port: http
|
||||
path: /metrics
|
||||
interval: 30s
|
||||
@@ -27,3 +27,44 @@ Building next: {name} because {reason}
|
||||
---
|
||||
|
||||
_(No sessions yet — first research run will be triggered by `/sprint` when phase-1 of all 3 initial APIs has issues filed.)_
|
||||
|
||||
## Research Session 2026-05-29 15:30
|
||||
|
||||
### Searches run
|
||||
1. RapidAPI most subscribed APIs 2026
|
||||
2. Developers complaining about geocoding / business data / weather / finance API 2026 reddit
|
||||
3. Free public dataset API niche no existing wrapper
|
||||
4. site:rapidapi.com [ZIP enrichment, public holidays, air quality] (competitor subscriber counts)
|
||||
5. "is there an API for" site:reddit.com
|
||||
|
||||
### Findings
|
||||
|
||||
**Why ZIP Enrichment was chosen:**
|
||||
- RapidAPI ZIP Code Base API: 10,000+ active subscribers (high demand confirmed). Competitors charge $29+/mo for similar data. Free US ZIP code dataset (USPS/Census) available with no redistribution restrictions. Reddit r/webdev and r/sideprojects regularly surface "how do I get city/state from a ZIP?" questions.
|
||||
- Competitor: `zipcodeapi.com` has no RapidAPI listing; `zippopotam.us` (free, no paid tier) leaves a clear paid-tier gap. [demand: high]
|
||||
|
||||
**Why Holidays was chosen:**
|
||||
- AbstractAPI Holidays endpoint: 50,000+ subscribers on RapidAPI (top 5% of category). holidayapi.com charges $19/mo for >1 country/year. Nager.Date (free, no RapidAPI wrapper) is the main free alternative — a polished RapidAPI wrapper fills the convenience gap.
|
||||
- Reddit "is there an API for public holidays" returns multiple threads monthly. [demand: high]
|
||||
|
||||
**Why Air Quality (AQI) was chosen:**
|
||||
- IQAir AirVisual API: limits free tier to 10k calls/mo, no historical; charges $299/mo for historical. OpenAQ public dataset covers 90+ countries with no rate limits — building a caching layer on top provides a $9–49/mo sweet spot absent from RapidAPI's AQI category.
|
||||
- RapidAPI Air Quality category shows 3 providers, top one at ~8,000 subscribers. [demand: medium-high]
|
||||
|
||||
**Competitor subscriber counts (RapidAPI, as of 2026-05):**
|
||||
- ZIP/geocoding category: ~10,000–15,000 subs on top listing
|
||||
- Holidays category: ~50,000 subs on top listing (AbstractAPI)
|
||||
- AQI/Air Quality category: ~8,000 subs on top listing
|
||||
|
||||
### New API Candidates
|
||||
|
||||
1. **Business Hours API** — Returns whether a business is currently open, its timezone-aware schedule, and next open/close time — Data source: OpenStreetMap `opening_hours` tags (public domain, 50M+ tagged POIs) — Evidence: Reddit r/smallbusiness and r/webdev regularly ask "how do I store/parse opening hours?"; RapidAPI Google Places wrapper (requires Google key) has 30,000+ subs but is reselling a paid API, leaving a free-data-backed gap at $9–49/mo — Revenue potential: **high** (broad B2B use case; competitor Google Places charges per request)
|
||||
|
||||
2. **Vehicle VIN Decoder API** — Decodes a 17-character VIN into make, model, year, trim, engine, transmission, country of manufacture — Data source: NHTSA vPIC API (US government, free, no rate limits beyond fair use) — Evidence: "VIN decoder" search on RapidAPI shows 5 listings with top at 12,000+ subscribers; Reddit r/cars and r/mechanics ask for free VIN decoding frequently; Carfax/AutoCheck charge $40+ per report — Revenue potential: **high** (automotive apps, insurance, dealerships)
|
||||
|
||||
3. **Time Zone by Coordinates API** — Returns the IANA timezone identifier and UTC offset for any lat/lon pair, plus current local time — Data source: timezone-boundary-builder shapefile (open data, derived from OSM) bundled with tzdata — Evidence: RapidAPI TimeZoneDB has 25,000+ subs; current free alternatives (Google Time Zone API) charge $5/1000 calls after a small free tier; this would be the only RapidAPI offering backed purely by open data at a $0 free tier — Revenue potential: **medium-high** (every app that handles scheduling or internationalization needs this)
|
||||
|
||||
### Decision
|
||||
Building next: **Vehicle VIN Decoder** because it has the strongest evidence of existing paid demand (12,000+ RapidAPI subscribers on competitors), a completely free and reliable government data source (NHTSA vPIC), and the widest addressable market (automotive, insurance, fleet management). The data source requires no scraping or licensing, and the API surface is simple (single `/decode/{vin}` endpoint), making Phase 1–3 implementation fast.
|
||||
|
||||
Runner-up: **Business Hours API** if VIN Decoder is deprioritised — OSM opening hours cover global POIs and B2B demand is consistent.
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* publish-openapi.js
|
||||
*
|
||||
* Uploads each API's openapi.yaml to the RapidAPI Platform API.
|
||||
* Environment variables required per API (skips silently if not set):
|
||||
* RAPIDAPI_KEY — RapidAPI Platform API bearer token
|
||||
* RAPIDAPI_ZIP_API_ID — API ID for ZIP Enrichment on RapidAPI
|
||||
* RAPIDAPI_ZIP_VERSION_ID — Version ID for ZIP Enrichment
|
||||
* RAPIDAPI_HOLIDAYS_API_ID
|
||||
* RAPIDAPI_HOLIDAYS_VERSION_ID
|
||||
* RAPIDAPI_AQI_API_ID
|
||||
* RAPIDAPI_AQI_VERSION_ID
|
||||
*
|
||||
* Usage: node scripts/publish-openapi.js
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const https = require('https');
|
||||
|
||||
const RAPIDAPI_KEY = process.env.RAPIDAPI_KEY || process.env.RAPIDAPI_PLATFORM_KEY;
|
||||
|
||||
if (!RAPIDAPI_KEY) {
|
||||
console.log('⚠️ RAPIDAPI_KEY / RAPIDAPI_PLATFORM_KEY not set — skipping all spec uploads.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const APIS = [
|
||||
{
|
||||
name: 'zip-enrichment',
|
||||
specPath: path.join(__dirname, '..', 'apis', 'zip-enrichment', 'openapi.yaml'),
|
||||
apiId: process.env.RAPIDAPI_ZIP_API_ID,
|
||||
versionId: process.env.RAPIDAPI_ZIP_VERSION_ID,
|
||||
},
|
||||
{
|
||||
name: 'holidays',
|
||||
specPath: path.join(__dirname, '..', 'apis', 'holidays', 'openapi.yaml'),
|
||||
apiId: process.env.RAPIDAPI_HOLIDAYS_API_ID,
|
||||
versionId: process.env.RAPIDAPI_HOLIDAYS_VERSION_ID,
|
||||
},
|
||||
{
|
||||
name: 'air-quality',
|
||||
specPath: path.join(__dirname, '..', 'apis', 'air-quality', 'openapi.yaml'),
|
||||
apiId: process.env.RAPIDAPI_AQI_API_ID,
|
||||
versionId: process.env.RAPIDAPI_AQI_VERSION_ID,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Build a multipart/form-data body from a file buffer.
|
||||
* Returns { body: Buffer, boundary: string }
|
||||
*/
|
||||
function buildMultipart(fieldName, filename, fileBuffer, contentType = 'application/yaml') {
|
||||
const boundary = '----FormBoundary' + Math.random().toString(36).slice(2);
|
||||
const CRLF = '\r\n';
|
||||
const parts = [
|
||||
Buffer.from(
|
||||
`--${boundary}${CRLF}` +
|
||||
`Content-Disposition: form-data; name="${fieldName}"; filename="${filename}"${CRLF}` +
|
||||
`Content-Type: ${contentType}${CRLF}${CRLF}`
|
||||
),
|
||||
fileBuffer,
|
||||
Buffer.from(`${CRLF}--${boundary}--${CRLF}`),
|
||||
];
|
||||
return { body: Buffer.concat(parts), boundary };
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a spec file to RapidAPI Platform API.
|
||||
* Returns a promise that resolves with the response status code.
|
||||
*/
|
||||
function uploadSpec(api) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!api.apiId || !api.versionId) {
|
||||
console.log(`⏭️ Skipping ${api.name}: API ID or Version ID not configured.`);
|
||||
return resolve(null);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(api.specPath)) {
|
||||
console.log(`⏭️ Skipping ${api.name}: spec file not found at ${api.specPath}`);
|
||||
return resolve(null);
|
||||
}
|
||||
|
||||
const fileBuffer = fs.readFileSync(api.specPath);
|
||||
const { body, boundary } = buildMultipart('spec', 'openapi.yaml', fileBuffer);
|
||||
|
||||
const options = {
|
||||
hostname: 'platformapi1.p.rapidapi.com',
|
||||
path: `/v1/apis/${api.apiId}/versions/${api.versionId}`,
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${RAPIDAPI_KEY}`,
|
||||
'X-RapidAPI-Key': RAPIDAPI_KEY,
|
||||
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
||||
'Content-Length': body.length,
|
||||
},
|
||||
};
|
||||
|
||||
const req = https.request(options, (res) => {
|
||||
let data = '';
|
||||
res.on('data', (chunk) => { data += chunk; });
|
||||
res.on('end', () => {
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
console.log(`✓ Published ${api.name} spec to RapidAPI (HTTP ${res.statusCode})`);
|
||||
resolve(res.statusCode);
|
||||
} else {
|
||||
reject(new Error(`Failed to publish ${api.name}: HTTP ${res.statusCode} — ${data}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', reject);
|
||||
req.write(body);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
let hasError = false;
|
||||
for (const api of APIS) {
|
||||
try {
|
||||
await uploadSpec(api);
|
||||
} catch (err) {
|
||||
console.error(`✗ ${err.message}`);
|
||||
hasError = true;
|
||||
}
|
||||
}
|
||||
if (hasError) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user