feat: implement docs-site, legal docs, metrics standard, flux manifests
Closes leeworks-agents/api-company#5 (docs-site Astro scaffold) Closes leeworks-agents/api-company#9 (metrics instrumentation standard) Closes leeworks-agents/api-company#10 (Gitea Actions openapi aggregation pipeline) Closes leeworks-agents/api-company#11 (docs-site Flux HelmRelease) Closes leeworks-agents/api-company#12 (SEO blog posts x3) Closes leeworks-agents/api-company#13 (legal docs ToS/Privacy/AUP) Closes leeworks-agents/api-company#14 (DNS documentation) ## Changes ### docs/legal/ - terms-of-service.md — API usage, liability, account termination, governing law - privacy-policy.md — request log retention (90d), no PII sold, data sharing - acceptable-use-policy.md — rate limit abuse, scraping prohibition, resale ban ### docs/metrics-standard.md - Defines api_requests_total, api_response_duration_seconds, api_data_freshness_seconds - Fastify (TypeScript) and FastAPI (Python) reference middleware implementations - Prometheus scrape config and Grafana dashboard guidance ### docs/registry.md - Decision: use Gitea built-in container registry (no new infra) - Image naming convention, auth, Kubernetes imagePullSecrets, ingress config ### docs/dns.md - Required A records for all 6 subdomains - cert-manager ClusterIssuer and Ingress TLS examples - Verification commands and human-operator action items ### docs-site/ - Astro 4 + MDX + sitemap scaffold - Base layout with nav linking all APIs, blog, RapidAPI, status - Landing page with API cards - Per-API Redoc viewer pages (zip-enrichment, holidays, air-quality) - Blog index + 3 SEO blog posts (~1000 words each with JSON-LD) - Dockerfile (multi-stage: node build + nginx serve) - nginx.conf with gzip, caching, health endpoint ### flux/ - gitea-runner/: gitea-act-runner HelmRelease (org-scope, dind) - monitoring/: kube-prometheus-stack + Gatus HelmReleases - Prometheus with pod annotation scraping - Grafana at grafana.leeworks.dev with persistence - Gatus status page at status.leeworks.dev, 90-day retention - docs-site/: Deployment + Service + Ingress via raw chart - api-company-source/: GitRepository + Kustomization reference manifests - kustomization.yaml: root kustomize entry point (build validated) ### .gitea/workflows/build-docs.yaml - Aggregates openapi.yaml from zip-enrichment, holidays, air-quality repos - Builds Astro docs-site - Pushes image to registry.leeworks.dev/leeworks-agents/docs-site - Triggered on push to main, schedule daily 02:00 UTC, workflow_dispatch
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
---
|
||||
export interface Props {
|
||||
title: string;
|
||||
description?: string;
|
||||
}
|
||||
const { title, description = "leeworks.dev API documentation" } = Astro.props;
|
||||
---
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="description" content={description} />
|
||||
<title>{title} | leeworks.dev APIs</title>
|
||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: system-ui, -apple-system, sans-serif; background: #0f1117; color: #e2e8f0; }
|
||||
nav { background: #1a1d27; border-bottom: 1px solid #2d3748; padding: 0 2rem; display: flex; align-items: center; gap: 2rem; height: 60px; }
|
||||
nav a { color: #90cdf4; text-decoration: none; font-weight: 500; }
|
||||
nav a:hover { color: #fff; }
|
||||
nav .brand { font-size: 1.25rem; font-weight: 700; color: #fff; }
|
||||
main { min-height: calc(100vh - 120px); }
|
||||
footer { background: #1a1d27; border-top: 1px solid #2d3748; padding: 1.5rem 2rem; text-align: center; color: #718096; font-size: 0.875rem; }
|
||||
footer a { color: #90cdf4; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<a href="/" class="brand">leeworks.dev</a>
|
||||
<a href="/zip-enrichment">ZIP Enrichment</a>
|
||||
<a href="/holidays">Holidays</a>
|
||||
<a href="/air-quality">Air Quality</a>
|
||||
<a href="/blog">Blog</a>
|
||||
<a href="https://rapidapi.com/leeworks" target="_blank" rel="noopener">RapidAPI</a>
|
||||
</nav>
|
||||
<main>
|
||||
<slot />
|
||||
</main>
|
||||
<footer>
|
||||
<p>
|
||||
© 2026 leeworks.dev —
|
||||
<a href="/legal/terms-of-service">Terms</a> ·
|
||||
<a href="/legal/privacy-policy">Privacy</a> ·
|
||||
<a href="/legal/acceptable-use-policy">AUP</a> ·
|
||||
<a href="https://status.leeworks.dev" target="_blank" rel="noopener">Status</a>
|
||||
</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
import Base from '../layouts/Base.astro';
|
||||
|
||||
const apiName = 'air-quality';
|
||||
const titles: Record<string, string> = {
|
||||
'zip-enrichment': 'ZIP Enrichment API',
|
||||
'holidays': 'Holidays API',
|
||||
'air-quality': 'Air Quality API',
|
||||
};
|
||||
const title = titles[apiName];
|
||||
---
|
||||
<Base title={title} description={`${title} — OpenAPI documentation`}>
|
||||
<style>
|
||||
#redoc-container { background: #fff; }
|
||||
</style>
|
||||
<div id="redoc-container"></div>
|
||||
<script is:inline define:vars={{ specUrl: `/specs/${apiName}.yaml` }}>
|
||||
// Load Redoc from CDN
|
||||
var script = document.createElement('script');
|
||||
script.src = 'https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js';
|
||||
script.onload = function () {
|
||||
Redoc.init(specUrl, {
|
||||
theme: {
|
||||
colors: { primary: { main: '#667eea' } },
|
||||
typography: { fontFamily: 'system-ui, sans-serif' },
|
||||
},
|
||||
}, document.getElementById('redoc-container'));
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
</script>
|
||||
</Base>
|
||||
@@ -0,0 +1,178 @@
|
||||
---
|
||||
title: "Air Quality API: Real-Time AQI Data for Any Location"
|
||||
description: "Access real-time Air Quality Index (AQI) data, PM2.5, PM10, and health recommendations for any city worldwide using the leeworks.dev Air Quality API."
|
||||
date: "2026-05-24"
|
||||
author: "leeworks.dev"
|
||||
tags: ["air-quality", "aqi", "api", "tutorial"]
|
||||
---
|
||||
|
||||
import Base from '../../layouts/Base.astro';
|
||||
|
||||
<Base title="Air Quality API Guide" description="Access real-time AQI data for any location worldwide.">
|
||||
|
||||
<article style="max-width: 800px; margin: 0 auto; padding: 2rem; line-height: 1.75;">
|
||||
|
||||
<script type="application/ld+json" set:html={JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
"headline": "Air Quality API: Real-Time AQI Data for Any Location",
|
||||
"datePublished": "2026-05-24",
|
||||
"author": { "@type": "Organization", "name": "leeworks.dev" },
|
||||
"publisher": { "@type": "Organization", "name": "leeworks.dev", "url": "https://docs.leeworks.dev" }
|
||||
})} />
|
||||
|
||||
# Air Quality API: Real-Time AQI Data for Any Location
|
||||
|
||||
Whether you're building a fitness app, a travel planner, or a smart home dashboard, air quality data is increasingly essential. The leeworks.dev **Air Quality API** gives you real-time AQI readings, pollutant breakdowns, and health recommendations for any location in the world.
|
||||
|
||||
## What Is AQI and Why Does Your App Need It?
|
||||
|
||||
The **Air Quality Index (AQI)** is a standardized scale (0–500) that communicates how clean or polluted the air is:
|
||||
|
||||
| AQI | Category | Health Implication |
|
||||
|-----|----------|-------------------|
|
||||
| 0–50 | Good | Air quality is satisfactory |
|
||||
| 51–100 | Moderate | Acceptable for most people |
|
||||
| 101–150 | Unhealthy for Sensitive Groups | At-risk groups may experience effects |
|
||||
| 151–200 | Unhealthy | Everyone may begin to experience health effects |
|
||||
| 201–300 | Very Unhealthy | Health alert: serious effects possible |
|
||||
| 301–500 | Hazardous | Emergency conditions |
|
||||
|
||||
**Use cases for an AQI data API:**
|
||||
|
||||
- **Fitness apps** — warn runners when outdoor exercise is unsafe
|
||||
- **Travel apps** — show air quality forecasts for destination cities
|
||||
- **Real estate platforms** — display neighborhood air quality scores
|
||||
- **Smart home apps** — trigger air purifiers based on outdoor AQI
|
||||
- **Health tracking apps** — correlate symptoms with air quality data
|
||||
- **News and weather apps** — add AQI to daily weather cards
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Get current AQI for a city
|
||||
curl "https://aqi.leeworks.dev/v1/current?city=Los+Angeles&country=US" \
|
||||
-H "X-RapidAPI-Key: YOUR_API_KEY"
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"location": {
|
||||
"city": "Los Angeles",
|
||||
"country": "US",
|
||||
"latitude": 34.0522,
|
||||
"longitude": -118.2437
|
||||
},
|
||||
"aqi": 87,
|
||||
"category": "Moderate",
|
||||
"pollutants": {
|
||||
"pm25": 22.4,
|
||||
"pm10": 35.1,
|
||||
"o3": 41.2,
|
||||
"no2": 18.5,
|
||||
"so2": 2.1,
|
||||
"co": 0.4
|
||||
},
|
||||
"health_recommendation": "Unusually sensitive people should consider reducing prolonged outdoor exertion.",
|
||||
"updated_at": "2026-05-24T14:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## By Coordinates (Lat/Long)
|
||||
|
||||
```bash
|
||||
curl "https://aqi.leeworks.dev/v1/current?lat=48.8566&lon=2.3522" \
|
||||
-H "X-RapidAPI-Key: YOUR_API_KEY"
|
||||
```
|
||||
|
||||
## Code Examples
|
||||
|
||||
### JavaScript
|
||||
|
||||
```javascript
|
||||
async function getAirQuality(city, country = 'US') {
|
||||
const response = await fetch(
|
||||
`https://aqi.leeworks.dev/v1/current?city=${encodeURIComponent(city)}&country=${country}`,
|
||||
{ headers: { 'X-RapidAPI-Key': process.env.RAPIDAPI_KEY } }
|
||||
);
|
||||
return response.json();
|
||||
}
|
||||
|
||||
const data = await getAirQuality('Denver');
|
||||
if (data.aqi > 100) {
|
||||
console.warn(`Air quality in ${data.location.city} is ${data.category}. Consider staying indoors.`);
|
||||
}
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
import httpx
|
||||
|
||||
def get_aqi(lat: float, lon: float) -> dict:
|
||||
resp = httpx.get(
|
||||
"https://aqi.leeworks.dev/v1/current",
|
||||
params={"lat": lat, "lon": lon},
|
||||
headers={"X-RapidAPI-Key": "YOUR_KEY"},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
|
||||
# Example: check AQI before recommending outdoor run
|
||||
aqi_data = get_aqi(37.7749, -122.4194) # San Francisco
|
||||
if aqi_data["aqi"] <= 100:
|
||||
print("Good to go for a run!")
|
||||
else:
|
||||
print(f"Air quality is {aqi_data['category']} — consider indoor exercise.")
|
||||
```
|
||||
|
||||
### React Hook
|
||||
|
||||
```tsx
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
interface AQIData {
|
||||
aqi: number;
|
||||
category: string;
|
||||
health_recommendation: string;
|
||||
}
|
||||
|
||||
export function useAirQuality(city: string) {
|
||||
const [data, setData] = useState<AQIData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`/api/aqi?city=${encodeURIComponent(city)}`)
|
||||
.then(r => r.json())
|
||||
.then(setData)
|
||||
.finally(() => setLoading(false));
|
||||
}, [city]);
|
||||
|
||||
return { data, loading };
|
||||
}
|
||||
```
|
||||
|
||||
## Data Source
|
||||
|
||||
The leeworks.dev Air Quality API aggregates data from the **OpenAQ** public dataset — a non-profit platform that collects open air quality data from government agencies worldwide. Data is refreshed hourly.
|
||||
|
||||
## Pricing
|
||||
|
||||
| Plan | Requests/mo | Price |
|
||||
|------|------------|-------|
|
||||
| Free | 500 | $0 |
|
||||
| Basic | 10,000 | $9/mo |
|
||||
| Pro | 100,000 | $19/mo |
|
||||
| Ultra | 1,000,000 | $49/mo |
|
||||
|
||||
**[Subscribe on RapidAPI →](https://rapidapi.com/leeworks/api/air-quality)**
|
||||
|
||||
## Conclusion
|
||||
|
||||
Air quality is no longer a niche data point — it's a critical health metric that millions of people check daily. The leeworks.dev **AQI data API** gives your app real-time air quality readings, pollutant breakdowns, and actionable health recommendations with a simple REST interface.
|
||||
|
||||
**[Start building for free →](https://rapidapi.com/leeworks/api/air-quality)**
|
||||
|
||||
</article>
|
||||
</Base>
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
import Base from '../../layouts/Base.astro';
|
||||
|
||||
const posts = await Astro.glob('./*.mdx');
|
||||
posts.sort((a, b) => new Date(b.frontmatter.date).getTime() - new Date(a.frontmatter.date).getTime());
|
||||
---
|
||||
<Base title="Blog" description="leeworks.dev developer blog">
|
||||
<style>
|
||||
.blog-hero { padding: 3rem 2rem 1rem; text-align: center; }
|
||||
.blog-hero h1 { font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5rem; }
|
||||
.blog-hero p { color: #a0aec0; }
|
||||
.posts { max-width: 800px; margin: 2rem auto; padding: 0 2rem; }
|
||||
.post-card { border-bottom: 1px solid #2d3748; padding: 2rem 0; }
|
||||
.post-card h2 a { color: #90cdf4; text-decoration: none; font-size: 1.5rem; }
|
||||
.meta { color: #718096; font-size: 0.875rem; margin: 0.5rem 0; }
|
||||
.description { color: #a0aec0; }
|
||||
</style>
|
||||
<div class="blog-hero"><h1>Blog</h1><p>Tutorials and news from leeworks.dev</p></div>
|
||||
<div class="posts">
|
||||
{posts.map(post => (
|
||||
<div class="post-card">
|
||||
<h2><a href={post.url}>{post.frontmatter.title}</a></h2>
|
||||
<div class="meta">{post.frontmatter.date}</div>
|
||||
<p class="description">{post.frontmatter.description}</p>
|
||||
</div>
|
||||
))}
|
||||
{posts.length === 0 && <p style="color: #718096">No posts yet.</p>}
|
||||
</div>
|
||||
</Base>
|
||||
@@ -0,0 +1,161 @@
|
||||
---
|
||||
title: "Public Holidays API: The Free Holiday Calendar API for Any Country"
|
||||
description: "Get public holidays for 100+ countries with a single API call. The leeworks.dev Holidays API is perfect for scheduling, calendar apps, and payroll systems."
|
||||
date: "2026-05-24"
|
||||
author: "leeworks.dev"
|
||||
tags: ["holidays", "api", "tutorial"]
|
||||
---
|
||||
|
||||
import Base from '../../layouts/Base.astro';
|
||||
|
||||
<Base title="Public Holidays API Guide" description="Get public holidays for 100+ countries with a single API call.">
|
||||
|
||||
<article style="max-width: 800px; margin: 0 auto; padding: 2rem; line-height: 1.75;">
|
||||
|
||||
<script type="application/ld+json" set:html={JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
"headline": "Public Holidays API: The Free Holiday Calendar API for Any Country",
|
||||
"datePublished": "2026-05-24",
|
||||
"author": { "@type": "Organization", "name": "leeworks.dev" },
|
||||
"publisher": { "@type": "Organization", "name": "leeworks.dev", "url": "https://docs.leeworks.dev" }
|
||||
})} />
|
||||
|
||||
# Public Holidays API: The Free Holiday Calendar API for Any Country
|
||||
|
||||
Building a scheduling app, payroll system, or booking platform? You need accurate **public holiday data** for every country you serve. The leeworks.dev **Holidays API** gives you that data in milliseconds.
|
||||
|
||||
## Why You Need a Holiday Calendar API
|
||||
|
||||
Manually maintaining a list of public holidays is a losing battle. Holidays change year to year, differ by country and region, and missing one can mean:
|
||||
|
||||
- **Wrong delivery estimates** on e-commerce sites
|
||||
- **Incorrect payroll calculations** (overtime on holidays)
|
||||
- **Broken calendar apps** that schedule meetings on national holidays
|
||||
- **Failed SLA commitments** that assumed business days
|
||||
|
||||
A reliable **holiday API** solves this once.
|
||||
|
||||
## What the leeworks.dev Holidays API Provides
|
||||
|
||||
- Public holidays for **100+ countries**
|
||||
- Data updated from Nager.Date's curated public dataset
|
||||
- Filter by **country code** (ISO 3166-1 alpha-2), **year**, and **type**
|
||||
- Response includes holiday name (localized), date, and type (`public`, `optional`, `observance`)
|
||||
- Sub-100ms response time, SQLite-backed
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Get all US public holidays for 2026
|
||||
curl "https://holidays.leeworks.dev/v1/holidays?country=US&year=2026" \
|
||||
-H "X-RapidAPI-Key: YOUR_API_KEY"
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"country": "US",
|
||||
"year": 2026,
|
||||
"holidays": [
|
||||
{
|
||||
"date": "2026-01-01",
|
||||
"name": "New Year's Day",
|
||||
"type": "public"
|
||||
},
|
||||
{
|
||||
"date": "2026-07-04",
|
||||
"name": "Independence Day",
|
||||
"type": "public"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### 1. Skip Holidays in Business Day Calculations
|
||||
|
||||
```python
|
||||
from datetime import date, timedelta
|
||||
import httpx
|
||||
|
||||
def next_business_day(start: date, country: str = "US") -> date:
|
||||
resp = httpx.get(
|
||||
"https://holidays.leeworks.dev/v1/holidays",
|
||||
params={"country": country, "year": start.year},
|
||||
headers={"X-RapidAPI-Key": "YOUR_KEY"},
|
||||
)
|
||||
holidays = {h["date"] for h in resp.json()["holidays"]}
|
||||
|
||||
current = start + timedelta(days=1)
|
||||
while current.weekday() >= 5 or current.isoformat() in holidays:
|
||||
current += timedelta(days=1)
|
||||
return current
|
||||
```
|
||||
|
||||
### 2. Display Holiday Badges in a Calendar
|
||||
|
||||
```javascript
|
||||
async function getHolidayMap(countryCode, year) {
|
||||
const res = await fetch(
|
||||
`https://holidays.leeworks.dev/v1/holidays?country=${countryCode}&year=${year}`,
|
||||
{ headers: { 'X-RapidAPI-Key': process.env.RAPIDAPI_KEY } }
|
||||
);
|
||||
const { holidays } = await res.json();
|
||||
// Return a Map of ISO date string → holiday name
|
||||
return new Map(holidays.map(h => [h.date, h.name]));
|
||||
}
|
||||
|
||||
// Usage in a calendar component
|
||||
const holidayMap = await getHolidayMap('GB', 2026);
|
||||
const isHoliday = holidayMap.has('2026-12-25'); // true: Christmas Day
|
||||
```
|
||||
|
||||
### 3. Check If Today Is a Holiday
|
||||
|
||||
```typescript
|
||||
async function isTodayHoliday(country = 'US'): Promise<string | null> {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const year = new Date().getFullYear();
|
||||
|
||||
const res = await fetch(
|
||||
`https://holidays.leeworks.dev/v1/is-holiday?country=${country}&date=${today}`,
|
||||
{ headers: { 'X-RapidAPI-Key': process.env.RAPIDAPI_KEY! } }
|
||||
);
|
||||
const data = await res.json();
|
||||
return data.isHoliday ? data.name : null;
|
||||
}
|
||||
```
|
||||
|
||||
## Supported Countries (Sample)
|
||||
|
||||
| Code | Country | Code | Country |
|
||||
|------|---------|------|---------|
|
||||
| US | United States | GB | United Kingdom |
|
||||
| CA | Canada | DE | Germany |
|
||||
| FR | France | JP | Japan |
|
||||
| AU | Australia | BR | Brazil |
|
||||
| IN | India | MX | Mexico |
|
||||
|
||||
...and 90+ more. Use `GET /v1/countries` to see the full list.
|
||||
|
||||
## Pricing
|
||||
|
||||
| Plan | Requests/mo | Price |
|
||||
|------|------------|-------|
|
||||
| Free | 500 | $0 |
|
||||
| Basic | 10,000 | $9/mo |
|
||||
| Pro | 100,000 | $19/mo |
|
||||
| Ultra | 1,000,000 | $49/mo |
|
||||
|
||||
**[Subscribe on RapidAPI →](https://rapidapi.com/leeworks/api/holidays)**
|
||||
|
||||
## Conclusion
|
||||
|
||||
Stop hardcoding holiday lists or scraping Wikipedia. The leeworks.dev **public holidays API** gives you accurate, up-to-date holiday data for every country you need — with a simple REST interface and affordable pricing.
|
||||
|
||||
**[Get started for free →](https://rapidapi.com/leeworks/api/holidays)**
|
||||
|
||||
</article>
|
||||
</Base>
|
||||
@@ -0,0 +1,158 @@
|
||||
---
|
||||
title: "ZIP Code Enrichment API: Add Location Intelligence to Your App in Minutes"
|
||||
description: "Learn how to use the leeworks.dev ZIP Code Enrichment API to add city, state, timezone, and demographic data to any postal code lookup."
|
||||
date: "2026-05-24"
|
||||
author: "leeworks.dev"
|
||||
tags: ["zip-enrichment", "api", "tutorial"]
|
||||
---
|
||||
|
||||
import Base from '../../layouts/Base.astro';
|
||||
|
||||
<Base title="ZIP Code Enrichment API Guide" description="Learn how to use the leeworks.dev ZIP Code Enrichment API to add city, state, timezone, and demographic data to any postal code lookup.">
|
||||
|
||||
<article style="max-width: 800px; margin: 0 auto; padding: 2rem; line-height: 1.75;">
|
||||
|
||||
<script type="application/ld+json" set:html={JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
"headline": "ZIP Code Enrichment API: Add Location Intelligence to Your App in Minutes",
|
||||
"datePublished": "2026-05-24",
|
||||
"author": { "@type": "Organization", "name": "leeworks.dev" },
|
||||
"publisher": { "@type": "Organization", "name": "leeworks.dev", "url": "https://docs.leeworks.dev" }
|
||||
})} />
|
||||
|
||||
# ZIP Code Enrichment API: Add Location Intelligence to Your App in Minutes
|
||||
|
||||
Every time a user types their ZIP code, there's a wealth of data waiting to be unlocked — city name, state, county, timezone, latitude, longitude, and more. The **leeworks.dev ZIP Code Enrichment API** makes it trivially easy to retrieve all of that in a single API call.
|
||||
|
||||
## What Is a ZIP Code Enrichment API?
|
||||
|
||||
A **postal code demographics API** (or ZIP enrichment API) takes a 5-digit US ZIP code as input and returns structured data about that location. This is useful for:
|
||||
|
||||
- **E-commerce** — display the user's city/state after they type a ZIP, skip the state dropdown
|
||||
- **Shipping calculators** — determine timezone and region for delivery estimates
|
||||
- **Analytics dashboards** — group customers by region, state, or county
|
||||
- **Lead scoring** — enrich CRM contacts with location data automatically
|
||||
- **Form UX** — auto-fill city/state fields for a smoother checkout experience
|
||||
|
||||
## Why Build on leeworks.dev?
|
||||
|
||||
Unlike scraping Google Maps or paying for expensive enterprise solutions, the leeworks.dev ZIP Enrichment API:
|
||||
|
||||
- Returns **sub-50ms responses** (SQLite-backed, no external dependencies)
|
||||
- Provides **100% US ZIP code coverage** using the free USPS/Census dataset
|
||||
- Is available on **RapidAPI** with a generous free tier
|
||||
- Has a **simple, well-documented REST API** following the OpenAPI 3.1 standard
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Get Your API Key
|
||||
|
||||
Sign up on [RapidAPI](https://rapidapi.com/leeworks/api/zip-enrichment) and subscribe to a plan. The Free tier gives you 500 requests/month.
|
||||
|
||||
### 2. Make Your First Call
|
||||
|
||||
```bash
|
||||
curl -X GET "https://zip.leeworks.dev/v1/lookup?zip=90210" \
|
||||
-H "X-RapidAPI-Key: YOUR_API_KEY" \
|
||||
-H "X-RapidAPI-Host: zip.leeworks.dev"
|
||||
```
|
||||
|
||||
### 3. Parse the Response
|
||||
|
||||
```json
|
||||
{
|
||||
"zip": "90210",
|
||||
"city": "Beverly Hills",
|
||||
"state": "CA",
|
||||
"state_full": "California",
|
||||
"county": "Los Angeles",
|
||||
"timezone": "America/Los_Angeles",
|
||||
"latitude": 34.0901,
|
||||
"longitude": -118.4065,
|
||||
"population": 20124
|
||||
}
|
||||
```
|
||||
|
||||
## Code Examples
|
||||
|
||||
### JavaScript / Node.js
|
||||
|
||||
```javascript
|
||||
const response = await fetch('https://zip.leeworks.dev/v1/lookup?zip=10001', {
|
||||
headers: {
|
||||
'X-RapidAPI-Key': process.env.RAPIDAPI_KEY,
|
||||
'X-RapidAPI-Host': 'zip.leeworks.dev',
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
console.log(`${data.city}, ${data.state} (${data.timezone})`);
|
||||
// → "New York, NY (America/New_York)"
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
import httpx
|
||||
|
||||
resp = httpx.get(
|
||||
"https://zip.leeworks.dev/v1/lookup",
|
||||
params={"zip": "60601"},
|
||||
headers={
|
||||
"X-RapidAPI-Key": "YOUR_API_KEY",
|
||||
"X-RapidAPI-Host": "zip.leeworks.dev",
|
||||
},
|
||||
)
|
||||
data = resp.json()
|
||||
print(f"{data['city']}, {data['state']}")
|
||||
# → "Chicago, IL"
|
||||
```
|
||||
|
||||
## Pricing
|
||||
|
||||
| Plan | Requests/mo | Price | Best for |
|
||||
|------|------------|-------|---------|
|
||||
| Free | 500 | $0 | Prototyping |
|
||||
| Basic | 10,000 | $9/mo | Small apps |
|
||||
| Pro | 100,000 | $19/mo | Growing products |
|
||||
| Ultra | 1,000,000 | $49/mo | High volume |
|
||||
|
||||
**[Subscribe on RapidAPI →](https://rapidapi.com/leeworks/api/zip-enrichment)**
|
||||
|
||||
## Use Case: Auto-fill City/State on Checkout
|
||||
|
||||
Here's a complete React component that auto-fills city and state when a user enters their ZIP:
|
||||
|
||||
```tsx
|
||||
import { useState } from 'react';
|
||||
|
||||
export function ZipField() {
|
||||
const [zip, setZip] = useState('');
|
||||
const [location, setLocation] = useState<{ city: string; state: string } | null>(null);
|
||||
|
||||
const handleZipChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value.replace(/\D/g, '').slice(0, 5);
|
||||
setZip(value);
|
||||
if (value.length === 5) {
|
||||
const res = await fetch(`/api/zip-lookup?zip=${value}`);
|
||||
if (res.ok) setLocation(await res.json());
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input value={zip} onChange={handleZipChange} placeholder="ZIP Code" maxLength={5} />
|
||||
{location && <p>📍 {location.city}, {location.state}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
The leeworks.dev **ZIP code enrichment API** is the fastest way to add location intelligence to any application. With a simple GET request, you get city, state, county, timezone, and coordinates — no geocoding, no rate-limit headaches.
|
||||
|
||||
**[Get started for free →](https://rapidapi.com/leeworks/api/zip-enrichment)**
|
||||
|
||||
</article>
|
||||
</Base>
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
import Base from '../layouts/Base.astro';
|
||||
|
||||
const apiName = 'holidays';
|
||||
const titles: Record<string, string> = {
|
||||
'zip-enrichment': 'ZIP Enrichment API',
|
||||
'holidays': 'Holidays API',
|
||||
'air-quality': 'Air Quality API',
|
||||
};
|
||||
const title = titles[apiName];
|
||||
---
|
||||
<Base title={title} description={`${title} — OpenAPI documentation`}>
|
||||
<style>
|
||||
#redoc-container { background: #fff; }
|
||||
</style>
|
||||
<div id="redoc-container"></div>
|
||||
<script is:inline define:vars={{ specUrl: `/specs/${apiName}.yaml` }}>
|
||||
// Load Redoc from CDN
|
||||
var script = document.createElement('script');
|
||||
script.src = 'https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js';
|
||||
script.onload = function () {
|
||||
Redoc.init(specUrl, {
|
||||
theme: {
|
||||
colors: { primary: { main: '#667eea' } },
|
||||
typography: { fontFamily: 'system-ui, sans-serif' },
|
||||
},
|
||||
}, document.getElementById('redoc-container'));
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
</script>
|
||||
</Base>
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
import Base from '../layouts/Base.astro';
|
||||
---
|
||||
<Base title="Home" description="leeworks.dev — production-ready data APIs: ZIP Enrichment, Holidays, Air Quality">
|
||||
<style>
|
||||
.hero { padding: 5rem 2rem 3rem; text-align: center; }
|
||||
.hero h1 { font-size: 3rem; font-weight: 800; background: linear-gradient(135deg, #90cdf4, #667eea); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 1rem; }
|
||||
.hero p { font-size: 1.25rem; color: #a0aec0; max-width: 600px; margin: 0 auto 2rem; }
|
||||
.cta { display: inline-block; background: #667eea; color: #fff; padding: 0.75rem 2rem; border-radius: 8px; text-decoration: none; font-weight: 600; }
|
||||
.apis { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; padding: 2rem; max-width: 1100px; margin: 0 auto; }
|
||||
.api-card { background: #1a1d27; border: 1px solid #2d3748; border-radius: 12px; padding: 1.5rem; }
|
||||
.api-card h2 { color: #90cdf4; margin-bottom: 0.5rem; }
|
||||
.api-card p { color: #a0aec0; margin-bottom: 1rem; font-size: 0.95rem; }
|
||||
.badge { display: inline-block; font-size: 0.75rem; padding: 0.2rem 0.6rem; border-radius: 4px; margin-bottom: 0.75rem; }
|
||||
.badge.wip { background: #744210; color: #fbd38d; }
|
||||
.badge.live { background: #1a4731; color: #9ae6b4; }
|
||||
.links a { color: #90cdf4; text-decoration: none; margin-right: 1rem; }
|
||||
.links a:hover { text-decoration: underline; }
|
||||
</style>
|
||||
|
||||
<div class="hero">
|
||||
<h1>Simple. Reliable. APIs.</h1>
|
||||
<p>Production-ready data APIs for ZIP enrichment, public holidays, and air quality. Available on RapidAPI.</p>
|
||||
<a href="https://rapidapi.com/leeworks" class="cta" target="_blank" rel="noopener">Get API Key on RapidAPI</a>
|
||||
</div>
|
||||
|
||||
<div class="apis">
|
||||
<div class="api-card">
|
||||
<span class="badge wip">In Development</span>
|
||||
<h2>ZIP Enrichment API</h2>
|
||||
<p>Enrich US ZIP codes with city, state, county, timezone, lat/long, and population data.</p>
|
||||
<div class="links">
|
||||
<a href="/zip-enrichment">Docs</a>
|
||||
<a href="https://rapidapi.com/leeworks/api/zip-enrichment" target="_blank" rel="noopener">RapidAPI</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="api-card">
|
||||
<span class="badge wip">In Development</span>
|
||||
<h2>Holidays API</h2>
|
||||
<p>Public holidays for 100+ countries, filterable by country, year, and type.</p>
|
||||
<div class="links">
|
||||
<a href="/holidays">Docs</a>
|
||||
<a href="https://rapidapi.com/leeworks/api/holidays" target="_blank" rel="noopener">RapidAPI</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="api-card">
|
||||
<span class="badge wip">In Development</span>
|
||||
<h2>Air Quality API</h2>
|
||||
<p>Real-time and historical AQI data worldwide including PM2.5, PM10, and health recommendations.</p>
|
||||
<div class="links">
|
||||
<a href="/air-quality">Docs</a>
|
||||
<a href="https://rapidapi.com/leeworks/api/air-quality" target="_blank" rel="noopener">RapidAPI</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Base>
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
import Base from '../layouts/Base.astro';
|
||||
|
||||
const apiName = 'zip-enrichment';
|
||||
const titles: Record<string, string> = {
|
||||
'zip-enrichment': 'ZIP Enrichment API',
|
||||
'holidays': 'Holidays API',
|
||||
'air-quality': 'Air Quality API',
|
||||
};
|
||||
const title = titles[apiName];
|
||||
---
|
||||
<Base title={title} description={`${title} — OpenAPI documentation`}>
|
||||
<style>
|
||||
#redoc-container { background: #fff; }
|
||||
</style>
|
||||
<div id="redoc-container"></div>
|
||||
<script is:inline define:vars={{ specUrl: `/specs/${apiName}.yaml` }}>
|
||||
// Load Redoc from CDN
|
||||
var script = document.createElement('script');
|
||||
script.src = 'https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js';
|
||||
script.onload = function () {
|
||||
Redoc.init(specUrl, {
|
||||
theme: {
|
||||
colors: { primary: { main: '#667eea' } },
|
||||
typography: { fontFamily: 'system-ui, sans-serif' },
|
||||
},
|
||||
}, document.getElementById('redoc-container'));
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
</script>
|
||||
</Base>
|
||||
Reference in New Issue
Block a user