Public endpoints

POST /api/subs/new

Starts the magic-link flow for Programs or passes through to n8n for Internal.

Query params:

  • calendar_id=programs|internal (required)

Body (JSON or form-encoded):

  • name (required)
  • email (required)
  • turnstile_token (optional, Programs only)

Behavior:

  • Programs: creates verify_requests with 30-minute TTL, enforces 60s cooldown per email, sends Resend email.
  • Internal: forwards to N8N_BASE/webhook/subs/new with { name, email }.

Responses:

  • 200 JSON { ok: true, message: ... } (Programs)
  • 303 redirect to /subscribe-programs?sent=1 (Programs, when Accept is not JSON)
  • 400 invalid input or Turnstile failure
  • 429 cooldown active (Retry-After header)
  • 500 missing bindings or provider errors

GET /api/subs/verify

Consumes a magic link for Programs and mints a token.

Query params:

  • calendar_id=programs
  • v=<verification code>

Responses:

  • 200 JSON: { ok, token, calId, expires_at, icsUrl, subscribeUrl, webcalUrl, googleUrl, outlookWeb }
  • 303 redirect to subscribeUrl (when Accept is not JSON)
  • 403 calendar disabled or subscriber disabled
  • 404 invalid/expired link

GET /cal/programs.ics

Token-gated ICS for Programs.

Query params:

  • token (required)
  • calendar_id=programs (required)

Behavior:

  • Validates token hash, calendar enabled, subscriber active, and expiry.
  • Logs access to calendar_access_log.
  • Returns programs.ics from R2 with ETag and Cache-Control: private, max-age=300.

GET /cal/internal.ics

Proxy to n8n Internal feed.

Query params:

  • calendar_id defaults to internal and is normalized

Behavior:

  • Forwards request to ${N8N_BASE}/webhook/rc-cal.
  • Maps 401/403 to 404 for client compatibility.
  • Sets Content-Disposition filename to <calid>.ics.

GET /api/turnstile-site-key

Returns { siteKey } for client-side Turnstile widget initialization.

Admin endpoints (edge-protected)

GET /api/admin/stats

Returns counts for subscribers, tokens, and recent verify activity.

GET /api/admin/subs/list

Returns up to 1000 subscribers for a calendar id. Note: this query expects a DB view or schema with calendar_id and token in the subscriber listing.

POST /api/admin/revoke

Disables a subscriber and revokes tokens for a calendar.

POST /api/admin/enable

Re-enables a subscriber by email.

GET /api/admin/mixpanel-test

Sends a single test event to Mixpanel and returns the response.