# 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

### Services

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

- [`GET /api/v1/services`](https://tellagen.com/api-reference/services#services-endpoint-get-api-v1-services) — List services
- [`POST /api/v1/services`](https://tellagen.com/api-reference/services#services-endpoint-post-api-v1-services) — Create service
- [`GET /api/v1/services/{id}`](https://tellagen.com/api-reference/services#services-endpoint-get-api-v1-services-id) — Get service
- [`PATCH /api/v1/services/{id}`](https://tellagen.com/api-reference/services#services-endpoint-patch-api-v1-services-id) — Update service
- [`DELETE /api/v1/services/{id}`](https://tellagen.com/api-reference/services#services-endpoint-delete-api-v1-services-id) — Archive service
- [`POST /api/v1/services/{id}/restore`](https://tellagen.com/api-reference/services#services-endpoint-post-api-v1-services-id-restore) — Restore service

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

## Services

Manages the service catalog. Create, update, archive, and restore operations require the manage_settings permission. Archived services remain available by ID and retain incident references. They are read-only and absent from default lists.

### `GET /api/v1/services`

**List services.** Returns one page sorted by configured tier rank, normalized service name, and service ID. The first page includes up to 100 historical service values from incidents that do not match a catalog service. These values are sorted by incident count. Later pages return an empty unmanaged_references array. If more than 100 unmatched values exist, unmanaged_references_truncated is true.

Required scope: `services:read`

Operation ID: `listServices`
Contract: `supported`
Retry guidance: This operation is repeat-safe. Respect Retry-After and use bounded retries.
Prerequisites: None

#### 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. |
| `include_archived` | `boolean` | No | Controls whether the response includes archived services. The default is false. Default: false. |

#### Example request

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

```bash
curl --request GET \
  --url 'https://{company}.api.tellagen.com/api/v1/services' \
  --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 |
| --- | --- | --- | --- |
| `services` | `object[]` | Always | The services in this page. |
| `services[].id` | `integer` | Always | The positive numeric ID of the service. |
| `services[].revision` | `integer` | Always | The resource version for concurrency control. Minimum: 1. |
| `services[].company_id` | `integer` | Always | The ID of the workspace that owns the service. |
| `services[].name` | `string` | Always | The service name after whitespace is removed from both ends. The length is measured in Unicode code points. Minimum length: 1 characters. Maximum length: 128 characters. |
| `services[].slug` | `string` | Always | A permanent lowercase service key. It starts with a Unicode letter and then contains lowercase Unicode letters, digits, or underscores. The maximum size is 64 UTF-8 bytes. |
| `services[].description` | `string` | Optional | The workspace description of the service. |
| `services[].owner_team_id` | `integer` | Optional | The positive numeric ID of the owning team. A service without an owning team omits this field. |
| `services[].owner_team_name` | `string` | Optional | The display name of the owning team. A service without an owning team omits this field. |
| `services[].owner_team_slug` | `string` | Optional | The permanent key of the owning team. A service without an owning team omits this field. |
| `services[].tier` | `string` | Always | The service-tier key from the workspace configuration. |
| `services[].tags` | `string[]` | Always | The normalized service tags. |
| `services[].external_id` | `string` | Optional | The service identifier from an external system. |
| `services[].observability_names` | `string[]` | Optional | Alternative service names for telemetry systems. The API trims values and removes case-insensitive duplicates. |
| `services[].created_at` | `string (RFC 3339)` | Always | The service creation time. |
| `services[].updated_at` | `string (RFC 3339)` | Always | The time of the latest service change. |
| `services[].archived_at` | `string (RFC 3339)` | Optional | The service archive time. This field is absent for an active service. |
| `unmanaged_references` | `object[]` | Always | Up to 100 historical incident service values that do not match a catalog service. Later pages return an empty array. Maximum items: 100. |
| `unmanaged_references[].value` | `string` | Always | The unmatched service value stored on incidents. |
| `unmanaged_references[].incident_count` | `integer` | Always | The number of incidents that contain this value. Minimum: 1. |
| `unmanaged_references_truncated` | `boolean` | Always | Indicates whether more than 100 unmatched values exist. This value is false on later pages. |
| `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 services, unmanaged incident references, and pagination fields.

```json
{
  "services": [
    {
      "id": 5,
      "revision": 3,
      "company_id": 12,
      "name": "Payment Service",
      "slug": "payment_service",
      "description": "Handles all payment processing",
      "owner_team_id": 42,
      "owner_team_name": "Platform Team",
      "owner_team_slug": "platform_team",
      "tier": "critical",
      "tags": [
        "critical",
        "payments"
      ],
      "external_id": "payments-prod",
      "observability_names": [
        "payments-api",
        "payments-worker"
      ],
      "created_at": "2025-01-15T10:00:00Z",
      "updated_at": "2025-01-15T10:00:00Z"
    }
  ],
  "unmanaged_references": [
    {
      "value": "Legacy Billing",
      "incident_count": 3
    }
  ],
  "unmanaged_references_truncated": false,
  "pagination": {
    "limit": 50,
    "has_more": true,
    "next_cursor": "opaque-server-cursor"
  }
}
```

#### 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/services`

**Create service.** Creates a service. If slug is omitted, the API creates a lowercase slug with underscores. A supplied tier must match a key in the workspace tier configuration. If tier is omitted, the API uses the default tier.

Required scope: `services:write`

Operation ID: `createService`
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, Manage workspace settings

#### 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/services' \
  --header 'Authorization: Bearer <token>' \
  --header 'Idempotency-Key: <idempotency-key>' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "Payment Service",
  "tier": "critical",
  "tags": [
    "critical",
    "payments"
  ],
  "external_id": "payments-prod",
  "observability_names": [
    "payments-api",
    "payments-worker"
  ]
}'
```

#### Request body

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

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | Yes | The service name. The length must be from 1 through 128 Unicode characters. Minimum length: 1 characters. Maximum length: 128 characters. |
| `slug` | `string` | No | A permanent lowercase service key. It must start with a Unicode letter and use at most 64 UTF-8 bytes. Later characters can be lowercase Unicode letters, digits, or underscores. |
| `description` | `string` | No | The workspace description of the service. |
| `owner_team_id` | `integer` | No | The positive ID of an active owning team. Minimum: 1. |
| `tier` | `string` | No | A service-tier key from the workspace configuration. If tier is omitted, the API uses the configured default tier. |
| `tags` | `string[]` | No | The service tags. The API trims each value and removes blank values. |
| `external_id` | `string` | No | The service identifier from an external system. The API removes whitespace from both ends. |
| `observability_names` | `string[]` | No | Alternative service names for telemetry systems. The API trims values and removes case-insensitive duplicates. |

Request JSON:

```json
{
  "name": "Payment Service",
  "tier": "critical",
  "tags": [
    "critical",
    "payments"
  ],
  "external_id": "payments-prod",
  "observability_names": [
    "payments-api",
    "payments-worker"
  ]
}
```

#### 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 |
| --- | --- | --- | --- |
| `service` | `object` | Always | The complete public record for a service. |
| `service.id` | `integer` | Always | The positive numeric ID of the service. |
| `service.revision` | `integer` | Always | The resource version for concurrency control. Minimum: 1. |
| `service.company_id` | `integer` | Always | The ID of the workspace that owns the service. |
| `service.name` | `string` | Always | The service name after whitespace is removed from both ends. The length is measured in Unicode code points. Minimum length: 1 characters. Maximum length: 128 characters. |
| `service.slug` | `string` | Always | A permanent lowercase service key. It starts with a Unicode letter and then contains lowercase Unicode letters, digits, or underscores. The maximum size is 64 UTF-8 bytes. |
| `service.description` | `string` | Optional | The workspace description of the service. |
| `service.owner_team_id` | `integer` | Optional | The positive numeric ID of the owning team. A service without an owning team omits this field. |
| `service.owner_team_name` | `string` | Optional | The display name of the owning team. A service without an owning team omits this field. |
| `service.owner_team_slug` | `string` | Optional | The permanent key of the owning team. A service without an owning team omits this field. |
| `service.tier` | `string` | Always | The service-tier key from the workspace configuration. |
| `service.tags` | `string[]` | Always | The normalized service tags. |
| `service.external_id` | `string` | Optional | The service identifier from an external system. |
| `service.observability_names` | `string[]` | Optional | Alternative service names for telemetry systems. The API trims values and removes case-insensitive duplicates. |
| `service.created_at` | `string (RFC 3339)` | Always | The service creation time. |
| `service.updated_at` | `string (RFC 3339)` | Always | The time of the latest service change. |
| `service.archived_at` | `string (RFC 3339)` | Optional | The service archive time. This field is absent for an active service. |

Returns the created service.

```json
{
  "service": {
    "id": 5,
    "revision": 3,
    "company_id": 12,
    "name": "Payment Service",
    "slug": "payment_service",
    "description": "Handles all payment processing",
    "owner_team_id": 42,
    "owner_team_name": "Platform Team",
    "owner_team_slug": "platform_team",
    "tier": "critical",
    "tags": [
      "critical",
      "payments"
    ],
    "external_id": "payments-prod",
    "observability_names": [
      "payments-api",
      "payments-worker"
    ],
    "created_at": "2025-01-15T10:00:00Z",
    "updated_at": "2025-01-15T10:00:00Z"
  }
}
```

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

#### Related operations

- [List available service tiers](https://tellagen.com/api-reference/configuration#configuration-endpoint-get-api-v1-configuration-service-tiers) (`GET /api/v1/configuration/service-tiers`)

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

**Get service.** Returns one service. The response contains the same service fields as the list operation. A service remains available by ID after it is archived.

Required scope: `services:read`

Operation ID: `getService`
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 service 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/services/{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 |
| --- | --- | --- | --- |
| `service` | `object` | Always | The complete public record for a service. |
| `service.id` | `integer` | Always | The positive numeric ID of the service. |
| `service.revision` | `integer` | Always | The resource version for concurrency control. Minimum: 1. |
| `service.company_id` | `integer` | Always | The ID of the workspace that owns the service. |
| `service.name` | `string` | Always | The service name after whitespace is removed from both ends. The length is measured in Unicode code points. Minimum length: 1 characters. Maximum length: 128 characters. |
| `service.slug` | `string` | Always | A permanent lowercase service key. It starts with a Unicode letter and then contains lowercase Unicode letters, digits, or underscores. The maximum size is 64 UTF-8 bytes. |
| `service.description` | `string` | Optional | The workspace description of the service. |
| `service.owner_team_id` | `integer` | Optional | The positive numeric ID of the owning team. A service without an owning team omits this field. |
| `service.owner_team_name` | `string` | Optional | The display name of the owning team. A service without an owning team omits this field. |
| `service.owner_team_slug` | `string` | Optional | The permanent key of the owning team. A service without an owning team omits this field. |
| `service.tier` | `string` | Always | The service-tier key from the workspace configuration. |
| `service.tags` | `string[]` | Always | The normalized service tags. |
| `service.external_id` | `string` | Optional | The service identifier from an external system. |
| `service.observability_names` | `string[]` | Optional | Alternative service names for telemetry systems. The API trims values and removes case-insensitive duplicates. |
| `service.created_at` | `string (RFC 3339)` | Always | The service creation time. |
| `service.updated_at` | `string (RFC 3339)` | Always | The time of the latest service change. |
| `service.archived_at` | `string (RFC 3339)` | Optional | The service archive time. This field is absent for an active service. |

Returns the service.

```json
{
  "service": {
    "id": 5,
    "revision": 3,
    "company_id": 12,
    "name": "Payment Service",
    "slug": "payment_service",
    "description": "Handles all payment processing",
    "owner_team_id": 42,
    "owner_team_name": "Platform Team",
    "owner_team_slug": "platform_team",
    "tier": "critical",
    "tags": [
      "critical",
      "payments"
    ],
    "external_id": "payments-prod",
    "observability_names": [
      "payments-api",
      "payments-worker"
    ],
    "created_at": "2025-01-15T10:00:00Z",
    "updated_at": "2025-01-15T10:00:00Z"
  }
}
```

#### 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/services/{id}`

**Update service.** Updates an active service. If a field is omitted, its current value remains unchanged. JSON null clears description, owner_team_id, or external_id. An empty tags or observability_names array removes all values from that field. The slug cannot change. An archived service returns 409.

Required scope: `services:write`

Operation ID: `updateService`
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, Manage workspace settings

#### 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 service 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/services/{id}' \
  --header 'Authorization: Bearer <token>' \
  --header 'If-Tellagen-Resource-Version: <resource-version>' \
  --header 'Content-Type: application/json' \
  --data '{
  "tier": "important",
  "tags": [],
  "external_id": null,
  "observability_names": [
    "payments-v2"
  ]
}'
```

#### Request body

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

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | No | The service name. The length must be from 1 through 128 Unicode characters. Minimum length: 1 characters. Maximum length: 128 characters. |
| `description` | `string \| null` | No | The replacement service description. A null value removes the description. |
| `owner_team_id` | `integer \| null` | No | The positive ID of an active owning team. A null value removes team ownership. Minimum: 1. |
| `tier` | `string` | No | A service-tier key from the workspace configuration. |
| `tags` | `string[]` | No | The complete replacement list of tags. An empty array removes all tags. |
| `external_id` | `string \| null` | No | The replacement external identifier. A null or blank value removes the identifier. |
| `observability_names` | `string[]` | No | The complete replacement list of telemetry names. An empty array removes all telemetry names. |

Request JSON:

```json
{
  "tier": "important",
  "tags": [],
  "external_id": null,
  "observability_names": [
    "payments-v2"
  ]
}
```

#### 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 |
| --- | --- | --- | --- |
| `service` | `object` | Always | The complete public record for a service. |
| `service.id` | `integer` | Always | The positive numeric ID of the service. |
| `service.revision` | `integer` | Always | The resource version for concurrency control. Minimum: 1. |
| `service.company_id` | `integer` | Always | The ID of the workspace that owns the service. |
| `service.name` | `string` | Always | The service name after whitespace is removed from both ends. The length is measured in Unicode code points. Minimum length: 1 characters. Maximum length: 128 characters. |
| `service.slug` | `string` | Always | A permanent lowercase service key. It starts with a Unicode letter and then contains lowercase Unicode letters, digits, or underscores. The maximum size is 64 UTF-8 bytes. |
| `service.description` | `string` | Optional | The workspace description of the service. |
| `service.owner_team_id` | `integer` | Optional | The positive numeric ID of the owning team. A service without an owning team omits this field. |
| `service.owner_team_name` | `string` | Optional | The display name of the owning team. A service without an owning team omits this field. |
| `service.owner_team_slug` | `string` | Optional | The permanent key of the owning team. A service without an owning team omits this field. |
| `service.tier` | `string` | Always | The service-tier key from the workspace configuration. |
| `service.tags` | `string[]` | Always | The normalized service tags. |
| `service.external_id` | `string` | Optional | The service identifier from an external system. |
| `service.observability_names` | `string[]` | Optional | Alternative service names for telemetry systems. The API trims values and removes case-insensitive duplicates. |
| `service.created_at` | `string (RFC 3339)` | Always | The service creation time. |
| `service.updated_at` | `string (RFC 3339)` | Always | The time of the latest service change. |
| `service.archived_at` | `string (RFC 3339)` | Optional | The service archive time. This field is absent for an active service. |

Returns the updated service.

```json
{
  "service": {
    "id": 5,
    "revision": 3,
    "company_id": 12,
    "name": "Payment Service",
    "slug": "payment_service",
    "description": "Handles all payment processing",
    "owner_team_id": 42,
    "owner_team_name": "Platform Team",
    "owner_team_slug": "platform_team",
    "tier": "important",
    "tags": [],
    "external_id": "payments-prod",
    "observability_names": [
      "payments-v2"
    ],
    "created_at": "2025-01-15T10:00:00Z",
    "updated_at": "2025-01-30T12:00:00Z"
  }
}
```

#### 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/services/{id}`

**Archive service.** Archives a service. A repeated request returns 204 without changing the archive time or resource version. By default, list operations exclude archived services. The include_archived=true parameter includes them. Archived services remain available by ID and retain incident references. They cannot be updated.

Required scope: `services:write`

Operation ID: `archiveService`
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, Manage workspace settings

#### 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 service 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/services/{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. |

### `POST /api/v1/services/{id}/restore`

**Restore service.** Restores an archived service and returns the active service. A repeated request returns the active service without changing its resource version. If the service has owner_team_id, that team must be active. Otherwise, the API returns 409 invalid_relationship_state.

Required scope: `services:write`

Operation ID: `restoreService`
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, Manage workspace settings

#### 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 service 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/services/{id}/restore' \
  --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 |
| --- | --- | --- | --- |
| `service` | `object` | Always | The complete public record for a service. |
| `service.id` | `integer` | Always | The positive numeric ID of the service. |
| `service.revision` | `integer` | Always | The resource version for concurrency control. Minimum: 1. |
| `service.company_id` | `integer` | Always | The ID of the workspace that owns the service. |
| `service.name` | `string` | Always | The service name after whitespace is removed from both ends. The length is measured in Unicode code points. Minimum length: 1 characters. Maximum length: 128 characters. |
| `service.slug` | `string` | Always | A permanent lowercase service key. It starts with a Unicode letter and then contains lowercase Unicode letters, digits, or underscores. The maximum size is 64 UTF-8 bytes. |
| `service.description` | `string` | Optional | The workspace description of the service. |
| `service.owner_team_id` | `integer` | Optional | The positive numeric ID of the owning team. A service without an owning team omits this field. |
| `service.owner_team_name` | `string` | Optional | The display name of the owning team. A service without an owning team omits this field. |
| `service.owner_team_slug` | `string` | Optional | The permanent key of the owning team. A service without an owning team omits this field. |
| `service.tier` | `string` | Always | The service-tier key from the workspace configuration. |
| `service.tags` | `string[]` | Always | The normalized service tags. |
| `service.external_id` | `string` | Optional | The service identifier from an external system. |
| `service.observability_names` | `string[]` | Optional | Alternative service names for telemetry systems. The API trims values and removes case-insensitive duplicates. |
| `service.created_at` | `string (RFC 3339)` | Always | The service creation time. |
| `service.updated_at` | `string (RFC 3339)` | Always | The time of the latest service change. |
| `service.archived_at` | `string (RFC 3339)` | Optional | The service archive time. This field is absent for an active service. |

Returns the complete active service.

```json
{
  "service": {
    "id": 5,
    "revision": 3,
    "company_id": 12,
    "name": "Payment Service",
    "slug": "payment_service",
    "description": "Handles all payment processing",
    "owner_team_id": 42,
    "owner_team_name": "Platform Team",
    "owner_team_slug": "platform_team",
    "tier": "critical",
    "tags": [
      "critical",
      "payments"
    ],
    "external_id": "payments-prod",
    "observability_names": [
      "payments-api",
      "payments-worker"
    ],
    "created_at": "2025-01-15T10:00:00Z",
    "updated_at": "2025-01-15T10:00:00Z"
  }
}
```

#### 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: `invalid_relationship_state`

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