API reference
Publishable keys
Publishable keys (pk_live_...) let a browser start web chats with one assistant from the origins you allow. They are safe to ship in page source; secret API keys are not.
- GET
/api/preview-linksGet an assistant's preview link - POST
/api/preview-linksMake a preview link (pk_preview_…) - PATCH
/api/preview-links/{id}Change who may open a preview link, or when it expires - DELETE
/api/preview-links/{id}Turn a preview link off - GET
/api/preview-links/{id}/pageRead a preview link for its page - GET
/api/publishable-keysList publishable keys - POST
/api/publishable-keysCreate a publishable key - DELETE
/api/publishable-keys/{id}Revoke a publishable key
Get an assistant's preview link
GET/api/preview-links
The assistant's live (unrevoked) preview link, expired or not, or null when it has none.
Secret API key. How it works
Query parameters
assistant_idstringrequired
Returns
200 OK
Response attributes
dataPreviewLink | nullOne of 2 shapes
PreviewLink
idstringrequiredAlso the page address: /preview/{id} on the console.
assistant_idstringrequiredproject_idstringrequiredkeystringrequiredThe preview key the page loads the widget with.
accessstringrequiredOne of
publicteamexpires_atstring | nullrequiredISO 8601 timestampexpiredbooleanrequiredcreated_atstringrequiredISO 8601 timestamp
Option 2
- 400 Validation error: usually
invalid_request, with the failing fields inissueswhen there are any - 401 Missing, malformed, unknown or revoked API key (
unauthorized) - 429 Rate limit exceeded (
rate_limited). WaitRetry-Afterseconds. Budgets are per 60-second window: developer API 120 per key and 600 per organization; public chat 30 per IP address, except polling for updates (60 per conversation and IP address) and analytics events (120 per IP address).
curl "https://api.myleadsfactory.tech/api/preview-links?assistant_id=asst_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f" \
-H "Authorization: Bearer $MLF_API_KEY"{
"data": {
"id": "id_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"assistant_id": "asst_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"project_id": "proj_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"key": "string",
"access": "public",
"expires_at": "2026-10-28T09:30:00.000Z",
"expired": true,
"created_at": "2026-10-28T09:30:00.000Z"
}
}Make a preview link (pk_preview_…)
POST/api/preview-links
A page where anyone with the link, or only signed-in members of the org, can try the assistant's web chat; it loads from any site and while web chat is off, and its chats are tests that send nothing outside. One live link per assistant: making one turns the previous link off, with its open chats.
Secret API key, not read-only. How it works
Request body
assistant_idstringrequiredaccessstringrequiredOne of
publicteamexpires_atstring | nullrequiredISO 8601 timestampIn the future; null works until it is turned off.
Returns
201 OK
Response attributes
dataPreviewLinkrequired8 attributes
idstringrequiredAlso the page address: /preview/{id} on the console.
assistant_idstringrequiredproject_idstringrequiredkeystringrequiredThe preview key the page loads the widget with.
accessstringrequiredOne of
publicteamexpires_atstring | nullrequiredISO 8601 timestampexpiredbooleanrequiredcreated_atstringrequiredISO 8601 timestamp
- 400 Validation error: usually
invalid_request, with the failing fields inissueswhen there are any - 401 Missing, malformed, unknown or revoked API key (
unauthorized) - 403 Refused, for example a read-only key on a write (
read_only_key), a role too low for the route (insufficient_role), or a route that needs a console session - 404 Not found, or outside what the key can see
- 429 Rate limit exceeded (
rate_limited). WaitRetry-Afterseconds. Budgets are per 60-second window: developer API 120 per key and 600 per organization; public chat 30 per IP address, except polling for updates (60 per conversation and IP address) and analytics events (120 per IP address).
curl -X POST "https://api.myleadsfactory.tech/api/preview-links" \
-H "Authorization: Bearer $MLF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"assistant_id": "asst_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"access": "public",
"expires_at": "2026-10-28T09:30:00.000Z"
}'{
"data": {
"id": "id_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"assistant_id": "asst_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"project_id": "proj_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"key": "string",
"access": "public",
"expires_at": "2026-10-28T09:30:00.000Z",
"expired": true,
"created_at": "2026-10-28T09:30:00.000Z"
}
}Change who may open a preview link, or when it expires
PATCH/api/preview-links/{id}
The URL and key stay the same.
Secret API key, not read-only. How it works
Path parameters
idstringrequired
Request body
accessstringOne of
publicteamexpires_atstring | nullISO 8601 timestamp
Returns
200 OK
Response attributes
dataPreviewLinkrequired8 attributes
idstringrequiredAlso the page address: /preview/{id} on the console.
assistant_idstringrequiredproject_idstringrequiredkeystringrequiredThe preview key the page loads the widget with.
accessstringrequiredOne of
publicteamexpires_atstring | nullrequiredISO 8601 timestampexpiredbooleanrequiredcreated_atstringrequiredISO 8601 timestamp
- 400 Validation error: usually
invalid_request, with the failing fields inissueswhen there are any - 401 Missing, malformed, unknown or revoked API key (
unauthorized) - 403 Refused, for example a read-only key on a write (
read_only_key), a role too low for the route (insufficient_role), or a route that needs a console session - 404 Not found, or outside what the key can see
- 429 Rate limit exceeded (
rate_limited). WaitRetry-Afterseconds. Budgets are per 60-second window: developer API 120 per key and 600 per organization; public chat 30 per IP address, except polling for updates (60 per conversation and IP address) and analytics events (120 per IP address).
curl -X PATCH "https://api.myleadsfactory.tech/api/preview-links/PREVIEW_LINK_ID" \
-H "Authorization: Bearer $MLF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"access": "public",
"expires_at": "2026-10-28T09:30:00.000Z"
}'{
"data": {
"id": "id_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"assistant_id": "asst_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"project_id": "proj_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"key": "string",
"access": "public",
"expires_at": "2026-10-28T09:30:00.000Z",
"expired": true,
"created_at": "2026-10-28T09:30:00.000Z"
}
}Turn a preview link off
DELETE/api/preview-links/{id}
The page stops loading, and the chats open on it end.
Secret API key, not read-only. How it works
Path parameters
idstringrequired
Returns
200 OK
Response attributes
dataPreviewLinkrequired8 attributes
idstringrequiredAlso the page address: /preview/{id} on the console.
assistant_idstringrequiredproject_idstringrequiredkeystringrequiredThe preview key the page loads the widget with.
accessstringrequiredOne of
publicteamexpires_atstring | nullrequiredISO 8601 timestampexpiredbooleanrequiredcreated_atstringrequiredISO 8601 timestamp
- 401 Missing, malformed, unknown or revoked API key (
unauthorized) - 403 Refused, for example a read-only key on a write (
read_only_key), a role too low for the route (insufficient_role), or a route that needs a console session - 404 Not found, or outside what the key can see
- 429 Rate limit exceeded (
rate_limited). WaitRetry-Afterseconds. Budgets are per 60-second window: developer API 120 per key and 600 per organization; public chat 30 per IP address, except polling for updates (60 per conversation and IP address) and analytics events (120 per IP address).
curl -X DELETE "https://api.myleadsfactory.tech/api/preview-links/PREVIEW_LINK_ID" \
-H "Authorization: Bearer $MLF_API_KEY"{
"data": {
"id": "id_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"assistant_id": "asst_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"project_id": "proj_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"key": "string",
"access": "public",
"expires_at": "2026-10-28T09:30:00.000Z",
"expired": true,
"created_at": "2026-10-28T09:30:00.000Z"
}
}Read a preview link for its page
GET/api/preview-links/{id}/page
Needs no session. A team-only link returns only access and org_id unless the caller is a member of that org, who also gets the key.
Secret API key. How it works
Path parameters
idstringrequired
Returns
200 OK
Response attributes
dataobject8 attributes
idstringrequiredaccessstringrequiredOne of
publicteamkeystringAbsent for a team-only link read by a non-member.
assistant_idstringnamestringThe widget's display name.
subtitlestring | nullexpires_atstring | nullISO 8601 timestamporg_idstringPresent only when the key is withheld.
- 404 Not found, or outside what the key can see
- 410 The link has expired
- 429 Rate limit exceeded (
rate_limited). WaitRetry-Afterseconds. Budgets are per 60-second window: developer API 120 per key and 600 per organization; public chat 30 per IP address, except polling for updates (60 per conversation and IP address) and analytics events (120 per IP address).
curl "https://api.myleadsfactory.tech/api/preview-links/PREVIEW_LINK_ID/page" \
-H "Authorization: Bearer $MLF_API_KEY"{
"data": {
"id": "id_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"access": "public",
"key": "string",
"assistant_id": "asst_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"name": "Front desk",
"subtitle": "string",
"expires_at": "2026-10-28T09:30:00.000Z",
"org_id": "org_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f"
}
}List publishable keys
GET/api/publishable-keys
Filter by project_id (covers project-scoped keys) or assistant_id (legacy pinned keys); no filter returns everything in scope. Site keys only unless kind says otherwise. Revoked keys are included, with revoked_at set.
Secret API key. How it works
Query parameters
project_idstringOnly keys for this project.
assistant_idstringOnly keys pinned to this assistant.
kindstringsite (the default) lists the keys for live websites, test the test keys, all both.
One of
sitetestall
Returns
200 OK
Response attributes
dataarray of WidgetKeyrequired10 attributes
idstringrequiredorg_idstringrequiredproject_idstringrequiredassistant_idstring | nullrequiredThe assistant a pinned key is bound to; null for a project-scoped key.
keystringrequiredPublic, revocable widget key,
pk_live_....kindstringsite keys go on the live website; test keys on a staging site or localhost.
One of
sitetestallowed_originsarray of stringTest keys: where the key loads. Site keys use the assistant's allowed_origins.
revoked_atstring | nullISO 8601 timestampcreated_atstringISO 8601 timestampupdated_atstringISO 8601 timestamp
- 400 Validation error: usually
invalid_request, with the failing fields inissueswhen there are any - 401 Missing, malformed, unknown or revoked API key (
unauthorized) - 429 Rate limit exceeded (
rate_limited). WaitRetry-Afterseconds. Budgets are per 60-second window: developer API 120 per key and 600 per organization; public chat 30 per IP address, except polling for updates (60 per conversation and IP address) and analytics events (120 per IP address).
curl "https://api.myleadsfactory.tech/api/publishable-keys" \
-H "Authorization: Bearer $MLF_API_KEY"{
"data": [
{
"id": "id_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"org_id": "org_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"project_id": "proj_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"assistant_id": "asst_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"key": "string",
"kind": "site",
"allowed_origins": [
"https://www.example.com"
],
"revoked_at": "2026-10-28T09:30:00.000Z",
"created_at": "2026-10-28T09:30:00.000Z",
"updated_at": "2026-10-28T09:30:00.000Z"
}
]
}Create a publishable key
POST/api/publishable-keys
Returns the key in data.key: pk_live_... for a site key, pk_test_... for a test key. project_id (or, with a project API key, nothing at all) mints a project-scoped key, usable with any assistant in the project: the embed names the assistant with data-mlf-assistant and public chat requests send assistant. assistant_id mints a key pinned to that assistant. An org-wide API key must send one of the two (400 otherwise). Publishable keys are safe in page source; which sites may use a site key is the assistant's channels.web_chat.allowed_origins. kind: test mints a test key for a staging site or localhost: it loads only on its own allowed_origins, and its chats run the assistant's draft, search the test knowledge publication and reach test webhooks only.
Secret API key, not read-only. How it works
Request body
project_idstringnot emptyMint a project-scoped key.
assistant_idstringnot emptyMint a key pinned to this assistant.
kindstringsitefor your live site,testfor a staging site or localhost.One of
sitetestDefault
"site"allowed_originsarray of stringup to 20 itemsTest keys only, and required for them: the HTTP(S) origins the key loads on.
Returns
201 OK
Response attributes
dataWidgetKeyrequired10 attributes
idstringrequiredorg_idstringrequiredproject_idstringrequiredassistant_idstring | nullrequiredThe assistant a pinned key is bound to; null for a project-scoped key.
keystringrequiredPublic, revocable widget key,
pk_live_....kindstringsite keys go on the live website; test keys on a staging site or localhost.
One of
sitetestallowed_originsarray of stringTest keys: where the key loads. Site keys use the assistant's allowed_origins.
revoked_atstring | nullISO 8601 timestampcreated_atstringISO 8601 timestampupdated_atstringISO 8601 timestamp
- 400 Validation error: usually
invalid_request, with the failing fields inissueswhen there are any - 401 Missing, malformed, unknown or revoked API key (
unauthorized) - 403 Refused, for example a read-only key on a write (
read_only_key), a role too low for the route (insufficient_role), or a route that needs a console session - 404
assistant_not_foundorproject_not_found: not in this key's scope - 429 Rate limit exceeded (
rate_limited). WaitRetry-Afterseconds. Budgets are per 60-second window: developer API 120 per key and 600 per organization; public chat 30 per IP address, except polling for updates (60 per conversation and IP address) and analytics events (120 per IP address).
curl -X POST "https://api.myleadsfactory.tech/api/publishable-keys" \
-H "Authorization: Bearer $MLF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"assistant_id": "asst_7d2e9b14c6a04f3e8b5d1a9c0e7f6b23"
}'{
"data": {
"id": "id_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"org_id": "org_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"project_id": "proj_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"assistant_id": "asst_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"key": "string",
"kind": "site",
"allowed_origins": [
"https://www.example.com"
],
"revoked_at": "2026-10-28T09:30:00.000Z",
"created_at": "2026-10-28T09:30:00.000Z",
"updated_at": "2026-10-28T09:30:00.000Z"
}
}Revoke a publishable key
DELETE/api/publishable-keys/{id}
Revocation, not deletion: the row stays and gains a revoked_at, so an embed that still carries the key gets a clean refusal rather than a missing record.
Secret API key, not read-only. How it works
Path parameters
idstringrequiredKey record id,
wkey_...(not thepk_live_key itself).
Returns
200 OK
Response attributes
dataWidgetKeyrequired10 attributes
idstringrequiredorg_idstringrequiredproject_idstringrequiredassistant_idstring | nullrequiredThe assistant a pinned key is bound to; null for a project-scoped key.
keystringrequiredPublic, revocable widget key,
pk_live_....kindstringsite keys go on the live website; test keys on a staging site or localhost.
One of
sitetestallowed_originsarray of stringTest keys: where the key loads. Site keys use the assistant's allowed_origins.
revoked_atstring | nullISO 8601 timestampcreated_atstringISO 8601 timestampupdated_atstringISO 8601 timestamp
- 401 Missing, malformed, unknown or revoked API key (
unauthorized) - 403 Refused, for example a read-only key on a write (
read_only_key), a role too low for the route (insufficient_role), or a route that needs a console session - 404 Not found, or outside what the key can see
- 429 Rate limit exceeded (
rate_limited). WaitRetry-Afterseconds. Budgets are per 60-second window: developer API 120 per key and 600 per organization; public chat 30 per IP address, except polling for updates (60 per conversation and IP address) and analytics events (120 per IP address).
curl -X DELETE "https://api.myleadsfactory.tech/api/publishable-keys/PUBLISHABLE_KEY_ID" \
-H "Authorization: Bearer $MLF_API_KEY"{
"data": {
"id": "id_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"org_id": "org_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"project_id": "proj_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"assistant_id": "asst_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"key": "string",
"kind": "site",
"allowed_origins": [
"https://www.example.com"
],
"revoked_at": "2026-10-28T09:30:00.000Z",
"created_at": "2026-10-28T09:30:00.000Z",
"updated_at": "2026-10-28T09:30:00.000Z"
}
}