# Tellagen API Reference

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

Documentation contract: `sha256:898ee484330817a84c170e0e87bf24f27e62e5838635ee4c9daf6a7a3d704ce9`. 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

## 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)
- [Build reliable requests](https://tellagen.com/api-reference#reliable-requests)
- [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)

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

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

Creates, lists, reads, updates, and removes events from incident timelines.

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

**List timeline events.** Returns one bounded page ordered by (started_at, id), ascending by default. Descending order reverses both fields. Dimensions combine with AND; selected tags and groups each combine with OR. matched_count describes the complete active predicate before cursor/limit. filter_counts includes suggestions and applies only time bounds. latest_started_at is incident-global, independent of all filters. Time bounds use [started_from, started_to). New opaque v2 cursors bind the incident, order and canonical filters; reuse with a different scope returns 400. Legacy timestamp/ID cursors are accepted only with ascending, time-only filters and suggestions included. Pagination is keyset traversal, not a cross-request snapshot; concurrent edits can move rows.

Required scope: `incidents:read`

Operation ID: `listTimelineEvents`
Contract: `supported`
Retry guidance: This operation is repeat-safe. Respect Retry-After and use bounded retries.
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. |
| `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. |
| `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,
    "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 workspace exceeded an active request limit. |
| `500` | An internal server error prevented completion of the request. |

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

**Create timeline event.** Creates a timeline event. 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: Retry an identical request with the same Idempotency-Key used on the first attempt, within 24 hours. Never retry an unknown outcome automatically.
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. |

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

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 workspace exceeded an active request limit. |
| `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: This operation is repeat-safe. Respect Retry-After and use bounded retries.
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. |

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 workspace exceeded an active request limit. |
| `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. If the request changes no value, the API returns 400 without side effects.

Required scope: `incidents:write`

Operation ID: `updateTimelineEvent`
Contract: `supported`
Retry guidance: Read the current resource and reconcile changes before retrying with its current version. A stale version returns 412.
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. |

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 workspace exceeded an active request limit. |
| `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: Do not retry automatically after an uncertain outcome. Read the resource state and follow this operation’s recovery guidance.
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 workspace exceeded an active request limit. |
| `500` | An internal server error prevented completion of the request. |
