Chat Metrics
Chat metrics let you pull the raw conversational data behind a chat AI Employee (AIE) into your own data warehouse or BI tool. Where the in-app metrics dashboard gives you charts inside Ema, the chat metrics export gives you the underlying rows — one per message — so you can join, aggregate, and analyze them however you need.
The export is served by the chat service over two endpoints: a modern, streaming messages export (32 columns) and a legacy, wire-compatible conversations export (22 columns). Both support CSV; the messages export also supports NDJSON and paginated JSON.
Admin-only, and enabled per environment. Both export endpoints require the system_admin role, and they are gated behind a feature flag (EMU_CHAT_METRICS_API_ENABLED): the routes are not registered until your environment is configured for the export. If the endpoints return a not-found, the export isn't enabled for your tenant yet.
Messages export (32 columns)
GET /metrics/messages returns row-level conversational data — one row per chat message — with the message joined to the workflow run that produced it, retrieval sources, user feedback, conversation review state, and non-PII user context. This is the export to use for new BI integrations.
Formats
Choose the format with the format query parameter (CSV is the default; the Accept header is honored as a fallback when format is omitted):
format | Behavior |
|---|---|
csv (default) | RFC 4180 CSV, streamed via chunked transfer with Content-Disposition: attachment. One header row, then one row per message. Streams the full filtered range. |
ndjson | One JSON record per line, no envelope, streamed. Streams the full filtered range. |
json | One paginated page: { items, next_cursor, page_info }. You walk next_cursor until it's null. |
Repeated or unbounded structures (citations, model breakdown, user attributes) are JSON-encoded into single columns (sources_json, model_breakdown_json, user_attributes_json) so a warehouse PARSE_JSON() works without exploding the row count.
Filters
| Parameter | Meaning |
|---|---|
since | Inclusive lower bound on the message created_at (RFC 3339). |
until | Exclusive upper bound on created_at (RFC 3339). Defaults to now. |
updated_since | Switches the time filter to a 30-day look-back over message, feedback, and review update times — returns rows whose message, feedback, or review state changed at or after this timestamp (RFC 3339). |
workflow_id | Restrict to one AI Employee (UUID). |
cursor | Opaque keyset cursor from the previous page's next_cursor. Only valid with format=json. |
limit | Page size for format=json (1–500, default 200). CSV and NDJSON ignore this and stream the full range. |
Supplying cursor or limit with a streaming format returns a validation error (422) — the streaming formats always walk the entire filtered range.
Columns
The CSV header is fixed and ordered — 32 columns. Each row carries these columns:
| Column | Notes |
|---|---|
message_id | |
role | Message author role. |
content | Message text (PII rendered per tenant policy — see below). |
created_at | |
session_id | |
session_title | |
session_started_at | |
session_deleted | |
session_deleted_at | |
channel_type | The channel the conversation came through. |
channel_display_name | |
workflow_id | The AI Employee. |
run_id | The workflow run that produced the message; empty when there is none. |
run_status | |
run_latency_ms | |
run_total_tokens | |
feedback_sentiment | Empty when no feedback. |
feedback_category | |
feedback_text | |
feedback_submitted_at | |
feedback_updated_at | |
review_resolution_status | Conversation review state; empty when not reviewed. |
review_comment_count | |
review_updated_at | |
user_actor_type | |
user_external_id | |
user_display_name | |
user_department | |
user_attributes_json | JSON object; {} when empty. |
sources_json | JSON array of retrieval sources; [] when empty. |
model_breakdown_json | JSON array of per-model token usage; [] when empty. |
schema_version | The export schema version. Adding a column at the end is non-breaking; reordering or removing is announced via a Sunset header and a schema_version bump. |
curl -G 'https://<your-tenant>/api/v1/chat/metrics/messages' \
-H 'Authorization: Bearer <token>' \
--data-urlencode 'format=csv' \
--data-urlencode 'since=2026-05-01T00:00:00Z' \
--data-urlencode 'until=2026-06-01T00:00:00Z' \
--data-urlencode 'workflow_id=8f3c....'
Streaming, limits, and errors
- One streaming export per tenant. CSV and NDJSON are subject to a per-tenant concurrent-stream cap — only one streaming export runs at a time, and the cap is shared across both endpoints so you can't open
/metrics/messagesand/metrics/conversationsin parallel to dodge it. A second concurrent request gets429with aRetry-After. Paginated JSON is not subject to the cap. - Mid-stream truncation. Because streaming sends
200 OKand the header row before all rows are known, an error after streaming has begun can't change the status code. Detect truncation by row count, not by HTTP status. - Slow queries. A query that exceeds the database statement timeout returns
503with aRetry-After; narrow the time window and retry. - Auditing. Every export is recorded as an audit event (with format, row count, and the filters used), because content may be returned in cleartext depending on tenant PII policy.
Conversations export (legacy, 22 columns)
GET /metrics/conversations is a wire-compatible replacement for the legacy chatbot/conversations export. It returns one row per chat message in the window, denormalized with conversation, external-user, feedback, and workflow-run-error context, in a fixed 22-column order that matches the legacy exporter exactly — so existing downstream warehouses keep working without a contract change.
Parameters
| Parameter | Meaning |
|---|---|
persona_id (required) | The AI Employee (UUID) — the chat session's workflow_id. |
start_date (required) | Inclusive lower bound, UTC YYYY-MM-DD. |
end_date (required) | Inclusive upper bound, UTC YYYY-MM-DD. The total range must be ≤ 31 days. |
format | csv (default) or json. json returns an array with the same field names as the CSV header. |
An empty range returns 200 with a header-only CSV. An invalid persona_id, malformed dates, or a range over 31 days returns 422.
Columns
The 22 columns, in order:
conversation_id, conversation_source, conversation_created_at, conversation_updated_at, encrypted_user_email, message_id, is_user_message, message_created_at, message_updated_at, workflow_run_error, external_tool_called, external_tool_call_success, external_tool_name, user_feedback_type, user_feedback_comment, feedback_message_sent, content_payload_json, user_context, unique_source_ids, country, band, events_per_message.
Which export to use. Use the messages export for new integrations — it streams, supports NDJSON and JSON, has richer columns, and carries a schema_version. Use the conversations export only when you must match the legacy 22-column contract a downstream warehouse already depends on.
A note on PII and content
Message content and user context may contain personal data. What's returned depends on your tenant's PII policy: content can be rendered in cleartext for tenants that have opted in, which is exactly why every export is audited. The export never silently falls back to raw cleartext when the PII service is unavailable — it returns 503 instead. Treat exported files as sensitive data and handle them under your organization's data-governance rules.
What's next
- Launching and monitoring — the in-app dashboard view of the same data.
- Debug logs — drill into the individual run behind a
run_idin the export. - API reference — full request and response schemas.