Skip to main content
WorldMonitor composes a per-user intelligence brief on Railway, stores each edition in Redis at brief:{userId}:{issueSlot}, writes a latest pointer at brief:latest:{userId}, and exposes these routes for dashboard readback, public sharing, and Telegram/Slack carousel rendering. Default cadence is daily, but each alert rule’s digestMode can schedule daily, twice-daily, or weekly editions. For source selection, filtering, deduplication, LLM grounding, and bias controls, see News Digest and Briefing Methodology.
All read routes require a valid Clerk session and a PRO tier, except the public share route (/api/brief/public/{hash}).

Latest brief (authenticated)

GET /api/latest-brief

Returns a short summary of the caller’s most recent composed brief, or { status: "composing" } if the requested/current slot has not produced a brief yet. issueDate remains the display/date field (YYYY-MM-DD). issueSlot is the frozen edition key (YYYY-MM-DD-HHMM) used for Redis lookup and HMAC binding; it is present on ready responses and on misses for an explicitly requested slot. The magazineUrl is freshly signed against {userId, issueSlot} so it only works for the authenticated owner.

GET /api/brief/{userId}/{issueSlot}

Full magazine reader for issueSlot (YYYY-MM-DD-HHMM). HMAC-signed URL required. The slot format lets two same-day digest sends produce distinct frozen editions.

Sharing

POST /api/brief/share-url?slot=YYYY-MM-DD-HHMM

Materialises a public share pointer for the caller’s brief on slot. If the slot is omitted, the route resolves brief:latest:{userId}. Idempotent — hash is a pure function of {userId, issueSlot, BRIEF_SHARE_SECRET}.

GET /api/brief/public/{hash}

Unauthenticated public read of a previously-shared brief. The hash resolves to a brief:public:{hash} → {userId, issueSlot} Redis pointer; if absent, the brief was never shared. Share pointers are written lazily (on share, not on compose).

GET /api/brief/carousel/{userId}/{issueDate}/{page}.png?t={token}

Server-rendered PNG page of the brief, intended for Telegram sendMediaGroup, Slack chat.postMessage, LinkedIn, etc.
  • page is 0, 1, or 2 (cover, threads, story); anything else is 404 invalid_page.
  • Rendered via @vercel/og.
  • Content-Type: image/png, 1200×630.
  • Gated by an HMAC capability token in the ?t= query parameter — a missing or wrong token returns 403.

Ancillary

GET /api/story?c={ISO2}&t={type}

Public read-only HTML page for a shared country story (default type ciianalysis), built for social-media crawlers. Parameters: c (country, required), t (story type), ts (timestamp), s (score), l (level). It is not a brief reader and takes no date parameter.

GET /api/og-story?c={ISO2}&t={type}

Open Graph preview image for /api/story, taking the same c/t/s/l parameters. Returns image/png, cached aggressively.

POST /api/chat-analyst

Streaming chat endpoint for the “Ask the analyst” in-dashboard assistant. Takes a user prompt + recent-signal context; returns SSE tokens.
  • Auth: Clerk JWT + PRO
  • Streams: text/event-stream
  • Back-end: intelligence/v1/chat-analyst-* handlers compose context + prompt
Denials carry the same three-way split as the brief endpoints above, so a client can tell a plan verdict from a session problem from an outage:

POST /api/widget-agent

Single-shot completion endpoint used by embedded widget iframes. Auth via X-WorldMonitor-Key (partner keys); the legacy X-Widget-Key / X-Pro-Key headers and the wm-widget-key cookie are also accepted. Rate-limited per key.