Skip to content
Liberrex

Developers

Developer documentation

Reference for the Liberrex REST API v1, agent webhooks, the Quality ingestion API, the website chat widget and channel webhooks. Every request and response is JSON over HTTPS.

API v1
Base URLhttps://ai-api.liberrex.com

Overview

The Liberrex API lets your systems work with the AI agents you build in Liberrex AI & Agents: send messages to agents, read conversations and their transcripts, follow credit usage, start task agents from other systems and send interactions to Quality for scoring.

Interfaces

InterfaceAuthenticationUse it to
API v1
/api/v1
API key (key_id:secret)Talk to agents and read conversations and usage from your server.
Agent webhooks
/api/workflows/…/webhook
Agent secret or HMAC signatureStart a task agent when something happens in another system.
Quality ingestion API
/api/quality/ingest
Workspace ingestion key (lxqk_…)Send e-mails, chats and call transcripts from your helpdesk or CRM to be scored.
Website chat widget
/api/public/chat/…
Public widget key and allowed websitesPut an agent on your website or build your own chat interface.
Channel webhooks
/api/hooks/channels/…
Provider signatures and verify tokensReceive WhatsApp, Messenger, Instagram, SMS and Telegram messages.

Core objects

ObjectDescription
WorkspaceYour company account. Every key, agent, conversation and credit belongs to one workspace; the API never returns data of another workspace.
ProjectA group of agents inside the workspace. A key can be restricted to one project.
AgentAn AI agent built in Liberrex (phone, chat, messaging, e-mail, task, scheduled or employee assistant), with draft and published versions.
ConversationOne call or chat handled by an agent, with its messages and post-conversation analysis. Conversations started through the API use the api channel.
ChannelWhere an agent answers: phone line, website chat widget, WhatsApp, SMS, Telegram, Messenger, Instagram or e-mail.
Credits and usageAgent activity uses credits from the workspace balance, for example one AI reply. GET /api/v1/usage returns the balance and the usage of the current period.
Endpoints
GET   /api/v1/health
GET   /api/v1/me
GET   /api/v1/agents
GET   /api/v1/agents/{agent_uuid}
POST  /api/v1/agents/{agent_uuid}/messages
GET   /api/v1/conversations
GET   /api/v1/conversations/{conversation_id}
POST  /api/v1/conversations/{conversation_id}/end
GET   /api/v1/usage

POST  /api/workflows/{workflow_uuid}/webhook
POST  /api/quality/ingest
GET   /api/public/chat/{widget_key}/config
POST  /api/public/chat/{widget_key}/messages
POST  /api/hooks/channels/{channel_uuid}

Quick start

  1. Create a key in Dashboard › API keys. Name it, optionally restrict it to a project, and select the read and execute access levels.
  2. Copy the full key when it is shown: it is displayed only once. Store it in a secret manager or an environment variable, never in source code.
  3. Call GET /api/v1/me to check the key, then GET /api/v1/agents to find an agent whose accepts_messages field is true.
  4. Send a message with POST /api/v1/agents/{agent_uuid}/messages. Keep the returned session_id to continue the conversation.

GET /api/v1/health needs no key: use it to check that the service answers.

The API is available on plans that include API access — see your plan in Billing.

Check the key, list agents, send a message

export LIBERREX_API_KEY="YOUR_KEY_ID:YOUR_SECRET"

# 1. Check the key
curl "https://ai-api.liberrex.com/api/v1/me" \
  -H "Authorization: Bearer $LIBERREX_API_KEY"

# 2. List your agents (copy the uuid of a chat agent)
curl "https://ai-api.liberrex.com/api/v1/agents" \
  -H "Authorization: Bearer $LIBERREX_API_KEY"

# 3. Send a message to the agent
curl -X POST "https://ai-api.liberrex.com/api/v1/agents/AGENT_UUID/messages" \
  -H "Authorization: Bearer $LIBERREX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"message": "Hello, I lost my card"}'

Authentication

Requests to /api/v1 are authenticated with a personal API key of the form key_id:secret. The key_id is public; the secret is shown once, when the key is created, and only its SHA-256 hash is stored.

Sending the key

OptionExampleNote
Authorization header (recommended)Authorization: Bearer key_id:secretUsed whenever it starts with Bearer.
X-API-Key headerX-API-Key: key_id:secretUsed when there is no Bearer Authorization header.
Query parameter?api_key=key_id:secretAvoid: URLs end up in logs and browser history.

Access levels

Each key holds one or more access levels, chosen when it is created. Each endpoint requires one level; admin grants every level.

LevelGrants
readGET /me, /agents, /agents/{agent_uuid}, /conversations, /conversations/{conversation_id} and /usage.
executePOST /agents/{agent_uuid}/messages and /conversations/{conversation_id}/end.
adminEvery level.
write, transcribe, synthesizeCan be selected in the dashboard; no v1 endpoint requires them today.

Key owner, scope and plan

  • A key acts for the person who created it, inside their workspace, with the permissions of their role. It is refused (401) as soon as the owner is deactivated or their role loses the API keys permission (manage:api_keys).
  • Reading conversations also requires the read:transcripts permission on the owner's role; otherwise the API answers 403 forbidden.
  • A key restricted to a project only sees the agents and conversations of that project. If the project is deleted, the key gets 403.
  • A key stops working (401) when it is deactivated, deleted or past its expiry date.
  • The API is available on plans that include API access — see your plan in Billing. On other plans, requests are refused with 403 and the code plan_required.

Security best practices

  • Call the API from your servers only. Never put a key in a web page, a mobile app or a public repository: anyone holding it acts with your permissions.
  • Create one key per integration, restricted to its project and to the access levels it needs (read only for reporting).
  • Rotate keys: create the new key, deploy it, check it with GET /api/v1/me, then delete the old one.
  • Keep keys out of URLs: do not use the api_key query parameter in production.
  • Follow the requests made with each key in GET /api/v1/usage (api_key.requests_this_period) and delete keys you no longer use.

Authenticated request

curl "https://ai-api.liberrex.com/api/v1/me" \
  -H "Authorization: Bearer $LIBERREX_API_KEY"
401 Unauthorized
HTTP/1.1 401 UNAUTHORIZED
Content-Type: application/json

{
  "success": false,
  "message": "Invalid API key",
  "code": "unauthorized"
}

Requests and responses

Base URL and format

All paths are relative to https://ai-api.liberrex.com. Use HTTPS. Request and response bodies are JSON encoded in UTF-8.

POST requests with a body must send Content-Type: application/json: without it the body is not read and the API answers 400.

Response envelope

Every v1 response is a JSON object with a boolean success. On success the result is in data; on error the body holds a readable message and a stable code. Test the code in your integration, not the message.

Messages are in English. Send Accept-Language: fr or Accept-Language: ar (a bare language code) to receive them in French or Arabic; codes are never translated.

Unknown paths and wrong methods get 404 or 405 with success set to false and a message, but no code.

Data types

ItemFormat
TimestampsISO 8601 in UTC, e.g. 2026-10-11T09:30:00Z. Fractional seconds may be present, and message times use the +00:00 offset: parse them with a standard ISO 8601 parser.
IdentifiersStrings. Agents and projects use UUIDs; conversation ids are opaque strings (API conversations start with api-). Do not parse them.
Empty valuesDocumented fields are always present; a missing value is null, an empty list or an empty object.
New fieldsFields may be added to responses: ignore the ones you do not use.

Pagination

Only GET /api/v1/conversations is paginated, with page (from 1) and per_page (1 to 100, default 20). The response carries a pagination object. Other lists return every item in scope in one response.

Filtering

EndpointParameters
GET /api/v1/agentssearch
GET /api/v1/conversationsagent, channel, status, since, until, include_tests
Success
{
  "success": true,
  "data": { … }
}
Error
{
  "success": false,
  "message": "message is required",
  "code": "invalid_request"
}
Pagination
"pagination": {
  "page": 2,
  "per_page": 20,
  "total": 57,
  "pages": 3
}

Errors

Errors use standard HTTP status codes and the error envelope. The code field takes one of the values below.

StatuscodeWhen
400invalid_requestA body field or a query parameter is missing or invalid, or the agent cannot chat.
401unauthorizedNo key, a malformed or wrong key, a deactivated or expired key, or an owner who no longer has access.
402insufficient_creditsNo credits left and the plan does not allow overage. Buy credits or change plan in Billing.
403forbiddenThe key lacks the required access level, the owner's role lacks a required permission, or the key's project no longer exists.
403plan_requiredThe plan of the workspace does not include API access.
404not_foundThe agent or conversation does not exist or is outside the key scope.
409conversation_closedThe conversation has ended or reached 200 exchanges.
429rate_limitedToo many requests with this key in the current minute.
500server_errorUnexpected error on our side. Retry later; contact support if it persists.

Order of checks

  1. Authentication (401). Refused keys are not counted against the rate limit.
  2. Rate limit (429).
  3. Access level, then owner permission (403 forbidden).
  4. Plan (403 plan_required).
  5. Validation and processing (400, 402, 404, 409, 500).

Every request that passes authentication is logged on the key and counted against its rate limit, including refused ones.

403 Forbidden
HTTP/1.1 403 FORBIDDEN
Content-Type: application/json
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 97

{
  "success": false,
  "message": "This API key does not have the 'execute' access level",
  "code": "forbidden"
}

Rate limits

Each key may send a set number of requests per minute: 100 by default. The value is returned as rate_limit_per_minute by GET /api/v1/me and GET /api/v1/usage. The counter is shared by all servers and resets at the start of each clock minute.

HeaderDescription
X-RateLimit-LimitRequests allowed per minute for this key.
X-RateLimit-RemainingRequests left in the current minute.
Retry-AfterOn 429 only: seconds until the counter resets.

The headers are sent on every response to an authenticated request, errors included. GET /api/v1/health is not rate limited.

Handling 429

  • Wait for the number of seconds in Retry-After, then retry. A request refused with 429 was not processed, so retrying it is safe, POST included.
  • Retry 500 errors on GET requests with exponential backoff. Do not blindly retry POST …/messages after a 500 or a timeout: the message may have been processed.
  • Spread batch jobs over time and read X-RateLimit-Remaining to slow down before reaching the limit.

Agent webhooks and the chat widget have their own limits: see their sections.

Retry with backoff

# curl retries 429 and 5xx responses and honours Retry-After (curl 7.66+).
# Only use --retry on GET requests: a POST may already have been processed.
curl --retry 5 "https://ai-api.liberrex.com/api/v1/conversations?page=2" \
  -H "Authorization: Bearer $LIBERREX_API_KEY"
429 Too Many Requests
HTTP/1.1 429 TOO MANY REQUESTS
Content-Type: application/json
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
Retry-After: 17

{
  "success": false,
  "message": "Rate limit exceeded: retry in 17 seconds",
  "code": "rate_limited"
}

API reference

Endpoints of the REST API v1, under https://ai-api.liberrex.com/api/v1. Every endpoint except the health check needs an API key; examples use the LIBERREX_API_KEY environment variable.

Check the service

GET/api/v1/health

Returns the status of the API. It needs no key, is not rate limited and does not carry rate limit headers: use it for uptime checks.

Authentication
None

Response

NameTypeDescription
statusstringAlways ok when the API answers.
versionstringAPI version.v1
timestringServer time (UTC).

Request

curl "https://ai-api.liberrex.com/api/v1/health"
200 OK
{
  "success": true,
  "data": {
    "status": "ok",
    "version": "v1",
    "time": "2026-10-11T09:30:00.412Z"
  }
}

Retrieve the current key

GET/api/v1/me

Returns the key used for the request, its owner and their permissions, the workspace and the plan in effect. Call it first to check a new key.

Authentication
API key with the read access level

Response

NameTypeDescription
key.key_idstringPublic part of the key (before the colon).
key.namestringName given to the key.
key.access_levelsstring[]Access levels of the key.read, write, execute, transcribe, synthesize, admin
key.projectobject | nullProject the key is restricted to (uuid and name); null for the whole workspace.
key.rate_limit_per_minuteintegerRequests allowed per minute with this key.
key.expires_atstring | nullExpiry time; null when the key does not expire.
key.created_atstringCreation time.
key.last_used_atstring | nullTime of the previous request made with the key.
ownerobjectThe person who created the key: uuid, name, email, role and permissions. The key acts with these permissions.
companyobjectThe workspace: id and name.
planstringIdentifier of the plan in effect for the workspace.

Errors

Authentication, access level, plan, rate limit and server errors are common to every endpoint: see Errors.

Request

curl "https://ai-api.liberrex.com/api/v1/me" \
  -H "Authorization: Bearer $LIBERREX_API_KEY"
200 OK
{
  "success": true,
  "data": {
    "key": {
      "key_id": "Xk3v9QmB2t7LpA1sZr8wQe",
      "name": "CRM integration",
      "access_levels": ["read", "execute"],
      "project": { "uuid": "4b1f2d9e-7c1a-4e8b-9a51-2f0c3d6e8a10", "name": "Customer care" },
      "rate_limit_per_minute": 100,
      "expires_at": null,
      "created_at": "2026-10-01T08:12:44Z",
      "last_used_at": "2026-10-11T09:29:58Z"
    },
    "owner": {
      "uuid": "9c2e6b1a-3f4d-4c8e-b7a2-5d9e0f1a2b3c",
      "name": "Sami Ben Ali",
      "email": "sami@example.com",
      "role": "developer",
      "permissions": ["manage:api_keys", "read:analytics", "read:transcripts"]
    },
    "company": { "id": "example-bank", "name": "Example Bank" },
    "plan": "your_plan_id"
  }
}

List agents

GET/api/v1/agents

Lists the agents in the scope of the key (the workspace, or the project of the key), most recently updated first. The list is not paginated: every agent in scope is returned.

Authentication
API key with the read access level

Query parameters

NameTypeRequiredDescription
searchstringOptionalOnly agents whose name contains this text (case-insensitive).

Response

NameTypeDescription
itemsAgent[]The agents. See the agent object below.
totalintegerNumber of agents returned.

The agent object

NameTypeDescription
uuidstringAgent identifier.
namestringAgent name.
descriptionstringAgent description; empty string when there is none.
typestring | nullAgent type.voice_inbound, voice_outbound, chat, messaging, email, task, scheduled, employee
projectobject | nullProject of the agent: uuid and name.
publishedbooleanWhether a version of the agent is published.
published_versioninteger | nullNumber of the published version.
published_atstring | nullWhen that version was published.
accepts_messagesbooleanWhether the agent has a chat step and can answer POST /api/v1/agents/{agent_uuid}/messages.
channelsobject[]Channels answered by the agent: id, name, type and status.
webhookobject | nullOnly for agents with a Webhook trigger: url (path of the trigger, relative to the base URL), secret_required and accepts_runs.
created_atstringCreation time.
updated_atstringLast update time.

Errors

Authentication, access level, plan, rate limit and server errors are common to every endpoint: see Errors.

Request

curl "https://ai-api.liberrex.com/api/v1/agents?search=support" \
  -H "Authorization: Bearer $LIBERREX_API_KEY"
200 OK
{
  "success": true,
  "data": {
    "items": [
      {
        "uuid": "c6d136ac-b6d4-4804-8290-385662d94417",
        "name": "Card support",
        "description": "Answers card questions and blocks lost cards",
        "type": "chat",
        "project": { "uuid": "4b1f2d9e-7c1a-4e8b-9a51-2f0c3d6e8a10", "name": "Customer care" },
        "published": true,
        "published_version": 3,
        "published_at": "2026-10-09T15:02:11Z",
        "accepts_messages": true,
        "channels": [
          { "id": 12, "name": "Website", "type": "chat", "status": "active" }
        ],
        "webhook": null,
        "created_at": "2026-09-30T10:00:00Z",
        "updated_at": "2026-10-09T15:01:57Z"
      }
    ],
    "total": 1
  }
}

Retrieve an agent

GET/api/v1/agents/{agent_uuid}

Returns one agent of the key scope.

Authentication
API key with the read access level

Path parameters

NameTypeRequiredDescription
agent_uuidstringRequiredIdentifier of the agent (uuid in GET /api/v1/agents).

Response

NameTypeDescription
uuidstringAgent identifier.
namestringAgent name.
descriptionstringAgent description; empty string when there is none.
typestring | nullAgent type.voice_inbound, voice_outbound, chat, messaging, email, task, scheduled, employee
projectobject | nullProject of the agent: uuid and name.
publishedbooleanWhether a version of the agent is published.
published_versioninteger | nullNumber of the published version.
published_atstring | nullWhen that version was published.
accepts_messagesbooleanWhether the agent has a chat step and can answer POST /api/v1/agents/{agent_uuid}/messages.
channelsobject[]Channels answered by the agent: id, name, type and status.
webhookobject | nullOnly for agents with a Webhook trigger: url (path of the trigger, relative to the base URL), secret_required and accepts_runs.
created_atstringCreation time.
updated_atstringLast update time.

Errors

StatuscodeWhen
404not_foundNo agent with this uuid in the key scope.

Authentication, access level, plan, rate limit and server errors are common to every endpoint: see Errors.

Request

curl "https://ai-api.liberrex.com/api/v1/agents/c6d136ac-b6d4-4804-8290-385662d94417" \
  -H "Authorization: Bearer $LIBERREX_API_KEY"
200 OK
{
  "success": true,
  "data": {
    "uuid": "0d8f5a7e-2b9c-4e61-9f3a-7c1d2e3f4a5b",
    "name": "Claim intake",
    "description": "Creates a claim from the CRM ticket",
    "type": "task",
    "project": { "uuid": "4b1f2d9e-7c1a-4e8b-9a51-2f0c3d6e8a10", "name": "Customer care" },
    "published": true,
    "published_version": 5,
    "published_at": "2026-10-08T11:20:40Z",
    "accepts_messages": false,
    "channels": [],
    "webhook": {
      "url": "/api/workflows/7f3c1b2a-9d4e-4f5a-8b6c-1d2e3f4a5b6c/webhook",
      "secret_required": true,
      "accepts_runs": true
    },
    "created_at": "2026-09-12T07:45:03Z",
    "updated_at": "2026-10-08T11:20:31Z"
  }
}

Send a message

POST/api/v1/agents/{agent_uuid}/messages

Sends one customer message to the agent and returns its reply. Without session_id a new conversation starts on the api channel; send the returned session_id with the next messages to continue it. The call is synchronous: it returns when the agent has answered, which can take several seconds.

Authentication
API key with the execute access level
  • The published version of the agent answers; an agent that was never published answers with its draft (version is then draft).
  • The agent must have a chat step (accepts_messages is true).
  • Each reply with text uses AI reply credits, like the website chat and messaging channels.
  • A conversation holds at most 200 exchanges; after that, start a new one.

Path parameters

NameTypeRequiredDescription
agent_uuidstringRequiredIdentifier of the agent (uuid in GET /api/v1/agents).

Body parameters

NameTypeRequiredDescription
messagestringRequiredThe customer message.1 to 4,000 characters, not blank.
session_idstringOptionalContinue a conversation: the session_id returned by its first message.Must be an API conversation of the same agent that is still in progress.
contactobjectOptionalThe person writing: name, email and phone (strings). Stored with the conversation.New conversations only. name up to 120, email up to 254, phone up to 40 characters; longer values are cut.
metadataobjectOptionalYour own data (for example a CRM id), returned with the conversation and given to the agent steps.New conversations only. JSON object up to 4 KB.

Response

NameTypeDescription
replystringThe agent reply; empty when the agent produced no text.
session_idstringConversation to continue: send it with the next message.
conversation_idstringSame value as session_id, for the Conversations endpoints.
agentobjectThe agent that wrote this reply (uuid, name, title, avatar). After a transfer it is the colleague who answered, not the agent you addressed.
handoffsarrayTransfers that happened while answering this message: from and to agent (uuid, name, title), reason, kind (transfer or escalation), at. Empty when none.
turnintegerNumber of this exchange in the conversation, starting at 1.
versionstringVersion that answered: v followed by the published version number, or draft.
latency_msintegerTime the agent took to answer, in milliseconds.

Conversation sessions

  • A message without session_id starts a new conversation. Send the returned session_id with the next messages: the agent keeps the context.
  • contact and metadata are stored when the conversation starts; later values are ignored.
  • End the conversation with POST /api/v1/conversations/{conversation_id}/end when it is over.

Continue and end a conversation

# First message: no session_id, a new conversation starts
SESSION=$(curl -s -X POST "https://ai-api.liberrex.com/api/v1/agents/AGENT_UUID/messages" \
  -H "Authorization: Bearer $LIBERREX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"message": "Hello, I lost my card"}' | jq -r '.data.session_id')

# Next messages: same session_id
curl -X POST "https://ai-api.liberrex.com/api/v1/agents/AGENT_UUID/messages" \
  -H "Authorization: Bearer $LIBERREX_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"message\": \"The last 4 digits are 1234\", \"session_id\": \"$SESSION\"}"

# Done: end the conversation (runs the analysis when it is on)
curl -X POST "https://ai-api.liberrex.com/api/v1/conversations/$SESSION/end" \
  -H "Authorization: Bearer $LIBERREX_API_KEY"

Errors

StatuscodeWhen
400invalid_requestThe body is not a JSON object, a field is missing or invalid, or the agent has no chat step.
402insufficient_creditsNo credits left and the plan does not allow overage.
404not_foundUnknown agent in the key scope, or unknown session_id for this agent.
409conversation_closedThe conversation has ended or reached 200 exchanges: start a new one.

Authentication, access level, plan, rate limit and server errors are common to every endpoint: see Errors.

Request

curl -X POST "https://ai-api.liberrex.com/api/v1/agents/c6d136ac-b6d4-4804-8290-385662d94417/messages" \
  -H "Authorization: Bearer $LIBERREX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "message": "Hello, I lost my card",
  "contact": {
    "name": "Sami",
    "email": "sami@example.com"
  },
  "metadata": {
    "crm_id": "C-42"
  }
}'
200 OK
{
  "success": true,
  "data": {
    "reply": "I am sorry to hear that. I can block the card right away.",
    "session_id": "api-9af090c2e0fb49218e4a57e3",
    "conversation_id": "api-9af090c2e0fb49218e4a57e3",
    "agent": { "uuid": "c6d136ac-b6d4-4804-8290-385662d94417", "name": "Card support", "title": "Card specialist", "avatar": null },
    "handoffs": [],
    "turn": 1,
    "version": "v3",
    "latency_ms": 1840
  }
}

List conversations

GET/api/v1/conversations

Lists the calls and chats of the agents in scope (phone, website chat, messaging channels and the API), newest first, with page-based pagination. Playground tests are left out unless you ask for them.

Authentication
API key with the read access level
Owner permission
read:transcripts

Query parameters

NameTypeRequiredDescription
agentstringOptionalOnly the conversations of this agent (agent uuid).
channelstringOptionalOnly this channel (exact match). When set, playground tests are not excluded automatically.api, web_chat, whatsapp, sms, telegram, messenger, instagram, email, sip, playground…
statusstringOptionalOnly this status.in_progress, completed, hangup, failed
sincestringOptionalStarted at or after this time.ISO 8601 date or date-time, e.g. 2026-10-01 or 2026-10-01T08:00:00Z. Without a time zone: UTC.
untilstringOptionalStarted before this time (exclusive).ISO 8601, like since.
include_testsbooleanOptionalInclude playground tests.1, true or yes. Default: false.
pageintegerOptionalPage number.Default 1; values below 1 count as 1.
per_pageintegerOptionalConversations per page.Default 20; clamped between 1 and 100.

Response

NameTypeDescription
itemsConversation[]The conversations of this page. See the conversation object below.
pagination.pageintegerCurrent page.
pagination.per_pageintegerPage size actually used.
pagination.totalintegerNumber of conversations matching the filters.
pagination.pagesintegerNumber of pages (at least 1).

The conversation object

NameTypeDescription
idstringConversation identifier. Treat it as an opaque string.
agentobject | nullThe agent that handled it: uuid and name.
channelstringChannel of the conversation.api, web_chat, whatsapp, sms, telegram, messenger, instagram, email, sip, playground…
sourcestringHow the conversation started, e.g. api, chat, live, outbound, scheduled or playground.
statusstringConversation status.in_progress, completed, hangup, failed
testbooleantrue for tests from the agent playground.
contactobjectAPI conversations: name, email and phone sent with the first message. Other channels: name and address (phone number, e-mail address or channel user id).
turnsintegerNumber of exchanges (one customer message and the agent reply).
started_atstringStart time.
ended_atstring | nullEnd time; null while the conversation is in progress.
duration_secondsnumber | nullDuration in seconds, when known.
summarystring | nullSummary from the post-conversation analysis, when it ran.

Errors

StatuscodeWhen
400invalid_requestsince or until is not an ISO 8601 date.
403forbiddenThe key owner's role does not include the read:transcripts permission.
404not_foundThe agent filter names an agent outside the key scope.

Authentication, access level, plan, rate limit and server errors are common to every endpoint: see Errors.

Request

curl "https://ai-api.liberrex.com/api/v1/conversations?channel=api&since=2026-10-01&per_page=50" \
  -H "Authorization: Bearer $LIBERREX_API_KEY"
200 OK
{
  "success": true,
  "data": {
    "items": [
      {
        "id": "api-9af090c2e0fb49218e4a57e3",
        "agent": { "uuid": "c6d136ac-b6d4-4804-8290-385662d94417", "name": "Card support" },
        "channel": "api",
        "source": "api",
        "status": "completed",
        "test": false,
        "contact": { "name": "Sami", "email": "sami@example.com", "phone": null },
        "turns": 2,
        "started_at": "2026-10-11T09:30:02.118Z",
        "ended_at": "2026-10-11T09:34:40.902Z",
        "duration_seconds": 278,
        "summary": "The customer reported a lost card; the card was blocked."
      }
    ],
    "pagination": { "page": 1, "per_page": 50, "total": 1, "pages": 1 }
  }
}

Retrieve a conversation

GET/api/v1/conversations/{conversation_id}

Returns one conversation with its messages, your metadata and the post-conversation analysis when there is one.

Authentication
API key with the read access level
Owner permission
read:transcripts

Path parameters

NameTypeRequiredDescription
conversation_idstringRequiredIdentifier of the conversation (id in the conversation object, or session_id returned when sending a message).

Response

NameTypeDescription
…Every field of the conversation object, plus:
messagesobject[]Messages in order: turn, role (user = the customer, assistant = the agent), text and at.
metadataobjectThe metadata sent with the first API message; {} for other channels.
analysisobject | nullPost-conversation analysis: success, summary, fields (the values extracted) and analyzed_at; null when it did not run.

Errors

StatuscodeWhen
403forbiddenThe key owner's role does not include the read:transcripts permission.
404not_foundNo conversation with this id in the key scope.

Authentication, access level, plan, rate limit and server errors are common to every endpoint: see Errors.

Request

curl "https://ai-api.liberrex.com/api/v1/conversations/api-9af090c2e0fb49218e4a57e3" \
  -H "Authorization: Bearer $LIBERREX_API_KEY"
200 OK
{
  "success": true,
  "data": {
    "id": "api-9af090c2e0fb49218e4a57e3",
    "agent": { "uuid": "c6d136ac-b6d4-4804-8290-385662d94417", "name": "Card support" },
    "channel": "api",
    "source": "api",
    "status": "completed",
    "test": false,
    "contact": { "name": "Sami", "email": "sami@example.com", "phone": null },
    "turns": 1,
    "started_at": "2026-10-11T09:30:02.118Z",
    "ended_at": "2026-10-11T09:34:40.902Z",
    "duration_seconds": 278,
    "summary": "The customer reported a lost card; the card was blocked.",
    "messages": [
      { "turn": 1, "role": "user", "text": "Hello, I lost my card", "at": "2026-10-11T09:30:02.120511+00:00" },
      { "turn": 1, "role": "assistant", "text": "I am sorry to hear that. I can block the card right away.", "at": "2026-10-11T09:30:03.961200+00:00" }
    ],
    "metadata": { "crm_id": "C-42" },
    "analysis": {
      "success": true,
      "summary": "The customer reported a lost card; the card was blocked.",
      "fields": { "card_blocked": true },
      "analyzed_at": "2026-10-11T09:34:52Z"
    }
  }
}

End a conversation

POST/api/v1/conversations/{conversation_id}/end

Closes a conversation started through the API and returns it. When post-conversation analysis is turned on for the agent, it runs in the background; read the result later with GET /api/v1/conversations/{conversation_id}. Ending a conversation that has already ended returns it unchanged. No request body.

Authentication
API key with the execute access level

Path parameters

NameTypeRequiredDescription
conversation_idstringRequiredIdentifier of the conversation (id in the conversation object, or session_id returned when sending a message).

Response

NameTypeDescription
…ConversationThe conversation object, with status completed.

Errors

StatuscodeWhen
404not_foundNo API conversation with this id in the key scope (conversations of other channels cannot be ended here).

Authentication, access level, plan, rate limit and server errors are common to every endpoint: see Errors.

Request

curl -X POST "https://ai-api.liberrex.com/api/v1/conversations/api-9af090c2e0fb49218e4a57e3/end" \
  -H "Authorization: Bearer $LIBERREX_API_KEY"
200 OK
{
  "success": true,
  "data": {
    "id": "api-9af090c2e0fb49218e4a57e3",
    "agent": { "uuid": "c6d136ac-b6d4-4804-8290-385662d94417", "name": "Card support" },
    "channel": "api",
    "source": "api",
    "status": "completed",
    "test": false,
    "contact": { "name": "Sami", "email": "sami@example.com", "phone": null },
    "turns": 2,
    "started_at": "2026-10-11T09:30:02.118Z",
    "ended_at": "2026-10-11T09:34:40.902Z",
    "duration_seconds": 278,
    "summary": null
  }
}

Retrieve usage

GET/api/v1/usage

Returns the plan of the workspace, the credit balance and what was used in the current billing period, by meter and by agent, plus the requests made with this key in the period.

Authentication
API key with the read access level

Response

NameTypeDescription
planobjectid and name of the plan in effect, subscribed_plan, status, cycle, included_credits and overage (whether usage may continue past the balance).
balanceobjectCredits left: plan (included credits), purchased and total.
period.start, period.endstring | nullCurrent billing period.
period.total_creditsnumberCredits used in the period.
period.itemsobject[]Usage per meter: metric, label, unit, quantity, credits and events.
period.agentsobject[]Credits per agent: agent_uuid, name and credits.
api_keyobjectThis key: key_id, requests_this_period and rate_limit_per_minute.

Errors

Authentication, access level, plan, rate limit and server errors are common to every endpoint: see Errors.

Request

curl "https://ai-api.liberrex.com/api/v1/usage" \
  -H "Authorization: Bearer $LIBERREX_API_KEY"
200 OK
{
  "success": true,
  "data": {
    "plan": {
      "id": "your_plan_id",
      "name": "Your plan",
      "subscribed_plan": "your_plan_id",
      "status": "active",
      "cycle": "monthly",
      "included_credits": 1000,
      "overage": false
    },
    "balance": { "plan": 412.5, "purchased": 0.0, "total": 412.5 },
    "period": {
      "start": "2026-10-01T00:00:00Z",
      "end": "2026-11-01T00:00:00Z",
      "total_credits": 587.5,
      "items": [
        { "metric": "ai_message", "label": "AI replies", "unit": "reply", "quantity": 587, "credits": 587.5, "events": 587 }
      ],
      "agents": [
        { "agent_uuid": "c6d136ac-b6d4-4804-8290-385662d94417", "name": "Card support", "credits": 587.5 }
      ]
    },
    "api_key": { "key_id": "Xk3v9QmB2t7LpA1sZr8wQe", "requests_this_period": 1203, "rate_limit_per_minute": 100 }
  }
}

Agent webhooks

POST/api/workflows/{workflow_uuid}/webhook

Task agents with a Webhook trigger run when another system calls their URL. The webhook does not use API keys: it is protected by the agent secret. The published version of the agent runs, and the call returns when the run has finished.

Trigger URL

Copy the URL from the agent page (Guardrails tab, Webhook protection), or read webhook.url from the agent object in the API and prefix it with the base URL. The path holds the workflow uuid of the agent, not the agent uuid.

Request

NameTypeDescription
Content-Typeheaderapplication/json for a JSON body. Form fields (application/x-www-form-urlencoded or multipart/form-data) are also accepted.
X-Webhook-SecretheaderThe agent secret itself.When the agent has a secret: send this header or X-Liberrex-Signature.
X-Liberrex-Signatureheadersha256= followed by the hex HMAC-SHA256 of the raw request body, keyed with the agent secret.
bodyobjectAny JSON object. The agent steps receive it as the trigger data.Up to 1 MB.

Signature

To prove that a request comes from you without sending the secret, sign the exact bytes of the body:

Signature
X-Liberrex-Signature: sha256=<hex(HMAC-SHA256(key = agent secret, message = raw body))>
  • Serialise the JSON once, sign that string and send that same string. Re-serialising after signing changes the bytes and the signature fails.
  • The signature covers the body only and carries no timestamp, so a captured request can be replayed. Use HTTPS, keep the secret private and rotate it if it leaks.
  • Without a secret the webhook accepts any call: create one before going to production.

Check your signature like Liberrex does

import crypto from "node:crypto";

// What Liberrex computes for X-Liberrex-Signature (use it to test your signing code).
export function isValidSignature(rawBody, header, secret) {
  if (!header?.startsWith("sha256=")) return false;
  const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const given = header.slice(7);
  return given.length === expected.length
    && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}

Limits

  • Body: 1 MB at most.
  • Each agent accepts 60 calls per minute by default (1 to 1,000, set in Webhook protection). Over the limit the webhook answers 429.

Responses

StatusBodyWhen
200{"success": true, "data": {…}}The run finished: workflow_uuid, trigger_type, run_uuid, results and execution_data.
400{"error": "Workflow is not active"}The agent does not accept runs (paused).
401{"error": "Invalid webhook secret"}Missing or wrong secret or signature.
404{"error": "Workflow not found"}Unknown workflow uuid.
413{"error": "Payload too large"}Body larger than 1 MB.
429{"error": "Too many requests"}Over the per-minute limit of the agent.
500{"success": false, "error": "…"}The run failed: see the Activity tab of the agent.

These responses do not use the v1 envelope: errors carry an error field and no code.

Signed request

BODY='{"ticket_id": "T-1042", "customer_phone": "+21620000000"}'
SIGNATURE=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | sed 's/^.* //')

curl -X POST "https://ai-api.liberrex.com/api/workflows/WORKFLOW_UUID/webhook" \
  -H "Content-Type: application/json" \
  -H "X-Liberrex-Signature: sha256=$SIGNATURE" \
  --data-binary "$BODY"

# Simpler, without a signature: send the secret itself
#   -H "X-Webhook-Secret: $WEBHOOK_SECRET"
200 OK
{
  "success": true,
  "data": {
    "workflow_uuid": "7f3c1b2a-9d4e-4f5a-8b6c-1d2e3f4a5b6c",
    "trigger_type": "webhook",
    "run_uuid": "5d0c8e2f-1a3b-4c5d-9e8f-7a6b5c4d3e2f",
    "results": { "…": "one entry per step that ran" },
    "execution_data": { "…": "the data the steps produced" }
  }
}

Quality ingestion API

POST/api/quality/ingest

Send e-mails, chats and call transcripts from your helpdesk, CRM or chat platform to Quality. Each interaction is queued and scored with your scorecards, like an upload.

Authentication

  • Create the ingestion key in Quality › Settings › Ingestion API. It starts with lxqk_ and is shown once. There is one key per workspace: creating a new key replaces the previous one, and the key can be revoked.
  • Send it as Authorization: Bearer lxqk_… or X-Quality-Key: lxqk_…. API v1 keys are not accepted here.
  • Ingestion needs a plan that includes API access, room in the monthly Quality interactions allowance and credits for the analysis; otherwise the API answers 400 with a message that says which.

Request body

One interaction object, or {"interactions": [ … ]} with 1 to 100 interactions.

Interaction object

NameTypeRequiredDescription
channelstringRequiredType of interaction.call, email, chat
messagesobject[]RequiredThe messages in order (also accepted: turns, transcript). Messages without text are skipped.At most 400 messages; extra ones are ignored.
external_idstringOptionalYour id for the interaction (also accepted: id, conversation_id). Used to skip duplicates.Up to 255 characters.
subchannelstringOptionalWhere it happened (also accepted: source_channel).Default: phone for call, email for email, web_chat for chat. Usual values: phone, voip, email, web_chat, whatsapp, messenger, instagram, sms, telegram, other.
subjectstringOptionalSubject or title (also accepted: title).Up to 300 characters.
agent_namestringOptionalEmployee or AI agent who handled it (also accepted: agent). Default: the agent-side author with the most messages.Up to 120 characters.
teamstringOptionalTeam, to filter and compare in Quality.Up to 120 characters.
agent_typestringOptionalai for an AI agent; any other value means a human agent.Default: human.
directionstringOptionalinbound or outbound.Default: inbound.
occurred_atstring | numberOptionalWhen it happened (also accepted: started_at, date).ISO 8601, Unix time in seconds or milliseconds, YYYY-MM-DD HH:MM[:SS] or DD/MM/YYYY [HH:MM[:SS]]. Default: time of the first message, else now.

Message object

NameTypeRequiredDescription
textstringRequiredMessage text (also accepted: body, content, message).Up to 6,000 characters; longer text is cut.
rolestringOptionalWho wrote it (also accepted: sender_type).Agent side: agent, assistant, bot, ai, operator, support, advisor, staff, employee, business. Customer side: customer, client, user, caller, visitor, contact, guest, end_user.
authorstringOptionalName of the writer (also accepted: from, name, sender, speaker).Up to 80 characters.
emailstringOptionalE-mail address of the writer (also accepted: address), used to recognise your agents by address or domain.
atstring | numberOptionalWhen it was sent (also accepted: timestamp, sent_at, date, created_at).Same formats as occurred_at.

Without a role, authors are matched with the agent e-mail domains, addresses and chat names set in Quality settings; otherwise the first author is taken as the customer. When personal data masking is on in Quality settings, texts are masked before they are stored.

Duplicates

An interaction whose external_id was already received on the same channel is skipped and listed in duplicates. Interactions without external_id are never deduplicated: send one so that retries are safe.

Response

NameTypeDescription
createdobject[]Created interactions: id, external_id and channel. They are scored in the background.
duplicatesstring[]external_id of each skipped duplicate.
errorsobject[]Refused items: index (position in the batch) and error.

Status 201 when at least one interaction was created, 200 otherwise (all duplicates or refused).

Errors

StatusmessageWhen
401Invalid or missing ingestion keyMissing, wrong or revoked key.
400Send a JSON objectThe body is not a JSON object (check Content-Type: application/json).
400Send between 1 and 100 interactionsEmpty or too large batch.
400…Plan without API access, monthly Quality allowance reached or no credits left: the message says which.

Errors use {"success": false, "message": …}, without a code. Refused items of a batch carry one of: Not an object, channel must be one of call, email, chat, messages (with text) are required.

Send a chat to Quality

curl -X POST "https://ai-api.liberrex.com/api/quality/ingest" \
  -H "Authorization: Bearer $LIBERREX_QUALITY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "interactions": [
    {
      "channel": "chat",
      "subchannel": "whatsapp",
      "external_id": "ticket-123",
      "agent_name": "Sarra",
      "team": "Support",
      "subject": "Late tow truck",
      "messages": [
        {
          "role": "customer",
          "author": "Client",
          "text": "The tow truck is still not here.",
          "at": "2026-05-12T14:03:00+01:00"
        },
        {
          "role": "agent",
          "author": "Sarra",
          "text": "I am sorry, it will arrive in 10 minutes.",
          "at": "2026-05-12T14:09:00+01:00"
        }
      ]
    }
  ]
}'
201 Created
{
  "success": true,
  "data": {
    "created": [
      { "id": "8f61c2d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "external_id": "ticket-123", "channel": "chat" }
    ],
    "duplicates": [],
    "errors": []
  }
}

Website chat widget

A Website chat widget channel puts an agent on your website with one script tag. Create the channel in Channels, choose the agent that answers it, list the allowed websites and turn on incoming messages.

Embed

Paste the snippet shown in the channel setup before </body> on every page where the chat should appear. The script has no dependency and sets no cookie; it keeps the visitor session id in localStorage. Title, welcome message, colour and position (right or left) are set on the channel.

Public endpoints

To build your own chat interface, call the endpoints the widget uses. They need no API key: the widget key (lxwk_…) is public, so the allowed websites are what protects the channel.

GET/api/public/chat/{widget_key}/config

Returns title, welcome, color, position and agent (the name of the agent answering).

POST/api/public/chat/{widget_key}/messages
NameTypeRequiredDescription
textstringRequiredThe visitor message.Cut to 2,000 characters.
session_idstringRequiredYour id for the visitor: the same id continues the conversation.Cut to 64 characters.
namestringOptionalVisitor name.Up to 80 characters.
pagestringOptionalPage the visitor is on.Up to 300 characters.
NameTypeDescription
replystringThe agent reply. When the agent could not answer, a short apology.
statusstringProcessing result.replied, processed, draft, failed, ignored, unassigned, duplicate
GET/api/public/chat/widget.js

The widget script, served with Access-Control-Allow-Origin: * and cached for 5 minutes.

Allowed origins and CORS

  • Allowed websites are a comma-separated list of origins (scheme, host and port, e.g. https://www.example.com), compared exactly. An empty list allows every website: do not leave it empty in production.
  • For allowed origins, OPTIONS preflight requests get 204 and responses carry Access-Control-Allow-Origin set to that origin. Requests from other origins get 403 without CORS headers, so the browser reports a CORS error.
  • The check relies on the Origin header sent by browsers. Requests without Origin (server to server) are not checked: the widget key is not a secret.

Limits and errors

StatusWhen
400text or session_id is missing.
403The website is not allowed.
404Unknown widget key, inactive channel or incoming messages turned off.
429More messages than the channel limit (12 per visitor per minute by default) from one visitor, or more than five times that limit from one IP address.
HTML
<script src="https://ai-api.liberrex.com/api/public/chat/widget.js" data-key="lxwk_YOUR_WIDGET_KEY" async></script>
GET …/config · 200 OK
{
  "success": true,
  "data": {
    "title": "Assistance",
    "welcome": "Bonjour 👋 Comment puis-je vous aider ?",
    "color": "#2563eb",
    "position": "right",
    "agent": "Card support"
  }
}

Send a visitor message

curl -X POST "https://ai-api.liberrex.com/api/public/chat/lxwk_YOUR_WIDGET_KEY/messages" \
  -H "Content-Type: application/json" \
  -H "Origin: https://www.example.com" \
  -d '{"text": "Bonjour", "session_id": "visitor-7f3a", "name": "Sami", "page": "/contact"}'
POST …/messages · 200 OK
{
  "success": true,
  "data": { "reply": "Bonjour ! Comment puis-je vous aider ?", "status": "replied" }
}

Channel webhooks

POST/api/hooks/channels/{channel_uuid}

Messaging providers deliver incoming messages to this URL. The exact URL, verify token and settings are shown in the setup panel of each channel. The URL is built from the public address of the server (WEBHOOK_BASE_URL); providers require https.

ChannelSetup at the providerVerification
WhatsAppMeta app › WhatsApp › Configuration: callback URL and verify token, then subscribe to messages.X-Hub-Signature-256: sha256= HMAC-SHA256 of the raw body, keyed with the app secret set on the channel.
MessengerMeta app › Messenger › Webhooks: callback URL and verify token; subscribe the Page to messages and messaging_postbacks.Same as WhatsApp.
InstagramMeta app › Instagram › Webhooks: callback URL and verify token, subscribe to messages.Same as WhatsApp.
SMS (Twilio)Twilio console › Phone Numbers › your number › Messaging: “A message comes in” › Webhook, HTTP POST, the channel URL.X-Twilio-Signature: Base64 HMAC-SHA1 of the URL followed by the sorted form fields, keyed with the auth token.
TelegramNothing to paste: turning on incoming messages registers the webhook with Telegram. Without a public HTTPS address the channel polls instead.X-Telegram-Bot-Api-Secret-Token must equal the secret token generated for the channel.
E-mailNothing to paste: the IMAP mailbox is checked regularly.—

Verification and responses

  • Meta checks the URL with a GET request: when hub.mode is subscribe and hub.verify_token equals the verify token of the channel (lxvt_…), the endpoint answers 200 with hub.challenge as plain text, otherwise 403.
  • On Meta channels without an app secret, events are accepted unsigned and the setup panel shows a warning: set the app secret.
  • A wrong signature gets 403 Invalid signature and the event is not processed. An unknown channel gets 404.
  • Accepted events get 200 (OK as plain text, or an empty TwiML response for SMS). Messages are processed in the background, only when the channel is active and incoming messages are on.
Meta URL verification
GET /api/hooks/channels/{channel_uuid}
    ?hub.mode=subscribe
    &hub.verify_token=lxvt_…
    &hub.challenge=1158201444

HTTP/1.1 200 OK
Content-Type: text/plain

1158201444
Signed event
POST /api/hooks/channels/{channel_uuid}
X-Hub-Signature-256: sha256=<hex(HMAC-SHA256(app secret, raw body))>

HTTP/1.1 200 OK
Content-Type: text/plain

OK

Platform mesh (MCP)

The platforms of your company (Liberrex AI, Animatoria, your ERP…) call each other’s tools over the Model Context Protocol. Every platform can publish tools (an MCP server) and call the tools of the others (an MCP client). Liberrex agents use connected platforms to read live data and, with a manager’s approval, to act there; the other platforms use Liberrex AI for its agents, copilot and sales recommendations.

Packages

PackageLanguageUse it to
liberrex-mcp-serverPython ≥ 3.10Publish your tools; also runs the Liberrex AI MCP server.
@liberrex/mcp-serverNode ≥ 20Publish your tools from Node, Bun, Deno or Workers.
liberrex-mcp-clientPython ≥ 3.10Call other platforms from async or sync code, and give their tools to an LLM.
@liberrex/mcp-clientNode ≥ 20The same from JavaScript or TypeScript (server side: keys are secrets).

Conventions

  • Transport: MCP Streamable HTTP at /mcp, stateless with JSON responses, so any instance answers any call.
  • Who calls: each calling platform has its own key, sent as Authorization: Bearer lxp_…. The server keeps only its SHA-256 hash, and limits each caller to some tenants, with or without write tools.
  • For whom: each call carries the person it acts for (user, tenant, roles, locale) in _meta["com.liberrex/identity"]. The called platform applies that person’s own permissions; a model never chooses the identity.
  • Tools declare their policy in _meta["com.liberrex/tool"]: the permission the person needs, and whether the tool changes data. Write tools are refused to callers without writes.
  • Errors are tool errors whose text starts with a code: [unauthorized] [identity_required] [tenant_not_allowed] [forbidden] [writes_disabled] [invalid_request] [not_found] [conflict] [rate_limited] [too_many_hops] [upstream_error] [internal_error].
  • A call can go through at most 4 platforms: each hop adds one to the trace, and servers refuse more.
A mesh tool call
{"method": "tools/call", "params": {"name": "search_opportunities", "arguments": {"status": "open"},
 "_meta": {
   "com.liberrex/identity": {"user": {"id": "42", "roles": ["opportunities.read"], "locale": "fr"},
                             "tenant": {"id": "acme"}},
   "com.liberrex/trace": {"id": "4f0b…", "origin": "liberrex-ai", "hops": 1}}}}

Connect a platform to Liberrex

Once a platform publishes its tools, connect it in the dashboard so that your agents can use them.

  1. On that platform, issue a key for Liberrex and list Liberrex among its callers, with the tenants it may act in and whether it may write.
  2. In Liberrex, open Developers › Connected platforms › Connect a platform. Enter a name, a platform id (it prefixes the tool names, e.g. animatoria_production_overview), the MCP address and the key.
  3. Choose the service user: the user and tenant your agents act as on that platform, and their roles. Calls see exactly what this user sees there, so pick a user with the access the agents need, no more.
  4. Choose what agents may do: read only, or read and write. Write tools are never offered on a read-only connection.
  5. Save: the tools are synced at once. The connection card shows its status, its tools (read and write) and the last error, if any. Sync again after the platform adds tools.

Security

  • Addresses must use https:// and may not point to internal networks. For local development only, MCP_ALLOW_PRIVATE_NETWORK=1 on the Liberrex server accepts http://localhost.
  • The key is encrypted at rest and never shown again (only its first characters). Managing connections needs the Manage keys & secrets permission; viewing them, View keys & secrets.
  • Each call times out after 25 seconds (MCP_CALL_TIMEOUT). Results are passed to the model as data: text that reads like instructions is flagged.
Issue a key for Liberrex
# On the platform that publishes (its own environment):
liberrex-mcp-server new-key liberrex-ai
# → a key for Liberrex (shown once) and the line to add here:
LIBERREX_MCP_PEERS='{"liberrex-ai": {"key": "sha256:9f86…", "tenants": ["acme"], "writes": false}}'

Publish your platform’s tools

Declare functions as tools: their typed parameters become the input schema, and a context parameter tells who calls and for whom. Mount the server at /mcp in your web app, or run it on its own.

Checks done before your function runs

CheckRefusal
The key belongs to a configured caller401 [unauthorized]
The person’s tenant is one the caller may act in[tenant_not_allowed]
The person holds the tool’s permission (roles capped by the caller)[forbidden] · [identity_required]
Write tools only for callers with writes[writes_disabled]
Any other exception is logged, never shown[internal_error] tool failed

Already integrated with the Liberrex copilot?

Every tool declared with the copilot SDK can be published in one line, with the same schemas, permissions and errors. Your platform computes the person’s roles itself instead of trusting the roles the caller sends. This is how Animatoria publishes its tools.

Callers

List the platforms allowed to call you in LIBERREX_MCP_PEERS: for each, the SHA-256 of its key, the tenants it may act in ("*" for all), whether it may call write tools, and optionally a ceiling on the roles it may pass. Give each caller its own key.

  • A caller can have several keys: add the new key, let the caller switch, then remove the old one. A key with an expiry date stops working at the end of that day (UTC).
  • Each caller has a budget of tool calls per minute (rate_per_minute, 120 by default); over it, calls are refused with [rate_limited], and a flood of HTTP requests is answered with 429.
  • Put the callers in a file named by LIBERREX_MCP_PEERS_FILE: it is re-read when it changes, so a caller is revoked or its key rotated without a restart. If the file becomes invalid, the previous callers stay in force.
  • Every call, allowed or refused, produces an audit event (tool, caller, person, tenant, trace id, status, duration), never the arguments. By default it is one JSON line in the log; pass your own audit function to send it to your SIEM.
Callers
# Several keys (rotation with overlap), an expiry date and a call budget per caller.
# In a file, the callers are re-read when it changes: revoke or rotate without a restart.
LIBERREX_MCP_PEERS_FILE=/etc/liberrex/mcp-peers.json

{"liberrex-ai": {"keys": [{"key": "sha256:9f86…", "expires": "2027-01-31"}, "sha256:4e07…"],
                 "tenants": ["acme"], "writes": true, "rate_per_minute": 300}}
An audit event
{"event": "tool_call", "platform": "animatoria", "tool": "create_mission", "write": true,
 "status": "ok", "peer": "liberrex-ai", "user": "42", "tenant": "acme",
 "trace_id": "5f1c…", "hops": 1, "duration_ms": 184, "at": "2026-10-16T09:12:03Z"}
Python
from liberrex_mcp_server import PeerRegistry, PlatformError, PlatformServer, ToolContext

server = PlatformServer("erp", title="Acme ERP", peers=PeerRegistry.from_env())

@server.tool(permission="invoices.read")
def overdue_invoices(ctx: ToolContext, customer: str | None = None) -> dict:
    """Overdue invoices the user can see."""
    who = ctx.require_identity()          # user_id, tenant_id, roles, locale
    return {"items": invoices.overdue(who.tenant_id, who.user_id, customer)}

@server.tool(permission="invoices.write", write=True)
def send_reminder(ctx: ToolContext, invoice_id: str) -> dict:
    """Send a payment reminder."""
    if not invoices.exists(invoice_id):
        raise PlatformError("not_found", f"No invoice {invoice_id}")
    return invoices.remind(invoice_id)

app.mount("/mcp", server.asgi())          # FastAPI / Starlette; or server.run_http(port=8765)
Python
# Already integrated with the Liberrex copilot? Publish every declared tool as is.
from liberrex_mcp_server.copilot import publish_copilot

publish_copilot(server, copilot, user=lambda identity: load_user_with_roles(identity))
TypeScript
import http from 'node:http';
import * as z from 'zod';
import { PeerRegistry, PlatformServer } from '@liberrex/mcp-server';

const server = new PlatformServer({ platform: 'erp', title: 'Acme ERP', peers: PeerRegistry.fromEnv() });

server.tool('overdue_invoices', {
  description: 'Overdue invoices the user can see.',
  input: { customer: z.string().optional() },
  permission: 'invoices.read',
}, async ({ customer }, ctx) => {
  const who = ctx.requireIdentity();
  return { items: await invoices.overdue(who.tenantId, who.userId, customer) };
});

http.createServer(server.nodeHandler()).listen(8765);   // or fetchHandler() for Workers / Bun / Deno

Call other platforms

List the platforms you call in LIBERREX_MCP_PLATFORMS (address and the key each one issued for you) and name your own platform in LIBERREX_MCP_PLATFORM. Each call sends the identity of the signed-in person: set it in your code, never from a model.

  • A tool error raises PlatformToolError with its code (forbidden, not_found, writes_disabled…); an unreachable platform or a refused key raises PlatformUnavailable.
  • The Python client has async calls and sync versions for Flask, Celery or scripts; tool lists are cached for 5 minutes.
  • To give tools to an LLM, convert them to OpenAI or Anthropic tool definitions: names become platform__tool, and route maps a model’s call back to the platform and tool. Ask a person before running a write tool.
Configuration
LIBERREX_MCP_PLATFORM=animatoria
LIBERREX_MCP_PLATFORMS='{"liberrex-ai": {"url": "https://ai-api.liberrex.com/mcp", "key": "lxp_…"},
                         "erp": {"url": "https://erp.acme.tn/mcp", "key": "lxp_…", "timeout": 20}}'
Python
from liberrex_mcp_client import Identity, Mesh, PlatformToolError

mesh = Mesh.from_env()   # LIBERREX_MCP_PLATFORMS + LIBERREX_MCP_PLATFORM
sara = Identity(user_id="42", tenant_id="acme", roles=("sales.read",), locale="fr")

recs = await mesh.call("liberrex-ai", "sales_recommendations",
                       {"mode": "next_best_product", "context": {"record": {"type": "Customer", "id": "C-118"}}},
                       identity=sara)

try:
    mesh.call_sync("erp", "send_reminder", {"invoice_id": "F-1024"}, identity=sara)   # Flask, Celery, scripts
except PlatformToolError as error:
    if error.code == "writes_disabled": ...
TypeScript
import { Mesh, anthropicTools, route } from '@liberrex/mcp-client';

const mesh = Mesh.fromEnv();
const me = { userId: '42', tenantId: 'acme', roles: ['sales.read'], locale: 'fr' };

const tools = await mesh.listTools('animatoria');        // cached 5 minutes
const specs = anthropicTools(tools);                      // names: animatoria__production_ranking
const [platform, tool] = route(call.name, tools);
const result = await mesh.call(platform, tool, call.input, { identity: me });

The Liberrex AI MCP server

Liberrex AI publishes its own tools on the mesh: other platforms (or a desktop MCP client) can use its agents, its embedded copilot and its sales recommendations. Run it next to the Liberrex API.

GroupToolsNeeds
Agentswhoami · list_agents · get_agent · ask_agent · list_copilot_agents · start_agent_group · ask_agent_group · get_agent_group · list_conversations · get_conversation · end_conversation · get_usageLIBERREX_API_KEY
Copilotcopilot_ask · copilot_list_threads · copilot_get_threadLIBERREX_SECRET_KEY
Sales intelligencesales_recommendations · render_surface · suggest_field · run_surface_action · create_action_batch · confirm_action · cancel_action · record_feedbackLIBERREX_SECRET_KEY
  • Copilot and sales tools act for the person each call names: Liberrex opens a copilot session for that user and tenant with their roles. Recommendation modes map to surfaces, e.g. LIBERREX_SURFACES=next_best_action=nba-queue,next_best_product=nbp-queue.
  • Writes are always two steps: a tool returns a confirmation card, and only confirm_action runs it, after the person approved it. LIBERREX_MCP_READ_ONLY=1 removes the tools that change data.
  • Over HTTP the server needs LIBERREX_MCP_PEERS (the platforms allowed to call it); over stdio it acts as LIBERREX_USER_ID in LIBERREX_TENANT_ID.
Bash
pip install liberrex-mcp-server
liberrex-mcp-server --check                         # keys, peers and tools
liberrex-mcp-server --transport http --port 8765    # the mesh endpoint at /mcp
liberrex-mcp-server                                 # stdio, for Claude Desktop / Cursor
Claude Desktop / Cursor
{
  "mcpServers": {
    "liberrex": {
      "command": "uvx", "args": ["liberrex-mcp-server"],
      "env": {
        "LIBERREX_API_KEY": "key_id:secret",
        "LIBERREX_SECRET_KEY": "lxsk_live_…",
        "LIBERREX_USER_ID": "u_42", "LIBERREX_TENANT_ID": "acme",
        "LIBERREX_SURFACES": "next_best_action=nba-queue,next_best_product=nbp-queue"
      }
    }
  }
}

Agents that use connected platforms

On an agent, open Connected platforms and choose which connections and tools it may use (all tools or a selection). Changes go live when the agent is published. The tools join the agent’s other tools on every message.

Approval of actions

  • A write tool never runs on the first call: it is recorded as a proposed action with an id (A1, A2…), and the agent asks the person to confirm.
  • When the person answers yes in their next message, the agent calls confirm_actions with the approved ids: each action runs exactly as proposed, once. Several actions can be approved with one yes; a proposal expires after two messages.
  • Scheduled runs cannot approve anything: they can only propose actions, to be approved in a conversation.
  • On a connection, choose who approves: a team member (the default) or the customer in the conversation. A team member must be signed in and hold the Approve agent actions permission (managers and administrators have it); the approval is recorded with their name.
  • If a platform’s answer contains text that tries to give the agent instructions, the agent can no longer change anything for the rest of that message: proposals and approvals are refused, reading still works.

Routines

  • An agent can have several Schedule steps, its routines (e.g. a daily brief, a Monday review, a monthly review). Each runs from its own step, with its own e-mail or delivery step.
  • What a routine writes is kept with its run. A chat step can recall the latest reports (recall_reports), so the agent can follow up on what it proposed: “where are the decisions of Monday’s review?”.
  • Test them in the agent’s Test tab › Routines: pick a routine and run it now. Test runs never send e-mails; they show the e-mail that would be sent.

Tool budget

By default a message allows 4 model rounds and 6 tool calls. Analysis agents can raise it on their chat step with tool_limits: {"rounds": 6, "calls": 12} (up to 8 rounds and 16 calls).

One yes approves several actions
Manager : Crée une mission pour Hedi Ayari et affecte la formation à Anis Riahi.
Agent   → animatoria_create_mission(...)   ⇢ needs_confirmation · A1
        → animatoria_assign_course(...)    ⇢ needs_confirmation · A2
Agent   : Deux actions sont prêtes (A1, A2)… Voulez-vous que je les exécute ?
Manager : Oui, je valide les deux.
Agent   → confirm_actions(["A1", "A2"])    ⇢ ok · mission #7 créée · formation affectée

Security, identity and audit

What an enterprise can check: who the agent acts as on each platform, who approved each change, and a record of every call. Every control below is enforced by the platforms themselves, not by the model.

Who the calls act as

ModeWhat happens
One service userEvery call runs as the user configured on the connection. Simple, and right for read-only connections or customer-facing agents.
Each person as themselvesEach member is linked to their own user on the platform (Connected platforms › Linked people). A call runs with that person’s own rights there; members who are not linked, and customer conversations, are refused.

Who may use an agent

On an agent, open Settings › Access to limit the roles that may chat with it or test it from the dashboard. Administrators always can. Changing it needs the Manage members permission.

Activity and export

  • Every call an agent makes to a connected platform is recorded: time, tool, read or write, status, the member and the approver, the trace id, the duration and a masked preview of the arguments (e-mails, long numbers and secrets are hidden).
  • Open a connection’s Activity to see the last 7 days and filter the calls; Export CSV gives the same rows for an audit or a SIEM. Export all activity covers every connection. Each export is itself written to the audit log.
  • The trace id is the same on both platforms, so a call can be followed from Liberrex to the platform’s own audit events.

Keys

  • Connection keys appear in Keys & secrets with the other credentials: set their expiry date on the connection to be reminded before they expire. Disable the connection to stop every call at once.
  • On the called platform, revoke the key or remove the caller from its peers file: calls stop within seconds.

Versioning and changelog

The version is part of the path (/api/v1) and is returned as version by GET /api/v1/health. Fields may be added to v1 responses: ignore the fields you do not use. Changes are listed below.

Changelog

Platform mesh — 16 October 2026

  • Liberrex MCP mesh: platforms publish and call each other’s tools with per-caller keys and per-call identity (Python and TypeScript packages).
  • Connected platforms in the dashboard; agents use their tools, and act there after a manager’s approval (one yes can approve several actions).
  • The Liberrex AI MCP server: agents, copilot and sales recommendations as tools for other platforms and desktop MCP clients.
  • Agents can have several routines (Schedule steps) and recall their latest reports in chat.

v1 — 11 October 2026

  • First release of the developer API: key check, agents, messages with conversation sessions, conversations, usage.
  • Per-key rate limits, access levels and project scope; keys of deactivated members stop working.
  • Documented agent webhooks, the Quality ingestion API, the website chat widget and channel webhooks.
  • Published the OpenAPI 3.1 specification of the API.
  • Removed the earlier /api/v1 placeholder endpoints (analytics, transcripts, knowledge base, model tests), which returned sample data.

Developer questions

Before you write the first request.

Can we call the API from a web page or a mobile app?

No. An API key acts with the rights of its owner, so it must stay on your servers: call the API from your backend and expose only what your front end needs. To chat with an agent from a browser, use the website chat widget, whose public endpoints are built for browsers and limited to the websites you allow.

Is there an SDK or an OpenAPI specification?

There is no official SDK: the API is plain HTTPS and JSON, so any HTTP client works, as the cURL, JavaScript, Python and PHP examples show. Download the OpenAPI 3.1 specification from the top of this page to generate a typed client or import the endpoints into your API tool.

How do we keep the context of a conversation across messages?

The first POST /api/v1/agents/{agent_uuid}/messages returns a session_id. Send it with each following message and the agent keeps the context, up to 200 exchanges. End the conversation with POST /api/v1/conversations/{conversation_id}/end to run the post-conversation analysis when it is turned on.

Does Liberrex send webhook events to our systems?

There are no event subscriptions today. Webhooks go the other way: your systems call an agent webhook to start a task agent, and the call returns the result of the run. When an agent must reach your systems during a conversation, it calls your HTTP APIs through tool steps in its flow.

What happens when we send too many requests?

Each key has a per-minute limit, 100 requests by default, counted per clock minute. Every authenticated response carries X-RateLimit-Limit and X-RateLimit-Remaining; above the limit the API answers 429 with a Retry-After header giving the seconds until the counter resets.

Ready to build on Liberrex?