Export conversation sessions API
Returns one row per (agent session × matched ticket) for a date window — the same shape as Filum's scheduled Google Sheets export, exposed as a paginated JSON endpoint. A single conversation may produce several rows when it is handed between multiple agents and/or produces multiple tickets.
Prerequisites
To use the API, you need a token. Tokens can be generated by an admin in the settings section of the Filum CX platform. The token's permission set must include the $export_conversation_session resource with the list action.
API
Access the API via the endpoint: https://contact-center.filum.ai/
GET /external/conversation-sessions
Summary
List per-agent conversation sessions joined with their tickets, filtered by conversation updatedAt window.
Query parameters
| Name | Description | Required | Schema |
|---|---|---|---|
startDate | Inclusive lower bound on the conversation updatedAt (ISO date, YYYY-MM-DD). Conversations created before this date are still returned if they had activity (new message, status change, etc.) inside the window. | Yes | string (date) |
endDate | Inclusive upper bound on the conversation updatedAt (ISO date). | Yes | string (date) |
timezone | IANA timezone for interpreting startDate / endDate boundaries (start at 00:00:00, end at 23:59:59.999 in this tz) and for computing business-hours metrics. Defaults to Asia/Singapore. | No | string |
organizationId | Organization id. Defaults to the authenticated member's organization. | No | string |
sessionAgentEmails | Comma-separated list of session agent emails to filter by. | No | string |
assigneeAccountIds | Comma-separated list of agent account ids (matches either the session-derived agent or the ticket's assignee_account_id). | No | string |
sessionEndReasons | Comma-separated list of session end reasons. Options: handoff_to_ai, terminal, replaced_by_other_handoff, service_account_takeover, open_at_end. | No | string |
page | Page number to retrieve. | No | integer (>= 1, default: 1) |
perPage | Results per page. | No | integer (1..100, default: 50) |
Header parameters
| Name | Description | Required | Schema |
|---|---|---|---|
authorization | The authorization token. Format: Bearer <your_api_token> | Yes | string |
organization-id | The unique identifier of your organization | Yes | string |
auth-type | The type of authorization. Options: token (for API tokens), account (for user sessions). | Yes | string |
Responses
| Code | Description | Content type |
|---|---|---|
| 200 | Successful response | application/json |
| 401 | Unauthorized | application/json |
| 422 | Validation error | application/json |
| 429 | Rate limit exceeded | application/json |
200 Successful response
Top-level envelope (same shape as /external/conversations):
| Field | Type | Description |
|---|---|---|
items | array of ConversationSession | The matching conversation-session rows |
total | integer | Total number of rows matching the filter (across all pages) |
page | integer | Current page number |
perPage | integer | Number of items per page |
pages | integer | Total number of pages |
ConversationSession item:
| Field | Type | Description |
|---|---|---|
| Session | One row per agent engagement window within a conversation. | |
sessionIndex | integer | null | Zero-based index of this session within its conversation. null for rows that represent a ticket with no matched session. |
sessionAgentEmail | string | Agent who handled this session. Blank when the session activity carried no agent identity (e.g. an unattended route_back_to_human). |
sessionAgentName | string | Agent display name. |
sessionAgentAccountId | integer | null | Agent account id from the activity stream. |
sessionStartAt | string (date-time) | null | When the agent's session window opened (assign / route_back_to_human / human_take_over). |
sessionTookOverAt | string (date-time) | null | When the agent explicitly took over, if distinct from the start. |
sessionEndAt | string (date-time) | null | When the session closed (terminal action or handoff). |
sessionStartAction | string | The activity action that opened the session. |
sessionEndAction | string | The activity action that closed it (end_conversation, route_to_ai, route_to_rating, timed_out, …). |
sessionEndReason | string | Higher-level reason: handoff_to_ai, terminal, replaced_by_other_handoff, service_account_takeover, open_at_end. |
| Conversation | Conversation-level fields (same value across every session row of the same conversation). | |
conversationId | string | The Mongo _id of the conversation. |
conversationCreatedAt | string (date-time) | null | |
conversationRoutedToAiAt | string (date-time) | null | First route_to_ai / route_back_to_ai event on the conversation. |
conversationRoutedToAgentAt | string (date-time) | null | First route_to_human / route_back_to_human event. |
conversationAssignedAt | string (date-time) | null | First assign event. |
conversationAgentTookOverAt | string (date-time) | null | First human_take_over event. |
sentToRatingAt | string (date-time) | null | The route_to_rating event that closed this session (per-session, not conv-level). |
conversationClosedAt | string (date-time) | null | The conversation's close timestamp from Mongo. |
conversationResolvedAt | string (date-time) | null | The conversation-level "resolved" anchor used by Resolution Time (= conversationClosedAt). |
| Customer | Snapshot of the user object on the conversation. | |
customerFilumId | string | |
customerName | string | |
customerPhone | string | |
customerEmail | string | |
| Ticket | The ticket matched to this session by (assignee_account_id, time window). Fields are blank when the row is a session with no matched ticket. | |
ticketId | integer | null | |
ticketCreatedAt | string (date-time) | null | |
ticketUpdatedAt | string (date-time) | null | |
ticketSubject | string | |
ticketDescription | string | |
ticketStatus | string | |
ticketPriority | string | |
ticketDueDate | string (date-time) | null | |
ticketAssignedAt | string (date-time) | null | |
ticketResolvedAt | string (date-time) | null | |
installedSourceId | integer | null | |
installedSourceName | string | |
channelType | string | E.g. chat. |
| Metric endpoints | The anchor timestamps used to compute durations. | |
botHandoffAt | string (date-time) | null | Latest route_to_human / route_back_to_human at or before the session start (bounded to "after the previous session ended" and within 24h of the session start). Falls back to sessionStartAt if no qualifying handoff exists. |
firstAgentAssignedAt | string (date-time) | null | sessionTookOverAt ?? sessionStartAt. |
firstAgentMessageAt | string (date-time) | null | First senderType=agent message strictly after firstAgentAssignedAt, inside the session window. |
lastAgentMessageAt | string (date-time) | null | Last senderType=agent message inside the session window. |
| Metrics (calendar hours) | Plain elapsed time, in seconds. null when an endpoint is missing. | |
waitTimeSeconds | integer | null | firstAgentMessageAt − botHandoffAt. |
firstResponseTimeSeconds | integer | null | firstAgentMessageAt − firstAgentAssignedAt. |
resolutionTimeSeconds | integer | null | conversationResolvedAt − botHandoffAt. |
| Metrics (business hours) | Same spans intersected with the org humanSchedule (from BrandConfig) in timezone. | |
waitTimeBusinessHoursSeconds | integer | null | |
firstResponseTimeBusinessHoursSeconds | integer | null | |
resolutionTimeBusinessHoursSeconds | integer | null | |
| CSAT | ||
rating | integer | null | Survey star rating, if the customer responded. |
resolutionYesNo | string | Yes if the survey carried an explicit resolution=1 metric or the rating was ≥ 4; No if resolution=0 or rating ≤ 3; empty when no survey. |
feedback | string | Free-text feedback, if any. |
submittedAt | string (date-time) | null | When the survey was submitted. |
| Custom fields | ||
customFields | object ({ [name: string]: string }) | Per-ticket custom fields, keyed by the field's display name. |
Response sample
{
"items": [
{
"sessionIndex": 0,
"sessionAgentEmail": "alice@yourorg.com",
"sessionAgentName": "Alice Tan",
"sessionAgentAccountId": 1799,
"sessionStartAt": "2026-05-08T15:02:33.000Z",
"sessionTookOverAt": "2026-05-08T15:08:55.000Z",
"sessionEndAt": "2026-05-08T15:09:56.000Z",
"sessionStartAction": "assign",
"sessionEndAction": "route_to_rating",
"sessionEndReason": "terminal",
"conversationId": "69fdf7f9e1e9bcb8cf2bc4b9",
"conversationCreatedAt": "2026-05-08T14:49:00.000Z",
"conversationRoutedToAiAt": "2026-05-08T14:49:00.000Z",
"conversationRoutedToAgentAt": "2026-05-08T14:52:00.000Z",
"conversationAssignedAt": "2026-05-08T15:02:00.000Z",
"conversationAgentTookOverAt": "2026-05-08T15:08:00.000Z",
"sentToRatingAt": "2026-05-08T15:09:38.000Z",
"conversationClosedAt": "2026-05-08T23:45:00.000Z",
"conversationResolvedAt": "2026-05-08T23:45:00.000Z",
"customerFilumId": "fil_123",
"customerName": "FaFa",
"customerPhone": "+60129666852",
"customerEmail": null,
"ticketId": 705854,
"ticketCreatedAt": "2026-05-08T15:09:54.741Z",
"ticketUpdatedAt": "2026-05-08T15:14:45.354Z",
"ticketSubject": "Request from WhatsApp - FaFa",
"ticketDescription": "",
"ticketStatus": "closed",
"ticketPriority": "low",
"ticketDueDate": null,
"ticketAssignedAt": "2026-05-08T15:02:00.000Z",
"ticketResolvedAt": "2026-05-08T15:09:54.741Z",
"installedSourceId": 6537,
"installedSourceName": "WhatsApp MY",
"channelType": "chat",
"botHandoffAt": "2026-05-08T14:52:21.000Z",
"firstAgentAssignedAt": "2026-05-08T15:08:55.000Z",
"firstAgentMessageAt": "2026-05-08T15:09:03.000Z",
"lastAgentMessageAt": "2026-05-08T15:09:32.000Z",
"waitTimeSeconds": 1002,
"firstResponseTimeSeconds": 8,
"resolutionTimeSeconds": 31959,
"waitTimeBusinessHoursSeconds": 1002,
"firstResponseTimeBusinessHoursSeconds": 8,
"resolutionTimeBusinessHoursSeconds": 28800,
"rating": 5,
"resolutionYesNo": "Yes",
"feedback": "",
"submittedAt": "2026-05-08T15:10:30.000Z",
"customFields": {
"Outlet Name": "ZUS KLCC",
"Ticket Category 1": "Order",
"Ticket Category 2": "Refund"
}
}
],
"total": 1,
"page": 1,
"perPage": 100,
"pages": 1
}
Usage examples
Pull all conversation sessions for last week
curl -X GET "https://contact-center.filum.ai/external/conversation-sessions?startDate=2026-06-02&endDate=2026-06-08&timezone=Asia/Kuala_Lumpur&perPage=100" \
-H "authorization: Bearer your_api_token_here" \
-H "organization-id: your_organization_id_here" \
-H "auth-type: token"
Filter to a single agent's sessions
curl -X GET "https://contact-center.filum.ai/external/conversation-sessions?startDate=2026-06-01&endDate=2026-06-08&sessionAgentEmails=alice@yourorg.com&perPage=200" \
-H "authorization: Bearer your_api_token_here" \
-H "organization-id: your_organization_id_here" \
-H "auth-type: token"
Only completed (rated) sessions, by account id
curl -X GET "https://contact-center.filum.ai/external/conversation-sessions?startDate=2026-06-01&endDate=2026-06-08&assigneeAccountIds=1799,1798&sessionEndReasons=terminal" \
-H "authorization: Bearer your_api_token_here" \
-H "organization-id: your_organization_id_here" \
-H "auth-type: token"
Paginate through a large window
PAGE=1
while : ; do
RESP=$(curl -sX GET "https://contact-center.filum.ai/external/conversation-sessions?startDate=2026-04-01&endDate=2026-04-30&page=$PAGE&perPage=100" \
-H "authorization: Bearer your_api_token_here" \
-H "organization-id: your_organization_id_here" \
-H "auth-type: token")
echo "$RESP"
PAGES=$(echo "$RESP" | jq -r .pages)
[ "$PAGE" -ge "$PAGES" ] && break
PAGE=$((PAGE+1))
done
Notes
Date filter semantics
startDate and endDate filter on the conversation's updatedAt, not createdAt. A long-running conversation that was created last month but had activity (new messages, status changes, etc.) inside your window is still returned. This matches what an analyst pulling "what happened this week" typically wants.
Multi-row conversations
A single conversation can appear in multiple rows when:
- It is handed off between multiple agents — each handler gets one session row.
- It produces multiple tickets — each ticket is paired with its session by
(assignee_account_id, time window). - An agent's session ends before the ticket is finally resolved —
conversationResolvedAtis shared across every session row of that conversation, so resolution-time calculations are anchored on the chat close (customer POV), not the per-session end.
Sessions that never carry an agent identity (e.g. an unattended route_back_to_human) are still emitted with blank session-agent fields and no matched ticket. Filter them out with WHERE session_agent_email != '' if you only want rows representing a real human handler.
Window guidance
The pipeline joins the data in memory over the date window. Days-to-weeks queries return quickly; multi-month historical pulls take longer and are best run as a one-off export instead — contact your Filum representative.
Rate limiting
The endpoint is rate-limited:
- Per second: 5 requests
- Per minute: 50 requests
Exceeding these limits returns 429 Too Many Requests. Implement retry with exponential backoff.
Error responses
401 Unauthorized
{ "detail": { "message": "Unauthorized" } }
Either the token is invalid, the organization-id header is missing, or the token's permission set does not include $export_conversation_session:list.
422 Validation error
{
"detail": [
{
"loc": ["query", "startDate"],
"msg": "field required",
"type": "value_error.missing"
}
]
}
429 Too many requests
{ "detail": "Rate limit exceeded" }