mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
* docs(mintlify): cover MCP, OAuth, non-RPC endpoints, and usage Audit against api/ + proto/ revealed 9 OpenAPI specs missing from nav, the scenario/v1 service undocumented, and MCP (32 tools + OAuth 2.1 flow) with no user-facing docs. The stale Docs_To_Review/API_REFERENCE.md still pointed at pre-migration endpoints that no longer exist. - Wire 9 orphaned specs into docs.json: ConsumerPrices, Forecast, Health, Imagery, Radiation, Resilience, Sanctions, Thermal, Webcam - Hand-write ScenarioService.openapi.yaml (3 RPCs) until it's proto-backed (tracked in issue #3207) - New MCP page with tool catalog + client setup (Claude Desktop/web, Cursor) - New MDX for OAuth, Platform, Brief, Commerce, Notifications, Shipping v2, Proxies - New Usage group: quickstart, auth matrix, rate limits, errors - Remove docs/Docs_To_Review/API_REFERENCE.md and EXTERNAL_APIS.md (referenced dead endpoints); add README flagging dir as archival * docs(mintlify): move scenario docs out of generated docs/api/ tree The pre-push hook enforces that docs/api/ is proto-generated only. Replace the hand-written ScenarioService.openapi.yaml with a plain MDX page (docs/api-scenarios.mdx) until the proto migration lands (tracked in issue #3207). * docs(mintlify): fix factual errors flagged in PR review Reviewer caught 5 endpoints where I speculated on shape/method/limits instead of reading the code. All fixes cross-checked against the source: - api-shipping-v2: route-intelligence is GET with query params (fromIso2, toIso2, cargoType, hs2), not POST with a JSON body. Response shape is {primaryRouteId, chokepointExposures[], bypassOptions[], warRiskTier, disruptionScore, ...}. - api-commerce: /api/product-catalog returns {tiers, fetchedAt, cachedUntil, priceSource} with tier groups free|pro|api_starter| enterprise, not the invented {currency, plans}. Document the DELETE purge path too. - api-notifications: Slack/Discord /oauth/start are POST + Clerk JWT + PRO (returning {oauthUrl}), not GET redirects. Callbacks remain GET. - api-platform: /api/version returns the latest GitHub Release ({version, tag, url, prerelease}), not deployed commit/build metadata. - api-oauth + mcp: /api/oauth/register limit is 5/60s/IP (match code), not 10/hour. Also caught while double-checking: /api/register-interest and /api/contact are 5/60min and 3/60min respectively (1-hour window, not 1-minute). Both require Turnstile. Removed the fabricated limits for share-url, notification-channels, create-checkout (they fall back to the default per-IP limit). * docs(mintlify): second-round fixes — verify every claim against source Reviewer caught 7 more cases where I described API behavior I hadn't read. Each fix below cross-checked against the handler. - api-commerce (product-catalog): tiers are flat objects with monthlyPrice/annualPrice/monthlyProductId/annualProductId on paid tiers, price+period for free, price:null for enterprise. There is no nested plans[] array. - api-commerce (referral/me): returns {code, shareUrl}, not counts. Code is a deterministic 8-char HMAC of the Clerk userId; binding into Convex is fire-and-forget via ctx.waitUntil. - api-notifications (notification-channels): actual action set is create-pairing-token, set-channel, set-web-push, delete-channel, set-alert-rules, set-quiet-hours, set-digest-settings. Replaced the made-up list. - api-shipping-v2 (webhooks): alertThreshold is numeric 0-100 (default 50), not a severity string. Subscriber IDs are wh_+24hex; secret is raw 64-char hex (no whsec_ prefix). POST registration returns 201. Added the management routes: GET /{id}, POST /{id}/rotate-secret, POST /{id}/reactivate. - api-platform (cache-purge): auth is Authorization: Bearer RELAY_SHARED_SECRET, not an admin-key header. Body takes keys[] and/or patterns[] (not {key} or {tag}), with explicit per-request caps and prefix-blocklist behavior. - api-platform (download): platform+variant query params, not file=<id>. Response is a 302 to a GitHub release asset; documented the full platform/variant tables. - mcp: server also accepts direct X-WorldMonitor-Key in addition to OAuth bearer. Fixed the curl example which was incorrectly sending a wm_live_ API key as a bearer token. - api-notifications (youtube/live): handler reads channel or videoId, not channelId. - usage-auth: corrected the auth-matrix row for /api/mcp to reflect that OAuth is one of two accepted modes. * docs(mintlify): fix Greptile review findings - mcp.mdx: 'Five' slow tools → 'Six' (list contains 6 tools) - api-scenarios.mdx: replace invalid JSON numeric separator (8_400_000_000) with plain integer (8400000000) Greptile's third finding — /api/oauth/register rate-limit contradiction across api-oauth.mdx / mcp.mdx / usage-rate-limits.mdx — was already resolved in commit4f2600b2a(reviewed commit waseb5654647).
116 lines
4.8 KiB
Plaintext
116 lines
4.8 KiB
Plaintext
---
|
|
title: "Notifications & Integrations"
|
|
description: "Notification channels, webhook delivery, and Telegram/Slack/Discord/YouTube integration endpoints."
|
|
---
|
|
|
|
## Notification channels
|
|
|
|
Users can register multiple delivery channels (webhook, Telegram, Slack, Discord, email) and bind alert rules to them.
|
|
|
|
### `GET /api/notification-channels`
|
|
|
|
Lists the caller's registered channels and alert rules.
|
|
|
|
```json
|
|
{
|
|
"channels": [
|
|
{ "id": "chn_01", "type": "webhook", "url": "https://hooks.example.com/...", "active": true },
|
|
{ "id": "chn_02", "type": "telegram", "chatId": "@alerts_xyz", "active": true }
|
|
],
|
|
"alertRules": [
|
|
{ "id": "rul_01", "channelId": "chn_01", "trigger": "brief_ready", "filter": null }
|
|
]
|
|
}
|
|
```
|
|
|
|
### `POST /api/notification-channels`
|
|
|
|
Action-dispatched writer. The body's `action` field selects the mutation:
|
|
|
|
| action | Purpose |
|
|
|--------|---------|
|
|
| `create-pairing-token` | Mint a one-time pairing token (optional `variant`) for the mobile / Tauri client to bind a push channel. |
|
|
| `set-channel` | Register or update a channel. For `webhook` channels the `webhookEnvelope` URL is validated HTTPS-only, must not resolve to a private/loopback address, and is AES-256-GCM encrypted before storage. Optional `email`, `webhookLabel` (truncated to 100 chars). |
|
|
| `set-web-push` | Register a browser Web Push subscription for the signed-in user. |
|
|
| `delete-channel` | Remove a channel by type (`email`, `webhook`, `telegram`, `web-push`, etc.). |
|
|
| `set-alert-rules` | Replace the caller's alert-rules set in one shot. |
|
|
| `set-quiet-hours` | Set do-not-disturb windows. |
|
|
| `set-digest-settings` | Configure digest cadence and channel routing. |
|
|
|
|
All actions require Clerk bearer + PRO (`tier >= 1`). Invalid actions return `400 Unknown action`. Requests are forwarded to Convex via `RELAY_SHARED_SECRET`.
|
|
|
|
## Webhook delivery contract
|
|
|
|
When an alert fires, registered webhook URLs receive:
|
|
|
|
- **Method**: `POST`
|
|
- **Headers**:
|
|
- `Content-Type: application/json`
|
|
- `X-WM-Signature: sha256=<HMAC-SHA256(body, channelSecret)>`
|
|
- `X-WM-Delivery-Id: <ulid>`
|
|
- `X-WM-Event: <event-name>`
|
|
- **Body** (envelope v1):
|
|
```json
|
|
{
|
|
"envelope": 1,
|
|
"event": "brief_ready",
|
|
"deliveryId": "01HX...",
|
|
"occurredAt": "2026-04-19T06:00:00Z",
|
|
"data": { "issueDate": "2026-04-19", "magazineUrl": "..." }
|
|
}
|
|
```
|
|
|
|
Signature verification: `hmac_sha256(rawBody, channelSecret) == X-WM-Signature[7:]`.
|
|
|
|
<Warning>
|
|
The envelope version is **shared across three producers** (`notification-relay`, `proactive-intelligence`, `seed-digest-notifications`). Bumping it requires coordinated updates.
|
|
</Warning>
|
|
|
|
### `POST /api/notify`
|
|
|
|
Internal ingestion endpoint called by Railway producers to enqueue a notification. Requires `RELAY_SHARED_SECRET`. Not a public API.
|
|
|
|
## Telegram
|
|
|
|
### `GET /api/telegram-feed?userId=...`
|
|
|
|
Returns the pre-rendered brief feed for a given Telegram-linked user. Used by the Telegram mini-app.
|
|
|
|
## YouTube
|
|
|
|
### `GET /api/youtube/embed?videoId=...`
|
|
|
|
SSR'd YouTube embed iframe with CSP-compatible wrapping. Used to bypass WKWebView autoplay restrictions on the desktop app.
|
|
|
|
### `GET /api/youtube/live?channel=<handle>` or `?videoId=<11-char-id>`
|
|
|
|
Returns live-stream metadata for a YouTube channel (`channel` — handle with or without `@` prefix) or a specific video (`videoId` — 11-char YouTube id). At least one of the two params is required; returns `400 Missing channel or videoId parameter` otherwise. Response cached 10 min for channel lookups, 1 hour for videoId lookups.
|
|
|
|
Proxies to the Railway relay first (residential proxy for YouTube scraping). On relay failure, falls back to YouTube oEmbed (for `videoId`) or direct channel scraping — both are unreliable from datacenter IPs.
|
|
|
|
## Slack integration
|
|
|
|
### `POST /api/slack/oauth/start`
|
|
|
|
Authenticated (Clerk JWT + PRO). Body is empty. Server generates a one-time CSRF state token, stores the caller's userId in Upstash keyed by that state (10-min TTL), and returns the Slack authorize URL for the frontend to open in a popup.
|
|
|
|
```json
|
|
{ "oauthUrl": "https://slack.com/oauth/v2/authorize?client_id=...&scope=incoming-webhook&..." }
|
|
```
|
|
|
|
Errors: 401 (missing/invalid JWT), 403 `pro_required`, 503 (OAuth not configured or Upstash unavailable).
|
|
|
|
### `GET /api/slack/oauth/callback`
|
|
|
|
Unauthenticated — the popup lands here after Slack redirects. Validates the state token, exchanges `code` for an incoming-webhook URL, AES-256-GCM encrypts the webhook, and stores it in Convex. Returns a tiny HTML page that `postMessage`s the opener and closes.
|
|
|
|
## Discord integration
|
|
|
|
### `POST /api/discord/oauth/start`
|
|
|
|
Authenticated (Clerk JWT + PRO). Same shape as the Slack start route — returns `{ oauthUrl }` for a popup.
|
|
|
|
### `GET /api/discord/oauth/callback`
|
|
|
|
Unauthenticated. Exchanges `code`, stores the guild webhook, and `postMessage`s the opener.
|