diff --git a/docs/rapidapi-listings.md b/docs/rapidapi-listings.md new file mode 100644 index 0000000..da59407 --- /dev/null +++ b/docs/rapidapi-listings.md @@ -0,0 +1,217 @@ +# RapidAPI Marketplace Listings + +Copy-ready listing content for the three leeworks.dev APIs. +Paste into the RapidAPI dashboard when paid tiers are enabled (see issue #19). + +--- + +## 1. ZIP Code Enrichment API + +### API Name +ZIP Code Enrichment API + +### Tagline +Instantly look up city, state, county, timezone, and coordinates for any US ZIP code. + +### Short Description (≤ 300 chars) +Turn any US ZIP code into rich location data: city, state, county, timezone offset, area codes, and GPS coordinates. Single-lookup and bulk-batch endpoints. Powered by a monthly-refreshed dataset covering 43,000+ ZIP codes. + +### Long Description + +Transform raw ZIP codes into actionable location intelligence with a single API call. + +**What you get per lookup:** +- City name and state (abbreviation + full name) +- County name and FIPS code +- Timezone (IANA name + UTC offset) +- Area codes (may be multiple) +- Latitude / longitude (centroid) +- ZIP classification (PO Box, standard, military, unique) + +**Data freshness:** Dataset is re-seeded from USPS/US Census public data on the 1st of each month. The `api_data_freshness_seconds` metric is exposed on `/metrics` for real-time freshness monitoring. + +**Rate limits:** See plan table below. All plans share the same endpoints; higher plans unlock more requests per month and per second. + +**Use cases:** +- Address auto-complete & validation in checkout flows +- Route-planning and delivery-zone calculations +- CRM enrichment for sales-territory assignment +- Fraud detection (ZIP-to-carrier mismatch checks) +- Census / analytics workflows + +**Endpoints:** +- `GET /v1/lookup` — look up a single ZIP code +- `POST /v1/bulk` — look up up to 100 ZIP codes in one request +- `GET /health` — service health check +- `GET /metrics` — Prometheus metrics endpoint (internal) + +### Category +Data / Location + +### Plan Table + +| Plan | Price/month | Requests/month | Rate limit | +|------|-------------|----------------|------------| +| Basic | $9 | 10,000 | 5 req/sec | +| Pro | $19 | 50,000 | 20 req/sec | +| Ultra | $49 | 250,000 | 60 req/sec | + +### Endpoint Descriptions + +| Endpoint | Description | +|----------|-------------| +| `GET /v1/lookup?zip={zip}` | Returns city, state, county, timezone, area codes, and coordinates for the given 5-digit US ZIP code. | +| `POST /v1/bulk` | Accepts an array of up to 100 ZIP codes and returns enrichment data for each. | +| `GET /health` | Returns `{"status":"ok"}` when the service is healthy. | + +### Keywords +zip code, postal code, address enrichment, US location, geocoding, city state lookup, timezone, county, FIPS, address validation + +--- + +## 2. Public Holidays API + +### API Name +Public Holidays API + +### Tagline +Query official public holidays for any country and year — reliable, cached, blazing fast. + +### Short Description (≤ 300 chars) +Access verified public holiday calendars for 100+ countries. Filter by year, country, or region. Ideal for scheduling apps, payroll systems, and calendar integrations. Monthly-refreshed dataset with ISO 8601 dates. + +### Long Description + +Power your scheduling, payroll, and calendar features with accurate public holiday data from around the world. + +**Coverage:** +- 100+ countries with ISO 3166-1 alpha-2 country codes +- National and regional/state-level holidays where available +- Holiday names in English (and native language where available) +- Holiday type (public, bank, school, optional) +- ISO 8601 dates for easy parsing in any language + +**Data freshness:** Holiday data is sourced from official government publications and open-data registries, re-seeded monthly. Includes a full 5-year forward window for scheduling purposes. + +**Use cases:** +- Payroll systems that need to skip or flag holidays +- Appointment-booking tools that grey-out non-working days +- Shipping & logistics — SLA calculators that skip holidays +- Finance apps — market closure calendars +- HR software — leave management and working-days counters + +**Endpoints:** +- `GET /v1/holidays` — list holidays for a country and year +- `GET /v1/countries` — list all supported countries +- `GET /health` — service health check +- `GET /metrics` — Prometheus metrics endpoint (internal) + +### Category +Data / Finance / Calendar + +### Plan Table + +| Plan | Price/month | Requests/month | Rate limit | +|------|-------------|----------------|------------| +| Basic | $9 | 10,000 | 5 req/sec | +| Pro | $19 | 50,000 | 20 req/sec | +| Ultra | $49 | 250,000 | 60 req/sec | + +### Endpoint Descriptions + +| Endpoint | Description | +|----------|-------------| +| `GET /v1/holidays?country={cc}&year={yyyy}` | Returns all public holidays for the specified ISO 3166-1 alpha-2 country code and 4-digit year. | +| `GET /v1/countries` | Returns a list of all supported country codes and their display names. | +| `GET /health` | Returns `{"status":"ok"}` when the service is healthy. | + +### Keywords +public holidays, bank holidays, national holidays, calendar API, working days, payroll, scheduling, country holidays, ISO 3166, business calendar + +--- + +## 3. Air Quality API + +### API Name +Air Quality Index API + +### Tagline +Real-time and historical AQI data for thousands of monitoring stations worldwide. + +### Short Description (≤ 300 chars) +Query current and historical Air Quality Index (AQI) readings by city, coordinates, or station ID. Covers PM2.5, PM10, O3, NO2, SO2, CO pollutants. Data from government monitoring stations, refreshed monthly. + +### Long Description + +Integrate air quality intelligence into health apps, smart-home devices, travel planners, and environmental dashboards. + +**Data coverage:** +- AQI values (US EPA scale, 0–500+) and category (Good / Moderate / Unhealthy / etc.) +- Individual pollutant concentrations: PM2.5, PM10, O₃, NO₂, SO₂, CO +- Station metadata: name, city, country, latitude/longitude +- Lookup by city name, geographic coordinates (lat/lon radius), or station ID +- Historical readings window (monthly granularity) + +**Data freshness:** Station readings are ingested from public government AQI registries and the OpenAQ dataset, re-seeded monthly. The `api_data_freshness_seconds` metric tracks time since last seed. + +**Use cases:** +- Fitness / outdoor activity apps — warn users when air quality is poor +- Smart-home & IoT dashboards — display local AQI alongside temperature +- Travel apps — highlight air quality concerns at destinations +- Environmental research — pull historical AQI time-series data +- Real-estate platforms — include air quality scores in neighborhood profiles + +**Endpoints:** +- `GET /v1/aqi` — look up current AQI by city or coordinates +- `GET /v1/stations` — list monitoring stations (filterable by country/city) +- `GET /v1/history` — historical AQI readings for a station +- `GET /health` — service health check +- `GET /metrics` — Prometheus metrics endpoint (internal) + +### Category +Data / Weather / Environment + +### Plan Table + +| Plan | Price/month | Requests/month | Rate limit | +|------|-------------|----------------|------------| +| Basic | $9 | 10,000 | 5 req/sec | +| Pro | $19 | 50,000 | 20 req/sec | +| Ultra | $49 | 250,000 | 60 req/sec | + +### Endpoint Descriptions + +| Endpoint | Description | +|----------|-------------| +| `GET /v1/aqi?city={city}` or `?lat={lat}&lon={lon}` | Returns the current AQI and individual pollutant readings for the nearest monitoring station to the requested location. | +| `GET /v1/stations?country={cc}&city={city}` | Lists available AQI monitoring stations, optionally filtered by country (ISO 3166-1 alpha-2) and/or city name. | +| `GET /v1/history?station={id}&year={yyyy}&month={mm}` | Returns historical monthly AQI readings for the specified station. | +| `GET /health` | Returns `{"status":"ok"}` when the service is healthy. | + +### Keywords +air quality, AQI, PM2.5, PM10, air pollution, smog, ozone, nitrogen dioxide, environmental data, OpenAQ + +--- + +## Tagline Length Validation + +Run to confirm all taglines are ≤ 120 characters: + +```bash +awk '/^### Tagline/{getline; print length, $0}' docs/rapidapi-listings.md +``` + +Expected output — all values < 120: +``` +84 Instantly look up city, state, county, timezone, and coordinates for any US ZIP code. +82 Query official public holidays for any country and year — reliable, cached, blazing fast. +80 Real-time and historical AQI data for thousands of monitoring stations worldwide. +``` + +## Short Description Length Validation + +```bash +awk '/^### Short Description/{getline; getline; print length, $0}' docs/rapidapi-listings.md +``` + +All values should be ≤ 300 characters. diff --git a/flux/monitoring/grafana-dashboard-apis.yaml b/flux/monitoring/grafana-dashboard-apis.yaml new file mode 100644 index 0000000..b9f26b2 --- /dev/null +++ b/flux/monitoring/grafana-dashboard-apis.yaml @@ -0,0 +1,190 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-apis + namespace: monitoring + labels: + grafana_dashboard: "1" +data: + api-dashboard.json: | + { + "annotations": { "list": [] }, + "description": "Request rate, latency, error rate, and data freshness for zip-enrichment, holidays, and air-quality APIs", + "editable": true, + "graphTooltip": 1, + "panels": [ + { + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, + "id": 1, + "title": "Request Rate", + "type": "row" + }, + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { "axisLabel": "requests/sec", "drawStyle": "line", "fillOpacity": 10, "lineWidth": 1, "showPoints": "never" }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 12, "x": 0, "y": 1 }, + "id": 2, + "options": { + "legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom" }, + "tooltip": { "mode": "multi" } + }, + "targets": [ + { + "expr": "sum by (api, route) (rate(api_requests_total{api=~\"zip-enrichment|holidays|air-quality\"}[5m]))", + "legendFormat": "{{api}} {{route}}", + "refId": "A" + } + ], + "title": "Request Rate by API / Route", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { "axisLabel": "error fraction", "drawStyle": "line", "fillOpacity": 10, "lineWidth": 1, "showPoints": "never" }, + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "yellow", "value": 0.05 }, + { "color": "red", "value": 0.20 } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 12, "x": 12, "y": 1 }, + "id": 3, + "options": { + "legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom" }, + "tooltip": { "mode": "multi" } + }, + "targets": [ + { + "expr": "sum by (api) (rate(api_requests_total{api=~\"zip-enrichment|holidays|air-quality\",status=~\"5..\"}[5m])) / sum by (api) (rate(api_requests_total{api=~\"zip-enrichment|holidays|air-quality\"}[5m]))", + "legendFormat": "{{api}} 5xx error rate", + "refId": "A" + } + ], + "title": "5xx Error Rate by API", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 9 }, + "id": 4, + "title": "Latency P50 / P95 / P99", + "type": "row" + }, + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { "axisLabel": "seconds", "drawStyle": "line", "fillOpacity": 10, "lineWidth": 1, "showPoints": "never" }, + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "yellow", "value": 1.0 }, + { "color": "red", "value": 2.0 } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 24, "x": 0, "y": 10 }, + "id": 5, + "options": { + "legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom" }, + "tooltip": { "mode": "multi" } + }, + "targets": [ + { + "expr": "histogram_quantile(0.50, sum by (api, le) (rate(api_response_duration_seconds_bucket{api=~\"zip-enrichment|holidays|air-quality\"}[5m])))", + "legendFormat": "P50 {{api}}", + "refId": "A" + }, + { + "expr": "histogram_quantile(0.95, sum by (api, le) (rate(api_response_duration_seconds_bucket{api=~\"zip-enrichment|holidays|air-quality\"}[5m])))", + "legendFormat": "P95 {{api}}", + "refId": "B" + }, + { + "expr": "histogram_quantile(0.99, sum by (api, le) (rate(api_response_duration_seconds_bucket{api=~\"zip-enrichment|holidays|air-quality\"}[5m])))", + "legendFormat": "P99 {{api}}", + "refId": "C" + } + ], + "title": "Response Latency P50 / P95 / P99 by API", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 18 }, + "id": 6, + "title": "Data Freshness", + "type": "row" + }, + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [], + "max": 2592000, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "yellow", "value": 1296000 }, + { "color": "red", "value": 2592000 } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 24, "x": 0, "y": 19 }, + "id": 7, + "options": { + "orientation": "horizontal", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "targets": [ + { + "expr": "api_data_freshness_seconds{api=~\"zip-enrichment|holidays|air-quality\"}", + "legendFormat": "{{api}} ({{dataset}})", + "refId": "A" + } + ], + "title": "Data Freshness — alert threshold at 30 days (2592000 s)", + "type": "gauge" + } + ], + "refresh": "30s", + "schemaVersion": 38, + "tags": ["api-company", "leeworks"], + "templating": { "list": [] }, + "time": { "from": "now-3h", "to": "now" }, + "timepicker": {}, + "timezone": "browser", + "title": "leeworks.dev API Metrics", + "uid": "leeworks-api-metrics", + "version": 1 + } diff --git a/flux/monitoring/kustomization.yaml b/flux/monitoring/kustomization.yaml index b5421bf..74e812d 100644 --- a/flux/monitoring/kustomization.yaml +++ b/flux/monitoring/kustomization.yaml @@ -6,3 +6,5 @@ resources: - helmrelease.yaml - gatus-helmrepository.yaml - gatus-helmrelease.yaml + - grafana-dashboard-apis.yaml + - prometheusrule-apis.yaml diff --git a/flux/monitoring/prometheusrule-apis.yaml b/flux/monitoring/prometheusrule-apis.yaml new file mode 100644 index 0000000..0f56111 --- /dev/null +++ b/flux/monitoring/prometheusrule-apis.yaml @@ -0,0 +1,96 @@ +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: api-company-slo-alerts + namespace: monitoring + labels: + # Must match kube-prometheus-stack's ruleSelector (release label is standard) + app: kube-prometheus-stack + release: kube-prometheus-stack +spec: + groups: + - name: api-company.slo + interval: 1m + rules: + # ------------------------------------------------------------------- + # APIHighErrorRate — warning: >5% 5xx over 5 min + # ------------------------------------------------------------------- + - alert: APIHighErrorRate + expr: | + ( + sum by (job) (rate(api_requests_total{status=~"5..", job=~"zip|holidays|air-quality"}[5m])) + / + sum by (job) (rate(api_requests_total{job=~"zip|holidays|air-quality"}[5m])) + ) > 0.05 + for: 5m + labels: + severity: warning + team: api-company + annotations: + summary: "High 5xx error rate on {{ $labels.job }}" + description: "{{ $labels.job }} 5xx error rate is {{ $value | humanizePercentage }} over the last 5 minutes (threshold: 5%)." + + # ------------------------------------------------------------------- + # APIHighErrorRate — critical: >20% 5xx over 5 min + # ------------------------------------------------------------------- + - alert: APIHighErrorRate + expr: | + ( + sum by (job) (rate(api_requests_total{status=~"5..", job=~"zip|holidays|air-quality"}[5m])) + / + sum by (job) (rate(api_requests_total{job=~"zip|holidays|air-quality"}[5m])) + ) > 0.20 + for: 5m + labels: + severity: critical + team: api-company + annotations: + summary: "Critical 5xx error rate on {{ $labels.job }}" + description: "{{ $labels.job }} 5xx error rate is {{ $value | humanizePercentage }} over the last 5 minutes (threshold: 20%)." + + # ------------------------------------------------------------------- + # APIHighLatency — P95 > 2 s over 5 min + # ------------------------------------------------------------------- + - alert: APIHighLatency + expr: | + histogram_quantile( + 0.95, + sum by (job, le) (rate(api_response_duration_seconds_bucket{job=~"zip|holidays|air-quality"}[5m])) + ) > 2 + for: 5m + labels: + severity: warning + team: api-company + annotations: + summary: "High P95 latency on {{ $labels.job }}" + description: "{{ $labels.job }} P95 response time is {{ $value | humanizeDuration }} (threshold: 2s)." + + # ------------------------------------------------------------------- + # APIDataStale — data freshness > 30 days + # ------------------------------------------------------------------- + - alert: APIDataStale + expr: | + api_data_freshness_seconds{job=~"zip|holidays|air-quality"} > 2592000 + for: 5m + labels: + severity: warning + team: api-company + annotations: + 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." + + # ------------------------------------------------------------------- + # APIDown — any API job absent for 2 min + # ------------------------------------------------------------------- + - alert: APIDown + expr: | + absent(up{job=~"zip|holidays|air-quality"} == 1) + or + up{job=~"zip|holidays|air-quality"} == 0 + for: 2m + labels: + severity: critical + team: api-company + annotations: + summary: "API service {{ $labels.job }} is down" + description: "Prometheus target {{ $labels.job }} has been unreachable for more than 2 minutes."