API reference
Chat analytics
Web chat health, quality, the visitor funnel, trends and incidents, with notification subscriptions.
- GET
/api/chat-analytics/failuresList failed chat requests - GET
/api/chat-analytics/funnelGet the visitor funnel - GET
/api/chat-analytics/healthGet chat health by site - GET
/api/chat-analytics/incidentsList chat health incidents - POST
/api/chat-analytics/incidents/{id}/muteMute an incident - POST
/api/chat-analytics/incidents/{id}/resolveResolve an incident - POST
/api/chat-analytics/incidents/{id}/unmuteUnmute an incident - POST
/api/chat-analytics/outcomes/backfillLabel how already analysed chats ended (staff only) - GET
/api/chat-analytics/qualityGet chat quality - GET
/api/chat-analytics/subscriptionsList notification subscriptions - POST
/api/chat-analytics/subscriptionsCreate a notification subscription - PATCH
/api/chat-analytics/subscriptions/{id}Update a notification subscription - DELETE
/api/chat-analytics/subscriptions/{id}Delete a notification subscription - GET
/api/chat-analytics/trendGet daily or weekly chat totals
List failed chat requests
GET/api/chat-analytics/failures
Each public chat request (config, events, start, send) that failed or was refused, with its status, outcome code, site, browser and request id, for chasing a broken embed. A malformed cursor is a 400.
Secret API key. How it works
Query parameters
project_idstringOnly this project. An org-wide key covers every project by default.
assistant_idarray of stringOnly these assistants. Repeat the parameter or comma-separate the values; a row matching any of them is kept.
fromstringISO 8601 timestampDefault: 24 hours before
to.tostringISO 8601 timestampDefault: now. Ranges over 90 days are rejected.
cursorstringnext_cursorfrom the previous page.limitinteger1 to 100Page size.
Default
50
Returns
200 OK
Response attributes
dataarray of ChatFailurerequired14 attributes
idstringrequiredoccurred_atstringrequiredISO 8601 timestamproutestringrequiredOne of
configeventscreatemessageoutcomestringrequiredstatus_codeintegerrequiredsitestring | nullsampledbooleanrequest_idstring | nullconversation_idstring | nullclient_message_idstring | nulldevicestring | nullbrowserstring | nullwidget_versionstring | nullassistant_idstring | null
next_cursorstring | nullrequiredhas_morebooleanrequired
- 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/chat-analytics/failures" \
-H "Authorization: Bearer $MLF_API_KEY"{
"data": [
{
"id": "id_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"occurred_at": "2026-10-28T09:30:00.000Z",
"route": "config",
"outcome": "string",
"status_code": 1,
"site": "string",
"sampled": true,
"request_id": "id_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"conversation_id": "conv_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"client_message_id": "msg_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"device": "string",
"browser": "string",
"widget_version": "string",
"assistant_id": "asst_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f"
}
],
"next_cursor": "eyJ0IjoiMjAyNi0xMC0yOCJ9",
"has_more": true
}Get the visitor funnel
GET/api/chat-analytics/funnel
Page views at each stage (loaded, shown, opened, typed, sent), then sends, conversations, leads, handoffs and bookings, optionally split by a dimension and compared with the window before.
Secret API key. How it works
Query parameters
project_idstringOnly this project. An org-wide key covers every project by default.
assistant_idarray of stringOnly these assistants. Repeat the parameter or comma-separate the values; a row matching any of them is kept.
fromstringISO 8601 timestampDefault: 24 hours before
to.tostringISO 8601 timestampDefault: now. Ranges over 90 days are rejected.
group_bystringSplit the funnel into one group per value of this dimension.
One of
nonesitepagedevicebrowsersourcehouropen_reasonvisitor_typeDefault
"none"comparestringpreviousadds each group's counts for the window of the same length just before.One of
previous
Returns
200 OK
Response attributes
dataChatFunnelrequired11 attributes
fromstringrequiredISO 8601 timestamptostringrequiredISO 8601 timestampgroup_bystringrequiredvisits_sincestring | nullISO 8601 timestampWhen this scope's first page view tied to a visit arrived; visit counts start here. Null before any did.
page_views_sincestring | nullISO 8601 timestampWhen this scope's first page view the widget reported arrived; page-view stages start here. Null before any did.
groupsarray of objectrequired15 attributes
page_viewsobjectrequiredDistinct page views per stage. Events without a page_view_id are excluded.
5 attributes
loadedintegerrequiredshownintegerrequiredopenedintegerrequiredtypedintegerrequiredsentintegerrequired
visitsobjectrequiredDistinct visits (a run of page views by one browser) per stage, with the same filters as page_views. Only events that carry a visit_id count: visits exist only with analytics consent and a current widget.
5 attributes
loadedintegerrequiredshownintegerrequiredopenedintegerrequiredtypedintegerrequiredsentintegerrequired
visitorsintegerrequiredDistinct visitor ids among widget_loaded events.
returning_visitsintegerrequiredDistinct visits among widget_loaded events whose visit_number is above 1.
identified_page_viewsintegerrequiredDistinct page views among widget_loaded events that carry a visit_id. Divide by tracked_page_views for coverage.
tracked_page_viewsintegerDistinct page views since visit tracking started for this scope (the response's visits_since); 0 before it started. The coverage denominator, so earlier, untracked weeks do not count as untracked traffic.
sendsobject | nullrequired5 attributes
pressedintegeracceptedintegeransweredintegerfailedintegerrefusedinteger
conversationsinteger | nullrequiredChats with a visitor message started in the window, in every grouping: a chat takes the page, source and device of the page view its first message was sent from, or the group
untrackedwhen there is none. Test, preview-host and internal chats are left out (see excluded).leadsinteger | nullrequiredLeads saved in the window, grouped as their chat is.
valid_leadsintegerLeads with at least one email or phone that is not a placeholder or the business's own number.
handoffsinteger | nullrequiredHandoffs requested in the window, grouped as their chat is.
bookedinteger | nullrequiredconvertedobjectThe conversion cohort: page views loaded in the window from which a chat started, and those whose chat saved a lead or a valid lead in the window. Divide by page_views.loaded for conversion; unlike the activity counts above, it leaves out chats with no page view or from before page tracking.
3 attributes
conversationsintegerrequiredleadsintegerrequiredvalid_leadsintegerrequired
keystring | nullrequiredpreviousobject13 attributes
page_viewsobjectrequiredDistinct page views per stage. Events without a page_view_id are excluded.
5 attributes
loadedintegerrequiredshownintegerrequiredopenedintegerrequiredtypedintegerrequiredsentintegerrequired
visitsobjectrequiredDistinct visits (a run of page views by one browser) per stage, with the same filters as page_views. Only events that carry a visit_id count: visits exist only with analytics consent and a current widget.
5 attributes
loadedintegerrequiredshownintegerrequiredopenedintegerrequiredtypedintegerrequiredsentintegerrequired
visitorsintegerrequiredDistinct visitor ids among widget_loaded events.
returning_visitsintegerrequiredDistinct visits among widget_loaded events whose visit_number is above 1.
identified_page_viewsintegerrequiredDistinct page views among widget_loaded events that carry a visit_id. Divide by tracked_page_views for coverage.
tracked_page_viewsintegerDistinct page views since visit tracking started for this scope (the response's visits_since); 0 before it started. The coverage denominator, so earlier, untracked weeks do not count as untracked traffic.
sendsobject | nullrequired5 attributes
pressedintegeracceptedintegeransweredintegerfailedintegerrefusedinteger
conversationsinteger | nullrequiredChats with a visitor message started in the window, in every grouping: a chat takes the page, source and device of the page view its first message was sent from, or the group
untrackedwhen there is none. Test, preview-host and internal chats are left out (see excluded).leadsinteger | nullrequiredLeads saved in the window, grouped as their chat is.
valid_leadsintegerLeads with at least one email or phone that is not a placeholder or the business's own number.
handoffsinteger | nullrequiredHandoffs requested in the window, grouped as their chat is.
bookedinteger | nullrequiredconvertedobjectThe conversion cohort: page views loaded in the window from which a chat started, and those whose chat saved a lead or a valid lead in the window. Divide by page_views.loaded for conversion; unlike the activity counts above, it leaves out chats with no page view or from before page tracking.
3 attributes
conversationsintegerrequiredleadsintegerrequiredvalid_leadsintegerrequired
groups_totalintegerrequiredGroups found; only the first 50 are returned (hours are never cut).
truncatedbooleanrequiredcoverageobjectrequiredCounted chats in the window, and those tied to a page view the widget reported.
2 attributes
conversationsintegerrequiredtrackedintegerrequired
excludedobjectrequiredChats in the window left out of every number, each under the first reason that applies: a test marker or test key, a preview or staging host, or an internal lead email.
3 attributes
testintegerrequiredpreviewintegerrequiredinternalintegerrequired
partialbooleanrequiredTrue when not every send in the window could be read, so the counts are a lower bound. Retry, or narrow the range.
- 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/chat-analytics/funnel" \
-H "Authorization: Bearer $MLF_API_KEY"{
"data": {
"from": "2026-10-28T09:30:00.000Z",
"to": "2026-10-28T09:30:00.000Z",
"group_by": "string",
"visits_since": "2026-10-28T09:30:00.000Z",
"page_views_since": "2026-10-28T09:30:00.000Z",
"groups": [
{
"page_views": {
"loaded": 1,
"shown": 1,
"opened": 1,
"typed": 1,
"sent": 1
},
"visits": {
"loaded": 1,
"shown": 1,
"opened": 1,
"typed": 1,
"sent": 1
},
"visitors": 1,
"returning_visits": 1,
"identified_page_views": 1,
"tracked_page_views": 1,
"sends": {
"pressed": 1,
"accepted": 1,
"answered": 1,
"failed": 1,
"refused": 1
},
"conversations": 1,
"leads": 1,
"valid_leads": 1,
"handoffs": 1,
"booked": 1,
"converted": {
"conversations": 1,
"leads": 1,
"valid_leads": 1
},
"key": "string",
"previous": {
"page_views": {
"loaded": {},
"shown": {},
"opened": {},
"typed": {},
"sent": {}
},
"visits": {
"loaded": {},
"shown": {},
"opened": {},
"typed": {},
"sent": {}
},
"visitors": 1,
"returning_visits": 1,
"identified_page_views": 1,
"tracked_page_views": 1,
"sends": {
"pressed": {},
"accepted": {},
"answered": {},
"failed": {},
"refused": {}
},
"conversations": 1,
"leads": 1,
"valid_leads": 1,
"handoffs": 1,
"booked": 1,
"converted": {
"conversations": {},
"leads": {},
"valid_leads": {}
}
}
}
],
"groups_total": 1,
"truncated": true,
"coverage": {
"conversations": 1,
"tracked": 1
},
"excluded": {
"test": 1,
"preview": 1,
"internal": 1
},
"partial": true
}
}Get chat health by site
GET/api/chat-analytics/health
For each site the chat runs on: page views, shown and opened panels, sends, failures and refusals, reply time percentiles, the last successful reply and open incidents, with a status (ok, warning, critical, idle). Defaults to the last 24 hours.
Secret API key. How it works
Query parameters
project_idstringOnly this project. An org-wide key covers every project by default.
assistant_idarray of stringOnly these assistants. Repeat the parameter or comma-separate the values; a row matching any of them is kept.
fromstringISO 8601 timestampDefault: 24 hours before
to.tostringISO 8601 timestampDefault: now. Ranges over 90 days are rejected.
Returns
200 OK
Response attributes
dataChatHealthrequired3 attributes
checkerobjectrequired2 attributes
swept_atstring | nullrequiredISO 8601 timestampstalebooleanrequired
partialbooleanrequiredTrue when not every send in the window could be read, so the counts are a lower bound. Retry, or narrow the range.
sitesarray of objectrequired14 attributes
sitestring | nullrequiredstatusstringrequiredOne of
okwarningcriticalidlepage_viewsintegerrequiredshownintegeropenedintegersendsintegerrequiredacceptedintegerfailedintegerrefusedintegerfailure_ratenumber | nullreply_ms_p50integer | nullreply_ms_p90integer | nulllast_success_atstring | nullISO 8601 timestampopen_incidentsintegerrequired
- 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/chat-analytics/health" \
-H "Authorization: Bearer $MLF_API_KEY"{
"data": {
"checker": {
"swept_at": "2026-10-28T09:30:00.000Z",
"stale": true
},
"partial": true,
"sites": [
{
"site": "string",
"status": "ok",
"page_views": 1,
"shown": 1,
"opened": 1,
"sends": 1,
"accepted": 1,
"failed": 1,
"refused": 1,
"failure_rate": 1,
"reply_ms_p50": 640,
"reply_ms_p90": 640,
"last_success_at": "2026-10-28T09:30:00.000Z",
"open_incidents": 1
}
]
}
}List chat health incidents
GET/api/chat-analytics/incidents
Problems the health checker opened on its own (for example a site whose sends started failing), newest activity first. An incident resolves itself when the site recovers or goes quiet, or by hand.
Secret API key. How it works
Query parameters
project_idstringOnly this project. An org-wide key covers every project by default.
assistant_idarray of stringOnly these assistants' incidents. Repeat the parameter or comma-separate the values; a row matching any of them is kept.
statusstringWhich incidents to list.
One of
openresolvedallDefault
"open"cursorstringnext_cursorfrom the previous page.limitinteger1 to 100Page size.
Default
50
Returns
200 OK
Response attributes
dataarray of ChatIncidentrequired23 attributes
idstringrequiredorg_idstringproject_idstringrequiredassistant_idstring | nullwidget_key_idstring | nullsitestring | nullrulestringrequiredseveritystringrequiredOne of
infowarningcriticalstatusstringrequiredOne of
openresolveddedupe_keystringfirst_seen_atstringISO 8601 timestamplast_seen_atstringISO 8601 timestampoccurrencesintegerexample_request_idstring | nullexample_conversation_idstring | nulldetailobject | nullmuted_untilstring | nullISO 8601 timestampmuted_bystring | nullresolved_atstring | nullISO 8601 timestampresolutionstring | nullOne of
recoveredquietmanualresolved_bystring | nullcreated_atstringISO 8601 timestampupdated_atstringISO 8601 timestamp
next_cursorstring | nullrequiredhas_morebooleanrequired
- 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/chat-analytics/incidents" \
-H "Authorization: Bearer $MLF_API_KEY"{
"data": [
{
"id": "id_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"org_id": "org_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"project_id": "proj_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"assistant_id": "asst_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"widget_key_id": "id_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"site": "string",
"rule": "string",
"severity": "info",
"status": "open",
"dedupe_key": "string",
"first_seen_at": "2026-10-28T09:30:00.000Z",
"last_seen_at": "2026-10-28T09:30:00.000Z",
"occurrences": 1,
"example_request_id": "id_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"example_conversation_id": "conv_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"detail": {},
"muted_until": "2026-10-28T09:30:00.000Z",
"muted_by": "string",
"resolved_at": "2026-10-28T09:30:00.000Z",
"resolution": "recovered",
"resolved_by": "string",
"created_at": "2026-10-28T09:30:00.000Z",
"updated_at": "2026-10-28T09:30:00.000Z"
}
],
"next_cursor": "eyJ0IjoiMjAyNi0xMC0yOCJ9",
"has_more": true
}Mute an incident
POST/api/chat-analytics/incidents/{id}/mute
Stops notifications for this incident for 15 minutes to 7 days. The incident stays open and keeps counting.
Secret API key, not read-only. How it works
Path parameters
idstringrequiredIncident id.
Request body
minutesintegerrequired15 to 10080
Returns
200 OK
Response attributes
dataChatIncidentrequired23 attributes
idstringrequiredorg_idstringproject_idstringrequiredassistant_idstring | nullwidget_key_idstring | nullsitestring | nullrulestringrequiredseveritystringrequiredOne of
infowarningcriticalstatusstringrequiredOne of
openresolveddedupe_keystringfirst_seen_atstringISO 8601 timestamplast_seen_atstringISO 8601 timestampoccurrencesintegerexample_request_idstring | nullexample_conversation_idstring | nulldetailobject | nullmuted_untilstring | nullISO 8601 timestampmuted_bystring | nullresolved_atstring | nullISO 8601 timestampresolutionstring | nullOne of
recoveredquietmanualresolved_bystring | nullcreated_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 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/chat-analytics/incidents/INCIDENT_ID/mute" \
-H "Authorization: Bearer $MLF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"minutes": 15
}'{
"data": {
"id": "id_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"org_id": "org_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"project_id": "proj_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"assistant_id": "asst_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"widget_key_id": "id_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"site": "string",
"rule": "string",
"severity": "info",
"status": "open",
"dedupe_key": "string",
"first_seen_at": "2026-10-28T09:30:00.000Z",
"last_seen_at": "2026-10-28T09:30:00.000Z",
"occurrences": 1,
"example_request_id": "id_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"example_conversation_id": "conv_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"detail": {},
"muted_until": "2026-10-28T09:30:00.000Z",
"muted_by": "string",
"resolved_at": "2026-10-28T09:30:00.000Z",
"resolution": "recovered",
"resolved_by": "string",
"created_at": "2026-10-28T09:30:00.000Z",
"updated_at": "2026-10-28T09:30:00.000Z"
}
}Resolve an incident
POST/api/chat-analytics/incidents/{id}/resolve
Closes the incident as manual. No request body. 409 already_resolved if it is closed already; a new failure opens a new incident.
Secret API key, not read-only. How it works
Path parameters
idstringrequiredIncident id.
Returns
200 OK
Response attributes
dataChatIncidentrequired23 attributes
idstringrequiredorg_idstringproject_idstringrequiredassistant_idstring | nullwidget_key_idstring | nullsitestring | nullrulestringrequiredseveritystringrequiredOne of
infowarningcriticalstatusstringrequiredOne of
openresolveddedupe_keystringfirst_seen_atstringISO 8601 timestamplast_seen_atstringISO 8601 timestampoccurrencesintegerexample_request_idstring | nullexample_conversation_idstring | nulldetailobject | nullmuted_untilstring | nullISO 8601 timestampmuted_bystring | nullresolved_atstring | nullISO 8601 timestampresolutionstring | nullOne of
recoveredquietmanualresolved_bystring | nullcreated_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
- 409 Conflict
- 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/chat-analytics/incidents/INCIDENT_ID/resolve" \
-H "Authorization: Bearer $MLF_API_KEY"{
"data": {
"id": "id_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"org_id": "org_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"project_id": "proj_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"assistant_id": "asst_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"widget_key_id": "id_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"site": "string",
"rule": "string",
"severity": "info",
"status": "open",
"dedupe_key": "string",
"first_seen_at": "2026-10-28T09:30:00.000Z",
"last_seen_at": "2026-10-28T09:30:00.000Z",
"occurrences": 1,
"example_request_id": "id_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"example_conversation_id": "conv_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"detail": {},
"muted_until": "2026-10-28T09:30:00.000Z",
"muted_by": "string",
"resolved_at": "2026-10-28T09:30:00.000Z",
"resolution": "recovered",
"resolved_by": "string",
"created_at": "2026-10-28T09:30:00.000Z",
"updated_at": "2026-10-28T09:30:00.000Z"
}
}Unmute an incident
POST/api/chat-analytics/incidents/{id}/unmute
Resumes notifications for a muted incident. No request body.
Secret API key, not read-only. How it works
Path parameters
idstringrequiredIncident id.
Returns
200 OK
Response attributes
dataChatIncidentrequired23 attributes
idstringrequiredorg_idstringproject_idstringrequiredassistant_idstring | nullwidget_key_idstring | nullsitestring | nullrulestringrequiredseveritystringrequiredOne of
infowarningcriticalstatusstringrequiredOne of
openresolveddedupe_keystringfirst_seen_atstringISO 8601 timestamplast_seen_atstringISO 8601 timestampoccurrencesintegerexample_request_idstring | nullexample_conversation_idstring | nulldetailobject | nullmuted_untilstring | nullISO 8601 timestampmuted_bystring | nullresolved_atstring | nullISO 8601 timestampresolutionstring | nullOne of
recoveredquietmanualresolved_bystring | nullcreated_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 POST "https://api.myleadsfactory.tech/api/chat-analytics/incidents/INCIDENT_ID/unmute" \
-H "Authorization: Bearer $MLF_API_KEY"{
"data": {
"id": "id_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"org_id": "org_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"project_id": "proj_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"assistant_id": "asst_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"widget_key_id": "id_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"site": "string",
"rule": "string",
"severity": "info",
"status": "open",
"dedupe_key": "string",
"first_seen_at": "2026-10-28T09:30:00.000Z",
"last_seen_at": "2026-10-28T09:30:00.000Z",
"occurrences": 1,
"example_request_id": "id_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"example_conversation_id": "conv_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"detail": {},
"muted_until": "2026-10-28T09:30:00.000Z",
"muted_by": "string",
"resolved_at": "2026-10-28T09:30:00.000Z",
"resolution": "recovered",
"resolved_by": "string",
"created_at": "2026-10-28T09:30:00.000Z",
"updated_at": "2026-10-28T09:30:00.000Z"
}
}Label how already analysed chats ended (staff only)
POST/api/chat-analytics/outcomes/backfill
Queues ended, counted web chats in the window whose analysis has no outcome label for an outcome-only run. It sends nothing to any CRM or webhook. At most 500 per call; call again while remaining is true.
Secret API key, not read-only. How it works
Query parameters
project_idstringOnly this project. An org-wide key covers every project by default.
assistant_idarray of stringOnly these assistants. Repeat the parameter or comma-separate the values; a row matching any of them is kept.
fromstringISO 8601 timestampDefault: 24 hours before
to.tostringISO 8601 timestampDefault: now. Ranges over 90 days are rejected.
Returns
200 OK
Response attributes
dataChatOutcomesBackfillrequired3 attributes
queuedintegerrequiredcandidatesintegerrequiredremainingbooleanrequiredMore chats in the window still need a label: call again.
- 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 - 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/chat-analytics/outcomes/backfill" \
-H "Authorization: Bearer $MLF_API_KEY"{
"data": {
"queued": 1,
"candidates": 1,
"remaining": true
}
}Get chat quality
GET/api/chat-analytics/quality
How well the chat is doing for visitors: reply and first-token time, missing replies, handoffs answered, how complete leads are, cost per lead, the assistant lines visitors most often stop after, and improvement (reply feedback, conversation ratings, containment, unanswered knowledge questions, unhelpful answers and comments, with emails, phone numbers and common name patterns masked).
Secret API key. How it works
Query parameters
project_idstringOnly this project. An org-wide key covers every project by default.
assistant_idarray of stringOnly these assistants. Repeat the parameter or comma-separate the values; a row matching any of them is kept.
fromstringISO 8601 timestampDefault: 24 hours before
to.tostringISO 8601 timestampDefault: now. Ranges over 90 days are rejected.
Returns
200 OK
Response attributes
dataChatQualityrequired13 attributes
reply_msobject7 attributes
bucketsarray of object2 attributes
le_msinteger | nullrequiredcountintegerrequired
p50integer | nullp90integer | nullp95integer | nullp99integer | nullsamplesintegerReplies measured.
slowintegerReplies slower than 15 seconds.
first_token_msobject2 attributes
p50integer | nullp90integer | null
repliesobject3 attributes
answeredintegermissingintegerrescuedinteger
sendsobject3 attributes
totalintegerfailedintegerquick_reply_sharenumber | null
handoffobject4 attributes
requestedintegeransweredintegerTaken over by an advisor.
repliedintegerAn advisor wrote after the request.
median_answer_secondsinteger | null
leadsobject10 attributes
totalintegerwith_emailintegerwith_phoneintegerwith_nameintegervalidintegerWith one email or phone that is not a placeholder or the business's own number.
flagsobject3 attributes
placeholder_emailintegerplaceholder_phoneintegerbusiness_phoneinteger
required_completeinteger | nullLeads holding every required field; null when no assistant marks any.
bookedintegerlostintegerrevenuearray of object2 attributes
currencystringrequiredamountnumberrequired
costobject2 attributes
totalarray of object2 attributes
currencystringrequiredamountnumberrequired
per_leadarray of object2 attributes
currencystringrequiredamountnumberrequired
stop_pointsarray of objectWhere ended chats stopped while the assistant waited on the visitor, by what it had asked (from the analysis labels).
3 attributes
keystringrequiredtextstringrequiredconversationsintegerrequired
outcomesobjectHow ended chats ended, from the analysis labels. Counts by label;
labelledofendedwere labelled.7 attributes
endedintegerlabelledintegerintentsobjectgoalobjectwaiting_onobjectcontact_declinedintegerfrictionobject
toolsarray of objectTool calls in the window's chats.
expectedfailures are the tool asking for what is missing; the rest are the platform failing.5 attributes
namestringcallsintegerfailedintegerexpectedintegererrorsobject
knowledgeobjectKnowledge searches by result: unavailable is the search failing, empty is missing content.
5 attributes
searchesintegerokintegeremptyintegerunavailableintegerotherinteger
improvementChatImprovementrequiredWhat visitors said about the chat, and where it could do better. Quoted text is one line, at most 200 characters, with emails and phone numbers masked, and names masked where they follow common patterns (an introduction such as "my name is" or a greeting such as "Thanks Jane"). Other personal details can remain.
6 attributes
repliesobjectrequiredThumbs up and down on assistant replies.
4 attributes
ratedintegerrequiredupintegerrequireddownintegerrequiredreasonsobjectrequiredThumbs-down count per reason.
chats_ratedobjectrequired3 attributes
countintegerrequiredaveragenumber | nullrequireddistributionarray of integerrequiredCount of 1 to 5 star ratings, in order.
containmentobjectrequired5 attributes
chatsintegerrequiredwithout_personintegerrequiredNever asked for a person.
with_leadintegerrequiredasked_for_personintegerrequiredone_messageintegerrequiredConversations, open or ended, with exactly one visitor message and no lead.
knowledge_gapsobjectrequiredReplies whose knowledge searches all came back empty.
2 attributes
totalintegerrequiredrecentarray of objectrequired3 attributes
questionstringrequiredconversation_idstringrequiredatstringrequiredISO 8601 timestamp
unhelpfularray of objectrequiredRecent replies rated down.
4 attributes
answerstringrequiredreasonstring | nullrequiredconversation_idstringrequiredatstringrequiredISO 8601 timestamp
commentsarray of objectrequiredRecent conversation ratings that came with a comment.
4 attributes
ratingintegerrequiredcommentstringrequiredconversation_idstringrequiredatstringrequiredISO 8601 timestamp
partialbooleanrequiredTrue when not every send in the window could be read, so the counts are a lower bound. Retry, or narrow the range.
- 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/chat-analytics/quality" \
-H "Authorization: Bearer $MLF_API_KEY"{
"data": {
"reply_ms": {
"buckets": [
{
"le_ms": 640,
"count": 1
}
],
"p50": 1,
"p90": 1,
"p95": 1,
"p99": 1,
"samples": 1,
"slow": 1
},
"first_token_ms": {
"p50": 1,
"p90": 1
},
"replies": {
"answered": 1,
"missing": 1,
"rescued": 1
},
"sends": {
"total": 1,
"failed": 1,
"quick_reply_share": 1
},
"handoff": {
"requested": 1,
"answered": 1,
"replied": 1,
"median_answer_seconds": 1
},
"leads": {
"total": 1,
"with_email": 1,
"with_phone": 1,
"with_name": 1,
"valid": 1,
"flags": {
"placeholder_email": 1,
"placeholder_phone": 1,
"business_phone": 1
},
"required_complete": 1,
"booked": 1,
"lost": 1,
"revenue": [
{
"currency": "string",
"amount": 1
}
]
},
"cost": {
"total": [
{
"currency": "string",
"amount": 1
}
],
"per_lead": [
{
"currency": "string",
"amount": 1
}
]
},
"stop_points": [
{
"key": "string",
"text": "Hi, can I book a trip to Lisbon in May?",
"conversations": 1
}
],
"outcomes": {
"ended": 1,
"labelled": 1,
"intents": {
"key": 1
},
"goal": {
"key": 1
},
"waiting_on": {
"key": 1
},
"contact_declined": 1,
"friction": {
"key": 1
}
},
"tools": [
{
"name": "Front desk",
"calls": 1,
"failed": 1,
"expected": 1,
"errors": {
"key": 1
}
}
],
"knowledge": {
"searches": 1,
"ok": 1,
"empty": 1,
"unavailable": 1,
"other": 1
},
"improvement": {
"replies": {
"rated": 1,
"up": 1,
"down": 1,
"reasons": {
"key": 1
}
},
"chats_rated": {
"count": 1,
"average": 1,
"distribution": [
1
]
},
"containment": {
"chats": 1,
"without_person": 1,
"with_lead": 1,
"asked_for_person": 1,
"one_message": 1
},
"knowledge_gaps": {
"total": 1,
"recent": [
{
"question": {},
"conversation_id": {},
"at": {}
}
]
},
"unhelpful": [
{
"answer": "string",
"reason": "string",
"conversation_id": "conv_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"at": "2026-10-28T09:30:00.000Z"
}
],
"comments": [
{
"rating": 1,
"comment": "string",
"conversation_id": "conv_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"at": "2026-10-28T09:30:00.000Z"
}
]
},
"partial": true
}
}List notification subscriptions
GET/api/chat-analytics/subscriptions
Where health alerts and the digest are sent: Slack incoming webhooks or your own HTTPS endpoint. Needs the admin role, which API keys have.
Secret API key. How it works
Query parameters
project_idstringOnly this project. An org-wide key covers every project by default.
Returns
200 OK
Response attributes
dataarray of ChatSubscriptionrequired9 attributes
idstringrequiredorg_idstringproject_idstringrequiredkindstringrequiredOne of
healthdigestchannelChatNotificationChannelrequired2 attributes
typestringrequiredOne of
slackwebhookurlstringrequireduri
enabledbooleanrequiredtimezonestringrequiredcreated_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 - 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/chat-analytics/subscriptions" \
-H "Authorization: Bearer $MLF_API_KEY"{
"data": [
{
"id": "whsub_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"org_id": "org_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"project_id": "proj_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"kind": "health",
"channel": {
"type": "slack",
"url": "https://example.com/hook"
},
"enabled": true,
"timezone": "string",
"created_at": "2026-10-28T09:30:00.000Z",
"updated_at": "2026-10-28T09:30:00.000Z"
}
]
}Create a notification subscription
POST/api/chat-analytics/subscriptions
Sends health alerts (incidents opening and resolving) or a digest summary to a Slack incoming webhook or an HTTPS URL. An org-wide key must name project_id.
Secret API key, not read-only. How it works
Request body
project_idstringkindstringrequiredOne of
healthdigestchannelChatNotificationChannelrequired2 attributes
typestringrequiredOne of
slackwebhookurlstringrequireduri
timezonestringIANA zone; default UTC.
Returns
201 OK
Response attributes
dataChatSubscriptionrequired9 attributes
idstringrequiredorg_idstringproject_idstringrequiredkindstringrequiredOne of
healthdigestchannelChatNotificationChannelrequired2 attributes
typestringrequiredOne of
slackwebhookurlstringrequireduri
enabledbooleanrequiredtimezonestringrequiredcreated_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 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/chat-analytics/subscriptions" \
-H "Authorization: Bearer $MLF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"kind": "health",
"channel": {
"type": "slack",
"url": "https://example.com/hook"
}
}'{
"data": {
"id": "whsub_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"org_id": "org_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"project_id": "proj_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"kind": "health",
"channel": {
"type": "slack",
"url": "https://example.com/hook"
},
"enabled": true,
"timezone": "string",
"created_at": "2026-10-28T09:30:00.000Z",
"updated_at": "2026-10-28T09:30:00.000Z"
}
}Update a notification subscription
PATCH/api/chat-analytics/subscriptions/{id}
Turn a subscription on or off, or change where it sends or its time zone. Send at least one field.
Secret API key, not read-only. How it works
Path parameters
idstringrequiredSubscription id.
Request body
enabledbooleanchannelChatNotificationChannel2 attributes
typestringrequiredOne of
slackwebhookurlstringrequireduri
timezonestring
Returns
200 OK
Response attributes
dataChatSubscriptionrequired9 attributes
idstringrequiredorg_idstringproject_idstringrequiredkindstringrequiredOne of
healthdigestchannelChatNotificationChannelrequired2 attributes
typestringrequiredOne of
slackwebhookurlstringrequireduri
enabledbooleanrequiredtimezonestringrequiredcreated_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 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/chat-analytics/subscriptions/SUBSCRIPTION_ID" \
-H "Authorization: Bearer $MLF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"channel": {
"type": "slack",
"url": "https://example.com/hook"
}
}'{
"data": {
"id": "whsub_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"org_id": "org_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"project_id": "proj_5f0c2a9e7b1d4c3e8a6f0b2d4c6e8a1f",
"kind": "health",
"channel": {
"type": "slack",
"url": "https://example.com/hook"
},
"enabled": true,
"timezone": "string",
"created_at": "2026-10-28T09:30:00.000Z",
"updated_at": "2026-10-28T09:30:00.000Z"
}
}Delete a notification subscription
DELETE/api/chat-analytics/subscriptions/{id}
Stops the subscription for good. Answers 204 with no body.
Secret API key, not read-only. How it works
Path parameters
idstringrequiredSubscription id.
Returns
204 Deleted
- 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/chat-analytics/subscriptions/SUBSCRIPTION_ID" \
-H "Authorization: Bearer $MLF_API_KEY"Get daily or weekly chat totals
GET/api/chat-analytics/trend
Funnel totals per day or per ISO week (weeks start on Monday), for charts over a longer range than the live views.
Secret API key. How it works
Query parameters
project_idstringOnly this project. An org-wide key covers every project by default.
assistant_idarray of stringOnly these assistants. Repeat the parameter or comma-separate the values; a row matching any of them is kept.
fromstringISO 8601 timestampDefault: 90 days before
to.tostringISO 8601 timestampDefault: now. Up to 400 days.
intervalstringOne point per day or per week.
One of
dayweekDefault
"day"
Returns
200 OK
Response attributes
dataChatTrendrequired2 attributes
intervalstringrequiredOne of
dayweekpointsarray of objectrequired10 attributes
periodstringrequiredYYYY-MM-DD; a Monday for weekly points.
page_viewsintegershownintegeropenedintegersendsintegeracceptedintegerfailedintegerconversationsintegerleadsintegerhandoffsinteger
- 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/chat-analytics/trend" \
-H "Authorization: Bearer $MLF_API_KEY"{
"data": {
"interval": "day",
"points": [
{
"period": "string",
"page_views": 1,
"shown": 1,
"opened": 1,
"sends": 1,
"accepted": 1,
"failed": 1,
"conversations": 1,
"leads": 1,
"handoffs": 1
}
]
}
}