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

# Retained history and search

> Search retained Slack messages, set retention, import accessible history, and inspect coverage

All paths on this page are relative to `https://inkbox.ai/api/v1/slack`. An archive belongs to one identity's workspace connection. The same workspace connected to another identity has a separate archive. Identity-level search reads across those workspace connections without combining their permissions.

## Capture and access

All eligible observed messages are captured automatically for every conversation the connection can access. It retains message text and file metadata, not file bytes. Capture is independent of webhook event selection. Your bot's own messages can be captured without becoming inbound wake events.

Capture does not grant access to the whole workspace. Installation, conversation membership, granted permissions, and Slack's available history still constrain what can be captured or imported. Slack Connect conversations follow the same connection-specific access rules.

Archive reads require an active identity and connected workspace. Inkbox checks current conversation access before returning retained messages. Each page can require a live Slack request for each distinct conversation represented on that page. A page can be short or empty after that check; continue with `next_cursor` when present. These live access checks can rate-limit an archive query (`429` with `Retry-After`) or fail (`502`). A concurrent archive or connection change can return `409`; retry the query after resolving the state. An archive is not a way to keep reading a channel after the bot loses access.

Pausing an identity stops its live operations and webhook wake delivery, but eligible incoming messages can still be captured. Settings reads/updates, coverage, and backfill also require an active identity and connected workspace. The delete-archive operation remains available while paused or disconnected.

## Get retention settings

```text theme={null}
GET /connections/{connection_id}/archive/settings
```

Returns `200`:

```json theme={null}
{
  "retention_days": null,
  "revision": 0
}
```

`retention_days: null` means no age-based expiration is configured; deletion and access rules still apply. `revision` changes when settings change.

## Update retention settings

```text theme={null}
PATCH /connections/{connection_id}/archive/settings
```

Requires an organization admin API key or the Console. Claimed agent keys can read settings but cannot change them.

| Field | Type | Required | Meaning |
| :- | :- | :- | :- |
| `retention_days` | integer or null | No | Keep messages within `1`–`3650` days of their Slack message timestamp; omitted or null means no age limit |

```json theme={null}
{
  "retention_days": 90
}
```

Returns `200` with the settings object. Omitted or null `retention_days` resets the retention period to no age limit.

Capture cannot be disabled or restricted to selected conversations. Use [webhook event selection](/docs/api/slack/webhooks) to choose which events wake your agent without limiting retained history.

Shortening `retention_days` makes older messages unavailable and schedules them for deletion. Increasing it later does not restore content already deleted. Use [delete archive](#delete-archive) to remove all retained content.

## List retained messages

```text theme={null}
GET /connections/{connection_id}/archive/messages
```

| Query parameter | Type | Default | Meaning |
| :- | :- | :- | :- |
| `conversation_id` | string | Omitted | Restrict to one conversation |
| `thread_ts` | string | Omitted | Include a thread root and its retained replies; requires `conversation_id` |
| `latest_per_conversation` | boolean | `false` | Return the latest matching message per conversation. Page size counts conversations instead of messages. |
| `after_ts` | string | Omitted | Only messages strictly after this timestamp |
| `before_ts` | string | Omitted | Only messages strictly before this timestamp |
| `limit` | integer | `50` | Page size, `1`–`100` |
| `cursor` | string | Omitted | Returned opaque cursor, up to 512 characters |

Timestamp filters contain 1–12 digits, a decimal point, and 1–6 fractional digits. Keep them as strings. Results are newest first, ordered by message timestamp, not capture time.

Use `latest_per_conversation=true` for a conversation list. Older messages in an already-listed conversation do not create additional pages. Keep the same filters when following `next_cursor`. Omit this option when reading full conversation or thread history.

### Response (200)

```json theme={null}
{
  "messages": [
    {
      "id": "44444444-4444-4444-8444-444444444444",
      "connection_id": "22222222-2222-4222-8222-222222222222",
      "conversation_id": "C0123456789",
      "message_ts": "1789552800.000100",
      "thread_ts": null,
      "user_id": "U0123456789",
      "text": "The project notes are ready.",
      "files": [],
      "mentioned": false,
      "source": "event",
      "captured_at": "2026-09-16T10:00:00Z"
    }
  ],
  "next_cursor": null,
  "page_boundary": null,
  "source": "archive"
}
```

Use the retained message's conversation ID and timestamp with the [message-link endpoint](/docs/api/slack/messages#get-a-message-link) to request a live permalink; the archive does not store one. Do not construct a link from a timestamp. `id` identifies the retained record; `message_ts` identifies the Slack message. Message `source` is `event`, `backfill`, or `action`. `user_id` and `thread_ts` can be null. `files` contains available metadata, not retained downloads.

When a chronological listing has `next_cursor`, its optional `page_boundary` contains `message_ts` (string) and `id` (UUID). Together they identify the inclusive lower edge of the scanned page, before access checks remove unavailable messages. The boundary need not match a returned message; even an empty page can have one. Use it to reconcile the scanned range when refreshing a list, not to infer access to a message. Continue pagination with the opaque `next_cursor`, keeping the same filters. Exhausted pages and ranked search responses have a null boundary. Older responses may omit it.

Observed edits update retained text. Observed deletions remove retained content; deleted messages are excluded from reads and search. A later history import does not restore a message that Inkbox has observed being deleted.

## Search across workspaces

```text theme={null}
GET /search
```

Search retained messages across the workspace connections owned by one identity. Omit `connection_id` to search all its connected workspaces, or supply it to narrow the query to one workspace. This does not search another identity's archive.

Use the [connection list](/docs/api/slack/connections#list-connections) to inspect each workspace's current status. Disconnected workspaces and those requiring reauthorization are excluded. Explicitly selecting a connection that requires reauthorization returns `409`.

A claimed agent-scoped API key can omit `identity_id`; the API uses its owning identity. An organization admin API key or the Console must specify `identity_id`. An explicit identity must belong to the caller's organization and, for an agent key, match that key's identity.

| Query parameter | Type | Default | Meaning |
| :- | :- | :- | :- |
| `q` | string | Required | Text query, `1`–`512` characters |
| `identity_id` | UUID | Agent key's identity | Identity to search; required for organization admin keys and the Console |
| `connection_id` | UUID | Omitted | Restrict to one workspace connection owned by the identity |
| `conversation_id` | string | Omitted | Restrict to one conversation |
| `user_id` | string | Omitted | Restrict to one author; starts with `U` or `W` |
| `after_ts`, `before_ts` | string | Omitted | Exclusive message-timestamp bounds |
| `limit` | integer | `50` | Page size, `1`–`100` |
| `cursor` | string | Omitted | Returned opaque cursor, up to 512 characters |

```bash cURL theme={null}
curl --get "https://inkbox.ai/api/v1/slack/search" \
  -H "X-API-Key: YOUR_API_KEY" \
  --data-urlencode "q=project notes" \
  --data-urlencode "identity_id=11111111-1111-4111-8111-111111111111"
```

Returns the same `200` response shape as [retained message listing](#list-retained-messages): `messages`, `next_cursor`, optional `page_boundary` (null for ranked search), and `source: "archive"`. Each message includes `connection_id`; use it to identify the workspace and to request message links, threads, or other follow-up operations. Do not infer the connection from `conversation_id` alone.

Search accepts plain keywords, not Slack search operators. Results are ranked by text relevance, then by message timestamp for ties. Pagination spans the entire identity search, not a separate page per workspace. Keep the identity, query, and filters unchanged when following `next_cursor`. Current conversation access is checked for each connection. A page can be short or empty after access checks; continue when `next_cursor` is present. If Slack cannot complete an access check, the API does not silently return results from only the remaining workspaces: the query returns an error, such as `429` or `502`, for you to handle before retrying.

Search covers retained message text, not every live workspace message or attachment contents. Semantic search is not supported. Missing results can mean uncaptured history, incomplete import, expiration, deletion, or lost access. Inspect retention settings and import coverage separately for each workspace.

## Search retained messages

```text theme={null}
GET /connections/{connection_id}/archive/search
```

This connection-scoped endpoint remains available for existing callers. Search matches retained message text using keyword/full-text search. It is not semantic search and does not search the entire live workspace or file contents.

| Query parameter | Type | Default | Meaning |
| :- | :- | :- | :- |
| `q` | string | Required | Text query, `1`–`512` characters |
| `conversation_id` | string | Omitted | Restrict to one conversation |
| `user_id` | string | Omitted | Restrict to one author; starts with `U` or `W` |
| `after_ts`, `before_ts` | string | Omitted | Exclusive message-timestamp bounds |
| `limit` | integer | `50` | Page size, `1`–`100` |
| `cursor` | string | Omitted | Returned cursor, up to 512 characters |

```bash cURL theme={null}
curl --get "https://inkbox.ai/api/v1/slack/connections/22222222-2222-4222-8222-222222222222/archive/search" \
  -H "X-API-Key: YOUR_API_KEY" \
  --data-urlencode "q=project notes" \
  --data-urlencode "conversation_id=C0123456789"
```

Returns the same `200` response shape as retained message listing. Results are ranked by text relevance, then by message timestamp for ties. Use plain keywords, not Slack search operators. Keep the same query and filters when continuing with a cursor. Missing results can mean uncaptured history, incomplete import, expiration, deletion, or lost access—not that a conversation never happened.

## Import accessible history

```text theme={null}
POST /connections/{connection_id}/archive/backfill
```

```json theme={null}
{
  "conversation_id": "C0123456789",
  "thread_ts": null,
  "restart": false
}
```

`conversation_id` is required. Omit or null `thread_ts` to import conversation history; supply a root timestamp to import that thread. The identity must be active and the workspace must be connected. A claimed agent key can request a backfill for its own connection.

Before queuing an import, the API checks live conversation access; inaccessible conversations can return `403` or `404`. New imports can also return `429` when too much import work is pending. Honor `Retry-After` before retrying.

Returns `202` with a [coverage record](#inspect-coverage), not completed history. With `restart: false` (the default), an observed, paused, or failed pass is queued; pending, running, and completed passes are left unchanged.

`restart: true` restarts the selected pass in any state, clears its cursor and progress bounds/count, and resets known thread passes when restarting an entire conversation. It does not restore deleted messages or override access limits. Use it deliberately, not on every status poll.

After an archive purge, new messages continue to be captured automatically, but backfill returns `409` while the prior content is still being removed. Honor `Retry-After` before retrying the import; it does not run silently against an unfinished purge.

Imports cover only messages within the configured retention period. Conversation imports can discover and queue threads separately. A completed conversation pass does not mean every thread is complete. Slack may impose restrictive page sizes and long delays between history requests. Rate-limited work waits before continuing. Do not infer complete workspace history from a successful request or an elapsed period.

## Inspect coverage

```text theme={null}
GET /connections/{connection_id}/archive/coverage
```

`limit` defaults to `100` and accepts `1`–`200`. Supply the returned UUID `cursor` to continue.

```json theme={null}
{
  "coverage": [
    {
      "conversation_id": "C0123456789",
      "thread_ts": null,
      "status": "pending",
      "imported_count": 0,
      "oldest_ts": null,
      "newest_ts": null,
      "error_code": null,
      "updated_at": "2026-09-16T10:00:00Z",
      "includes_all_threads": false,
      "access_revoked": false
    }
  ],
  "next_cursor": null
}
```

| Status | Meaning |
| :- | :- |
| `observed` | The conversation has been observed; this is not an import-completeness claim. |
| `pending` | Import work is queued or waiting to continue. |
| `running` | A page is being fetched. |
| `complete` | This conversation or thread pass exhausted its available pages. |
| `failed` | Inspect `error_code` before requesting another pass. |
| `paused` | Import cannot continue under its current connection state. |

`oldest_ts` and `newest_ts` describe captured and imported message bounds, not proof that every message between them is present. `imported_count` is import progress, not a count of unique retained messages. `includes_all_threads` is currently `false`. Inspect separate thread records. `access_revoked` reflects the last observed membership event, not an authoritative current-access check. Do not use it to bypass the live checks on retained reads.

## Delete archive

```text theme={null}
DELETE /connections/{connection_id}/archive
```

Requires an organization admin API key or the Console. Returns `202`:

```json theme={null}
{
  "status": "pending"
}
```

Existing retained content becomes unavailable to reads immediately and is removed asynchronously. Pending imports stop, but new messages continue to be captured automatically. This does not delete messages in Slack. Request a new import explicitly if you want to recover accessible earlier history after deletion completes.

Disconnecting or uninstalling a workspace connection stops new capture through that connection and schedules its retained content for deletion. Reconnecting resumes capture automatically but does not recover deleted history. Deleting the owning identity removes its archive. Other identities and workspace connections remain independent.
