Docs Reference
REST API
Every route of https://api.retafunnel.com/api/v1 with its scope and notes.
Everything the dashboard and the MCP server do is available over plain HTTPS at https://api.retafunnel.com/api/v1. Send the header Authorization: Bearer rfk_… on every request. Create API keys in the dashboard under Integrations → API keys and give each key only the scopes it needs: read (reports, sessions, read-only SQL), write (outcomes, segments, syncs) or setup (sites, funnels, test mode, simulations). Responses are JSON: { "data": … } on success, { "error": { "code", "message" } } on failure.
Core routes#
| Method | Path | Scope | Notes |
|---|---|---|---|
| GET | /me |
any | workspace, scopes |
| GET | /schema |
read | data dictionary JSON |
| GET | /sites · POST /sites |
read / setup | |
| GET | /funnels · POST /funnels · PUT /funnels/:id |
read / setup | |
| GET | /segments · POST /segments · PUT /segments/:id · DELETE |
read / write | body { name, target, rules, sync_meta?, sync_google? } |
| POST | /segments/preview |
read | { rules, target } → { count, sample[] } |
| POST | /segments/:id/sync |
write | { platform: 'meta'|'google' } |
| GET | /sessions |
read | query params: site_id, from, to, limit, cursor + any session_facts filter as filter[field][op]=value |
| GET | /sessions/:id |
read | facts + ordered events |
| GET | /reports/overview |
read | site_id?, funnel_id?, from, to |
| GET | /reports/funnel |
read | funnel_id, from, to, group_by? |
| GET | /reports/video-retention |
read | site_id, video_id, from, to |
| GET | /reports/quiz-dropoff |
read | quiz_id, site_id?, from, to |
| GET | /reports/clicks |
read | site_id, path?, from, to |
| GET | /reports/scroll |
read | site_id, path, from, to |
| POST | /query |
read | { sql } read-only over v_*, 10 s timeout, 10k rows |
| POST | /outcomes |
write | { email?, phone?, visitor_id?, name, value?, currency?, external_id?, occurred_at? } |
| GET | /install/health |
read | site_id → last event, counts by type last 24h, tracker versions seen |
| POST | /install/test-event |
setup | site_id → fires a synthetic test session (alias of simulate with scenario smoke) |
| PUT | /sites/:id |
setup | update name, allowed_origins, consent_mode |
| GET/POST | /sites/:id/pages |
read / setup | page_registry; POST body { url, page_type, elements, html? }, upsert by url; html is kept to derive test plans |
| GET | /webhooks |
read | Stripe, Calendly and generic webhook URLs (workspace token created lazily) |
| GET | /segments/templates |
read | DEFAULT_SEGMENT_TEMPLATES |
| GET/POST | /connect-links · DELETE /connect-links/:id |
admin | returns plaintext key once + connect_url |
| GET | /integrations · PUT/DELETE /integrations/:provider · POST /integrations/meta/test |
read / admin | secrets never returned |
| GET | /sync-log |
read | |
| GET/PUT | /sites/:id/test-mode |
read / setup | { until }; PUT body { minutes } (0 disables) |
| GET | /sites/:id/test-plan |
read | url → expected event checklist derived from page_registry + instrumented HTML (fetches the URL server-side if not registered) |
| POST | /sites/:id/simulate |
setup | { scenario: 'smoke'|'full_funnel'|'vsl_50_no_lead'|'quiz_abandon'|'lead_and_purchase', url? } → generates a test session through the real ingest path, returns session_id |
| GET | /sites/:id/test-sessions |
read | since → test sessions with facts and event list, each with expected_vs_received when a test plan exists |
| POST | /sites/:id/simulate-outcome |
setup | { name, email?, visitor_id?, value? } → runs the outcome path as test |
| POST | /segments/:id/dry-run |
read | what would be sent to Meta/Google (counts, sample hashed payload), nothing sent |
| DELETE | /sites/:id/test-data |
setup | purge test rows for the site |
Webhooks (no API key, signature-verified): POST /outcomes/stripe/:workspace_token, POST /outcomes/calendly/:workspace_token, POST /outcomes/generic (uses API key). The workspace token lives in integrations.settings.webhook_token and is created by GET /webhooks.
Reports, funnels, sales and experiments#
| Method | Path | Scope | Notes |
|---|---|---|---|
| GET | /funnels/templates |
read | FUNNEL_TEMPLATES from shared |
| POST | /funnels/from-template |
setup | { site_id, template, name?, base_url? } → funnel (+ default segments, + page_registry hints) |
| GET | /funnels/:id/checklist |
read | Checklist (below) |
| POST | /funnels/:id/launch |
setup | sets status live if checklist has no failing required items (or force: true) |
| GET | /reports/ads |
read | site_id?, from, to, level=campaign|adset|ad, platform? |
| GET | /reports/page |
read | site_id, path, from, to |
| GET | /reports/compare |
read | kind=funnel|page, a=<id or path>, b=<id or path>, from, to → both reports plus deltas |
| GET | /reports/sales |
read | from, to, model, group_by |
| GET/POST | /annotations · PUT/DELETE /annotations/:id |
read / write | |
| GET/POST | /experiments · PUT /experiments/:id · GET /experiments/:id/report |
read / write | report includes two-proportion z-test p-value and uplift per variant vs control (first variant) |
| GET/POST | /ad-entities |
read / setup | POST upserts names manually; GET lists with unresolved ids first |
| POST | /ad-entities/sync |
setup | pulls names (and spend for the range) from Meta/Google for ids seen in the last 90 days |
| GET | /persons · GET /persons/:id · PUT /persons/:id |
read / write | leads list with status (lead/booked/customer), attribution first/last, outcomes; PUT for notes/external_ids. GET /persons/:id includes timeline: every session of the person in order (session_id, started_at, utm_*, campaign_id/adset_id/ad_id with names, variant, landing_path, max_video_pct, max_scroll_pct, quiz_last_step, is_lead_session, outcomes[]) and attribution: { first_touch, last_touch, linear: [{session_id, weight}] } |
| POST | /outcomes/import |
write | CSV text (email,phone,name,value,currency,occurred_at,external_id) → import batch with matched/unmatched rows |
| GET | /outcomes/export |
read | CSV of leads in range with visitor_id, email_hash, attribution, for pasting into a CRM |
| GET | /ad-url-templates |
read | per platform |
Every report route accepts compare=previous and then returns { current, previous, delta }, where the previous period is the same length immediately before the requested one.
Full contract (types, shapes, MCP tools): docs/CONTRACTS.md on GitHub.
Last updated