Skip to main content
Market Watch panels are user-configured surfaces that live above the fixed “Radar pulse” panes on /dashboard2/market-watch. Each panel is either a preset (one of eight pre-built fetchers) or a screen (filter expressed over a single read-only source).
These endpoints are session-authenticated only (Supabase cookie), not Bearer-API. They power the dashboard UI; external automation should use the public/agent APIs.
The system is gated three ways:
  1. Per-route rate limit — same withRequestLog wrapper as the rest of the dashboard. RPM + monthly hard cap per tier.
  2. Tier panel caps — number of panels you can keep is market_watch_panel_cap from tier_config. Screen panels have a tighter cap (market_watch_screen_panel_cap).
  3. Per-panel refresh floor — server clamps any schedule.cadenceMinutes to market_watch_min_refresh_seconds and a manual refresh has its own per-panel cooldown.
Default limits at launch:

Hydration

Returns the legacy fixed-pane payload, every active panel for the calling user (with its cached payload), and the current tier limits + counts. Response is private, max-age=60, stale-while-revalidate=300. Panel fan-out is bounded at pMap(concurrency=4) so even an institutional tier with 200 panels only runs 4 factory calls at a time. Cache hits never reach Postgres.

Create a panel

Body:
Returns 201 with { "panel": { id, title, kind, status, sortIdx, refreshFloorSeconds } }. Failures use a stable reason enum:

Presets (v1)

Pass preset.id from this list:

Screen sources

For kind: "screen", pass sources as an array containing exactly one of:
v1 ships executor coverage for latest_market_prices; other sources validate but execute as empty.

Update / delete / reorder

PATCH body accepts any subset of { title, spec, status, sortIdx }. Spec PATCH replaces the full spec — no JSON merge. Cache for the panel is invalidated after commit. Reorder body: { "order": [{ "id": "...", "sortIdx": 0 }, ...] }. All ids must be owned by the caller, else 404 panel_not_found_or_not_owned.

Manual refresh

Synchronous: claims a per-user-per-panel cooldown via Upstash SET NX EX, runs the panel with allowStale=false, returns the fresh envelope.
Cooldown violations return 429 with Retry-After:

Tier-gate headers

Every route inherits the dashboard rate-limit headers from withRequestLog:
  • X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset — minute window.
  • On block: 429 with Retry-After and X-SF-Block-Reason.

Errors and recovery

  • Redis outagerunPanelWithCache falls through to bypass (factory runs inline, no cache write). Cooldown bypass is graceful too: the panel’s refresh_floor_seconds floor still gates the cache window once Redis recovers.
  • Validator failure — never persists. The reason enum is stable and safe to surface.
  • Factory error — recorded in market_watch_panel_runs.error plus market_watch_panels.last_error. Replays still work via the next refresh.

Admin observability

Admin-only (via ADMIN_EMAILS). Returns four aggregates:
No fan-out beyond these four bounded queries.