LanaChat

LanaChat API: quick start, authentication and errors

8 min readUpdated Markdown

Everything your team does in the Console, your code can do too. The LanaChat API reads and changes your whole workspace: creators, Telegram accounts, AI Brains, scripts, the Vault, fans and conversations, tracking links, billing and your team. It is plain JSON over HTTPS, one URL per resource, with the same permissions as the Console. Webhooks push events to your server, and the MCP server lets an AI assistant such as Claude or Cursor work in your workspace with the same token.

Note This is the public API reference. Endpoints marked Planned are the published contract the backend is built from: they do not answer yet. The MCP connection uses the same access tokens.

What you can build #

  • A sales dashboard in your own tools: today's revenue per creator, per link, per source.
  • Alerts in Slack or Telegram when a fan buys, when a conversation needs review, or when an account loses its connection.
  • A setup script that adds a creator, connects her accounts, uploads her Vault and writes her first script.
  • A tracking pipeline: create one link per ad, read clicks, conversations and buyers every hour.
  • An AI assistant that drafts scripts and persona changes for you to review, through MCP.

Quick start in 5 minutes #

  1. In the Console, open Users › API & MCP and press Create token.
  2. Name it (for example "Reporting script"), choose Custom permissions and tick what it needs, for example Read fans and conversations and Read sales.
  3. Save, then press Copy token. The token is shown once.

4. Keep it in an environment variable, so it never sits in your code:

export LANACHAT_TOKEN="lc_live_8f2c1d0e9b7a4c6f"

5. Make your first call. It returns your workspace and what the token may do:

curl https://api.lanachat.ai/v1/workspace \
  -H "Authorization: Bearer $LANACHAT_TOKEN"
const res = await fetch('https://api.lanachat.ai/v1/workspace', {
  headers: { Authorization: `Bearer ${process.env.LANACHAT_TOKEN}` },
});
if (!res.ok) throw new Error((await res.json()).error.message);
const workspace = await res.json();
console.log(workspace.name, workspace.token.permissions);
import os
import requests

r = requests.get(
    "https://api.lanachat.ai/v1/workspace",
    headers={"Authorization": f"Bearer {os.environ['LANACHAT_TOKEN']}"},
    timeout=30,
)
r.raise_for_status()
print(r.json()["name"])
{
  "id": "ws_7Hq2",
  "name": "Mia Agency",
  "kind": "agency",
  "timezone": "Europe/Paris",
  "token": { "id": "tok_31", "name": "Reporting script", "permissions": ["can_fan_read", "can_sales_read"], "expires_at": "2027-01-06T00:00:00Z" }
}

That's it: every other endpoint works the same way. Ready-made scripts are in API quick recipes.

Every resource #

ResourceWhat it coversGuide
WorkspaceGET /workspace: the workspace and the token's permissionsThis page
CreatorsCreators, identity, persona and setup progressCreators
Telegram accountsAccounts, pools, pool settings and storiesTelegram accounts
AI BrainsBrains, drafts, publishing, versions and the PlaygroundAI Brains
ScriptsScripts, steps and foldersScripts
VaultMedia, folders and signed uploadsVault
Fans and conversationsFans, lists, sales of a fan, messages, takeoverConversations
TrackingLinks, domains, pixels, events and statsTracking
BillingBalance, ledger, deposits, licences, withdrawals, invoicesBilling
TeamMembers, roles and access tokensTeam
WebhooksEvents pushed to your server, signatures and retriesWebhooks
MCPThe server for AI assistants and its toolsMCP

Base URL and versions #

Every path in these guides is relative to:

https://api.lanachat.ai/v1

The version is in the path. A breaking change gets a new version (/v2); new fields and new endpoints are added to /v1 without notice, so ignore the fields you do not know.

Authentication #

Every request carries your token in the Authorization header, after the word Bearer:

Authorization: Bearer lc_live_8f2c1d0e9b7a4c6f
  • Create, edit, rotate and revoke tokens in Users › API & MCP, or with the token endpoints. Only the workspace owner and agency owners can open that page.
  • A token is shown once, when you create it. Store it like a password; Rotate or Revoke it from the same page.
  • A request without a valid token answers 401. A token without the permission an endpoint needs answers 403.
  • Tokens never go in a URL or in browser code: call the API from your server.
  • Use one token per script or assistant, so each can be changed or revoked on its own.

Workspaces #

A token belongs to one workspace, the one it was created in. Every request reads and writes that workspace only; there is no workspace id in the paths. An owner token (the workspace that manages several agencies) may act on one of its agencies with the header LanaChat-Workspace: ws_…; any other token sending that header gets 403.

Permissions #

Each endpoint lists the permission it needs. They are the ones you tick when you create a token:

PermissionIn the ConsoleGives
can_setupManage workspace setupCreators, media, Telegram accounts, scripts and team members
can_engineAccess AI configurationAI Brain drafts, scripts' AI settings and tests
can_publishPublish AI Brain draftsPublishing a brain draft (needs can_engine)
can_ai_testTest Advanced AI draftsTrying drafts that use Advanced AI (owner workspace only)
can_ai_tuneAdjust Advanced AI settingsChanging the Advanced AI sensitivity settings (owner workspace only)
can_ai_publishPublish Advanced AI profilesPublishing profiles that use Advanced AI (owner workspace only)
can_commercial_reportsRead commercial experiment reportsExperiments and their review reports (owner workspace only)
can_fan_readRead fans and conversationsFans, conversations and messages, read only
can_fan_importImport fan historyImporting past fans and messages
can_sales_readRead salesSales, stats and the dashboard numbers
can_chatNew: Chat with fansSending messages, reactions and taking over a conversation
can_trackingNew: Manage trackingLinks, domains, pools, pixels and events
ownerOwner tokenBilling, roles, webhooks and access tokens; only the workspace owner can create it
  • Turning Access AI configuration (can_engine) off also turns off publishing, Advanced AI and experiment reports.
  • can_chat, can_tracking and the owner token are planned: the Console does not offer them yet.

Requests and responses #

  • Bodies are JSON (Content-Type: application/json), in UTF-8. Uploads go through a signed URL, never through the API itself.
  • Ids are strings with a prefix that says what they are: cr_ creator, acc_ Telegram account, pool_, sto_ story, br_ brain, scr_ script, med_ media, fan_, conv_ conversation, msg_, lnk_ link, px_ pixel, mem_ member, tok_ token.
  • Times are ISO 8601 in UTC (2026-10-06T14:05:00Z). Days in stats are UTC days.
  • Money is in US cents (amount_cents: 1900 is $19.00). Telegram Stars are whole numbers (stars: 250).
  • PATCH changes only the fields you send. PUT replaces the whole object.
  • A POST that creates something answers 201 with the new object; other successful calls answer 200.

Pagination #

Every list answers one page and a cursor to the next one:

curl "https://api.lanachat.ai/v1/fans?limit=50&cursor=eyJpZCI6ImZhbl85MjAifQ" \
  -H "Authorization: Bearer $LANACHAT_TOKEN"
{
  "data": [ { "id": "fan_921", "display_name": "Marco", "creator_id": "cr_lea" } ],
  "next_cursor": "eyJpZCI6ImZhbl85NzAifQ"
}
  • limit is 1 to 100, 25 by default.
  • next_cursor is null on the last page. A cursor is opaque: pass it back as it is, never build one.
  • Lists are sorted newest first unless the endpoint says otherwise.

To read every page, loop until next_cursor is null:

async function all(path) {
  const items = [];
  let cursor = null;
  do {
    const url = new URL('https://api.lanachat.ai/v1' + path);
    url.searchParams.set('limit', '100');
    if (cursor) url.searchParams.set('cursor', cursor);
    const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.LANACHAT_TOKEN}` } });
    if (!res.ok) throw new Error((await res.json()).error.message);
    const page = await res.json();
    items.push(...page.data);
    cursor = page.next_cursor;
  } while (cursor);
  return items;
}

const fans = await all('/fans?creator_id=cr_lea');
import os
import requests

def all_items(path, **params):
    items, cursor = [], None
    while True:
        r = requests.get(
            "https://api.lanachat.ai/v1" + path,
            params={**params, "limit": 100, **({"cursor": cursor} if cursor else {})},
            headers={"Authorization": f"Bearer {os.environ['LANACHAT_TOKEN']}"},
            timeout=30,
        )
        r.raise_for_status()
        page = r.json()
        items += page["data"]
        cursor = page["next_cursor"]
        if not cursor:
            return items

fans = all_items("/fans", creator_id="cr_lea")

Errors #

An error answers a 4xx or 5xx status and always the same body:

{
  "error": {
    "code": "insufficient_balance",
    "message": "Your balance is $12.40 and this pack costs $170.00. Top up first."
  }
}
StatuscodeWhen
400invalid_requestA field is missing or has the wrong format; message says which
401unauthorizedNo token, or a revoked or expired token
402insufficient_balanceThe balance does not cover a purchase
403forbiddenThe token lacks the permission the endpoint needs
404not_foundThe id does not exist in this workspace
409conflictThe change would break a rule (a duplicate slug, the last sending account of a creator)
429rate_limitedToo many requests; wait for Retry-After seconds
500server_errorOur side failed; retry later with the same Idempotency-Key

message is a plain English sentence you can show to a person. Branch your code on code, never on message.

Rate limits #

Each token may send 600 requests per minute. Every answer carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds until the window resets). Above the limit the API answers 429 with a Retry-After header. Sending a message to a fan is also bound by Telegram's own limits: see the pool rules.

A small helper that waits and tries again on 429 and 5xx:

async function lanachat(method, path, body) {
  const key = crypto.randomUUID();   // one key per action: a retry never does the work twice
  for (let attempt = 0; attempt < 5; attempt++) {
    const res = await fetch('https://api.lanachat.ai/v1' + path, {
      method,
      headers: {
        Authorization: `Bearer ${process.env.LANACHAT_TOKEN}`,
        'Content-Type': 'application/json',
        ...(method === 'POST' ? { 'Idempotency-Key': key } : {}),
      },
      body: body ? JSON.stringify(body) : undefined,
    });
    if (res.status === 429 || res.status >= 500) {
      const wait = Number(res.headers.get('Retry-After')) || 2 ** attempt;
      await new Promise((r) => setTimeout(r, wait * 1000));
      continue;
    }
    const data = await res.json();
    if (!res.ok) throw Object.assign(new Error(data.error.message), { code: data.error.code, status: res.status });
    return data;
  }
  throw new Error('LanaChat API: too many retries');
}

Use it everywhere: await lanachat('GET', '/creators'), await lanachat('POST', '/links', { … }).

Idempotency #

Send an Idempotency-Key header (any unique string up to 255 characters, a UUID is fine) on every POST. If the same key comes back within 24 hours, the API answers the first result again instead of doing the work twice: a network error never sends a message or buys a pack twice.

curl -X POST https://api.lanachat.ai/v1/billing/licences/buy \
  -H "Authorization: Bearer $LANACHAT_TOKEN" \
  -H "Idempotency-Key: 4b7f2a8e-1c3d-4e5f-9a0b-6c7d8e9f0a1b" \
  -H "Content-Type: application/json" \
  -d '{"pack": 10, "qty": 1}'

The same key with a different body answers 409. In a shell, $(uuidgen) makes a fresh key for each new action.

Webhooks #

Instead of asking the API every minute, let LanaChat call you. A webhook sends an HTTPS POST to your server when a fan writes for the first time, buys, is refunded, when a media is ready or an account loses its connection. Every delivery is signed (HMAC-SHA256) and retried for 24 hours. The events, payloads and the signature check in Node and Python are in Webhooks.

MCP for AI assistants #

MCP is an open standard that lets an AI assistant use tools in an app. LanaChat's MCP server lets Claude, Cursor or any MCP client read your creators, draft scripts, edit a brain draft and read your sales, with the same access token and the same permissions. In Claude Code, one command connects it:

claude mcp add --transport http lanachat https://app.lanachat.ai/mcp \
  --header "Authorization: Bearer $LANACHAT_TOKEN"

The configuration for every assistant and the list of tools are in API & MCP.

Latest news about Lana on Telegram

New releases and beta invites, first in the channel.