> ## 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.

# Interactions hub — workflows for support leads

> Task-oriented setup guide for the unified Interactions hub: choose the read or export endpoint, pick roles and scopes, shape queries with filter recipes, and run the three operator workflows — keyword lookup, cross-channel contact history, and audit export.

# Interactions hub — workflows for support leads

The **Interactions** hub is the operator surface that puts every conversation and every call in one searchable, exportable list. This is the setup and workflows guide: what each role can do, which endpoint does what, the filter combinations that answer the questions support leads and developers actually ask, and how drill-in and export work end to end. For the filter-parameter matrix and cursor semantics, read the [Interaction Search reference](/guides/interaction-search); for the console tour, read [Walk the Interactions console](/guides/interactions-console).

## 1. Who this surface is for and what it reads

Use the hub when the question spans messaging and voice at once — otherwise the per-channel surfaces (inbox, call logs) are closer to the record.

Two API endpoints back the page, with deliberately different gates:

| Endpoint                       | Reads                                                | Roles admitted                          | Extra scope                          |
| ------------------------------ | ---------------------------------------------------- | --------------------------------------- | ------------------------------------ |
| `GET /interactions`            | The search list, one cursor-paginated page at a time | `owner`, `admin`, `developer`, `viewer` | none beyond an authenticated session |
| `GET /interactions/export.csv` | A bulk CSV over the same filter set                  | `owner`, `admin`, `developer`           | `contacts:read` on the API key       |

Row shape differs by interaction kind. A **conversation** row points back to the inbox thread and carries the lifecycle status plus the delivery state of its latest message. A **call** row points back to the call-detail page and carries the disposition and direction. The `type` field (`conversation` or `call`) distinguishes them, and the row's `href` is the deep link to the owning surface. The role split and the row shape are why the hub works as a triage surface: anyone on the team can find the record; only role-holders with the export scope can move the record set off-platform.

## 2. Choose the recipe to match the question

Filters apply with AND semantics across axes and OR inside one comma-separated value list. These recipes cover the recurring support-lead and developer questions; the full parameter matrix is on the [Interaction Search reference](/guides/interaction-search).

| Goal                           | Recipe (query params)                                              |
| ------------------------------ | ------------------------------------------------------------------ |
| Find a conversation by keyword | `q=<contact name or phone prefix>`                                 |
| Everything one contact touched | `contact_id=cnt_…`                                                 |
| Voice calls only               | `types=call&channels=voice`                                        |
| Messaging only                 | `types=conversation`                                               |
| One channel's threads          | `types=conversation&channels=whatsapp`                             |
| A bounded window               | `since=<ISO>&until=<ISO>`                                          |
| Audit slice of one channel     | `types=call&channels=voice&since=…&until=…` on the export endpoint |

Notes on the semantics that matter when you compose recipes:

* `q` is a prefix-anchored match on the linked contact's name, phone, or email — a partial phone prefix resolves before you remember the full number.
* `channels=voice` is the gate on the calls arm. Filter to messaging channels only and call rows drop out; re-add `voice` and they return.
* `statuses` accepts one list for both arms. A conversation-only status (for example `active`) matches zero rows on the calls arm rather than failing, so clear status chips when you switch the `types` filter.
* Unrecognised channel, type, or status values are ignored rather than rejected — a stale chip does not break the query.

Run a recipe from curl the same way the dashboard does:

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

## 3. Drill-in routes to the owning record

A result row is a pointer into the surface that owns the record, and the row order stays put when you come back.

1. **Open the row.** A conversation row routes to the inbox thread (reply, tag, close); a call row routes to the call-detail page (per-leg log, and the recording where your role can see it).
2. **Or jump sideways to the contact.** The **Contact** column deep-links to the contact record — the right move when the next question is the customer's full profile, answered by the [unified contact timeline](/guides/contact-timeline).
3. **Return and continue.** The list retains its filter state, so a find → open → fix → back loop stays on the same recency page. Two rows sharing a timestamp page through in a stable order (the composite cursor below), so the **Load more** walk never skips or repeats a row while you drill in.

## 4. How pagination stays stable — the composite keyset cursor

Pages are ordered by `last_activity_at` descending; the cursor is a base64 `(last_activity_at, id)` composite, the same opaque-keyset shape every Orbit list uses. Adding the `id` as the tiebreaker is what makes the ordering stable: two interactions that land at the same second still sort deterministically, so walking pages neither skips nor repeats a row.

One caveat to plan around: recency order moves with new activity. If a stale thread re-fires while you page — a new message lands on an old conversation — that thread legitimately re-enters high in the ranking and can reappear as a small-batch duplicate across **Load more** clicks, or a skipped row from the walker's perspective. Hold the exported snapshot on the export endpoint; treat the walk as live. The runbook [Interactions list and CSV export edge cases](/troubleshooting/interactions-list.csv-export) covers the live-pagination edge cases and the export 403s in detail.

## 5. Roles and the viewer-side 403 toast

Read and export are gated on different doors by design. Any of the four roles can run the search and page the list. The bulk export narrows to `owner`, `admin`, or `developer`, and (for API-key callers) requires the `contacts:read` scope.

When a `viewer` clicks **Export CSV**, the API rejects with `403 Permission denied` and the dashboard raises a friendly toast asking the operator to route the export to an owner/admin or to widen their role — not a raw error page. The search and paging continue to work; only the export stays gated. Operators hitting this should pair with an owner or admin to either run the export or have `contacts:read` added to their role; API-key users re-issue the key with the scope included.

## 6. Export to CSV — cap, narrowing, and per-request idempotency

Run the export when the answer has to travel — QA sampling, a compliance review, or a hand-off to an external reviewer. The export applies the filters on screen verbatim, so narrow first, then export.

* **Row ceiling.** Up to 20,000 rows per request; a larger result set is truncated at the cap and the response carries an `X-Export-Truncated: true` header. Narrow the date window or channel set and re-export.
* **Per-request idempotency.** Each request is a fresh, self-contained snapshot: there are no stored export jobs, no server-side cursor carried across requests, and re-issuing the same query produces the CSV for that run's filter state without side effects. Two identical calls are equivalent to one — no double-processing.
* **Audit and rate limit.** Every export writes an audit entry naming the row count, truncation flag, and applied cap, and the endpoint throttles to 5 exports per minute.
* **PII redaction.** A workspace opted into redaction under **Settings → Privacy** gets masked contact fields identically on screen and in the CSV — one gate, both doors.

SDK-style paginated export (JavaScript):

```javascript theme={null}
async function exportInteractions(params) {
  const query = new URLSearchParams(params);
  const response = await fetch(
    "https://api.orbit.devotel.io/api/v1/interactions/export.csv?" + query,
    { headers: { "X-API-Key": process.env.ORBIT_API_KEY } },
  );
  if (!response.ok) throw new Error("Export failed: " + response.status);
  if (response.headers.get("X-Export-Truncated") === "true") {
    console.warn("Result exceeds 20,000 rows — narrow the filters and re-export");
  }
  return response.text(); // CSV body
}

// Example: one channel over one month — one request, one CSV
const csv = await exportInteractions({
  types: "call",
  channels: "voice",
  since: "2026-08-01T00:00:00Z",
  until: "2026-09-01T00:00:00Z",
  filename: "aug-voice-calls",
});
```

## Composite navigation: where this guide sits

* Read the [unified interaction model concept](/concepts/interactions-unified-model) for the projection definition — what the union reads, and what a conversation row versus a call row carries.
* Read the [Interaction Search reference](/guides/interaction-search) for the parameter matrix and the cursor contract.
* Read [Walk the Interactions console](/guides/interactions-console) for the chip-by-chip console tour.
* Read [Find a conversation across channels](/guides/interactions-find-a-conversation) for the end-to-end walkthrough from a half-remembered customer detail to a ticket.
* Read the [Interactions list and CSV export edge cases](/troubleshooting/interactions-list.csv-export) runbook when paging misbehaves or export 403s.

## Troubleshooting

* **Empty list.** Clear one filter axis at a time — a cross-arm status chip or a narrow preset on a quiet workspace returns nothing. **All types**, **All channels**, **All time** is the reset state.
* **Calls are missing.** `voice` must be present in the channel selection (or no channel filter set), and `types` must include **Call**. A messaging-only chip set strips calls by design.
* **Export fails with 403.** Held role is `viewer`, or the API key lacks `contacts:read`. Search stays available — route the export to an owner/admin or widen the role.
* **CSV truncated below the visible list.** 20,000-row ceiling. Narrow the filters and confirm via `X-Export-Truncated`.

## See also

* [Unified interaction model concept](/concepts/interactions-unified-model) — the projection the hub reads.
* [Interaction Search reference](/guides/interaction-search) — the filter matrix and cursor semantics.
* [Walk the Interactions console](/guides/interactions-console) — the chip-by-chip console tour.
* [Find a conversation across channels](/guides/interactions-find-a-conversation) — the end-to-end walkthrough.
* [Interactions list and CSV export edge cases](/troubleshooting/interactions-list.csv-export) — the runbook for pagination and export 403s.
* [Read and export the unified contact timeline](/guides/contact-timeline) — the per-contact feed when one customer's history is the target.
