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.

RouteWhat it does
GET/w/{workspace}/lanesEvery lane with status, stop count, and timestamps.
POST/w/{workspace}/lanesLIVE KEYCreate 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 KEYRename, 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 KEYReplace 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 KEYCopy a lane with its stops and screens.
DELETE/w/{workspace}/lanes/{id}LIVE KEYDelete a lane and everything in it.
GET/w/{workspace}/lanes/{id}/linksProspect links (personalized URLs) for a lane.
POST/w/{workspace}/lanes/{id}/linksLIVE KEYMint a prospect link; open the lane with ?for={token}.{ "prospectName": "Cobalt Health", "prospectDomain": "cobalthealth.com" }
DELETE/w/{workspace}/lanes/{id}/links/{linkId}LIVE KEYRetire a prospect link.

Screens

Captured screens are images on a lane; every uploaded screen also becomes a stop at the end of the lane.

RouteWhat it does
GET/w/{workspace}/lanes/{id}/screensScreens with their public URLs and dimensions.
POST/w/{workspace}/lanes/{id}/screensLIVE KEYmultipart/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 KEYRelabel a screen.{ "label": "Pipelines" }
DELETE/w/{workspace}/lanes/{id}/screens/{screenId}LIVE KEYRemove a screen.

Leads

People who filled in a form mid-lane, with the engagement of their own drive.

RouteWhat it does
GET/w/{workspace}/leadsList, 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 KEYChange status or notes.{ "status": "qualified", "notes": "Call Tuesday" }
DELETE/w/{workspace}/leads/{id}LIVE KEYDelete a lead (drive analytics stay).
PATCH/w/{workspace}/leadsLIVE KEYBulk status change.{ "ids": ["…"], "status": "synced" }
DELETE/w/{workspace}/leadsLIVE KEYBulk delete.{ "ids": ["…"] }
GET/w/{workspace}/leads/export.csvEvery matching lead as CSV (same filters as the list, or ids=).

Analytics

Everything the Analytics page shows, as JSON.

RouteWhat it does
GET/w/{workspace}/analyticsKPIs 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/liveSessions 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.

RouteWhat it does
GET/p/{workspace}/{laneSlug}The player payload for a live lane: stops, screens, accent, tracking ids; ?for={token} adds the prospect.
POST/ePlayer beacon: up to 100 events per call (play, stop, cta, lead, complete) with session and visitor ids. The player sends these for you.
POST/leadsThe 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.

EventFires when
lane.playeda drive starts — one per play beacon, so expect volume
lane.completeda drive reaches the end of a lane
lead.captureda form is submitted mid-lane (new lead)
lane.publisheda 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]