# Tellagen API Reference

> Generated from the same canonical API contract as the human-readable reference at https://tellagen.com/api-reference.

Documentation contract: `sha256:a5cd8e66988c9c8b615f339b37d49e7572853f4f1e6eb040361a39174365e687`. This fingerprint identifies the generated documentation contract, not the API major version.

## Endpoint index

### Timeline

[Timeline Markdown export](https://tellagen.com/api-reference/timeline/markdown)

- [`GET /api/v1/incidents/{id}/timeline`](https://tellagen.com/api-reference/timeline#timeline-endpoint-get-api-v1-incidents-id-timeline) — List timeline events
- [`POST /api/v1/incidents/{id}/timeline`](https://tellagen.com/api-reference/timeline#timeline-endpoint-post-api-v1-incidents-id-timeline) — Create timeline event
- [`GET /api/v1/timeline/{id}`](https://tellagen.com/api-reference/timeline#timeline-endpoint-get-api-v1-timeline-id) — Get timeline event
- [`PATCH /api/v1/timeline/{id}`](https://tellagen.com/api-reference/timeline#timeline-endpoint-patch-api-v1-timeline-id) — Update timeline event
- [`DELETE /api/v1/timeline/{id}`](https://tellagen.com/api-reference/timeline#timeline-endpoint-delete-api-v1-timeline-id) — Delete timeline event
- [`GET /api/v1/timeline/{id}/deliveries`](https://tellagen.com/api-reference/timeline#timeline-endpoint-get-api-v1-timeline-id-deliveries) — List status update deliveries
- [`POST /api/v1/timeline/{id}/deliveries/{deliveryId}/retry`](https://tellagen.com/api-reference/timeline#timeline-endpoint-post-api-v1-timeline-id-deliveries-deliveryid-retry) — Retry a status update delivery

## Base URL

`https://{company}.api.tellagen.com/api/v1/`

Replace `{company}` with your company subdomain. Requests and JSON responses use `application/json` unless an endpoint documents an empty response.

## Authentication

Create an API token in **Settings > API keys** and send it as a Bearer token:

`Authorization: Bearer tllg_YOUR_TOKEN`

Tokens are shown only once when created. Grant only the scopes the client needs.

## Integration guides

- [Make your first request](https://tellagen.com/api-reference#first-request)
- [Retries and updates](https://tellagen.com/api-reference#reliable-requests)
- [Rate limits](https://tellagen.com/api-reference/guides/rate-limits)
- [Filter and paginate collections](https://tellagen.com/api-reference/guides/pagination)
- [Create and move an incident through its workflow](https://tellagen.com/api-reference/guides/workflows)
- [Send custom-field values](https://tellagen.com/api-reference/guides/field-values)
- [Receive webhooks (preview)](https://tellagen.com/api-reference/guides/webhooks)

## Versioning

All endpoints in this reference use `/api/v1` and are supported.

**Compatible changes.** Tellagen can add optional response fields or enum values. Ignore response fields that your client does not use.

**Breaking changes.** Tellagen uses a new major version for incompatible changes.

**Deprecated endpoints.** Tellagen marks an endpoint deprecated at least 180 days before removal. Deprecated responses identify the replacement endpoint.

## Scopes

| Scope | Description |
| --- | --- |
| `incidents:read` | List and retrieve incidents, and discover writable workflow statuses and severities |
| `incidents:write` | Create and update incidents and timeline events. Requires an active responder seat for the key owner. |
| `slack_context:read` | Read Slack context collected for incident investigation |
| `services:read` | List and retrieve services, and discover writable service tiers |
| `services:write` | Create, update, and archive services. Requires an active responder seat and the manage_settings role permission. |
| `teams:read` | List and retrieve teams and members |
| `teams:write` | Create, update, and archive teams; manage members. Requires an active responder seat and the manage_teams role permission. |
| `custom_fields:read` | List and retrieve custom field definitions and values |
| `custom_fields:write` | Create, update, and archive custom fields; set incident field values. Definition changes require the manage_settings role permission. Incident value changes require an active responder seat. |
| `webhooks:manage` | Create, pause, resume, revoke, and rotate workspace webhook subscriptions. Requires the manage_settings role permission. |
| `customer_revenue:read` | Read customer revenue and financial impact. Requires the view_customer_revenue role permission and an active key owner membership. |
| `status_updates:email` | Email recorded status updates to the workspace's email groups (never to typed-in addresses). Not granted by default. Requires an active responder seat for the key owner and incidents:write. |

## Prerequisite definitions

- **Active responder seat** (`active_responder`): The API key owner must have an active responder seat.
- **Manage workspace settings** (`manage_settings`): The API key owner must have the manage_settings role permission.
- **Manage teams** (`manage_teams`): The API key owner must have the manage_teams role permission.
- **Incident is not archived** (`mutable_incident`): The incident must not be archived. Workflow-state restrictions are described by each operation.
- **Linked Slack channel** (`slack_channel`): The incident must have a linked Slack channel.

## Errors

Errors always include a stable `code` and a human-readable `message`. They can also include `field`, `details`, and `request_id`.

You can send `X-Request-ID` with a request. If you omit it, the API generates one. The response header and error body return the same value.

### Missing scope (403)

```json
{
  "code": "missing_scope",
  "message": "insufficient scope: incidents:write required",
  "details": {
    "required_scope": "incidents:write"
  },
  "request_id": "01JEXAMPLE8R4N5W6Y7Z"
}
```

### Stale resource version (412)

```json
{
  "code": "precondition_failed",
  "message": "the resource changed; fetch it again and retry with the current resource version",
  "field": "If-Tellagen-Resource-Version",
  "request_id": "01JEXAMPLE8R4N5W6Y7Z"
}
```

### Rate limit (429)

```json
{
  "code": "rate_limited",
  "message": "rate limit exceeded",
  "details": {
    "retry_after_seconds": 1
  },
  "request_id": "01JEXAMPLE8R4N5W6Y7Z"
}
```

When you contact support, include the request ID, method, path, status, and time. Omit tokens and private request bodies.

| Code | Meaning | Next action | Retry |
| --- | --- | --- | --- |
| `authentication_required` | The request has no valid API token or session. | Send an active Bearer token for this workspace. Replace an expired or revoked key. | After correcting authentication. |
| `missing_scope` | The token lacks the scope named in details.required_scope. | Read details.required_scope. Ask a workspace manager for a replacement key with that scope. | After correcting access. |
| `responder_seat_required` | The key owner needs the seat named in details.required_seat. | Ask a workspace manager to review the key owner’s responder seat. | After correcting access. |
| `missing_role_permission` | The key owner lacks details.required_permission or one of details.required_roles. | Ask a workspace manager to review the owner’s role and the permission named in details. | After correcting access. |
| `permission_denied` | Access is denied without exposing private authorization state. | Verify the workspace, key owner, and resource access with a workspace manager. | After correcting access. |
| `invalid_resource_state` | The operation is not valid for the current resource state. | Read the current resource. Restore an archived resource or choose an operation allowed in its current state. | After reconciling state. |
| `invalid_relationship_state` | Restore requires a retained owner or parent relationship to identify an active resource. | Restore the retained parent or owning team before restoring this resource. | After correcting the relationship. |
| `duplicate_slug` | A team already uses the requested slug. | Read the existing team or choose a different slug. | After changing the request. |
| `invalid_json` | The request body is not one valid JSON document. | Send one valid JSON object with Content-Type: application/json. | After correcting the body. |
| `unknown_field` | The request contains an unsupported JSON property named in field. | Remove or correct the property named in field. Use the request schema, not the response schema. | After correcting the body. |
| `request_body_too_large` | The request body is larger than 1 MiB. | Reduce the body to 1 MiB or less. | After reducing the body. |
| `multiple_json_values` | The request body contains more than one JSON document. | Send one JSON document rather than concatenated documents. | After correcting the body. |
| `idempotency_key_reused` | The key was already used with different request content. | Recover the original request for this key. Use a new key only for a different intended operation. | Do not resend changed content with the same key. |
| `idempotency_in_progress` | An equal request with this key is still running. Retry later. | Wait for Retry-After, then resend the identical body with the same key. | After the indicated delay. |
| `idempotency_outcome_unknown` | An earlier request may have completed, but its result could not be stored. Do not retry it automatically. | Read the resource state and contact support with the request ID before deciding whether to create again. | Never retry automatically. |
| `precondition_required` | An API-key PATCH request omitted If-Tellagen-Resource-Version. | Read the resource, then send its Tellagen-Resource-Version as If-Tellagen-Resource-Version. | After adding the current version. |
| `invalid_precondition` | If-Tellagen-Resource-Version is not a positive integer. | Use the positive integer version returned for this resource. Do not use an ETag or a timestamp. | After correcting the header. |
| `precondition_failed` | The resource changed after the client read it. | Read the resource again. Reconcile your intended change with the current state before using its new version. | After reconciling the change. |
| `rate_limited` | Retry after details.retry_after_seconds or the Retry-After header. | Wait for Retry-After or details.retry_after_seconds. Limit concurrency across the workspace. | After the indicated delay, with the original idempotency key where supported. |
| `internal` | The server failed without exposing an internal error. | Keep the request ID. Read current state before retrying a write whose outcome is uncertain. | Retry reads with bounded backoff. Follow operation-specific rules for writes. |
| `invalid_slack_channel_name_request` | The channel-name request is invalid. | Use custom mode with name, or workspace_template mode without name. | After correcting the request. |
| `slack_not_configured` | The workspace has no usable Slack integration. | Ask a workspace manager to connect or repair Slack. | After repairing the integration. |
| `incident_slack_channel_missing` | The incident has no linked Slack channel. | Link a channel through the incident workspace before requesting a rename. | After linking a channel. |
| `slack_channel_not_found` | Slack cannot find the linked channel. | Verify the channel still exists and that the integration can access it. | After repairing channel access. |
| `slack_channel_name_taken` | Another Slack channel uses the requested name. | Choose another custom name or revise the workspace naming template. | After choosing an available name. |
| `invalid_idempotency_key` | The idempotency key has an invalid length or character. | Use 1–128 visible, non-space ASCII characters. A UUID is suitable. | After correcting the key. |

| Status | Meaning |
| --- | --- |
| `400` | Bad Request — invalid input or missing required fields |
| `401` | Unauthorized — missing or invalid token |
| `403` | Forbidden — insufficient scope or responder access |
| `404` | Not Found — resource does not exist |
| `409` | Conflict — resource state prevents the request |
| `412` | Precondition Failed — the supplied resource version is stale |
| `428` | Precondition Required — this operation needs If-Tellagen-Resource-Version |
| `429` | Too Many Requests — rate limit exceeded |
| `500` | Internal Server Error |

Authenticated requests share a workspace limit. Anonymous requests and failed authentication attempts use the trusted client IP. Responses include `X-RateLimit-Limit` and `X-RateLimit-Remaining`. A `429` response also includes `Retry-After`.

## Timeline

Record updates, decisions, and evidence on an incident timeline.

### `GET /api/v1/incidents/{id}/timeline`

**List timeline events.** Lists timeline events from oldest to newest by default. Descending order reverses the time and ID order.

Different filters must all match. Multiple tags or groups match any selected value. The time range includes started_from and excludes started_to.

The matched_count field counts all matching events before pagination. The filter_counts field gives suggestions based only on the time range. The latest_started_at field gives the latest event time across the incident.

Use the returned cursor with the same incident, filters, and order. Changes between requests can move events between pages.


Required scope: `incidents:read`

Operation ID: `listTimelineEvents`
Contract: `supported`
Retry guidance: You can repeat this request. If the API returns 429, wait for Retry-After. Set a maximum number of attempts.
Prerequisites: None

#### Path parameters

Required nested fields apply when their parent object is present. Optional does not imply nullable.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `integer` | Yes | A positive numeric incident ID returned by this API. The permanent incident reference is suitable for stored references and external links. Minimum: 1. |

#### Query parameters

Required nested fields apply when their parent object is present. Optional does not imply nullable.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `limit` | `integer` | No | The maximum number of items in one page. The default is 50, and the maximum is 100. Minimum: 1. Maximum: 100. Default: 50. |
| `cursor` | `string` | No | The next_cursor value from the previous page. Clients treat this value as opaque and do not create, inspect, or change it. Minimum length: 1 characters. Maximum length: 4096 characters. |
| `order` | `string` | No | Sort direction for both started_at and id. Default: "asc". Allowed values: `"asc"` \| `"desc"` |
| `type_or_source` | `string` | No | Case-insensitive type or source, with outer whitespace ignored. ai matches sources ai, ai_investigation, investigation, or type investigation with source manual. |
| `tag` | `string[]` | No | Match any repeated tag case-insensitively. Tag whitespace is significant. |
| `key_only` | `boolean` | No | Include only key events when true. Accepts true or false. Default: false. |
| `group` | `integer \| string[]` | No | Match any repeated positive group ID or ungrouped for null group. Allowed values: `"ungrouped"` |
| `q` | `string` | No | Literal case-insensitive substring of title, body, ai_context, space-joined tags or group name; outer query whitespace is ignored. Percent and underscore are literal characters. Fields match independently. Null group text is no group ungrouped; empty or unavailable group name is unknown group followed by a hash and its ID. |
| `suggestions` | `string` | No | Include, exclude or select only ai_suggested events, identified by lowercase type without trimming. Only mode applies other predicates only when explicitly supplied; it does not inherit workspace filters. Default: "include". Allowed values: `"include"` \| `"exclude"` \| `"only"` |
| `started_from` | `string (RFC 3339)` | No | Includes events with started_at equal to or later than this RFC 3339 time. |
| `started_to` | `string (RFC 3339)` | No | Includes events with started_at earlier than this RFC 3339 time. |

#### Example request

Replace `{company}`, `<token>`, and any other placeholders with your values.

```bash
curl --request GET \
  --url 'https://{company}.api.tellagen.com/api/v1/incidents/{id}/timeline' \
  --header 'Authorization: Bearer <token>'
```

#### Success response

Status: `200 OK`

#### Response fields

Returned means the field is present when its parent object is present. A nullable field can contain null.

| Name | Type | Returned | Description |
| --- | --- | --- | --- |
| `events` | `object[]` | Always | The timeline events in this page. |
| `events[].id` | `integer` | Always | The positive numeric ID of the timeline event. |
| `events[].revision` | `integer` | Always | The resource version for concurrency control. Minimum: 1. |
| `events[].incident_id` | `integer` | Always | The positive numeric ID of the incident. |
| `events[].started_at` | `string (RFC 3339)` | Always | The event start time. |
| `events[].at` | `string (RFC 3339)` | Always | A deprecated response alias for started_at. Both fields contain the same value. |
| `events[].ended_at` | `string (RFC 3339) \| null` | Always | The event end time. A null value means that the event has no end time. |
| `events[].is_ongoing` | `boolean` | Always | Indicates whether the event is still in progress. |
| `events[].group_id` | `integer \| null` | Always | The timeline-group ID. A null value means that the event has no group. |
| `events[].is_key` | `boolean` | Always | Indicates whether responders marked this as a key incident event. |
| `events[].source` | `string` | Always | The system or actor that created the event. |
| `events[].source_ref` | `string` | Always | The source-specific reference for the event. |
| `events[].type` | `string` | Always | The event type used for grouping and display. |
| `events[].icon` | `string` | Always | The event icon. The maximum length is 8 Unicode characters. Maximum length: 8 characters. |
| `events[].title` | `string` | Always | The optional display title of the event. |
| `events[].body` | `string` | Always | The event description. |
| `events[].collaboration_generation` | `integer` | Always | The version of collaborative content associated with the event. |
| `events[].tags` | `string[]` | Always | The normalized event tags. |
| `events[].ai_context` | `string` | Always | The saved AI context for a generated event. An empty string means that no context is stored. |
| `events[].created_by` | `string` | Always | The identifier of the user or system that created the event. |
| `events[].comment_count` | `integer` | Always | The number of comments on the event. |
| `events[].attachments` | `object[]` | Always | The files attached to the event. |
| `events[].attachments[].id` | `integer` | Always | The positive numeric ID of the attachment. |
| `events[].attachments[].timeline_event_id` | `integer` | Always | The positive numeric ID of the timeline event. |
| `events[].attachments[].filename` | `string` | Always | The original file name. |
| `events[].attachments[].url` | `string` | Always | A temporary download URL. An empty string means that no URL is currently available. |
| `events[].attachments[].url_available` | `boolean` | Always | Indicates whether url contains an available download URL. |
| `events[].attachments[].content_type` | `string` | Always | The media type of the attached file. |
| `events[].attachments[].size_bytes` | `integer` | Always | The file size in bytes. |
| `events[].attachments[].created_at` | `string (RFC 3339)` | Always | The attachment creation time. |
| `events[].attachments[].created_by` | `string` | Always | The identifier of the user who attached the file. |
| `events[].source_window_started_at` | `string (RFC 3339) \| null` | Always | The start of the source-data window. A null value means that no window is recorded. |
| `events[].source_window_ended_at` | `string (RFC 3339) \| null` | Always | The end of the source-data window. A null value means that no window is recorded. |
| `events[].evidence_links` | `object[]` | Always | The evidence references associated with the event. |
| `events[].evidence_links[].source` | `string` | Always | The system or provider that supplied the evidence. |
| `events[].evidence_links[].url` | `string` | Always | The absolute URL of the evidence. |
| `events[].evidence_links[].label` | `string` | Optional | A short display name for the evidence. |
| `events[].evidence_links[].summary` | `string` | Optional | A short summary of the evidence. |
| `events[].evidence_links[].artifact_id` | `string` | Optional | The identifier of a stored evidence artifact. |
| `events[].evidence_links[].artifact_digest` | `string` | Optional | The digest used to make sure that the artifact content did not change. |
| `events[].evidence_links[].result_bytes` | `integer` | Optional | The original evidence result size in bytes. |
| `events[].evidence_links[].truncated` | `boolean` | Optional | Indicates whether the stored evidence omits part of the original result. |
| `events[].operational_update` | `object \| null` | Optional | The validated operational status payload. Null or absent for ordinary and legacy timeline events. Must match exactly one of 2 schema variants. |
| `events[].operational_update.current_impact` | `string` | Always | The current customer or system impact in human-authored text. Leading and trailing whitespace is removed before the Unicode length limit is enforced. Minimum length: 1 characters. Maximum length: 2000 characters. |
| `events[].operational_update.current_action` | `string` | Always | The response work currently in progress. Leading and trailing whitespace is removed before the Unicode length limit is enforced. Minimum length: 1 characters. Maximum length: 2000 characters. |
| `events[].operational_update.next_update_at` | `string (RFC 3339) \| null` | Always | The planned next update time as RFC 3339 |
| `events[].operational_update.audience` | `string` | Always | The intended audience for the status update. Possible values: `"responders"` \| `"stakeholders"` \| `"customers"` |
| `events[].operational_update.channel` | `string` | Always | Records the destination workflow. manual_external means the caller must deliver the copied message themselves. Possible values: `"incident_record"` \| `"manual_external"` |
| `events[].operational_update.destination` | `string` | Always | The human-readable external destination for manual_external Maximum length: 200 characters. |
| `events[].operational_update.communications_owner_user_id` | `integer \| null` | Always | The active incident participant responsible for coordinating manual delivery |
| `events[].operational_update.delivery_state` | `string` | Always | The server-derived delivery state. Tellagen does not claim that a manual external message was sent. Possible values: `"recorded"` \| `"pending_manual_delivery"` |
| `filter_counts` | `object` | Always | Counts for all events that match the timeline time filters. |
| `filter_counts.all_events` | `integer` | Always | The total number of events in the time-filtered collection, including AI suggestions. |
| `filter_counts.recorded_events` | `integer` | Always | The number of events in the time-filtered collection that are not AI suggestions. It matches matched_count for suggestions=exclude with no other filters. Minimum: 0. |
| `filter_counts.key_events` | `integer` | Always | The number of key events in the time-filtered collection that are not AI suggestions. Minimum: 0. |
| `filter_counts.type_counts` | `object` | Always | Event counts keyed by event type. |
| `filter_counts.source_counts` | `object` | Always | Event counts keyed by event source. |
| `filter_counts.tag_counts` | `object` | Always | Event counts keyed by tag. |
| `matched_count` | `integer` | Always | Complete active predicate count before cursor and limit. Minimum: 0. |
| `latest_started_at` | `string (RFC 3339) \| null` | Always | Latest started_at in the incident independent of filters; null for an empty incident. |
| `has_more` | `boolean` | Always | Indicates whether another page is available. |
| `next_cursor` | `string` | Optional | The opaque cursor for the next page. This field is absent on the final page. |
| `pagination` | `object` | Always | Pagination details for a collection response. |
| `pagination.limit` | `integer` | Always | The maximum number of items requested for this page. Minimum: 1. Maximum: 100. |
| `pagination.has_more` | `boolean` | Always | Indicates whether another page is available. |
| `pagination.next_cursor` | `string` | Optional | The opaque cursor for the next page. This field is absent on the final page. |

Returns timeline events, pagination fields, and counts for the complete filtered collection.

```json
{
  "events": [
    {
      "id": 100,
      "revision": 4,
      "incident_id": 1,
      "started_at": "2026-08-31T12:10:00Z",
      "at": "2026-08-31T12:10:00Z",
      "ended_at": null,
      "is_ongoing": false,
      "group_id": null,
      "is_key": false,
      "source": "api",
      "source_ref": "api:123:deploy-42",
      "type": "change",
      "icon": "🔧",
      "title": "Payment service restart",
      "body": "Restarted the payment service.",
      "collaboration_generation": 0,
      "tags": [],
      "ai_context": "",
      "created_by": "api-owner@example.com",
      "comment_count": 0,
      "attachments": [
        {
          "id": 501,
          "timeline_event_id": 100,
          "filename": "restart-log.txt",
          "url": "https://files.example.com/signed/restart-log.txt",
          "url_available": true,
          "content_type": "text/plain",
          "size_bytes": 2048,
          "created_at": "2026-08-31T12:11:00Z",
          "created_by": "oncall@example.com"
        }
      ],
      "source_window_started_at": null,
      "source_window_ended_at": null,
      "evidence_links": []
    }
  ],
  "filter_counts": {
    "all_events": 1,
    "recorded_events": 1,
    "key_events": 0,
    "type_counts": {
      "change": 1
    },
    "source_counts": {
      "api": 1
    },
    "tag_counts": {}
  },
  "matched_count": 1,
  "latest_started_at": "2026-08-31T12:10:00Z",
  "has_more": false,
  "pagination": {
    "limit": 50,
    "has_more": false
  }
}
```

#### Endpoint error responses

| Status | Meaning |
| --- | --- |
| `400` | The API rejected the request because its syntax or one of its values is invalid. |
| `401` | The request does not contain valid authentication. |
| `403` | Authentication succeeded, but the caller lacks the required API scope or workspace permission. |
| `404` | The requested resource does not exist or is not available to the caller. |
| `409` | The request conflicts with the current resource state. idempotency_key_reused means that the key belongs to different request content. idempotency_in_progress means that an identical request is still in progress. idempotency_outcome_unknown means that an earlier request may have completed, but its result could not be stored and must not be retried automatically. |
| `429` | The request exceeded an applicable sliding limit. Free API keys allow 20 requests per second and 600 requests per 60 seconds. Team API keys allow 100 requests per second and 3,000 requests per 60 seconds. Browser users allow 100 requests per second. Browser company allows 500 requests per second and 5,000 requests per 10 seconds. Browser company means the sum of user traffic for one company. Anonymous client IPs allow 20 requests per second and 200 requests per 10 seconds. The rate limiter does not count requests that it rejects. |
| `500` | An internal server error prevented completion of the request. |

### `POST /api/v1/incidents/{id}/timeline`

**Create timeline event.** Creates a timeline event. Include operational_update with type operational_update to record a structured human-authored status snapshot; the API derives its delivery_state and canonical body. Add share to a status update to also post it to Slack as it is recorded; the API stores the update and one delivery per destination together, returns the deliveries, and posts in the background (status_update_delivery_disabled when the workspace has not turned delivery on). For API-key requests, the API sets source to api. The API controls source, AI context, evidence provenance, and reserved event types.

Required scope: `incidents:write`

Operation ID: `createTimelineEvent`
Contract: `supported`
Retry guidance: Within 24 hours, retry with the original Idempotency-Key and identical body. If the result is unknown, do not retry automatically. Check whether the resource exists.
Prerequisites: Active responder seat, Incident is not archived

#### Path parameters

Required nested fields apply when their parent object is present. Optional does not imply nullable.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `integer` | Yes | A positive numeric incident ID returned by this API. The permanent incident reference is suitable for stored references and external links. Minimum: 1. |

#### Request headers

Required nested fields apply when their parent object is present. Optional does not imply nullable.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Idempotency-Key` | `string` | No | A key for safe retries of a create request. It must contain 1–128 visible, non-space ASCII characters. The same key applies only to an identical request. Minimum length: 1 characters. Maximum length: 128 characters. Pattern: ^[\x21-\x7E]+$. |

#### Example request

Replace `{company}`, `<token>`, and any other placeholders with your values.

For `<idempotency-key>`, choose a unique key on the first attempt. Reuse it only when retrying the identical request.

```bash
curl --request POST \
  --url 'https://{company}.api.tellagen.com/api/v1/incidents/{id}/timeline' \
  --header 'Authorization: Bearer <token>' \
  --header 'Idempotency-Key: <idempotency-key>' \
  --header 'Content-Type: application/json' \
  --data '{
  "body": "Restarted the payment service.",
  "started_at": "2026-08-31T12:10:00Z",
  "type": "change",
  "icon": "🔧",
  "external_reference": "deploy-42"
}'
```

#### Request body

Required nested fields apply when their parent object is present. Optional does not imply nullable.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `body` | `string` | Yes | The event description. It must contain at least one non-space character. Minimum length: 1 characters. |
| `started_at` | `string (RFC 3339)` | No | The RFC 3339 start time. If the request omits started_at, the API uses the current time. |
| `at` | `string (RFC 3339)` | No | A deprecated request alias for started_at. If both fields are present, their values must match. |
| `ended_at` | `string (RFC 3339)` | No | The RFC 3339 end time. It must be later than started_at. |
| `is_ongoing` | `boolean` | No | Controls whether the event is ongoing. If this value is true, ended_at must be omitted. |
| `group_id` | `integer \| null` | No | The positive timeline-group ID for this incident. A null value creates an event without a group. |
| `is_key` | `boolean` | No | Controls whether responders see this as a key incident event. |
| `type` | `string` | No | The client-defined event type. The API rejects event types reserved for generated events. |
| `icon` | `string` | No | The event icon. The maximum length is 8 Unicode characters. Maximum length: 8 characters. |
| `title` | `string` | No | The optional display title of the event. |
| `tags` | `string[]` | No | The event tags. The API normalizes each value. |
| `external_reference` | `string` | No | A client correlation reference for API-key requests. The API trims, limits, and namespaces the value. The maximum length is 200 characters. Maximum length: 200 characters. |
| `operational_update` | `object \| null` | No | The structured human-authored status payload. Use this only with type operational_update; delivery_state is derived by the server and cannot be supplied here. A null value is treated as absent. Must match exactly one of 2 schema variants. |
| `operational_update.current_impact` | `string` | Yes | The current customer or system impact in human-authored text. Minimum length: 1 characters. Maximum length: 2000 characters. |
| `operational_update.current_action` | `string` | Yes | The response work currently in progress. Minimum length: 1 characters. Maximum length: 2000 characters. |
| `operational_update.next_update_at` | `string (RFC 3339) \| null` | Yes | The planned next update time as RFC 3339 |
| `operational_update.audience` | `string` | Yes | The intended audience for the status update. Allowed values: `"responders"` \| `"stakeholders"` \| `"customers"` |
| `operational_update.channel` | `string` | Yes | Use manual_external only when the caller will deliver the copied message themselves. Allowed values: `"incident_record"` \| `"manual_external"` |
| `operational_update.destination` | `string` | Yes | Required for manual_external and empty for incident_record. Maximum length: 200 characters. |
| `operational_update.communications_owner_user_id` | `integer \| null` | Yes | An active incident participant responsible for coordinating manual delivery |
| `share` | `object` | No | Where to deliver a status update as it is recorded. It is request-only: the API stores one delivery per destination, never the share itself. It needs type operational_update with channel incident_record. Other Slack channels must be announcement channels an admin configured in Settings → Status updates. Email goes to workspace members at once; any other address first confirms once by email, and its delivery waits as awaiting_confirmation. Email needs the status_updates:email scope (403 status_update_email_scope_required), and an API key can name email groups only (403 status_update_adhoc_email_api_key for email_addresses). |
| `share.slack_incident_channel` | `boolean` | No | Posts a new message to the incident's Slack channel. The incident must have a channel (status_update_no_incident_channel). |
| `share.slack_channel_ids` | `string[]` | No | Announcement channels to post to as well, by Slack channel ID. Each must be configured in Settings → Status updates; any other channel is rejected with status_update_channel_not_configured. Tellagen never joins these channels, and later updates for the same incident reply in the thread of its latest post there. Maximum items: 5. |
| `share.email_group_ids` | `integer[]` | No | Email groups from Settings → Status updates. An unknown or deleted group is rejected with status_update_email_group_not_found. One update emails at most 100 people (status_update_too_many_recipients). An address that unsubscribed, bounced or complained gets a suppressed delivery, never an email. Maximum items: 50. |
| `share.email_addresses` | `string[]` | No | Addresses typed in while posting (sessions only). They need the workspace rule that allows them (status_update_adhoc_email_not_allowed), and external addresses need a paid plan and the confirmed-external rule (status_update_external_email_not_allowed). At most 20 (status_update_too_many_recipients). Maximum items: 20. |

Request JSON:

```json
{
  "body": "Restarted the payment service.",
  "started_at": "2026-08-31T12:10:00Z",
  "type": "change",
  "icon": "🔧",
  "external_reference": "deploy-42"
}
```

#### Success response

Status: `201 Created`

#### Response fields

Returned means the field is present when its parent object is present. A nullable field can contain null.

| Name | Type | Returned | Description |
| --- | --- | --- | --- |
| `event` | `object` | Always | The complete public record for a timeline event. |
| `event.id` | `integer` | Always | The positive numeric ID of the timeline event. |
| `event.revision` | `integer` | Always | The resource version for concurrency control. Minimum: 1. |
| `event.incident_id` | `integer` | Always | The positive numeric ID of the incident. |
| `event.started_at` | `string (RFC 3339)` | Always | The event start time. |
| `event.at` | `string (RFC 3339)` | Always | A deprecated response alias for started_at. Both fields contain the same value. |
| `event.ended_at` | `string (RFC 3339) \| null` | Always | The event end time. A null value means that the event has no end time. |
| `event.is_ongoing` | `boolean` | Always | Indicates whether the event is still in progress. |
| `event.group_id` | `integer \| null` | Always | The timeline-group ID. A null value means that the event has no group. |
| `event.is_key` | `boolean` | Always | Indicates whether responders marked this as a key incident event. |
| `event.source` | `string` | Always | The system or actor that created the event. |
| `event.source_ref` | `string` | Always | The source-specific reference for the event. |
| `event.type` | `string` | Always | The event type used for grouping and display. |
| `event.icon` | `string` | Always | The event icon. The maximum length is 8 Unicode characters. Maximum length: 8 characters. |
| `event.title` | `string` | Always | The optional display title of the event. |
| `event.body` | `string` | Always | The event description. |
| `event.collaboration_generation` | `integer` | Always | The version of collaborative content associated with the event. |
| `event.tags` | `string[]` | Always | The normalized event tags. |
| `event.ai_context` | `string` | Always | The saved AI context for a generated event. An empty string means that no context is stored. |
| `event.created_by` | `string` | Always | The identifier of the user or system that created the event. |
| `event.comment_count` | `integer` | Always | The number of comments on the event. |
| `event.attachments` | `object[]` | Always | The files attached to the event. |
| `event.attachments[].id` | `integer` | Always | The positive numeric ID of the attachment. |
| `event.attachments[].timeline_event_id` | `integer` | Always | The positive numeric ID of the timeline event. |
| `event.attachments[].filename` | `string` | Always | The original file name. |
| `event.attachments[].url` | `string` | Always | A temporary download URL. An empty string means that no URL is currently available. |
| `event.attachments[].url_available` | `boolean` | Always | Indicates whether url contains an available download URL. |
| `event.attachments[].content_type` | `string` | Always | The media type of the attached file. |
| `event.attachments[].size_bytes` | `integer` | Always | The file size in bytes. |
| `event.attachments[].created_at` | `string (RFC 3339)` | Always | The attachment creation time. |
| `event.attachments[].created_by` | `string` | Always | The identifier of the user who attached the file. |
| `event.source_window_started_at` | `string (RFC 3339) \| null` | Always | The start of the source-data window. A null value means that no window is recorded. |
| `event.source_window_ended_at` | `string (RFC 3339) \| null` | Always | The end of the source-data window. A null value means that no window is recorded. |
| `event.evidence_links` | `object[]` | Always | The evidence references associated with the event. |
| `event.evidence_links[].source` | `string` | Always | The system or provider that supplied the evidence. |
| `event.evidence_links[].url` | `string` | Always | The absolute URL of the evidence. |
| `event.evidence_links[].label` | `string` | Optional | A short display name for the evidence. |
| `event.evidence_links[].summary` | `string` | Optional | A short summary of the evidence. |
| `event.evidence_links[].artifact_id` | `string` | Optional | The identifier of a stored evidence artifact. |
| `event.evidence_links[].artifact_digest` | `string` | Optional | The digest used to make sure that the artifact content did not change. |
| `event.evidence_links[].result_bytes` | `integer` | Optional | The original evidence result size in bytes. |
| `event.evidence_links[].truncated` | `boolean` | Optional | Indicates whether the stored evidence omits part of the original result. |
| `event.operational_update` | `object \| null` | Optional | The validated operational status payload. Null or absent for ordinary and legacy timeline events. Must match exactly one of 2 schema variants. |
| `event.operational_update.current_impact` | `string` | Always | The current customer or system impact in human-authored text. Leading and trailing whitespace is removed before the Unicode length limit is enforced. Minimum length: 1 characters. Maximum length: 2000 characters. |
| `event.operational_update.current_action` | `string` | Always | The response work currently in progress. Leading and trailing whitespace is removed before the Unicode length limit is enforced. Minimum length: 1 characters. Maximum length: 2000 characters. |
| `event.operational_update.next_update_at` | `string (RFC 3339) \| null` | Always | The planned next update time as RFC 3339 |
| `event.operational_update.audience` | `string` | Always | The intended audience for the status update. Possible values: `"responders"` \| `"stakeholders"` \| `"customers"` |
| `event.operational_update.channel` | `string` | Always | Records the destination workflow. manual_external means the caller must deliver the copied message themselves. Possible values: `"incident_record"` \| `"manual_external"` |
| `event.operational_update.destination` | `string` | Always | The human-readable external destination for manual_external Maximum length: 200 characters. |
| `event.operational_update.communications_owner_user_id` | `integer \| null` | Always | The active incident participant responsible for coordinating manual delivery |
| `event.operational_update.delivery_state` | `string` | Always | The server-derived delivery state. Tellagen does not claim that a manual external message was sent. Possible values: `"recorded"` \| `"pending_manual_delivery"` |
| `deliveries` | `object[]` | Optional | The destinations recorded with a shared status update, one per destination. Only the create response of a request with share includes it. |
| `deliveries[].id` | `integer` | Always | The delivery ID. |
| `deliveries[].timeline_event_id` | `integer` | Always | The status update this delivery belongs to. |
| `deliveries[].incident_id` | `integer` | Always | The incident ID. |
| `deliveries[].kind` | `string` | Always | The provider. Possible values: `"slack"` \| `"email"` |
| `deliveries[].destination` | `string` | Always | Why the destination was chosen. Possible values: `"incident_channel"` \| `"announcement_channel"` \| `"email"` |
| `deliveries[].target` | `string` | Always | The Slack channel ID. |
| `deliveries[].target_label` | `string` | Always | The Slack channel name without #, when known; otherwise empty. |
| `deliveries[].status` | `string` | Always | queued waits for its first or next attempt; sending is being posted; sent is delivered; failed needs a person (for example, invite the app) and is retried only on request; dead stopped after the attempt limit or 2 hours; skipped was never attempted (a demo workspace or Slack not connected). Possible values: `"queued"` \| `"sending"` \| `"sent"` \| `"failed"` \| `"dead"` \| `"skipped"` \| `"awaiting_confirmation"` \| `"suppressed"` |
| `deliveries[].attempts` | `integer` | Always | The number of attempts so far. Minimum: 0. |
| `deliveries[].next_attempt_at` | `string (RFC 3339) \| null` | Always | When a queued delivery is attempted next; null in every other state. |
| `deliveries[].permalink` | `string` | Always | The Slack message link once sent; otherwise empty. |
| `deliveries[].error_code` | `string` | Always | Why the delivery is not sent, such as not_in_channel, channel_not_found, channel_archived, slack_auth, slack_not_connected, rate_limited, temporary, expired, not_configured (the announcement channel was removed in Settings before the post went out), or demo. Empty when nothing is wrong. |
| `deliveries[].error_message` | `string` | Always | The reason in words |
| `deliveries[].can_retry` | `boolean` | Always | True for a failed or dead Slack delivery. |
| `deliveries[].sent_at` | `string (RFC 3339) \| null` | Always | When the delivery was sent. |
| `deliveries[].created_at` | `string (RFC 3339)` | Always | When the update was recorded with this destination. |
| `deliveries[].updated_at` | `string (RFC 3339)` | Always | When the delivery state last changed. |

Returns the created timeline event.

```json
{
  "event": {
    "id": 100,
    "revision": 4,
    "incident_id": 1,
    "started_at": "2026-08-31T12:10:00Z",
    "at": "2026-08-31T12:10:00Z",
    "ended_at": null,
    "is_ongoing": false,
    "group_id": null,
    "is_key": false,
    "source": "api",
    "source_ref": "api:123:deploy-42",
    "type": "change",
    "icon": "🔧",
    "title": "Payment service restart",
    "body": "Restarted the payment service.",
    "collaboration_generation": 0,
    "tags": [],
    "ai_context": "",
    "created_by": "api-owner@example.com",
    "comment_count": 0,
    "attachments": [
      {
        "id": 501,
        "timeline_event_id": 100,
        "filename": "restart-log.txt",
        "url": "https://files.example.com/signed/restart-log.txt",
        "url_available": true,
        "content_type": "text/plain",
        "size_bytes": 2048,
        "created_at": "2026-08-31T12:11:00Z",
        "created_by": "oncall@example.com"
      }
    ],
    "source_window_started_at": null,
    "source_window_ended_at": null,
    "evidence_links": []
  }
}
```

#### Response headers

Returned means the field is present when its parent object is present. A nullable field can contain null.

| Name | Type | Returned | Description |
| --- | --- | --- | --- |
| `Tellagen-Resource-Version` | `integer` | Always | The positive resource version for If-Tellagen-Resource-Version in the next API-key PATCH request. Minimum: 1. |

#### Endpoint error responses

Endpoint-specific codes: `idempotency_key_reused`, `idempotency_in_progress`, `idempotency_outcome_unknown`

| Status | Meaning |
| --- | --- |
| `400` | The API rejected the request because its syntax or one of its values is invalid. |
| `401` | The request does not contain valid authentication. |
| `403` | Authentication succeeded, but the caller lacks the required API scope or workspace permission. |
| `404` | The requested resource does not exist or is not available to the caller. |
| `409` | The request conflicts with the current resource state. idempotency_key_reused means that the key belongs to different request content. idempotency_in_progress means that an identical request is still in progress. idempotency_outcome_unknown means that an earlier request may have completed, but its result could not be stored and must not be retried automatically. |
| `429` | The request exceeded an applicable sliding limit. Free API keys allow 20 requests per second and 600 requests per 60 seconds. Team API keys allow 100 requests per second and 3,000 requests per 60 seconds. Browser users allow 100 requests per second. Browser company allows 500 requests per second and 5,000 requests per 10 seconds. Browser company means the sum of user traffic for one company. Anonymous client IPs allow 20 requests per second and 200 requests per 10 seconds. The rate limiter does not count requests that it rejects. |
| `500` | An internal server error prevented completion of the request. |

### `GET /api/v1/timeline/{id}`

**Get timeline event.** Returns one timeline event. Attachment download URLs expire. If url_available is false or a URL expires, a new GET request can return a new URL.

Required scope: `incidents:read`

Operation ID: `getTimelineEvent`
Contract: `supported`
Retry guidance: You can repeat this request. If the API returns 429, wait for Retry-After. Set a maximum number of attempts.
Prerequisites: None

#### Path parameters

Required nested fields apply when their parent object is present. Optional does not imply nullable.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `integer` | Yes | A positive numeric timeline-event ID returned by this API. Minimum: 1. |

#### Example request

Replace `{company}`, `<token>`, and any other placeholders with your values.

```bash
curl --request GET \
  --url 'https://{company}.api.tellagen.com/api/v1/timeline/{id}' \
  --header 'Authorization: Bearer <token>'
```

#### Success response

Status: `200 OK`

#### Response fields

Returned means the field is present when its parent object is present. A nullable field can contain null.

| Name | Type | Returned | Description |
| --- | --- | --- | --- |
| `event` | `object` | Always | The complete public record for a timeline event. |
| `event.id` | `integer` | Always | The positive numeric ID of the timeline event. |
| `event.revision` | `integer` | Always | The resource version for concurrency control. Minimum: 1. |
| `event.incident_id` | `integer` | Always | The positive numeric ID of the incident. |
| `event.started_at` | `string (RFC 3339)` | Always | The event start time. |
| `event.at` | `string (RFC 3339)` | Always | A deprecated response alias for started_at. Both fields contain the same value. |
| `event.ended_at` | `string (RFC 3339) \| null` | Always | The event end time. A null value means that the event has no end time. |
| `event.is_ongoing` | `boolean` | Always | Indicates whether the event is still in progress. |
| `event.group_id` | `integer \| null` | Always | The timeline-group ID. A null value means that the event has no group. |
| `event.is_key` | `boolean` | Always | Indicates whether responders marked this as a key incident event. |
| `event.source` | `string` | Always | The system or actor that created the event. |
| `event.source_ref` | `string` | Always | The source-specific reference for the event. |
| `event.type` | `string` | Always | The event type used for grouping and display. |
| `event.icon` | `string` | Always | The event icon. The maximum length is 8 Unicode characters. Maximum length: 8 characters. |
| `event.title` | `string` | Always | The optional display title of the event. |
| `event.body` | `string` | Always | The event description. |
| `event.collaboration_generation` | `integer` | Always | The version of collaborative content associated with the event. |
| `event.tags` | `string[]` | Always | The normalized event tags. |
| `event.ai_context` | `string` | Always | The saved AI context for a generated event. An empty string means that no context is stored. |
| `event.created_by` | `string` | Always | The identifier of the user or system that created the event. |
| `event.comment_count` | `integer` | Always | The number of comments on the event. |
| `event.attachments` | `object[]` | Always | The files attached to the event. |
| `event.attachments[].id` | `integer` | Always | The positive numeric ID of the attachment. |
| `event.attachments[].timeline_event_id` | `integer` | Always | The positive numeric ID of the timeline event. |
| `event.attachments[].filename` | `string` | Always | The original file name. |
| `event.attachments[].url` | `string` | Always | A temporary download URL. An empty string means that no URL is currently available. |
| `event.attachments[].url_available` | `boolean` | Always | Indicates whether url contains an available download URL. |
| `event.attachments[].content_type` | `string` | Always | The media type of the attached file. |
| `event.attachments[].size_bytes` | `integer` | Always | The file size in bytes. |
| `event.attachments[].created_at` | `string (RFC 3339)` | Always | The attachment creation time. |
| `event.attachments[].created_by` | `string` | Always | The identifier of the user who attached the file. |
| `event.source_window_started_at` | `string (RFC 3339) \| null` | Always | The start of the source-data window. A null value means that no window is recorded. |
| `event.source_window_ended_at` | `string (RFC 3339) \| null` | Always | The end of the source-data window. A null value means that no window is recorded. |
| `event.evidence_links` | `object[]` | Always | The evidence references associated with the event. |
| `event.evidence_links[].source` | `string` | Always | The system or provider that supplied the evidence. |
| `event.evidence_links[].url` | `string` | Always | The absolute URL of the evidence. |
| `event.evidence_links[].label` | `string` | Optional | A short display name for the evidence. |
| `event.evidence_links[].summary` | `string` | Optional | A short summary of the evidence. |
| `event.evidence_links[].artifact_id` | `string` | Optional | The identifier of a stored evidence artifact. |
| `event.evidence_links[].artifact_digest` | `string` | Optional | The digest used to make sure that the artifact content did not change. |
| `event.evidence_links[].result_bytes` | `integer` | Optional | The original evidence result size in bytes. |
| `event.evidence_links[].truncated` | `boolean` | Optional | Indicates whether the stored evidence omits part of the original result. |
| `event.operational_update` | `object \| null` | Optional | The validated operational status payload. Null or absent for ordinary and legacy timeline events. Must match exactly one of 2 schema variants. |
| `event.operational_update.current_impact` | `string` | Always | The current customer or system impact in human-authored text. Leading and trailing whitespace is removed before the Unicode length limit is enforced. Minimum length: 1 characters. Maximum length: 2000 characters. |
| `event.operational_update.current_action` | `string` | Always | The response work currently in progress. Leading and trailing whitespace is removed before the Unicode length limit is enforced. Minimum length: 1 characters. Maximum length: 2000 characters. |
| `event.operational_update.next_update_at` | `string (RFC 3339) \| null` | Always | The planned next update time as RFC 3339 |
| `event.operational_update.audience` | `string` | Always | The intended audience for the status update. Possible values: `"responders"` \| `"stakeholders"` \| `"customers"` |
| `event.operational_update.channel` | `string` | Always | Records the destination workflow. manual_external means the caller must deliver the copied message themselves. Possible values: `"incident_record"` \| `"manual_external"` |
| `event.operational_update.destination` | `string` | Always | The human-readable external destination for manual_external Maximum length: 200 characters. |
| `event.operational_update.communications_owner_user_id` | `integer \| null` | Always | The active incident participant responsible for coordinating manual delivery |
| `event.operational_update.delivery_state` | `string` | Always | The server-derived delivery state. Tellagen does not claim that a manual external message was sent. Possible values: `"recorded"` \| `"pending_manual_delivery"` |
| `deliveries` | `object[]` | Optional | The destinations recorded with a shared status update, one per destination. Only the create response of a request with share includes it. |
| `deliveries[].id` | `integer` | Always | The delivery ID. |
| `deliveries[].timeline_event_id` | `integer` | Always | The status update this delivery belongs to. |
| `deliveries[].incident_id` | `integer` | Always | The incident ID. |
| `deliveries[].kind` | `string` | Always | The provider. Possible values: `"slack"` \| `"email"` |
| `deliveries[].destination` | `string` | Always | Why the destination was chosen. Possible values: `"incident_channel"` \| `"announcement_channel"` \| `"email"` |
| `deliveries[].target` | `string` | Always | The Slack channel ID. |
| `deliveries[].target_label` | `string` | Always | The Slack channel name without #, when known; otherwise empty. |
| `deliveries[].status` | `string` | Always | queued waits for its first or next attempt; sending is being posted; sent is delivered; failed needs a person (for example, invite the app) and is retried only on request; dead stopped after the attempt limit or 2 hours; skipped was never attempted (a demo workspace or Slack not connected). Possible values: `"queued"` \| `"sending"` \| `"sent"` \| `"failed"` \| `"dead"` \| `"skipped"` \| `"awaiting_confirmation"` \| `"suppressed"` |
| `deliveries[].attempts` | `integer` | Always | The number of attempts so far. Minimum: 0. |
| `deliveries[].next_attempt_at` | `string (RFC 3339) \| null` | Always | When a queued delivery is attempted next; null in every other state. |
| `deliveries[].permalink` | `string` | Always | The Slack message link once sent; otherwise empty. |
| `deliveries[].error_code` | `string` | Always | Why the delivery is not sent, such as not_in_channel, channel_not_found, channel_archived, slack_auth, slack_not_connected, rate_limited, temporary, expired, not_configured (the announcement channel was removed in Settings before the post went out), or demo. Empty when nothing is wrong. |
| `deliveries[].error_message` | `string` | Always | The reason in words |
| `deliveries[].can_retry` | `boolean` | Always | True for a failed or dead Slack delivery. |
| `deliveries[].sent_at` | `string (RFC 3339) \| null` | Always | When the delivery was sent. |
| `deliveries[].created_at` | `string (RFC 3339)` | Always | When the update was recorded with this destination. |
| `deliveries[].updated_at` | `string (RFC 3339)` | Always | When the delivery state last changed. |

Returns the timeline event.

```json
{
  "event": {
    "id": 100,
    "revision": 4,
    "incident_id": 1,
    "started_at": "2026-08-31T12:10:00Z",
    "at": "2026-08-31T12:10:00Z",
    "ended_at": null,
    "is_ongoing": false,
    "group_id": null,
    "is_key": false,
    "source": "api",
    "source_ref": "api:123:deploy-42",
    "type": "change",
    "icon": "🔧",
    "title": "Payment service restart",
    "body": "Restarted the payment service.",
    "collaboration_generation": 0,
    "tags": [],
    "ai_context": "",
    "created_by": "api-owner@example.com",
    "comment_count": 0,
    "attachments": [
      {
        "id": 501,
        "timeline_event_id": 100,
        "filename": "restart-log.txt",
        "url": "https://files.example.com/signed/restart-log.txt",
        "url_available": true,
        "content_type": "text/plain",
        "size_bytes": 2048,
        "created_at": "2026-08-31T12:11:00Z",
        "created_by": "oncall@example.com"
      }
    ],
    "source_window_started_at": null,
    "source_window_ended_at": null,
    "evidence_links": []
  }
}
```

#### Response headers

Returned means the field is present when its parent object is present. A nullable field can contain null.

| Name | Type | Returned | Description |
| --- | --- | --- | --- |
| `Tellagen-Resource-Version` | `integer` | Always | The positive resource version for If-Tellagen-Resource-Version in the next API-key PATCH request. Minimum: 1. |

#### Endpoint error responses

| Status | Meaning |
| --- | --- |
| `400` | The API rejected the request because its syntax or one of its values is invalid. |
| `401` | The request does not contain valid authentication. |
| `403` | Authentication succeeded, but the caller lacks the required API scope or workspace permission. |
| `404` | The requested resource does not exist or is not available to the caller. |
| `409` | The request conflicts with the current resource state. idempotency_key_reused means that the key belongs to different request content. idempotency_in_progress means that an identical request is still in progress. idempotency_outcome_unknown means that an earlier request may have completed, but its result could not be stored and must not be retried automatically. |
| `429` | The request exceeded an applicable sliding limit. Free API keys allow 20 requests per second and 600 requests per 60 seconds. Team API keys allow 100 requests per second and 3,000 requests per 60 seconds. Browser users allow 100 requests per second. Browser company allows 500 requests per second and 5,000 requests per 10 seconds. Browser company means the sum of user traffic for one company. Anonymous client IPs allow 20 requests per second and 200 requests per 10 seconds. The rate limiter does not count requests that it rejects. |
| `500` | An internal server error prevented completion of the request. |

### `PATCH /api/v1/timeline/{id}`

**Update timeline event.** Validates the final event state, then updates the supplied fields. Typed operational status snapshots are immutable; attempts to change their body, title, type, or start time return a validation error and the caller should post another status update. If the request changes no value, the API returns 400 without side effects.

Required scope: `incidents:write`

Operation ID: `updateTimelineEvent`
Contract: `supported`
Retry guidance: If the API returns 412, read the resource again. Compare the latest changes before you retry with the new version.
Prerequisites: Active responder seat, Incident is not archived

#### Path parameters

Required nested fields apply when their parent object is present. Optional does not imply nullable.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `integer` | Yes | A positive numeric timeline-event ID returned by this API. Minimum: 1. |

#### Request headers

Required nested fields apply when their parent object is present. Optional does not imply nullable.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `If-Tellagen-Resource-Version` | `integer` | Yes | The current positive resource version from the latest response. API-key PATCH requests must send this value. Minimum: 1. |

#### Example request

Replace `{company}`, `<token>`, and any other placeholders with your values.

Use the Tellagen-Resource-Version header from a GET of this resource for `<resource-version>`.

```bash
curl --request PATCH \
  --url 'https://{company}.api.tellagen.com/api/v1/timeline/{id}' \
  --header 'Authorization: Bearer <token>' \
  --header 'If-Tellagen-Resource-Version: <resource-version>' \
  --header 'Content-Type: application/json' \
  --data '{
  "body": "Restarted the payment service and confirmed recovery.",
  "ended_at": "2026-08-31T12:18:00Z"
}'
```

#### Request body

Required nested fields apply when their parent object is present. Optional does not imply nullable.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `body` | `string` | No | The replacement event description. It must contain at least one non-space character. Minimum length: 1 characters. |
| `title` | `string` | No | The replacement event title. An empty string removes the title. |
| `started_at` | `string (RFC 3339)` | No | The replacement RFC 3339 start time. |
| `at` | `string (RFC 3339)` | No | A deprecated request alias for started_at. If both fields are present, their values must match. |
| `ended_at` | `string (RFC 3339)` | No | The replacement RFC 3339 end time. It must be later than started_at. |
| `clear_ended_at` | `boolean` | No | A true value removes ended_at. |
| `is_ongoing` | `boolean` | No | Controls whether the event is ongoing. |
| `group_id` | `integer \| null` | No | The positive timeline-group ID. A null value removes the event from its current group. |
| `is_key` | `boolean` | No | Controls whether responders see this as a key incident event. |
| `type` | `string` | No | The client-defined event type. The API rejects event types reserved for generated events. |
| `icon` | `string` | No | The replacement event icon. The maximum length is 8 Unicode characters. Maximum length: 8 characters. |
| `tags` | `string[]` | No | The complete replacement list of event tags. The API normalizes each value. |

Request JSON:

```json
{
  "body": "Restarted the payment service and confirmed recovery.",
  "ended_at": "2026-08-31T12:18:00Z"
}
```

#### Success response

Status: `200 OK`

#### Response fields

Returned means the field is present when its parent object is present. A nullable field can contain null.

| Name | Type | Returned | Description |
| --- | --- | --- | --- |
| `event` | `object` | Always | The complete public record for a timeline event. |
| `event.id` | `integer` | Always | The positive numeric ID of the timeline event. |
| `event.revision` | `integer` | Always | The resource version for concurrency control. Minimum: 1. |
| `event.incident_id` | `integer` | Always | The positive numeric ID of the incident. |
| `event.started_at` | `string (RFC 3339)` | Always | The event start time. |
| `event.at` | `string (RFC 3339)` | Always | A deprecated response alias for started_at. Both fields contain the same value. |
| `event.ended_at` | `string (RFC 3339) \| null` | Always | The event end time. A null value means that the event has no end time. |
| `event.is_ongoing` | `boolean` | Always | Indicates whether the event is still in progress. |
| `event.group_id` | `integer \| null` | Always | The timeline-group ID. A null value means that the event has no group. |
| `event.is_key` | `boolean` | Always | Indicates whether responders marked this as a key incident event. |
| `event.source` | `string` | Always | The system or actor that created the event. |
| `event.source_ref` | `string` | Always | The source-specific reference for the event. |
| `event.type` | `string` | Always | The event type used for grouping and display. |
| `event.icon` | `string` | Always | The event icon. The maximum length is 8 Unicode characters. Maximum length: 8 characters. |
| `event.title` | `string` | Always | The optional display title of the event. |
| `event.body` | `string` | Always | The event description. |
| `event.collaboration_generation` | `integer` | Always | The version of collaborative content associated with the event. |
| `event.tags` | `string[]` | Always | The normalized event tags. |
| `event.ai_context` | `string` | Always | The saved AI context for a generated event. An empty string means that no context is stored. |
| `event.created_by` | `string` | Always | The identifier of the user or system that created the event. |
| `event.comment_count` | `integer` | Always | The number of comments on the event. |
| `event.attachments` | `object[]` | Always | The files attached to the event. |
| `event.attachments[].id` | `integer` | Always | The positive numeric ID of the attachment. |
| `event.attachments[].timeline_event_id` | `integer` | Always | The positive numeric ID of the timeline event. |
| `event.attachments[].filename` | `string` | Always | The original file name. |
| `event.attachments[].url` | `string` | Always | A temporary download URL. An empty string means that no URL is currently available. |
| `event.attachments[].url_available` | `boolean` | Always | Indicates whether url contains an available download URL. |
| `event.attachments[].content_type` | `string` | Always | The media type of the attached file. |
| `event.attachments[].size_bytes` | `integer` | Always | The file size in bytes. |
| `event.attachments[].created_at` | `string (RFC 3339)` | Always | The attachment creation time. |
| `event.attachments[].created_by` | `string` | Always | The identifier of the user who attached the file. |
| `event.source_window_started_at` | `string (RFC 3339) \| null` | Always | The start of the source-data window. A null value means that no window is recorded. |
| `event.source_window_ended_at` | `string (RFC 3339) \| null` | Always | The end of the source-data window. A null value means that no window is recorded. |
| `event.evidence_links` | `object[]` | Always | The evidence references associated with the event. |
| `event.evidence_links[].source` | `string` | Always | The system or provider that supplied the evidence. |
| `event.evidence_links[].url` | `string` | Always | The absolute URL of the evidence. |
| `event.evidence_links[].label` | `string` | Optional | A short display name for the evidence. |
| `event.evidence_links[].summary` | `string` | Optional | A short summary of the evidence. |
| `event.evidence_links[].artifact_id` | `string` | Optional | The identifier of a stored evidence artifact. |
| `event.evidence_links[].artifact_digest` | `string` | Optional | The digest used to make sure that the artifact content did not change. |
| `event.evidence_links[].result_bytes` | `integer` | Optional | The original evidence result size in bytes. |
| `event.evidence_links[].truncated` | `boolean` | Optional | Indicates whether the stored evidence omits part of the original result. |
| `event.operational_update` | `object \| null` | Optional | The validated operational status payload. Null or absent for ordinary and legacy timeline events. Must match exactly one of 2 schema variants. |
| `event.operational_update.current_impact` | `string` | Always | The current customer or system impact in human-authored text. Leading and trailing whitespace is removed before the Unicode length limit is enforced. Minimum length: 1 characters. Maximum length: 2000 characters. |
| `event.operational_update.current_action` | `string` | Always | The response work currently in progress. Leading and trailing whitespace is removed before the Unicode length limit is enforced. Minimum length: 1 characters. Maximum length: 2000 characters. |
| `event.operational_update.next_update_at` | `string (RFC 3339) \| null` | Always | The planned next update time as RFC 3339 |
| `event.operational_update.audience` | `string` | Always | The intended audience for the status update. Possible values: `"responders"` \| `"stakeholders"` \| `"customers"` |
| `event.operational_update.channel` | `string` | Always | Records the destination workflow. manual_external means the caller must deliver the copied message themselves. Possible values: `"incident_record"` \| `"manual_external"` |
| `event.operational_update.destination` | `string` | Always | The human-readable external destination for manual_external Maximum length: 200 characters. |
| `event.operational_update.communications_owner_user_id` | `integer \| null` | Always | The active incident participant responsible for coordinating manual delivery |
| `event.operational_update.delivery_state` | `string` | Always | The server-derived delivery state. Tellagen does not claim that a manual external message was sent. Possible values: `"recorded"` \| `"pending_manual_delivery"` |
| `deliveries` | `object[]` | Optional | The destinations recorded with a shared status update, one per destination. Only the create response of a request with share includes it. |
| `deliveries[].id` | `integer` | Always | The delivery ID. |
| `deliveries[].timeline_event_id` | `integer` | Always | The status update this delivery belongs to. |
| `deliveries[].incident_id` | `integer` | Always | The incident ID. |
| `deliveries[].kind` | `string` | Always | The provider. Possible values: `"slack"` \| `"email"` |
| `deliveries[].destination` | `string` | Always | Why the destination was chosen. Possible values: `"incident_channel"` \| `"announcement_channel"` \| `"email"` |
| `deliveries[].target` | `string` | Always | The Slack channel ID. |
| `deliveries[].target_label` | `string` | Always | The Slack channel name without #, when known; otherwise empty. |
| `deliveries[].status` | `string` | Always | queued waits for its first or next attempt; sending is being posted; sent is delivered; failed needs a person (for example, invite the app) and is retried only on request; dead stopped after the attempt limit or 2 hours; skipped was never attempted (a demo workspace or Slack not connected). Possible values: `"queued"` \| `"sending"` \| `"sent"` \| `"failed"` \| `"dead"` \| `"skipped"` \| `"awaiting_confirmation"` \| `"suppressed"` |
| `deliveries[].attempts` | `integer` | Always | The number of attempts so far. Minimum: 0. |
| `deliveries[].next_attempt_at` | `string (RFC 3339) \| null` | Always | When a queued delivery is attempted next; null in every other state. |
| `deliveries[].permalink` | `string` | Always | The Slack message link once sent; otherwise empty. |
| `deliveries[].error_code` | `string` | Always | Why the delivery is not sent, such as not_in_channel, channel_not_found, channel_archived, slack_auth, slack_not_connected, rate_limited, temporary, expired, not_configured (the announcement channel was removed in Settings before the post went out), or demo. Empty when nothing is wrong. |
| `deliveries[].error_message` | `string` | Always | The reason in words |
| `deliveries[].can_retry` | `boolean` | Always | True for a failed or dead Slack delivery. |
| `deliveries[].sent_at` | `string (RFC 3339) \| null` | Always | When the delivery was sent. |
| `deliveries[].created_at` | `string (RFC 3339)` | Always | When the update was recorded with this destination. |
| `deliveries[].updated_at` | `string (RFC 3339)` | Always | When the delivery state last changed. |

Returns the updated timeline event.

```json
{
  "event": {
    "id": 100,
    "revision": 4,
    "incident_id": 1,
    "started_at": "2026-08-31T12:10:00Z",
    "at": "2026-08-31T12:10:00Z",
    "ended_at": "2026-08-31T12:18:00Z",
    "is_ongoing": false,
    "group_id": null,
    "is_key": false,
    "source": "api",
    "source_ref": "api:123:deploy-42",
    "type": "change",
    "icon": "🔧",
    "title": "Payment service restart",
    "body": "Restarted the payment service and confirmed recovery.",
    "collaboration_generation": 0,
    "tags": [],
    "ai_context": "",
    "created_by": "api-owner@example.com",
    "comment_count": 0,
    "attachments": [
      {
        "id": 501,
        "timeline_event_id": 100,
        "filename": "restart-log.txt",
        "url": "https://files.example.com/signed/restart-log.txt",
        "url_available": true,
        "content_type": "text/plain",
        "size_bytes": 2048,
        "created_at": "2026-08-31T12:11:00Z",
        "created_by": "oncall@example.com"
      }
    ],
    "source_window_started_at": null,
    "source_window_ended_at": null,
    "evidence_links": []
  }
}
```

#### Response headers

Returned means the field is present when its parent object is present. A nullable field can contain null.

| Name | Type | Returned | Description |
| --- | --- | --- | --- |
| `Tellagen-Resource-Version` | `integer` | Always | The positive resource version for If-Tellagen-Resource-Version in the next API-key PATCH request. Minimum: 1. |

#### Endpoint error responses

| Status | Meaning |
| --- | --- |
| `400` | The API rejected the request because its syntax or one of its values is invalid. |
| `401` | The request does not contain valid authentication. |
| `403` | Authentication succeeded, but the caller lacks the required API scope or workspace permission. |
| `404` | The requested resource does not exist or is not available to the caller. |
| `409` | The request conflicts with the current resource state. idempotency_key_reused means that the key belongs to different request content. idempotency_in_progress means that an identical request is still in progress. idempotency_outcome_unknown means that an earlier request may have completed, but its result could not be stored and must not be retried automatically. |
| `412` | The resource changed after the client obtained its If-Tellagen-Resource-Version value. |
| `428` | An API-key PATCH request did not include the required If-Tellagen-Resource-Version header. |
| `429` | The request exceeded an applicable sliding limit. Free API keys allow 20 requests per second and 600 requests per 60 seconds. Team API keys allow 100 requests per second and 3,000 requests per 60 seconds. Browser users allow 100 requests per second. Browser company allows 500 requests per second and 5,000 requests per 10 seconds. Browser company means the sum of user traffic for one company. Anonymous client IPs allow 20 requests per second and 200 requests per 10 seconds. The rate limiter does not count requests that it rejects. |
| `500` | An internal server error prevented completion of the request. |

### `DELETE /api/v1/timeline/{id}`

**Delete timeline event.** Deletes the event and its attachment records. The API removes stored attachment objects asynchronously. Findings promoted from the event return to draft. A repeated request returns 404. Concurrent attachment changes return 409. The request is safe to retry after this conflict.

Required scope: `incidents:write`

Operation ID: `deleteTimelineEvent`
Contract: `supported`
Retry guidance: If the result is unknown, read the resource before another attempt. The error details explain the next step.
Prerequisites: Active responder seat, Incident is not archived

#### Path parameters

Required nested fields apply when their parent object is present. Optional does not imply nullable.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `integer` | Yes | A positive numeric timeline-event ID returned by this API. Minimum: 1. |

#### Example request

Replace `{company}`, `<token>`, and any other placeholders with your values.

```bash
curl --request DELETE \
  --url 'https://{company}.api.tellagen.com/api/v1/timeline/{id}' \
  --header 'Authorization: Bearer <token>'
```

#### Success response

Status: `204 No Content`

No response body.

#### Endpoint error responses

| Status | Meaning |
| --- | --- |
| `400` | The API rejected the request because its syntax or one of its values is invalid. |
| `401` | The request does not contain valid authentication. |
| `403` | Authentication succeeded, but the caller lacks the required API scope or workspace permission. |
| `404` | The requested resource does not exist or is not available to the caller. |
| `409` | The request conflicts with the current resource state. idempotency_key_reused means that the key belongs to different request content. idempotency_in_progress means that an identical request is still in progress. idempotency_outcome_unknown means that an earlier request may have completed, but its result could not be stored and must not be retried automatically. |
| `429` | The request exceeded an applicable sliding limit. Free API keys allow 20 requests per second and 600 requests per 60 seconds. Team API keys allow 100 requests per second and 3,000 requests per 60 seconds. Browser users allow 100 requests per second. Browser company allows 500 requests per second and 5,000 requests per 10 seconds. Browser company means the sum of user traffic for one company. Anonymous client IPs allow 20 requests per second and 200 requests per 10 seconds. The rate limiter does not count requests that it rejects. |
| `500` | An internal server error prevented completion of the request. |

### `GET /api/v1/timeline/{id}/deliveries`

**List status update deliveries.** Returns where a status update was delivered, one row per destination, with its live state. A timeline event that was recorded without share returns an empty list.

This API is in preview while email delivery is added.


Required scope: `incidents:read`

Operation ID: `listStatusUpdateDeliveries`
Contract: `preview`
Retry guidance: You can repeat this request. If the API returns 429, wait for Retry-After. Set a maximum number of attempts.
Prerequisites: None

#### Path parameters

Required nested fields apply when their parent object is present. Optional does not imply nullable.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `integer` | Yes | A positive numeric timeline-event ID returned by this API. Minimum: 1. |

#### Example request

Replace `{company}`, `<token>`, and any other placeholders with your values.

```bash
curl --request GET \
  --url 'https://{company}.api.tellagen.com/api/v1/timeline/{id}/deliveries' \
  --header 'Authorization: Bearer <token>'
```

#### Success response

Status: `200 OK`

#### Response fields

Returned means the field is present when its parent object is present. A nullable field can contain null.

| Name | Type | Returned | Description |
| --- | --- | --- | --- |
| `timeline_event_id` | `integer` | Always | The status update's timeline event ID. |
| `incident_id` | `integer` | Always | The incident ID. |
| `deliveries` | `object[]` | Always | One row per destination, incident channel first. Empty when the update was recorded without sharing. |
| `deliveries[].id` | `integer` | Always | The delivery ID. |
| `deliveries[].timeline_event_id` | `integer` | Always | The status update this delivery belongs to. |
| `deliveries[].incident_id` | `integer` | Always | The incident ID. |
| `deliveries[].kind` | `string` | Always | The provider. Possible values: `"slack"` \| `"email"` |
| `deliveries[].destination` | `string` | Always | Why the destination was chosen. Possible values: `"incident_channel"` \| `"announcement_channel"` \| `"email"` |
| `deliveries[].target` | `string` | Always | The Slack channel ID. |
| `deliveries[].target_label` | `string` | Always | The Slack channel name without #, when known; otherwise empty. |
| `deliveries[].status` | `string` | Always | queued waits for its first or next attempt; sending is being posted; sent is delivered; failed needs a person (for example, invite the app) and is retried only on request; dead stopped after the attempt limit or 2 hours; skipped was never attempted (a demo workspace or Slack not connected). Possible values: `"queued"` \| `"sending"` \| `"sent"` \| `"failed"` \| `"dead"` \| `"skipped"` \| `"awaiting_confirmation"` \| `"suppressed"` |
| `deliveries[].attempts` | `integer` | Always | The number of attempts so far. Minimum: 0. |
| `deliveries[].next_attempt_at` | `string (RFC 3339) \| null` | Always | When a queued delivery is attempted next; null in every other state. |
| `deliveries[].permalink` | `string` | Always | The Slack message link once sent; otherwise empty. |
| `deliveries[].error_code` | `string` | Always | Why the delivery is not sent, such as not_in_channel, channel_not_found, channel_archived, slack_auth, slack_not_connected, rate_limited, temporary, expired, not_configured (the announcement channel was removed in Settings before the post went out), or demo. Empty when nothing is wrong. |
| `deliveries[].error_message` | `string` | Always | The reason in words |
| `deliveries[].can_retry` | `boolean` | Always | True for a failed or dead Slack delivery. |
| `deliveries[].sent_at` | `string (RFC 3339) \| null` | Always | When the delivery was sent. |
| `deliveries[].created_at` | `string (RFC 3339)` | Always | When the update was recorded with this destination. |
| `deliveries[].updated_at` | `string (RFC 3339)` | Always | When the delivery state last changed. |

Returns the status update's deliveries.

```json
{
  "timeline_event_id": 100,
  "incident_id": 1,
  "deliveries": [
    {
      "id": 7,
      "timeline_event_id": 100,
      "incident_id": 1,
      "kind": "slack",
      "destination": "incident_channel",
      "target": "C0123ABCD",
      "target_label": "inc-1516",
      "status": "sent",
      "attempts": 1,
      "next_attempt_at": null,
      "permalink": "https://example.slack.com/archives/C0123ABCD/p1788134400000100",
      "error_code": "",
      "error_message": "",
      "can_retry": false,
      "sent_at": "2026-08-31T12:10:04Z",
      "created_at": "2026-08-31T12:10:00Z",
      "updated_at": "2026-08-31T12:10:04Z"
    }
  ]
}
```

#### Endpoint error responses

| Status | Meaning |
| --- | --- |
| `400` | The API rejected the request because its syntax or one of its values is invalid. |
| `401` | The request does not contain valid authentication. |
| `403` | Authentication succeeded, but the caller lacks the required API scope or workspace permission. |
| `404` | The requested resource does not exist or is not available to the caller. |
| `409` | The request conflicts with the current resource state. idempotency_key_reused means that the key belongs to different request content. idempotency_in_progress means that an identical request is still in progress. idempotency_outcome_unknown means that an earlier request may have completed, but its result could not be stored and must not be retried automatically. |
| `429` | The request exceeded an applicable sliding limit. Free API keys allow 20 requests per second and 600 requests per 60 seconds. Team API keys allow 100 requests per second and 3,000 requests per 60 seconds. Browser users allow 100 requests per second. Browser company allows 500 requests per second and 5,000 requests per 10 seconds. Browser company means the sum of user traffic for one company. Anonymous client IPs allow 20 requests per second and 200 requests per 10 seconds. The rate limiter does not count requests that it rejects. |
| `500` | An internal server error prevented completion of the request. |

### `POST /api/v1/timeline/{id}/deliveries/{deliveryId}/retry`

**Retry a status update delivery.** Queues a failed or stopped Slack delivery again with a fresh attempt budget and a new 2-hour window. Any other state returns 409 with status_update_delivery_not_retryable. A post to an announcement channel that was removed from Settings → Status updates returns 400 with status_update_channel_not_configured. The workspace must have status update delivery turned on.

This API is in preview while email delivery is added.


Required scope: `incidents:write`

Operation ID: `retryStatusUpdateDelivery`
Contract: `preview`
Retry guidance: If the result is unknown, read the resource before another attempt. The error details explain the next step.
Prerequisites: Active responder seat, Incident is not archived

#### Path parameters

Required nested fields apply when their parent object is present. Optional does not imply nullable.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `integer` | Yes | A positive numeric timeline-event ID returned by this API. Minimum: 1. |
| `deliveryId` | `integer` | Yes | A positive numeric status update delivery ID returned by this API. Minimum: 1. |

#### Example request

Replace `{company}`, `<token>`, and any other placeholders with your values.

```bash
curl --request POST \
  --url 'https://{company}.api.tellagen.com/api/v1/timeline/{id}/deliveries/{deliveryId}/retry' \
  --header 'Authorization: Bearer <token>'
```

#### Success response

Status: `200 OK`

#### Response fields

Returned means the field is present when its parent object is present. A nullable field can contain null.

| Name | Type | Returned | Description |
| --- | --- | --- | --- |
| `delivery` | `object` | Always | One destination of a recorded status update and its delivery state. A delivery that cannot be sent within 2 hours, or after 6 attempts, stops (dead). |
| `delivery.id` | `integer` | Always | The delivery ID. |
| `delivery.timeline_event_id` | `integer` | Always | The status update this delivery belongs to. |
| `delivery.incident_id` | `integer` | Always | The incident ID. |
| `delivery.kind` | `string` | Always | The provider. Possible values: `"slack"` \| `"email"` |
| `delivery.destination` | `string` | Always | Why the destination was chosen. Possible values: `"incident_channel"` \| `"announcement_channel"` \| `"email"` |
| `delivery.target` | `string` | Always | The Slack channel ID. |
| `delivery.target_label` | `string` | Always | The Slack channel name without #, when known; otherwise empty. |
| `delivery.status` | `string` | Always | queued waits for its first or next attempt; sending is being posted; sent is delivered; failed needs a person (for example, invite the app) and is retried only on request; dead stopped after the attempt limit or 2 hours; skipped was never attempted (a demo workspace or Slack not connected). Possible values: `"queued"` \| `"sending"` \| `"sent"` \| `"failed"` \| `"dead"` \| `"skipped"` \| `"awaiting_confirmation"` \| `"suppressed"` |
| `delivery.attempts` | `integer` | Always | The number of attempts so far. Minimum: 0. |
| `delivery.next_attempt_at` | `string (RFC 3339) \| null` | Always | When a queued delivery is attempted next; null in every other state. |
| `delivery.permalink` | `string` | Always | The Slack message link once sent; otherwise empty. |
| `delivery.error_code` | `string` | Always | Why the delivery is not sent, such as not_in_channel, channel_not_found, channel_archived, slack_auth, slack_not_connected, rate_limited, temporary, expired, not_configured (the announcement channel was removed in Settings before the post went out), or demo. Empty when nothing is wrong. |
| `delivery.error_message` | `string` | Always | The reason in words |
| `delivery.can_retry` | `boolean` | Always | True for a failed or dead Slack delivery. |
| `delivery.sent_at` | `string (RFC 3339) \| null` | Always | When the delivery was sent. |
| `delivery.created_at` | `string (RFC 3339)` | Always | When the update was recorded with this destination. |
| `delivery.updated_at` | `string (RFC 3339)` | Always | When the delivery state last changed. |

Returns the queued delivery.

```json
{
  "delivery": {
    "id": 7,
    "timeline_event_id": 100,
    "incident_id": 1,
    "kind": "slack",
    "destination": "incident_channel",
    "target": "C0123ABCD",
    "target_label": "inc-1516",
    "status": "queued",
    "attempts": 0,
    "next_attempt_at": "2026-08-31T12:20:00Z",
    "permalink": "",
    "error_code": "",
    "error_message": "",
    "can_retry": false,
    "sent_at": null,
    "created_at": "2026-08-31T12:10:00Z",
    "updated_at": "2026-08-31T12:20:00Z"
  }
}
```

#### Endpoint error responses

| Status | Meaning |
| --- | --- |
| `400` | The API rejected the request because its syntax or one of its values is invalid. |
| `401` | The request does not contain valid authentication. |
| `403` | Authentication succeeded, but the caller lacks the required API scope or workspace permission. |
| `404` | The requested resource does not exist or is not available to the caller. |
| `409` | The request conflicts with the current resource state. idempotency_key_reused means that the key belongs to different request content. idempotency_in_progress means that an identical request is still in progress. idempotency_outcome_unknown means that an earlier request may have completed, but its result could not be stored and must not be retried automatically. |
| `429` | The request exceeded an applicable sliding limit. Free API keys allow 20 requests per second and 600 requests per 60 seconds. Team API keys allow 100 requests per second and 3,000 requests per 60 seconds. Browser users allow 100 requests per second. Browser company allows 500 requests per second and 5,000 requests per 10 seconds. Browser company means the sum of user traffic for one company. Anonymous client IPs allow 20 requests per second and 200 requests per 10 seconds. The rate limiter does not count requests that it rejects. |
| `500` | An internal server error prevented completion of the request. |
