> ## Documentation Index
> Fetch the complete documentation index at: https://docs.armin.cx/llms.txt
> Use this file to discover all available pages before exploring further.

# CSAT and session exports

> Export individual CSAT surveys and recorded agent login intervals as paginated JSON.

Use the Chatarmin CX Public API to review individual survey results or load recorded agent login intervals into your reporting system.

## Access and limits

Create an API key assigned to a support agent in [Settings → API](https://armin.cx/app/_/settings/api). Send it in the `cx-api-key` header. Both read-only endpoints require `analytics:read` and organization-wide dashboard access; they do not fall back to a personal or team-only export.

| Endpoint                        | Assigned agent's required permissions                        | Additional API scope                         |
| ------------------------------- | ------------------------------------------------------------ | -------------------------------------------- |
| `GET /public/v1/csat-surveys`   | `dashboard.org_metrics` and `dashboard.ticket_csat`          | `tickets:read` only when requesting comments |
| `GET /public/v1/agent-sessions` | `dashboard.org_metrics` and `dashboard.other_agents_metrics` | None beyond `analytics:read`                 |

Filtering to your own agent ID does not waive these permissions. Both endpoints use the analytics rate tier of 10 requests/minute. They return JSON, not CSV/Excel, and do not change tickets, send surveys, or modify sessions. For aggregate scores, response rates, and online time, use [Reports and metrics](/api/reports).

## Export CSAT surveys

Choose one date population: surveys **submitted** in a period, or surveys **sent** in a period, including those still unanswered. Only surveys attached to existing, non-deleted, non-side, non-imported tickets in the workspace are eligible.

| Query parameter                      | Contract                                                                                     |
| ------------------------------------ | -------------------------------------------------------------------------------------------- |
| `submitted_from`, `submitted_before` | Complete submission-date pair, or use the sent-date pair instead.                            |
| `sent_from`, `sent_before`           | Complete sent-date pair. Do not combine with submission-date parameters.                     |
| `credited_agent_id`                  | Optional exact stored survey agent ID. Treat IDs as opaque strings, not UUIDs or names.      |
| `score_lte`                          | Optional integer from 1 to 5, inclusive. Unscored surveys do not match this filter.          |
| `include_comments`                   | Literal `true` or `false`; defaults to `false`. `true` additionally requires `tickets:read`. |
| `limit`                              | Integer 1-100, default 50.                                                                   |
| `cursor`                             | Optional opaque continuation token from `pagination.next_cursor`.                            |

Supply exactly one complete date pair as ISO 8601 instants with `Z` or an explicit offset. The lower bound is inclusive and the upper bound exclusive: `[from, before)`. The upper bound must be later and no more than 90 × 24 hours after the lower bound. Unlike report-query `UNTIL`, `before` is not an inclusive calendar date. Unknown query parameters are rejected.

Start with a small sent-date export without comments:

```bash theme={null}
curl --get 'https://api.armin.cx/public/v1/csat-surveys' \
  -H "cx-api-key: $CX_API_KEY" \
  --data-urlencode 'sent_from=2026-08-01T00:00:00Z' \
  --data-urlencode 'sent_before=2026-08-08T00:00:00Z' \
  --data-urlencode 'limit=100'
```

To review low scores for one credited agent, use submission dates. Replace `agent_example` with your workspace's agent ID. Request comments only if your integration needs customer-entered text and the key has the additional scope:

```bash theme={null}
curl --get 'https://api.armin.cx/public/v1/csat-surveys' \
  -H "cx-api-key: $CX_API_KEY" \
  --data-urlencode 'submitted_from=2026-08-01T00:00:00Z' \
  --data-urlencode 'submitted_before=2026-08-08T00:00:00Z' \
  --data-urlencode 'credited_agent_id=agent_example' \
  --data-urlencode 'score_lte=3' \
  --data-urlencode 'include_comments=true' \
  --data-urlencode 'limit=100'
```

### Read survey results

The following invented example shows the default response without comments. Results are ordered ascending by your selected date field, then survey ID. `score` and `submitted_at` may be `null`; `ticket_number`, `credited_agent_id`, and `credited_agent_name` are also nullable.

```json theme={null}
{
  "success": true,
  "data": [
    {
      "id": "11111111-1111-4111-8111-111111111111",
      "ticket_id": "22222222-2222-4222-8222-222222222222",
      "ticket_number": 1234,
      "credited_agent_id": "agent_example",
      "credited_agent_name": "Alex Example",
      "attribution_source": "survey_agent_id",
      "score": 3,
      "sent_at": "2026-08-01T09:00:00.000000Z",
      "submitted_at": "2026-08-02T10:00:00.000000Z"
    }
  ],
  "pagination": { "has_more": false, "next_cursor": null },
  "meta": {
    "attribution": "Stored survey agent_id only; no read-time resolver fallback or historical reattribution. Missing credit remains unattributed.",
    "comments_included": false,
    "privacy": "Comments are customer-entered text and may contain personal data. They are opt-in and require tickets:read in addition to analytics:read."
  }
}
```

`credited_agent_id` is the survey's stored `agent_id`, not a read-time guess from the ticket's resolver or assignee. Missing credit stays `null` with `attribution_source: "unattributed"`. A credited name may be unavailable even when an ID is stored. Legacy survey credit is not rewritten, so historical credit is not proof of who resolved the ticket.

For newly created surveys without explicitly supplied credit, default attribution snapshots the ticket's recorded `solved_by` only when its current status is `resolved` and that resolver is eligible in the same workspace. There is no fallback to an assignee or message author. The legacy `AI` marker and AI agents excluded from analytics accounting do not receive default credit, including after an excluded agent is deleted. Reopening or reassigning the ticket later does not recompute that snapshot. This default can credit an eligible AI resolver; it is not a human-only guarantee.

With `include_comments=true`, each item additionally has `comment` as a string or `null`, and `meta.comments_included` is `true`. Without it, the field is omitted entirely. Comments may contain personal data: restrict downstream access and avoid copying them into logs. The export does not return customer contact details, survey tokens or links, or ticket bodies.

## Export agent sessions

Use recorded login intervals to inspect session starts, ends, and last activity. These are the existing mutable login-session records, not a new presence-event store. There is no stored break or event history in this export.

| Query parameter  | Contract                                                                                           |
| ---------------- | -------------------------------------------------------------------------------------------------- |
| `from`, `before` | Required ISO-offset instants, `[from, before)`, with a positive duration of at most 90 × 24 hours. |
| `agent_id`       | Optional exact agent ID, an opaque string rather than a UUID or name.                              |
| `limit`          | Integer 1-100, default 50.                                                                         |
| `cursor`         | Optional opaque continuation token.                                                                |

```bash theme={null}
curl --get 'https://api.armin.cx/public/v1/agent-sessions' \
  -H "cx-api-key: $CX_API_KEY" \
  --data-urlencode 'from=2026-08-01T00:00:00Z' \
  --data-urlencode 'before=2026-08-08T00:00:00Z' \
  --data-urlencode 'agent_id=agent_example' \
  --data-urlencode 'limit=100'
```

The endpoint selects **overlapping recorded intervals**, not just sessions started in the period: `session_start < before` and either `session_end > from` or `session_end` is `null`. A session ending exactly at `from` or starting exactly at `before` is excluded. A session starting before the period can be returned. Original timestamps are retained without clipping, and overlapping records are not merged.

This invented response includes a stale open record deliberately:

```json theme={null}
{
  "success": true,
  "data": [
    {
      "id": "33333333-3333-4333-8333-333333333333",
      "agent_id": "agent_example",
      "session_start": "2026-07-31T23:00:00.000000Z",
      "session_end": null,
      "last_activity": "2026-08-01T00:15:00.000000Z",
      "updated_at": "2026-08-01T00:15:00.000000Z"
    }
  ],
  "pagination": { "has_more": false, "next_cursor": null },
  "meta": {
    "date_filter": "recorded_interval_overlap",
    "order_by": "session_start,id",
    "stale_open_records_included": true,
    "mutable": true,
    "break_history_stored": false,
    "description": "Recorded intervals overlapping the requested range, with original timestamps retained. Stale open records are included; no end is inferred from last_activity. Null session_end is not proof of current availability. These are mutable login sessions, not presence or break events. Replica reads are eventually consistent; pagination is not a snapshot."
  }
}
```

A null `session_end` is **not proof of current availability**. Inspect `last_activity` separately; this export never infers an end from it. Returned session rows contain only `id`, `agent_id`, `session_start`, `session_end`, `last_activity`, and `updated_at`, never IP addresses, browser details, or user agents. To obtain deduplicated, period-clipped online seconds with time-bucket allocation, query `agent_online_time` through [Reports and metrics](/api/reports#hourly-replies-and-online-time), rather than summing raw durations.

## Continue an export

When `pagination.has_more` is `true`, use `pagination.next_cursor` as the next request's `cursor`. Stop when `has_more` is `false`. Preserve the key, workspace, assigned agent, scopes, and every filter, including `limit` and comment inclusion. Changing them invalidates the cursor. Treat timestamps and cursors as opaque strings; rebuilding a cursor from a timestamp can lose precision.

For the low-score query above, set `NEXT_CURSOR` to the returned token and repeat the same parameters:

```bash theme={null}
curl --get 'https://api.armin.cx/public/v1/csat-surveys' \
  -H "cx-api-key: $CX_API_KEY" \
  --data-urlencode 'submitted_from=2026-08-01T00:00:00Z' \
  --data-urlencode 'submitted_before=2026-08-08T00:00:00Z' \
  --data-urlencode 'credited_agent_id=agent_example' \
  --data-urlencode 'score_lte=3' \
  --data-urlencode 'include_comments=true' \
  --data-urlencode 'limit=100' \
  --data-urlencode "cursor=$NEXT_CURSOR"
```

Session pagination works the same way and orders by `session_start`, then `id`, ascending. Neither export is a frozen snapshot: replica reads are eventually consistent, survey responses can arrive later, and session ends or activity timestamps can change during pagination. For ongoing imports, reread bounded overlapping windows and update downstream rows by `id`; this is not a change-event feed. For adjacent date windows, reuse the previous `before` as the next `from`.

## Troubleshooting

| Response or symptom             | What to check                                                                                                                                                                           |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`                           | Supply a valid API key.                                                                                                                                                                 |
| `403 FORBIDDEN_SCOPE`           | Check the assigned agent, `analytics:read`, both required dashboard permissions, and `tickets:read` for comments. Narrowing an agent filter does not bypass the organization-wide gate. |
| `400 VALIDATION_ERROR`          | Use exactly the supported parameters and one complete date pair, valid offsets, increasing bounds within 90 days, and `limit` at most 100. Keep cursor parameters unchanged.            |
| `429`                           | Wait for `Retry-After`; see [rate limits](/api/rate-limits).                                                                                                                            |
| Export and report totals differ | Check sent versus submitted dates, stored credit, live ticket eligibility, replica freshness, and report-projection coverage. They are not interchangeable audit snapshots.             |

If a small valid request still fails, contact support with the response `request_id`, endpoint, and redacted filters. Never share API keys or customer comments in diagnostic logs.
