Developers
The Tellane API.
One REST surface — the same one the dashboard runs on — plus signed webhooks for what happens in your lanes and an MCP server for assistants. Base URL https://api.tellane.com.
Authentication
Create a key under Settings → API keys and send it as a bearer token. A live key (tl_live_…) can read and edit lanes, stops, screens, and leads — the routes marked LIVE KEY below. A test key (tl_test_…) can only read. Keys belong to one workspace and can never touch the team, billing, integrations, or other keys.
curl https://api.tellane.com/w/{workspace}/lanes \
-H "Authorization: Bearer tl_live_…"{workspace} is your workspace slug — the subdomain of your public address. Errors are JSON: { "error": "…", "code": "…" } with the usual status (400 invalid, 401 no or bad key, 403 not allowed for this key, 404, 409 conflict, 429).
Rate limits
600 requests per minute per key. Every answer carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (unix seconds); past the limit you get a 429 with Retry-After.
Lanes
A lane is an interactive demo. Every lane route is under your workspace.
| Route | What it does |
|---|---|
GET/w/{workspace}/lanes | Every lane with status, stop count, and timestamps. |
POST/w/{workspace}/lanesLIVE KEY | Create a draft lane.{ "name": "Homepage tour" } |
GET/w/{workspace}/lanes/{id} | One lane with its stops and screens (also GET …/lanes/slug/{slug}). |
PATCH/w/{workspace}/lanes/{id}LIVE KEY | Rename, publish, archive, set the CTA, require an email.{ "status": "live", "ctaUrl": "https://…", "requireEmail": false }status: draft | live | archived. Going live fires lane.published. |
PUT/w/{workspace}/lanes/{id}/stopsLIVE KEY | Replace the whole stop list (1–50 stops, in order).[{ "name": "Welcome", "screenId": null, "hotspotX": 50, "hotspotY": 50, "title": "…", "body": "…", "branch": null, "leadForm": false, "hotspotOnly": false, "zoom": false }]hotspotOnly hides the tooltip's Next button (the viewer clicks the hotspot); zoom eases the screen in toward the hotspot. Both default to false. |
POST/w/{workspace}/lanes/{id}/duplicateLIVE KEY | Copy a lane with its stops and screens. |
DELETE/w/{workspace}/lanes/{id}LIVE KEY | Delete a lane and everything in it. |
GET/w/{workspace}/lanes/{id}/links | Prospect links (personalized URLs) for a lane. |
POST/w/{workspace}/lanes/{id}/linksLIVE KEY | Mint a prospect link; open the lane with ?for={token}.{ "prospectName": "Cobalt Health", "prospectDomain": "cobalthealth.com" } |
DELETE/w/{workspace}/lanes/{id}/links/{linkId}LIVE KEY | Retire a prospect link. |
Screens
Captured screens are images on a lane; every uploaded screen also becomes a stop at the end of the lane.
| Route | What it does |
|---|---|
GET/w/{workspace}/lanes/{id}/screens | Screens with their public URLs and dimensions. |
POST/w/{workspace}/lanes/{id}/screensLIVE KEY | multipart/form-data: files[] (PNG/JPEG/WebP/GIF, ≤ 10 MB each, ≤ 30 per request, ≤ 80 per lane), labels[], source. |
PATCH/w/{workspace}/lanes/{id}/screens/{screenId}LIVE KEY | Relabel a screen.{ "label": "Pipelines" } |
DELETE/w/{workspace}/lanes/{id}/screens/{screenId}LIVE KEY | Remove a screen. |
Leads
People who filled in a form mid-lane, with the engagement of their own drive.
| Route | What it does |
|---|---|
GET/w/{workspace}/leads | List, newest first. Query: q, status (new|qualified|synced), intent (high|medium|low), lane (id), sort (newest|score), page (0-based), size (≤ 100). Returns rows, total, and workspace-wide stats. |
GET/w/{workspace}/leads/{id} | One lead plus drive: the stops they reached, timestamps, device, source, prospect link. |
PATCH/w/{workspace}/leads/{id}LIVE KEY | Change status or notes.{ "status": "qualified", "notes": "Call Tuesday" } |
DELETE/w/{workspace}/leads/{id}LIVE KEY | Delete a lead (drive analytics stay). |
PATCH/w/{workspace}/leadsLIVE KEY | Bulk status change.{ "ids": ["…"], "status": "synced" } |
DELETE/w/{workspace}/leadsLIVE KEY | Bulk delete.{ "ids": ["…"] } |
GET/w/{workspace}/leads/export.csv | Every matching lead as CSV (same filters as the list, or ids=). |
Analytics
Everything the Analytics page shows, as JSON.
| Route | What it does |
|---|---|
GET/w/{workspace}/analytics | KPIs vs the previous period, daily series, funnel, day×hour heatmap, sources, devices, countries, referrers, accounts, per-lane totals, per-stop reach, prospect links, recent drives. Query: range (7d|30d|90d, default 30d), lane (id). |
GET/w/{workspace}/analytics/live | Sessions active in the last 30 seconds. |
Public routes
No key needed — these are what the player itself calls, useful if you embed lanes your own way.
| Route | What it does |
|---|---|
GET/p/{workspace}/{laneSlug} | The player payload for a live lane: stops, screens, accent, tracking ids; ?for={token} adds the prospect. |
POST/e | Player beacon: up to 100 events per call (play, stop, cta, lead, complete) with session and visitor ids. The player sends these for you. |
POST/leads | The lead form: workspaceId, laneId, sessionId, email, optional name/company/answers. 20 per minute per IP. |
Webhooks
Register https endpoints under Settings → Webhooks. Each event is one JSON POST: { "id", "event", "createdAt", "attempt", "data" }, retried five times on a 1 m → 5 m → 30 m → 2 h → 12 h backoff until your endpoint answers 2xx within 10 seconds. The delivery log in Settings shows every attempt.
| Event | Fires when |
|---|---|
lane.played | a drive starts — one per play beacon, so expect volume |
lane.completed | a drive reaches the end of a lane |
lead.captured | a form is submitted mid-lane (new lead) |
lane.published | a lane goes from draft to live |
Headers: X-Tellane-Event, X-Tellane-Delivery, X-Tellane-Timestamp, and X-Tellane-Signature: t=<unix>,v1=<hex> — an HMAC-SHA256 over <t>.<raw body> with the endpoint's secret. Verify it before trusting a delivery:
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(secret, header, rawBody, maxAgeSeconds = 300) {
const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > maxAgeSeconds) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const a = Buffer.from(expected, "hex"), b = Buffer.from(parts.v1 ?? "", "hex");
return a.length === b.length && timingSafeEqual(a, b);
}MCP
POST https://api.tellane.com/mcp speaks the Model Context Protocol (Streamable HTTP, stateless) and authenticates with an API key — a test key is enough, every tool is read-only: workspace_info, list_lanes, get_lane, analytics_summary, list_leads. Claude Desktop, Claude Code, Cursor, ChatGPT, and Copilot all take this shape:
{
"mcpServers": {
"tellane": {
"url": "https://api.tellane.com/mcp",
"headers": {
"Authorization": "Bearer tl_test_YOUR_KEY"
}
}
}
}Questions, or a route you need that isn't here? [email protected]