API documentation - Bot Analytics Export
The All Events Dump API returns the raw, event-level data behind Bot Analytics — every user message, the bot's response, whether it was handled, the session it belonged to, and the user's profile fields. Use it to feed your own data warehouse, BI tool or reporting pipeline instead of exporting from the dashboard by hand.
The API is region-specific: you call the authentication and analytics hosts of the region your workspace is hosted in.
Before you startYou need an API credential with the Read Analytics Data scope, created in Admin Console → API credentials. See API credentials.
How it works
- Create an API credential in the Admin Console with the Read Analytics Data scope.
- Generate an access token from your region's authentication URL.
- Call the analytics endpoint for your region with the access token, a date range and pagination parameters.
- Page through the results until you have fetched all records.
Step 1: Create an API credential
- In the Admin Console, go to API credentials.
- Click Add credentials.
- Enter an Auth name (up to 50 characters) — name it after the consuming system, for example
Snowflake – bot events. - In Allowed scope, select Read Analytics Data.
- Click Create credentials.
- Copy the Client ID, Client secret, Username and Password, and store them in a secure location such as a secrets manager.
Secrets are shown only onceThe client secret and password cannot be viewed again after you leave the page — not by you and not by Leena AI support. If you lose them, create a new credential and revoke the old one.
Find your regionThe How to use this key command shown on the credential screen already contains your region's authentication URL (for example
https://us-east-1-acl.leena.ai/...). Use the same region for the analytics URL below.
Step 2: Choose your regional base URLs
Use the Auth URL to generate tokens and the Analytics URL to fetch data. Both must belong to the same region — a token issued in one region is not valid in another.
| Region | Auth URL | Analytics URL |
|---|---|---|
| US East (us-east-1) | https://us-east-1-acl.leena.ai | https://us-east-1-analytics-api.leena.ai |
| EU West (eu-west-1) | https://eu-west-1-acl.leena.ai | https://eu-west-1-analytics-api.leena.ai |
| EU Central (eu-central-1) | https://eu-central-1-acl.leena.ai | https://eu-central-1-analytics-api.leena.ai |
| Canada Central (canadacentral) | https://canadacentral-acl.leena.ai | https://canadacentral-analytics-api.leena.ai |
| Asia Pacific – Singapore (ap-southeast-1) | https://ap-southeast-1-acl.leena.ai | https://ap-southeast-1-analytics-api.leena.ai |
| Asia Pacific – India (ap-south-1) | https://acl.leena.ai | https://analytics-api.leena.ai |
| Qatar Central (qatarcentral) | https://qatarcentral-acl.leena.ai | https://qatarcentral-analytics-api.leena.ai |
| Middle East Central 2 (me-central2) | https://me-central2-acl.leena.ai | https://me-central2-analytics-api.leena.ai |
Dedicated instancesIf your workspace runs on a dedicated instance, your hosts use your instance name instead of a region code. Contact your Leena AI representative for the exact URLs.
In the examples below, replace <AUTH_URL> and <ANALYTICS_URL> with the values for your region.
Step 3: Generate an access token
Send a POST request to your region's token endpoint. The Authorization header is HTTP Basic auth built from clientId:clientSecret, Base64-encoded. The username and password go in the request body.
Endpoint
POST <AUTH_URL>/api/v1.0/oauth/token
Request
curl --location '<AUTH_URL>/api/v1.0/oauth/token' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic <base64(clientId:clientSecret)>' \
--data '{
"username": "<username>",
"password": "<password>",
"grant_type": "password"
}'Response — 200 OK
{
"token_type": "Bearer",
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 1800,
"refresh_token": "82e2cbfa-4d1a-42fc-857e-7da2faad7b70"
}
Tokens expire after 30 minutes
expires_inis in seconds. Generate a new token, or use the refresh token withPOST <AUTH_URL>/api/v1.0/oauth/refresh-token, before the access token expires. Long exports that page through many results should check token age between requests.
For more on grant types, refresh tokens and credential rotation, see API credentials – token generation.
Step 4: Fetch all events
Endpoint
GET <ANALYTICS_URL>/api/analytics/query/external
Headers
| Header | Value |
|---|---|
Authorization | Bearer <access_token> |
You don't need to pass a bot ID. The workspace is identified from the access token.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
metricId | string | Yes | Fixed value: botAnalytics:allEventsDump (URL-encoded: botAnalytics%3AallEventsDump). |
isCustomMetric | boolean | Yes | Fixed value: true. |
responseType | string | Yes | Fixed value: table. |
from | string (ISO 8601) | Yes | Start of the date range, e.g. 2026-01-01 or 2026-01-01T00:00:00+05:30. A date without a time is read as 00:00 UTC. |
to | string (ISO 8601) | Yes | End of the date range, in the same format as from. To cover all of January, use from=2026-01-01&to=2026-02-01. |
page | integer | No | Page number, starting at 1. Default: 1. |
limit | integer | No | Number of records per page. Default: 10. |
timezone | string (IANA) | No | Timezone used to render time values, e.g. America/New_York. Default: Asia/Calcutta. |
Request
curl -G '<ANALYTICS_URL>/api/analytics/query/external' \
--header 'Authorization: Bearer <access_token>' \
--data-urlencode 'metricId=botAnalytics:allEventsDump' \
--data-urlencode 'isCustomMetric=true' \
--data-urlencode 'responseType=table' \
--data-urlencode 'from=2026-01-01' \
--data-urlencode 'to=2026-02-01' \
--data-urlencode 'page=1' \
--data-urlencode 'limit=100'Response — 200 OK
The example below is shortened to one column definition and one event.
{
"isSuccess": true,
"result": {
"current": 1,
"page": 1,
"limit": 10,
"total": 47668,
"isManageColumnsEnabled": true,
"columns": [
{
"key": "userMessage",
"label": "User message",
"type": "number",
"sortable": true,
"isDynamicColumn": false,
"isMandatory": false,
"isVisible": true,
"sticky": false
}
],
"tableData": [
{
"employeeId": "EMP1024",
"userMessage": "How many leaves do I have left?",
"botResponse": "You have 12 days of annual leave remaining.",
"timestamp": "Jan 14 2026, 3:08 PM",
"utcTimestamp": "Jan 14 2026, 9:38 AM",
"messageType": "plainText",
"isHandled": "true",
"isSurveyResponse": "false",
"sessionId": "69676978e4451ee81cc38d48",
"isEffective": "true",
"returningUser": "false",
"rating": "",
"gptRequestId": "",
"handledSubcategory": "",
"skills": "",
"articles": "",
"Channel": "slack",
"eventId": "6967640ed690eb787a38a218",
"firstName": "Alex",
"lastName": "Morgan",
"email": "[email protected]",
"displayName": "Alex Morgan"
}
]
}
}| Field | Type | Description |
|---|---|---|
isSuccess | boolean | true if the query succeeded. |
result.tableData | array | The events on this page. Each element is one event, keyed by the column keys listed in Response columns. |
result.columns | array | Column metadata. Each entry's key is the property name used in tableData, and label is its display name. |
result.total | integer | Total number of events across all pages for the date range. |
result.page | integer | Page number returned. result.current holds the same value. |
result.limit | integer | Page size returned. |
result.isManageColumnsEnabled | boolean | Dashboard display setting. Can be ignored. |
Read values as stringsAll values in
tableDataare returned as strings:
- True/false fields contain
"true"or"false".- Empty fields contain
"".- Timestamps are formatted as
MMM DD YYYY, h:mm A, e.g."Jan 14 2026, 3:08 PM".The
typeproperty inresult.columnsdescribes how the dashboard renders a column, not the data type of its values. Don't use it to parse the data.
Response columns
Each event in tableData contains the following fields. Use the Key column to read values from each row. Keys are case-sensitive: note that Channel starts with a capital letter.
Event fields
These fields are always present.
| Key | Label | Description |
|---|---|---|
employeeId | Employee Id | Employee ID of the user. Empty if the user has no employee ID in their profile. |
userMessage | User message | The plain text, or the button title, that the user sent. System values such as INVALID_POSTBACK can appear for non-text events, for example file uploads. |
botResponse | Bot response | The bot's response to the user message. If the bot sent several messages in reply, they're joined with a semicolon (;). |
timestamp | Timestamp | Time of the user message in the request timezone (see the timezone parameter; default Asia/Calcutta). |
utcTimestamp | Utc Timestamp | Time of the user message in UTC. |
messageType | Message Type | The type of event, e.g. plainText (typed message), quickReply (button or quick-reply selection), fileUpload. Includes "message seen" events, which are not counted as interactions. |
isHandled | Is Handled | "true" if the bot answered or executed the message or request. |
isSurveyResponse | Is Survey Response | "true" if the message was a response to a survey. |
sessionId | Session Id | Unique ID of the user's session. A session ends after 30 minutes of inactivity. |
isEffective | Is Effective | "true" if the session was effective. A session with no handled request or interaction is ineffective. |
returningUser | Returning User | "true" if the user had two or more sessions in the selected date range. |
rating | Rating | Thumbs up or thumbs down feedback the user gave on the bot response. Empty if no feedback was given. |
gptRequestId | Gpt Request Id | Unique ID of the request. One request can include several interactions, messages or events. Use it to group events that belong to the same request. |
handledSubcategory | Handled Subcategory | What happened to the request (see values below). |
skills | Skills | Skills used to answer the user's query for the request. |
articles | Articles | Knowledge articles used to answer the user's query. defaultFallback means the bot didn't understand the message and sent its fallback response. |
Channel | Channel | The channel the event came from, e.g. slack, msteams, web. |
eventId | Event Id | Unique ID of the event. Use it as the primary key when storing or de-duplicating events. |
Handled Subcategory values
| Value | Meaning |
|---|---|
| Handled successfully | The request was executed completely and successfully. |
| Handled unsuccessfully | The request could not be completed because of a validation error — for example, a leave application when the user has no leave balance. |
| Failed | An error occurred while executing the request. |
| User Abandoned | The user didn't respond to the bot's follow-up question after the initial query. |
| User Aborted | The user cancelled the request while the bot was generating the response. |
User profile fields
After the event fields, each event includes the user's profile fields. Which fields appear depends on your workspace's user profile configuration, so the set varies between workspaces. For example:
| Key | Label | Description |
|---|---|---|
firstName | First Name | User's first name. |
lastName | Last Name | User's last name. |
displayName | Display Name | User's display name. |
fullName | Full Name | User's full name. |
email | User's email address. | |
organization | Organization | User's organization. |
company | Company | User's company. |
hrId | Hr Id | User's HR system ID. |
grade | Grade | User's grade or level. |
Read result.columns to get the exact list of fields for your workspace. Fields that aren't filled in for a user are returned as "".
For definitions of sessions, interactions and handled queries, see the Bot Analytics glossary.
Paginating through results
Results are paginated. To fetch all events for a date range:
- Request
page=1with your chosenlimit. - Read
result.totaland calculate the number of pages:ceil(total / limit). - Request each following page by incrementing
pageuntil you have fetched all pages.
Best practices
- Pull data in smaller date windows (for example one day or one week at a time) for high-volume workspaces. Large ranges and page sizes increase response time.
- For scheduled syncs, request the window since your last successful run, e.g. yesterday's data each morning.
- Responses are not cached, so every call returns the latest processed data. Recent events can take some time to be processed before they appear.
- Store credentials in a secrets manager. Don't share them in documents, email or chat.
Errors
| Status | Message | What to do |
|---|---|---|
401 | You are not authorized to perform this action | The Authorization header is missing, or the token is invalid or expired. Generate a new token. Check that the token and analytics URLs are from the same region. |
401 | Invalid authorization header. Must be a bearer token | Send the header as Authorization: Bearer <access_token>. |
401 | You are not authorized to perform this action. Invalid scope. | The credential doesn't have the Read Analytics Data scope. Create a credential with that scope. |
401 (token endpoint) | — | Wrong client ID, client secret, username or password, or the credential has been revoked. |
500 | Metric not found | Check that metricId=botAnalytics:allEventsDump and isCustomMetric=true are both set. |
Related pages
Updated about 1 hour ago
