Last updated 2026-07-15
API reference
Overview
This page is for developers and operators integrating with meridian APIs — everyday visitors can skip it. All API routes are Next.js App Router handlers under src/app/api/. Weather and geocode require OPENWEATHER_API_KEY. Cron routes require Authorization: Bearer CRON_SECRET. Admin routes require an authenticated admin session cookie (meridian_admin_session) after login at /login, unless ALLOW_DEV_ADMIN_BYPASS applies in development.
GET /api/weather
Query: lat, lon, scope (current|hourly|daily|minutely), optional trigger, lang. Returns weather payload plus fetchedAt, cacheHit, freshness, source, trigger, tokensUsed. X-Cache header reflects cache layer. Errors: 400 invalid params, 404 location not found, 429 rate_limited, 502 upstream_error or service_unavailable.
POST /api/weather/batch
Body: { cities: [{ lat, lon, scopes?: string[], id?, lang?, maxAgeMs?, trigger? }], trigger?, lang? }. Returns { cities: [{ lat, lon, scopes: { scope: { data, meta } | { error } } }] }. Scopes are per-city, not a top-level array. Rate-limited to 20 requests/minute per IP. Used by dashboard and city detail hooks.
GET /api/weather/history
Query: lat, lon, optional from, to (ISO dates), limit. Returns { summary, observations, forecasts: { hourly, daily } } from weather_observations and weather_forecast_archive tables.
GET /api/geocode
Query: q (min 2 chars), optional context params. Returns normalised array: name, country, state, lat, lon, label. Upstream limit 5 results. Cached in L2 with geocode scope. Rate-limited to 60 requests/minute per IP.
GET /api/recent-checks
No params. Returns { checks, source } where source is popular when search-triggered rows exist, or empty when none. Default limit 20 from location_weather_checks ranked by search volume (search_select and search_preview triggers). API has no showcase fallback — the home UI may still show demo popular cities when empty if SHOW_DEMO_POPULAR_SEARCHES is on. Near you column does not use this route.
/api/subscriptions
GET ?clientId= — list active subscriptions for client. POST — create { clientId, email, type, cityName?, cityLat?, cityLon?, frequency?, alertOnRain?, alertOnStorm?, alertPrefs? }. PATCH — update alertPrefs on a city_alerts row { clientId, id, alertPrefs }. DELETE — body { clientId, cityLat, cityLon, types[] }. Types: newsletter, city_weekly, city_alerts.
GET /api/unsubscribe
Query: token (unsubscribe_token UUID). Deactivates subscription and returns HTML confirmation.
GET /api/alerts/[alertId]
Returns normalised alert: id, senderName, event, start, end, description. Sourced from cached alert scope.
Cron routes
GET /api/cron/weekly-digests — send weekly digest emails grouped by subscriber email. GET /api/cron/weather-alerts — evaluate alertPrefs against OpenWeather, Open-Meteo, and NWS feeds and send alert emails. Both require Bearer CRON_SECRET.
Admin routes
Usage and config: GET /api/admin/usage; GET|PATCH /api/admin/config; legacy PATCH /api/admin/settings { refreshIntervalMs }. Users and auth: GET|POST /api/admin/users; POST /api/admin/users/invite; GET /api/admin/me. Data: GET /api/admin/checks; GET /api/admin/locations; GET|PATCH /api/admin/subscriptions; GET /api/admin/mailing-summary; GET /api/admin/analytics. Connectors: GET|PATCH /api/admin/connections; GET|PATCH /api/admin/openweather-key; GET|PATCH /api/admin/email-key. Email CMS: GET|POST|PATCH /api/admin/email-templates; POST /api/admin/email/test, /compose, /sync. AdSense: GET /api/admin/adsense/report; POST /api/admin/adsense/sync; OAuth GET /api/admin/adsense/oauth/start, /callback, /disconnect. CMS: GET|PATCH /api/admin/cms-pages. All require meridian_admin_session unless dev bypass.
Ads routes
GET /api/ads/config — { scriptEnabled, clientId, consentRequired }. GET /api/ads?placement=dashboard|hero|recent-checks|city-detail — placement config with slotId when set. GET /api/ads/placeholder-bg — hero lookup for placeholder surfaces. App route GET /ads.txt — AdSense publisher line from env. Active AdSlot placements: dashboard, hero, city-detail. recent-checks slot env exists but home has no AdSlot.
Other public routes
GET /api/platform/limits — public quota snapshot. POST /api/analytics/collect — first-party analytics beacon. GET /api/location/region — IP/region helper. POST /api/weather/inaccurate-report — flag bad data. GET /api/weather/map-tile/[layer]/[z]/[x]/[y] — OSM hero overlay tiles. Auth: POST /api/auth/login, /logout; POST /api/auth/forgot-password; POST /api/auth/reset-password/[token]; GET|POST /api/auth/invite/[token]; GET /api/auth/session.
Error shape
JSON errors typically { error: code, message: string }. ApiError codes include invalid_request, service_unavailable, location_not_found, rate_limited, upstream_error, unauthorized, not_found, limit_reached.