API reference
Team and access tokens API
Your team is the people who work in the workspace. Each member has a role (built-in or custom) that sets what they can do, the creators they work on, and a personal access key to sign in. Access tokens are the keys of the API and of the MCP connection. All these endpoints need an owner token: in the examples, $LANACHAT_TOKEN holds one.
Every endpoint on this page is marked Planned: it is the published contract the backend is built from, and it does not answer yet.
Endpoints #
| Method | Path | Description | Permission | Status |
|---|---|---|---|---|
| GET | /members | List members (status=active,disabled, role_id=, q=) | owner | Planned |
| POST | /members | Add a member with a role and creators | owner | Planned |
| GET | /members/{id} | One member, last activity and overrides | owner | Planned |
| PATCH | /members/{id} | Change role, creators, overrides; disable or enable | owner | Planned |
| DELETE | /members/{id} | Delete a member (their past messages keep their name) | owner | Planned |
| POST | /members/{id}/access-key | Create a personal access key (shown once) | owner | Planned |
| POST | /members/{id}/invitation | E-mail an access key to the member | owner | Planned |
| DELETE | /members/{id}/access-key | Revoke the member's key: they are signed out | owner | Planned |
| GET | /roles | Built-in and custom roles with their permission table | owner | Planned |
| POST | /roles | Create a custom role | owner | Planned |
| PATCH | /roles/{id} | Rename a role or change its permissions | owner | Planned |
| DELETE | /roles/{id} | Delete a custom role no member has | owner | Planned |
| GET | /tokens | API and MCP access tokens (include_revoked=true) | owner | Planned |
| POST | /tokens | Create a token with its permissions (shown once) | owner | Planned |
| PATCH | /tokens/{id} | Rename a token or change its permissions | owner | Planned |
| POST | /tokens/{id}/rotate | Replace the secret; the old one stops at once | owner | Planned |
| DELETE | /tokens/{id} | Revoke a token | owner | Planned |
The member object #
{
"id": "mem_4",
"name": "Ana",
"email": "[email protected]",
"status": "active",
"role_id": "role_chatter",
"creator_ids": ["cr_lea", "cr_mia"],
"overrides": { "billing.read": false, "fans.export": true },
"access_key": { "active": true, "created_at": "2026-09-20T09:00:00Z" },
"last_active_at": "2026-10-06T13:40:00Z"
}| Field | Type | Description |
|---|---|---|
id | string | Starts with mem_ |
name | string | Their name in the Console |
email | string or null | Where the invitation goes |
status | string | active or disabled |
role_id | string | Their role |
creator_ids | array of strings | The creators they work on |
overrides | object | Permissions set for this member only, on top of the role |
access_key | object | Whether they have an active personal access key, and since when; never the key itself |
last_active_at | string or null | Their last activity |
A disabled member cannot sign in and keeps their history, role and creators.
The role object #
{
"id": "role_chatter",
"name": "Chatter",
"builtin": true,
"permissions": { "live.read": true, "live.reply": true, "fans.read": true, "fans.export": false, "billing.read": false },
"members": 6
}| Field | Type | Description |
|---|---|---|
id | string | The role |
name | string | Its name |
builtin | boolean | true for Owner, Manager and Chatter |
permissions | object | One key per row of the permission table, true or false |
members | integer | How many members have it |
Permission keys follow the rows of the permission table in Team Management › Roles, grouped by area: Dashboard, Creators, AI Brain, Conversations, Vault, Scripts, Playground, Telegram and Employees. Owner always has every permission and cannot be edited. Manager and Chatter are built in: you can change their permissions but not rename or delete them.
The access token object #
{
"id": "tok_31",
"name": "Reporting script",
"permissions": ["can_fan_read", "can_sales_read"],
"status": "active",
"created_at": "2026-10-06T15:10:00Z",
"expires_at": "2027-01-04T15:10:00Z"
}| Field | Type | Description |
|---|---|---|
id | string | Starts with tok_ |
name | string | Up to 100 characters, required |
permissions | array of strings | The permissions it has |
status | string | Active, expired or revoked |
created_at, expires_at | string | ISO 8601, UTC |
token | string | The secret, only in the answer that creates or rotates it |
Members #
List members #
/membersPlannedNeeds owner| Parameter | Type | Required | Description |
|---|---|---|---|
status | string | No | active or disabled; both separated by a comma |
role_id | string | No | Only members with this role |
q | string | No | Part of a name |
limit, cursor | number, string | No | Pagination |
curl "https://api.lanachat.ai/v1/members?status=active&role_id=role_chatter" \
-H "Authorization: Bearer $LANACHAT_TOKEN"const res = await fetch('https://api.lanachat.ai/v1/members?status=active&role_id=role_chatter', {
headers: {
Authorization: `Bearer ${process.env.LANACHAT_TOKEN}`,
},
});
if (!res.ok) throw new Error((await res.json()).error.message);
console.log(await res.json());import os
import requests
r = requests.get(
"https://api.lanachat.ai/v1/members?status=active&role_id=role_chatter",
headers={
"Authorization": f"Bearer {os.environ['LANACHAT_TOKEN']}",
},
timeout=30,
)
r.raise_for_status()
print(r.json()){
"data": [
{ "id": "mem_4", "name": "Ana", "email": "[email protected]", "status": "active", "role_id": "role_chatter", "creator_ids": ["cr_lea", "cr_mia"], "overrides": {}, "access_key": { "active": true, "created_at": "2026-09-20T09:00:00Z" }, "last_active_at": "2026-10-06T13:40:00Z" }
],
"next_cursor": null
}Add a member #
/membersPlannedNeeds owner| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Their name |
email | string | No | Needed to send an invitation |
role_id | string | Yes | A built-in or custom role |
creator_ids | array of strings | No | The creators they work on |
curl -X POST https://api.lanachat.ai/v1/members \
-H "Authorization: Bearer $LANACHAT_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"name": "Ana", "email": "[email protected]", "role_id": "role_chatter", "creator_ids": ["cr_lea"]}'const res = await fetch('https://api.lanachat.ai/v1/members', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LANACHAT_TOKEN}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Ana',
email: '[email protected]',
role_id: 'role_chatter',
creator_ids: ['cr_lea'],
}),
});
if (!res.ok) throw new Error((await res.json()).error.message);
console.log(await res.json());import os
import uuid
import requests
r = requests.post(
"https://api.lanachat.ai/v1/members",
headers={
"Authorization": f"Bearer {os.environ['LANACHAT_TOKEN']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"name": "Ana",
"email": "[email protected]",
"role_id": "role_chatter",
"creator_ids": ["cr_lea"],
},
timeout=30,
)
r.raise_for_status()
print(r.json())The answer is the new member (201), active, without an access key yet.
Get a member #
/members/{id}PlannedNeeds ownercurl https://api.lanachat.ai/v1/members/mem_4 \
-H "Authorization: Bearer $LANACHAT_TOKEN"const res = await fetch('https://api.lanachat.ai/v1/members/mem_4', {
headers: {
Authorization: `Bearer ${process.env.LANACHAT_TOKEN}`,
},
});
if (!res.ok) throw new Error((await res.json()).error.message);
console.log(await res.json());import os
import requests
r = requests.get(
"https://api.lanachat.ai/v1/members/mem_4",
headers={
"Authorization": f"Bearer {os.environ['LANACHAT_TOKEN']}",
},
timeout=30,
)
r.raise_for_status()
print(r.json())The answer is the member object. An unknown id answers 404.
Update a member #
/members/{id}PlannedNeeds owner| Parameter | Type | Required | Description |
|---|---|---|---|
role_id | string | No | New role |
creator_ids | array of strings | No | Replaces their list of creators |
overrides | object | No | Permissions for this member only |
status | string | No | active or disabled |
curl -X PATCH https://api.lanachat.ai/v1/members/mem_4 \
-H "Authorization: Bearer $LANACHAT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"creator_ids": ["cr_lea", "cr_mia"], "overrides": {"fans.export": true}}'const res = await fetch('https://api.lanachat.ai/v1/members/mem_4', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.LANACHAT_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ creator_ids: ['cr_lea', 'cr_mia'], overrides: { 'fans.export': true } }),
});
if (!res.ok) throw new Error((await res.json()).error.message);
console.log(await res.json());import os
import requests
r = requests.patch(
"https://api.lanachat.ai/v1/members/mem_4",
headers={
"Authorization": f"Bearer {os.environ['LANACHAT_TOKEN']}",
},
json={"creator_ids": ["cr_lea", "cr_mia"], "overrides": {"fans.export": True}},
timeout=30,
)
r.raise_for_status()
print(r.json())The answer is the updated member. status: "disabled" cuts access at once; the role and creators are kept.
Delete a member #
/members/{id}PlannedNeeds ownercurl -X DELETE https://api.lanachat.ai/v1/members/mem_4 \
-H "Authorization: Bearer $LANACHAT_TOKEN"const res = await fetch('https://api.lanachat.ai/v1/members/mem_4', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.LANACHAT_TOKEN}`,
},
});
if (!res.ok) throw new Error((await res.json()).error.message);
console.log(await res.json());import os
import requests
r = requests.delete(
"https://api.lanachat.ai/v1/members/mem_4",
headers={
"Authorization": f"Bearer {os.environ['LANACHAT_TOKEN']}",
},
timeout=30,
)
r.raise_for_status()
print(r.json())The member is removed and their creators are unassigned. Their past messages keep their name. To keep them for later, disable them instead.
Personal access keys #
Team members don't use a password: they sign in with a personal access key. Keys start with emp_.
Create an access key #
/members/{id}/access-keyPlannedNeeds ownercurl -X POST https://api.lanachat.ai/v1/members/mem_4/access-key \
-H "Authorization: Bearer $LANACHAT_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"const res = await fetch('https://api.lanachat.ai/v1/members/mem_4/access-key', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LANACHAT_TOKEN}`,
'Idempotency-Key': crypto.randomUUID(),
},
});
if (!res.ok) throw new Error((await res.json()).error.message);
console.log(await res.json());import os
import uuid
import requests
r = requests.post(
"https://api.lanachat.ai/v1/members/mem_4/access-key",
headers={
"Authorization": f"Bearer {os.environ['LANACHAT_TOKEN']}",
"Idempotency-Key": str(uuid.uuid4()),
},
timeout=30,
)
r.raise_for_status()
print(r.json()){ "member_id": "mem_4", "sign_in_url": "https://app.lanachat.ai/login/?mode=employee", "access_key": "emp_6Yq2…", "shown_once": true }Send both the sign-in address and the key to the person through a channel you trust. A disabled member answers 409: enable them first.
E-mail an access key #
/members/{id}/invitationPlannedNeeds ownercurl -X POST https://api.lanachat.ai/v1/members/mem_4/invitation \
-H "Authorization: Bearer $LANACHAT_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"const res = await fetch('https://api.lanachat.ai/v1/members/mem_4/invitation', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LANACHAT_TOKEN}`,
'Idempotency-Key': crypto.randomUUID(),
},
});
if (!res.ok) throw new Error((await res.json()).error.message);
console.log(await res.json());import os
import uuid
import requests
r = requests.post(
"https://api.lanachat.ai/v1/members/mem_4/invitation",
headers={
"Authorization": f"Bearer {os.environ['LANACHAT_TOKEN']}",
"Idempotency-Key": str(uuid.uuid4()),
},
timeout=30,
)
r.raise_for_status()
print(r.json())The key is e-mailed to the address on their card. A member without an e-mail address, or a disabled one, answers 409.
Revoke an access key #
/members/{id}/access-keyPlannedNeeds ownercurl -X DELETE https://api.lanachat.ai/v1/members/mem_4/access-key \
-H "Authorization: Bearer $LANACHAT_TOKEN"const res = await fetch('https://api.lanachat.ai/v1/members/mem_4/access-key', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.LANACHAT_TOKEN}`,
},
});
if (!res.ok) throw new Error((await res.json()).error.message);
console.log(await res.json());import os
import requests
r = requests.delete(
"https://api.lanachat.ai/v1/members/mem_4/access-key",
headers={
"Authorization": f"Bearer {os.environ['LANACHAT_TOKEN']}",
},
timeout=30,
)
r.raise_for_status()
print(r.json())The key stops at once and the member is signed out. Create a new key to let them back in.
Roles #
List roles #
/rolesPlannedNeeds ownercurl https://api.lanachat.ai/v1/roles \
-H "Authorization: Bearer $LANACHAT_TOKEN"const res = await fetch('https://api.lanachat.ai/v1/roles', {
headers: {
Authorization: `Bearer ${process.env.LANACHAT_TOKEN}`,
},
});
if (!res.ok) throw new Error((await res.json()).error.message);
console.log(await res.json());import os
import requests
r = requests.get(
"https://api.lanachat.ai/v1/roles",
headers={
"Authorization": f"Bearer {os.environ['LANACHAT_TOKEN']}",
},
timeout=30,
)
r.raise_for_status()
print(r.json())A list of role objects: Owner, Manager, Chatter and your custom roles.
Create a custom role #
/rolesPlannedNeeds owner| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | The role's name |
permissions | object | No | The rows to turn on; the others are off |
curl -X POST https://api.lanachat.ai/v1/roles \
-H "Authorization: Bearer $LANACHAT_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"name": "Night chatter", "permissions": {"live.read": true, "live.reply": true, "fans.read": true}}'const res = await fetch('https://api.lanachat.ai/v1/roles', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LANACHAT_TOKEN}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Night chatter',
permissions: { 'live.read': true, 'live.reply': true, 'fans.read': true },
}),
});
if (!res.ok) throw new Error((await res.json()).error.message);
console.log(await res.json());import os
import uuid
import requests
r = requests.post(
"https://api.lanachat.ai/v1/roles",
headers={
"Authorization": f"Bearer {os.environ['LANACHAT_TOKEN']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"name": "Night chatter",
"permissions": {"live.read": True, "live.reply": True, "fans.read": True},
},
timeout=30,
)
r.raise_for_status()
print(r.json())The answer is the new role (201) with builtin: false.
Update a role #
/roles/{id}PlannedNeeds owner| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | No | New name (custom roles only) |
permissions | object | No | The rows to change; the others are kept |
curl -X PATCH https://api.lanachat.ai/v1/roles/role_chatter \
-H "Authorization: Bearer $LANACHAT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"permissions": {"fans.export": false}}'const res = await fetch('https://api.lanachat.ai/v1/roles/role_chatter', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.LANACHAT_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ permissions: { 'fans.export': false } }),
});
if (!res.ok) throw new Error((await res.json()).error.message);
console.log(await res.json());import os
import requests
r = requests.patch(
"https://api.lanachat.ai/v1/roles/role_chatter",
headers={
"Authorization": f"Bearer {os.environ['LANACHAT_TOKEN']}",
},
json={"permissions": {"fans.export": False}},
timeout=30,
)
r.raise_for_status()
print(r.json())The change applies at once to every member with that role, except their own overrides. Editing Owner, or renaming Manager or Chatter, answers 409.
Delete a custom role #
/roles/{id}PlannedNeeds ownercurl -X DELETE https://api.lanachat.ai/v1/roles/role_night \
-H "Authorization: Bearer $LANACHAT_TOKEN"const res = await fetch('https://api.lanachat.ai/v1/roles/role_night', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.LANACHAT_TOKEN}`,
},
});
if (!res.ok) throw new Error((await res.json()).error.message);
console.log(await res.json());import os
import requests
r = requests.delete(
"https://api.lanachat.ai/v1/roles/role_night",
headers={
"Authorization": f"Bearer {os.environ['LANACHAT_TOKEN']}",
},
timeout=30,
)
r.raise_for_status()
print(r.json())Deleting a role that members still have answers 409: give them another role first. Built-in roles cannot be deleted.
Access tokens #
List tokens #
/tokensPlannedNeeds owner| Parameter | Type | Required | Description |
|---|---|---|---|
include_revoked | boolean | No | true also lists revoked tokens |
curl "https://api.lanachat.ai/v1/tokens?include_revoked=true" \
-H "Authorization: Bearer $LANACHAT_TOKEN"const res = await fetch('https://api.lanachat.ai/v1/tokens?include_revoked=true', {
headers: {
Authorization: `Bearer ${process.env.LANACHAT_TOKEN}`,
},
});
if (!res.ok) throw new Error((await res.json()).error.message);
console.log(await res.json());import os
import requests
r = requests.get(
"https://api.lanachat.ai/v1/tokens?include_revoked=true",
headers={
"Authorization": f"Bearer {os.environ['LANACHAT_TOKEN']}",
},
timeout=30,
)
r.raise_for_status()
print(r.json())A list of access token objects, without their secret.
Create a token #
/tokensPlannedNeeds owner| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | What it is for, up to 100 characters |
permissions | array of strings | Yes | The permissions it gets |
expires_in_days | integer | No | When it stops |
curl -X POST https://api.lanachat.ai/v1/tokens \
-H "Authorization: Bearer $LANACHAT_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"name": "Reporting script", "permissions": ["can_fan_read", "can_sales_read"], "expires_in_days": 90}'const res = await fetch('https://api.lanachat.ai/v1/tokens', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LANACHAT_TOKEN}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Reporting script',
permissions: ['can_fan_read', 'can_sales_read'],
expires_in_days: 90,
}),
});
if (!res.ok) throw new Error((await res.json()).error.message);
console.log(await res.json());import os
import uuid
import requests
r = requests.post(
"https://api.lanachat.ai/v1/tokens",
headers={
"Authorization": f"Bearer {os.environ['LANACHAT_TOKEN']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"name": "Reporting script",
"permissions": ["can_fan_read", "can_sales_read"],
"expires_in_days": 90,
},
timeout=30,
)
r.raise_for_status()
print(r.json()){ "id": "tok_31", "name": "Reporting script", "token": "lc_live_xxxxxxxxxxxxxxxx", "permissions": ["can_fan_read", "can_sales_read"], "expires_at": "2027-01-04T15:10:00Z" }token is in this answer only. A token can never give more than the owner token that created it. A missing name answers 400.
Update a token #
/tokens/{id}PlannedNeeds owner| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | No | New name |
permissions | array of strings | No | Replaces its permissions |
curl -X PATCH https://api.lanachat.ai/v1/tokens/tok_31 \
-H "Authorization: Bearer $LANACHAT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"permissions": ["can_sales_read"]}'const res = await fetch('https://api.lanachat.ai/v1/tokens/tok_31', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.LANACHAT_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ permissions: ['can_sales_read'] }),
});
if (!res.ok) throw new Error((await res.json()).error.message);
console.log(await res.json());import os
import requests
r = requests.patch(
"https://api.lanachat.ai/v1/tokens/tok_31",
headers={
"Authorization": f"Bearer {os.environ['LANACHAT_TOKEN']}",
},
json={"permissions": ["can_sales_read"]},
timeout=30,
)
r.raise_for_status()
print(r.json())The answer is the token, without its secret. Turning can_engine off also removes publishing, Advanced AI and experiment-report permissions. A revoked token answers 409.
Rotate a token #
/tokens/{id}/rotatePlannedNeeds ownercurl -X POST https://api.lanachat.ai/v1/tokens/tok_31/rotate \
-H "Authorization: Bearer $LANACHAT_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"const res = await fetch('https://api.lanachat.ai/v1/tokens/tok_31/rotate', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LANACHAT_TOKEN}`,
'Idempotency-Key': crypto.randomUUID(),
},
});
if (!res.ok) throw new Error((await res.json()).error.message);
console.log(await res.json());import os
import uuid
import requests
r = requests.post(
"https://api.lanachat.ai/v1/tokens/tok_31/rotate",
headers={
"Authorization": f"Bearer {os.environ['LANACHAT_TOKEN']}",
"Idempotency-Key": str(uuid.uuid4()),
},
timeout=30,
)
r.raise_for_status()
print(r.json())The answer is the token with a new token secret, shown once. The old secret stops at once, so update your script right after. A revoked token answers 409.
Revoke a token #
/tokens/{id}PlannedNeeds ownercurl -X DELETE https://api.lanachat.ai/v1/tokens/tok_31 \
-H "Authorization: Bearer $LANACHAT_TOKEN"const res = await fetch('https://api.lanachat.ai/v1/tokens/tok_31', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.LANACHAT_TOKEN}`,
},
});
if (!res.ok) throw new Error((await res.json()).error.message);
console.log(await res.json());import os
import requests
r = requests.delete(
"https://api.lanachat.ai/v1/tokens/tok_31",
headers={
"Authorization": f"Bearer {os.environ['LANACHAT_TOKEN']}",
},
timeout=30,
)
r.raise_for_status()
print(r.json())The token stops at once. A token already revoked answers 409.
A token works like a password. Revoke it as soon as you stop using it or think it leaked.
Errors #
Every endpoint answers errors in the same JSON body, {"error": {"code": "…", "message": "…"}}, with the common codes 400, 401, 402, 403, 404, 409, 429 and 500 listed in Errors. What the endpoints of this page say they answer:
| Endpoint | Status | When |
|---|---|---|
| Get a member | 404 | An unknown id answers 404. |
| Create an access key | 409 | A disabled member answers 409: enable them first. |
| E-mail an access key | 409 | A member without an e-mail address, or a disabled one, answers 409. |
| Update a role | 409 | Editing Owner, or renaming Manager or Chatter, answers 409. |
| Delete a custom role | 409 | Deleting a role that members still have answers 409: give them another role first. |
| Create a token | 400 | A missing name answers 400. |
| Update a token | 409 | A revoked token answers 409. |
| Rotate a token | 409 | A revoked token answers 409. |
| Revoke a token | 409 | A token already revoked answers 409. |