Every analytics event of the workspace in [from, to), for loading into a data warehouse: conversations, messages, CSAT, tool calls, Agent Assist (copilot.*) and the rest. One row per event_id, oldest first (occurred_at, then event_id). Test and playground traffic is left out unless include_test=true.
| Column | Type | Meaning |
|---|---|---|
event_id | uuid | Stable id of the event. Upsert on it: the same event is never two rows. |
occurred_at | date-time | When it happened, UTC, to the second. |
event_type | string | e.g. conversation.created, message.sent, copilot.rated. |
channel | string | Where it happened: widget, whatsapp, salesforce, salesforce_agent_assist, inbox_copilot, … |
conversation_id | uuid or empty | The Ayro conversation, when there is one. |
agent_id | uuid or empty | The Ayro AI agent involved. |
skill_id | uuid or empty | The skill involved. |
value | number or empty | The event's number: a CSAT score, a latency in ms, +1 / -1 / 0 for a thumb. |
dims | object | The event's other attributes. A JSON string in CSV. |
Loading it: upsert on event_id. Each event is exported once, in the window holding its first occurrence, and its occurred_at does not change between exports. Its dims can: a judged answer replaces the first version of answer.judged, so upsert rather than insert-only. Some events are written late (a backfill, a rating given days after the answer), so re-read the last few days on every run rather than only the newest day. occurred_at is to the second; within one second the order is not meaningful. Columns are only ever added, never renamed or removed; new event_type values and dims keys can appear at any time.
Authentication: a workspace API key (Bearer krns_sk_…) with the analytics:read scope, or an unscoped key. Create one per pipeline in Settings → API Keys.
Output: format=ndjson (recommended for pipelines) or csv (default), gzipped unless compression=none. Sent as an attachment with no Content-Encoding, so curl -o saves the .gz as is. The data starts streaming at once; the exact row count is in the NDJSON trailer.
Detecting an incomplete file: NDJSON ends with {"_export":"complete","rows":<n>,"from":"2026-09-01T00:00:00Z","to":"2026-10-01T00:00:00Z"} (the window, UTC), or with {"_export":"failed",…} when the export broke midway. A failing CSV download is aborted, so the client sees an error (curl -f exits non-zero) and never a short file that looks whole.
Limits: to - from at most 548 days (the retention of analytics events); one export at a time per workspace (409); 30 exports per workspace per day (429); a few exports at a time across all customers (503 when busy). Every refusal carries Retry-After; a refused or invalid request does not count toward the 30. A month of a busy workspace (millions of events) takes a few minutes; for a first load, pull one month per request, in sequence, rather than the whole range at once.
Event types
| Event type | Meaning | dims |
|---|---|---|
conversation.created | A conversation starts.value: 1One row per conversation; occurred_at is its first message | — |
conversation.resolved | The conversation reaches resolved: the bot finished, the visitor closed it, a teammate closed it, or it went idle.value: 1One row per conversation (reopen cycle, when cycle_count is set); occurred_at is the first time | resolved_by ∈ idle, agent, operator_idle, visitor, customer, teammate (visitor and customer both mean the visitor closed it); cycle_count |
conversation.escalated | The conversation is handed to a human or another system.value: 1One row per conversation (reopen cycle, when cycle_count is set); occurred_at is the first hand-off, later hand-offs in the same conversation are not separate events | escalated_by: the destination, e.g. salesforce_live_chat, zendesk_ticket, tidio_live_chat, kronos_live, api; cycle_count |
conversation.reopened | A visitor writes again in a resolved conversation.value: ms from the last agent reply to the visitor coming back, or emptyOne row per reopen cycle | cycle_count, reopened_by = visitor |
conversation.first_response | The agent first answers in a cycle.value: response time in msOne row per reopen cycle; occurred_at is the answer | cycle_count |
conversation.language_detected | The conversation language is detected or corrected.value: 1One row per conversation; a correction replaces the earlier verdict | language (ISO code), source ∈ auto, manual |
message.sent | A message is sent in a conversation.value: message length in characters (not a count)One row per message | sender ∈ visitor, agent, teammate, internal_note |
message.classified | A visitor message gets an intent.value: 1One row per message | intent (the workspace's intent slugs, or other), message_id |
tool.invoked | The agent calls a tool.value: emptyOne row per tool call | tool_name, outcome ∈ ok, error, empty, tool_type ∈ custom, composio, personalized, mcp, builtin, escalation, message_id |
knowledge.retrieved | The agent searches the knowledge base.value: chunks returned (0 when nothing came back)One row per search | outcome ∈ ok, error, empty, kb_id, top_similarity, query_length, message_id |
csat.requested | A satisfaction survey is shown or sent.value: emptyOne row per conversation | reason ∈ agent_request, resolved_async, visitor_minimize, live_chat_ended |
human_csat | The visitor rates the conversation. Not written for conversations a human handled.value: score 1–5One row per conversation | — |
human_csat.commented | The visitor adds a comment to the rating.value: the score 1–5, or emptyOne row per conversation | source ∈ widget, api, email, agent |
ai_csat | The AI judge scores the conversation.value: score 1–5One row per conversation | prompt_version (0 = default rubric) |
answer.judged | An agent answer is checked: first from its tool trace, then replaced by the judge's verdict.value: 1One row per answer (the visitor turn); the verdict replaces the first version | message_id, intent, grounded ("true"/"false", a string), answer_source ∈ answered_from_knowledge, answered_from_procedure, answered_from_tool, answered_from_model, knowledge_gap, integration_gap, no_answer, problem_type, severity ∈ low, medium, high, tool_type |
sentry.issue_created | A recurring problem across conversations is opened.value: conversations affectedOne row per issue | issue_id, problem_type, intent, suggested_action ∈ connect_integration, draft_article, review_escalation, none, severity, status |
sentry.issue_updated | A problem's status or size changes.value: conversations affectedOne row per change | as sentry.issue_created; status ∈ open, resolved, dismissed |
copilot.suggested | Agent Assist gives a human agent a suggestion (Salesforce panel or Ayro inbox).value: 1One row per suggestion | interaction_id, surface ∈ salesforce, inbox, action ∈ ask, suggest_reply, summarize, full_assist, … , trigger = auto_open when the panel ran it without a click, sf_org_id, sf_user_id, sf_record_id (Salesforce) or teammate_id (inbox) |
copilot.rated | The human agent sets, changes or clears a thumb.value: 1 up, -1 down, 0 clearedOne row per thumb change; the latest per interaction_id (by occurred_at, then changed_at_ms) is the current one | as copilot.suggested, plus rating ∈ up, down, none, changed_at_ms |
copilot.inserted | The human agent puts the suggestion into the reply box.value: 1One row per suggestion; occurred_at is the first insert | as copilot.suggested, plus inserted_into ∈ reply, note, replace |
copilot.sent | The inserted suggestion is sent (Ayro inbox only).value: 1One row per suggestion; occurred_at is the first send | as copilot.suggested |
teammate.invited | An owner invites a teammate.value: emptyOne row per action | invited_by_user_id, invited_user_id, role, was_new_user |
teammate.invite_resent | An invitation is sent again.value: emptyOne row per action | resent_by_user_id, target_user_id |
teammate.role_changed | A teammate's role changes.value: emptyOne row per action | changed_by_user_id, target_user_id, old_role, new_role |
teammate.removed | A teammate is removed or leaves.value: emptyOne row per action | removed_by_user_id, removed_user_id, was_self |
curl -fSL -H "Authorization: Bearer krns_sk_…" \
"https://api.getkronos.ai/api/analytics/export/events?from=2026-09-01T00:00:00Z&to=2026-10-01T00:00:00Z&format=ndjson" \
-o events.ndjson.gz| Time | Status | User Agent | |
|---|---|---|---|
Retrieving recent requests… | |||
400A missing or invalid parameter, or a window that is too wide.
401Missing or invalid API key.
403The key lacks the analytics:read scope.
409An export is already running for this workspace; retry after Retry-After seconds.
429The daily limit of 30 exports is used up; Retry-After says when it resets (00:00 UTC).
503Exports are busy across all workspaces; retry after Retry-After seconds.
