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

# Interaction Search deep dive

> The consolidated reference for the unified Interactions page: where it lives, how messaging conversations and voice calls fold into one recency-ordered list, every filter combination, the CSV export contract, and the viewer 403 fallback.

# Interaction Search deep dive

The **Interactions** page is the unified, cross-channel search surface for your workspace. It lists every messaging conversation and every voice call in one recency-ordered feed, applies the same filters to both arms, and exports the result to CSV. This guide covers the surface end to end: navigation, auth, the data model, filter combinations, the export contract, and how it relates to global search.

Open it from the dashboard sidebar under **Interactions**, or call `GET /api/v1/interactions` directly.

## 1. Where the surface lives and who can use it

### Navigation

* **Dashboard:** **Interactions** in the left sidebar, at `/interactions`.
* **API:** `GET /api/v1/interactions` for the paginated search; `GET /api/v1/interactions/export.csv` for the bulk CSV export.

### Auth posture

The page splits read and export into two deliberate gates:

| Action | Roles | Required scope |
| - | - | - |
| Search the list | owner, admin, developer, viewer | Standard session |
| Export CSV | owner, admin, developer | `contacts:read` |

The search list inherits the same read posture as the conversations list, call log, and [`/search/global`](/concepts/global-search-fan-out): any authenticated workspace member can read it. The CSV export is a bulk, org-wide pull of contact-linked interaction history, so it is gated more tightly.

### The friendly 403 for viewers

When a viewer clicks **Export CSV**, the API returns `403 Permission denied` and the dashboard shows a toast instead of a raw error page:

```text theme={null}
Export failed. Permission denied (403). Ask your workspace owner or
administrator to run the export, or to add the contacts:read scope to
your role.
```

The search itself keeps working. If you see this toast, ask an owner or admin to either run the export or widen your role to include `contacts:read`.

## 2. The unified cross-channel model

An interaction is not a new record type. It is a **derived projection** that unions two existing record families:

* **Conversation rows** — inbox threads from every messaging channel (SMS, WhatsApp, email, RCS, Viber, Instagram, Messenger, Telegram, LINE, web chat, Apple Messages, WeChat, Kakao, Zalo, and the AI agent channel). Each row carries the thread lifecycle status and the delivery status of its latest message.
* **Call rows** — voice call-log entries, synthesized with `channel: "voice"`. Each row carries the call disposition and direction, and deep-links into the call-detail page.

Because both arms read the same underlying tables, a filter decision you make here holds true on the inbox and call-log surfaces. There is no third collection to keep in sync.

### How recency ordering works

Rows sort by `last_activity_at` descending:

* A conversation row is stamped by its **latest message time** (`last_message_at`), falling back to `created_at`.
* A call row is stamped by the **call start** (`ended_at` → `answered_at` → `started_at` → `created_at`), so a long running call enters the list when it begins, not when it ends.

The result is a single timeline where the most recent customer touch — regardless of channel — appears first. Pagination is a composite `(last_activity_at, id)` keyset cursor, so two rows that share the same second page through in a stable order without skips or duplicates.

### Voice transcripts and message exchanges together

Voice calls do not carry a message preview; they render as a `Call` badge with direction. The transcript and recording live on the call-detail page behind the row. Message exchanges render a plain-text snippet of the latest message (HTML stripped for email). The list's job is to point, not replay.

### Relationship to global search

Both surfaces answer "find something" across pillars, but they differ in scope and output:

* [`/search/global`](/concepts/global-search-fan-out) is the Cmd-K command palette. It returns ranked matches across contacts, conversations, calls, agents, numbers, and segments, capped per pillar, with no export.
* **Interactions** is a dedicated page over the conversation + call union. It returns one recency-ordered list, supports deep filters, and exports to CSV.

Use global search when you want the fastest ranked match across the whole workspace. Use Interactions when you need to browse, filter, or export a cross-channel timeline.

## 3. Filter combinations

All filters combine with **AND** across axes and **OR** within one chip row. Unknown channel, type, or status values are ignored rather than rejected.

### By channel

The channel chip row mirrors the inbox vocabulary:

`sms`, `whatsapp`, `email`, `rcs`, `viber`, `instagram`, `messenger`, `telegram`, `line`, `web_chat`, `apple_messages`, `wechat`, `kakao`, `zalo`, `video`, `agent`, `voice`.

`voice` is the gate on call rows. Select only messaging channels and calls drop out. Select `voice` to include calls; select only `voice` to see calls exclusively.

### By direction

Direction applies **only to call rows**. Conversation rows have no single direction, so their direction is `null`. Filter by `direction` in the API with `inbound` or `outbound` to isolate incoming or outgoing calls.

### By date window

The dashboard offers presets: **Last 24h**, **Last 7 days** (default), **Last 30 days**, **All time**. The API accepts explicit ISO-8601 UTC bounds via `since` and `until`. `until` must be on or after `since`.

### Per contact

* Use the search box for a prefix-anchored match against the linked contact's name, phone, or email.
* Use the `contact_id` query parameter to restrict to one contact exactly.
* Click the **Contact** column in the dashboard to open the contact record.

### By agent

Filter by the `agent` channel to surface AI agent conversations in the unified list. The channel acts like any other channel chip: combine it with a date window or contact to see everything an agent handled in a given period.

### By status

The `statuses` filter is free-form because conversation and call vocabularies differ. A status that exists on one arm matches zero rows on the other rather than failing the request. Common conversation statuses include `active` and `closed`; common call statuses include `completed`, `no_answer`, `busy`, and `declined`.

### API example: combine channel, type, and date

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/interactions?types=call&channels=voice&direction=outbound&since=2026-09-01T00:00:00Z&limit=25" \
  -H "X-API-Key: $ORBIT_API_KEY"
```

## 4. CSV export

Select **Export CSV** in the page header to download the currently filtered result set. Narrow first, then export.

### What is gated

Only owner, admin, and developer roles with `contacts:read` can export. Viewers see the friendly 403 toast described above. The export is rate-limited to **5 per minute**.

### Column contract

Columns are emitted in this order:

| Column | Meaning |
| - | - |
| `id` | Conversation or call id |
| `type` | `conversation` or `call` |
| `channel` | Thread channel, or `voice` for calls |
| `status` | Lifecycle status |
| `delivery_status` | Latest message status for conversation rows; empty for calls |
| `contact_id` | Linked contact id, if any |
| `contact_name` | Resolved contact name |
| `contact_phone` | Resolved phone |
| `contact_email` | Resolved email |
| `direction` | `inbound` or `outbound` for calls; empty for conversations |
| `preview` | Plain-text snippet for conversations; empty for calls |
| `last_activity_at` | Recency timestamp, rendered in the requester's timezone when `?tz=` is supplied |

### Row-order guarantee

CSV rows follow the same recency ordering as the on-screen list: `last_activity_at` descending, with id as the tie-breaker. The export walks the same cursor chain the dashboard uses.

### Limits and truncation

* **Ceiling:** 20,000 rows per export.
* If the result set exceeds the ceiling, the CSV is truncated and the response carries `X-Export-Truncated: true`.
* **Filename:** `<stem>-YYYY-MM-DD.csv`. The optional `filename` query parameter is sanitized to `[A-Za-z0-9._-]` and capped at 80 characters.
* **Audit:** every export writes an audit entry with row count, truncation flag, and applied cap.

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/interactions/export.csv?channels=voice&since=2026-09-01T00:00:00Z&filename=sept-calls" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -o sept-calls.csv
```

### PII redaction

If your workspace has opted into transcript PII redaction under **Settings → Privacy**, the same masking applies to the CSV and the on-screen list. `contact_name`, `contact_phone`, `contact_email`, and `preview` are masked identically in both surfaces.

## 5. Walkthrough: find the call where the customer disputed the charge

A customer emailed support saying they disputed a charge during a call last week. You need the call row, not the email thread.

1. Open **Interactions**.
2. Type the customer's name or phone into the search box.
3. Chip **Call** under type and **voice** under channel.
4. Pick **Last 7 days**.
5. Look for the call row around the date the customer mentioned. The **Preview** column shows `Voice call`; the **Channel / Status** column shows `voice` and the disposition.
6. Click the row to open the call-detail page and review the recording or transcript.

If the customer had also emailed about the dispute, switching the type chip back to **All types** would show the email thread and the call in the same recency-ordered list.

## 6. Walkthrough: export a slice as a viewer and hit the 403 fallback

A viewer is asked to pull last month's WhatsApp interactions for a QA review.

1. Open **Interactions**.
2. Chip **Conversation** under type and **whatsapp** under channel.
3. Pick **Last 30 days**.
4. Click **Export CSV**.
5. The dashboard shows the friendly 403 toast because the viewer role cannot export.
6. The viewer copies the current filter state from the page and asks an owner or admin to run the export, or requests the `contacts:read` scope.

The same export from an admin session succeeds:

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/interactions/export.csv?types=conversation&channels=whatsapp&since=2026-09-05T00:00:00Z&filename=whatsapp-qa" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -o whatsapp-qa.csv
```

## 7. Troubleshooting

If the list or export behaves unexpectedly, start with these checks:

* **Empty results** — Loosen one filter at a time. A status value from one arm matches zero rows on the other. Confirm the date-range preset is not too narrow.
* **Calls are missing** — `voice` must be in the channel chip set, or no channel filter at all. The type filter must also include **Call**.
* **Export fails with 403** — You hold a viewer role or lack `contacts:read`. Search is unaffected.
* **CSV has fewer rows than expected** — You hit the 20,000-row ceiling; narrow the filters and re-export. The `X-Export-Truncated` header confirms truncation.

For the full troubleshooting guide, including cursor pagination edge cases and the flat 403 response shape, see [Interactions list CSV export troubleshooting](/troubleshooting/interactions-list.csv-export).

## See also

* [Search every conversation and call in one list](/guides/interaction-search) — the original reference for the unified list.
* [Walk the Interactions console](/guides/interactions-console) — a tour of the filter strip and columns.
* [Find a conversation across channels](/guides/interactions-find-a-conversation) — end-to-end workflow from query to ticket hand-off.
* [Global search fan-out](/concepts/global-search-fan-out) — the cross-pillar search model Interactions shares its read posture with.
* [Unified interaction model](/concepts/interactions-unified-model) — the projection definition behind the unified list.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.