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

# Slack task streams

> Show native task progress in an existing Slack thread and close the stream when work finishes

Use a task stream to show progress while your runtime works on a request. Update stable task cards instead of posting a new message for every step. Task streams are separate from [native processing status](/docs/api/slack/operations.md#set-native-processing-status), ordinary messages, and the work your runtime performs.

All paths on this page are relative to `https://inkbox.ai/api/v1/slack`. Use the selected connection's current conversation access and [contact rules](/docs/api/slack/contact-rules.md). Starting or stopping a stream does not start or cancel your agent's work.

## Check availability

Inspect [connection capabilities](/docs/api/slack/users.md#inspect-capabilities). The `task_streaming` capability requires `chat:write`. `scopes_satisfied: true` confirms that permission, not that the app and workspace support native task streams. `native_task_streaming` is `missing_scope` or `unknown`; `unknown` does not mean supported or unsupported.

Check every operation's result. Unsupported native requests fail without automatically posting a substitute message. Your runtime can choose an ordinary [message](/docs/api/slack/messages.md#send-message) with [edits](/docs/api/slack/operations.md#edit-or-delete-a-message) before starting a native stream, or after a definitively unsupported start. Do not post that fallback after an uncertain start: the native stream may already exist.

## Stream lifecycle

1. Start the stream in the existing request thread. Supply the intended recipient's Slack user and workspace IDs.
2. Wait for the start operation to be `succeeded`. Save its `id` as the stream ID.
3. Append task updates in order. Wait for each operation's outcome before sending the next.
4. Stop the stream when work finishes or stops. Include final task updates if needed.

Use the start operation's UUID for append and stop, **not** its `message_ts` or an append operation's UUID. The conversation, thread, and connection remain fixed for that stream. A stream from an earlier connection authorization is not reusable after reconnecting.

Every write requires its own `Idempotency-Key`. Save each key before making the request. Reuse that key with the same input to recover the same logical operation, not to request another update. See [operation idempotency](/docs/api/slack/operations.md#idempotent-operations).

## Start a stream

```text theme={null}
POST /connections/{connection_id}/conversations/{conversation_id}/streams
```

```json theme={null}
{
  "thread_ts": "1789552800.000100",
  "recipient_user_id": "U0123456789",
  "recipient_team_id": "T0123456789",
  "task_display_mode": "timeline",
  "chunks": [
    {
      "type": "task_update",
      "id": "check-notes",
      "title": "Checking project notes",
      "status": "in_progress"
    }
  ]
}
```

| Field | Requirement |
| :- | :- |
| `thread_ts` | Required existing thread timestamp, kept as a string. Up to 64 characters, with digits on each side of the decimal point. |
| `recipient_user_id` | Required Slack user ID. Starts with `U` or `W`, followed by uppercase letters or digits; up to 64 characters total. |
| `recipient_team_id` | Required recipient workspace ID. Starts with `T`, followed by uppercase letters or digits; up to 64 characters total. |
| `task_display_mode` | `timeline` or `plan`. Defaults to `timeline`. |
| `chunks` | Required array of 1–20 task or plan updates. |

Take the destination and recipient from the request you are answering. Do not invent a thread timestamp or use a different workspace's connection.

Returns `200` with a [Slack operation](/docs/api/slack/operations.md#idempotent-operations), whose `operation` is `stream_start`. On success, `id` identifies the stream, `message_ts` identifies its Slack message, and `thread_ts` identifies its original thread. HTTP `200` alone does not mean the stream started.

## Append task updates

```text theme={null}
POST /connections/{connection_id}/conversations/{conversation_id}/streams/{stream_id}/append
```

```json theme={null}
{
  "chunks": [
    {
      "type": "task_update",
      "id": "check-notes",
      "title": "Checking project notes",
      "status": "in_progress",
      "details": "Reading the latest changes"
    }
  ]
}
```

`stream_id` is the successful start operation's UUID. `chunks` requires 1–20 updates. Append does not accept a new recipient, thread, or message timestamp. Returns an operation with `operation: "stream_append"` and its own `id`.

## Stop a stream

```text theme={null}
POST /connections/{connection_id}/conversations/{conversation_id}/streams/{stream_id}/stop
```

```json theme={null}
{
  "chunks": [
    {
      "type": "task_update",
      "id": "check-notes",
      "title": "Project notes checked",
      "status": "complete",
      "output": "Ready to share the summary"
    }
  ]
}
```

Send a JSON body. `chunks` is optional and accepts 0–20 updates; `{}` closes the stream without additional task updates. Returns an operation with `operation: "stream_stop"`. A successful stop closes the stream to new append and stop operations. Reading or replaying the same completed operation with its original key remains possible.

Stopping the stream does not send a separate final answer. Send any final reply or attachment through the ordinary [message](/docs/api/slack/messages.md#send-message) or [file](/docs/api/slack/files.md#upload-a-file) endpoints.

## Chunk types

Use a stable `id` when updating the same task. Task status and operation status are different: `complete` describes a task card, while `succeeded` describes an API operation.

| Type | Fields |
| :- | :- |
| `task_update` | Required `id`, `title`, and `status`. Optional `details` and `output`. |
| `plan_update` | Required `title`. Updates the plan title. |

Task IDs are 1–256 characters from letters, digits, `.`, `_`, `:`, or `-`. Titles are 1–256 characters. Optional `details` and `output` each accept up to 256 characters. Task `status` is `in_progress`, `complete`, or `error`.

```json theme={null}
{
  "type": "plan_update",
  "title": "Prepare the project summary"
}
```

These endpoints accept task and plan updates, not arbitrary text-stream or block payloads.

## Use the SDK

The task-stream methods and operation-by-key lookup require SDK or CLI version `0.7.16` or later. The examples below show the successful path after you have checked capabilities. Replace the example coordinates with those from the request you are answering, and save a distinct key for each logical operation before sending it.

```python Python theme={null}
from inkbox import Inkbox, SlackTaskUpdate

inkbox = Inkbox(api_key="YOUR_API_KEY")
connection_id = "22222222-2222-4222-8222-222222222222"
conversation_id = "C0123456789"
task: SlackTaskUpdate = {
    "type": "task_update",
    "id": "check-notes",
    "title": "Checking project notes",
    "status": "in_progress",
}

stream = inkbox.slack.start_stream(
    connection_id,
    conversation_id,
    thread_ts="1789552800.000100",
    recipient_user_id="U0123456789",
    recipient_team_id="T0123456789",
    chunks=[task],
    idempotency_key="project-progress-start-01",
)
if stream.status != "succeeded":
    raise RuntimeError("Inspect the start operation before continuing")

update = inkbox.slack.append_stream(
    connection_id,
    conversation_id,
    stream.id,
    chunks=[{**task, "details": "Reading the latest changes"}],
    idempotency_key="project-progress-update-01",
)
if update.status != "succeeded":
    raise RuntimeError("Inspect the update operation before continuing")

closed = inkbox.slack.stop_stream(
    connection_id,
    conversation_id,
    stream.id,
    chunks=[{**task, "status": "complete"}],
    idempotency_key="project-progress-stop-01",
)
print(closed.id, closed.status)
```

```typescript TypeScript theme={null}
import { Inkbox, type SlackTaskUpdate } from "@inkbox/sdk";

const inkbox = new Inkbox({ apiKey: "YOUR_API_KEY" });
const connectionId = "22222222-2222-4222-8222-222222222222";
const conversationId = "C0123456789";
const task: SlackTaskUpdate = {
  type: "task_update",
  id: "check-notes",
  title: "Checking project notes",
  status: "in_progress",
};

const stream = await inkbox.slack.startStream(connectionId, conversationId, {
  threadTs: "1789552800.000100",
  recipientUserId: "U0123456789",
  recipientTeamId: "T0123456789",
  chunks: [task],
  idempotencyKey: "project-progress-start-01",
});
if (stream.status !== "succeeded") {
  throw new Error("Inspect the start operation before continuing");
}

const update = await inkbox.slack.appendStream(
  connectionId, conversationId, stream.id,
  {
    chunks: [{ ...task, details: "Reading the latest changes" }],
    idempotencyKey: "project-progress-update-01",
  },
);
if (update.status !== "succeeded") {
  throw new Error("Inspect the update operation before continuing");
}

const closed = await inkbox.slack.stopStream(
  connectionId, conversationId, stream.id,
  {
    chunks: [{ ...task, status: "complete" }],
    idempotencyKey: "project-progress-stop-01",
  },
);
console.log(closed.id, closed.status);
```

Inspect the stop result too. A non-successful stop does not confirm that the stream closed. The `id` returned by append or stop identifies that operation; keep using the original start's `id` to address the stream.

## Handle uncertain outcomes

Read the [operation by ID](/docs/api/slack/operations.md#get-operation), or recover it using its original [idempotency key](/docs/api/slack/operations.md#get-operation-by-key) if the response was lost. Lookup does not repeat the write. Operation lookup is separate from [message-send action lookup](/docs/api/slack/messages.md#recover-action-by-key).

```python Python theme={null}
operation = inkbox.slack.get_operation_by_key(
    connection_id,
    idempotency_key="project-progress-start-01",
)
print(operation.id, operation.status)
```

```typescript TypeScript theme={null}
const operation = await inkbox.slack.getOperationByKey(connectionId, {
  idempotencyKey: "project-progress-start-01",
});
console.log(operation.id, operation.status);
```

```bash CLI theme={null}
inkbox slack operation get-by-key \
  --connection-id 22222222-2222-4222-8222-222222222222 \
  --idempotency-key project-progress-start-01
```

* `in_progress`: wait and inspect the same operation again.
* `succeeded`: use the confirmed result before advancing the stream.
* `failed`: inspect `error_code` and honor `retry_after` before deciding whether a new operation is appropriate.
* `unknown`: the write may have happened. Do not blindly start another stream or repeat the write with a new key.

A stream with an `in_progress` or `unknown` append or stop returns `409` for any new append or stop. An `unknown` outcome does not clear through lookup or replay, so you cannot subsequently update or close that stream through this API. A successfully closed stream also rejects new writes with `409`. A missing or unavailable start operation returns `404`.

A `404` from operation-by-key lookup is not proof that a lost start request cannot complete. Keep its original key and identical input; do not start a replacement stream under a new key because a lookup has not found it yet.

An unknown operation does not automatically become confirmed. If its outcome cannot be established, keep the uncertainty rather than creating duplicate progress messages. Native progress is optional; it should not prevent your runtime from finishing or cancelling its own work.


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