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

# API 응답 형식과 JSON 응답 예시

> 텔레메트리 목록, 커서 페이지, 실시간 이벤트, 변경 요청, 성공 상태, 오류 상태에 대한 LogBrew API 응답을 확인하세요.

이 페이지는 [텔레메트리 API](/ko/reference/telemetry-api)의 응답 유형마다 예시를 하나씩 보여 줍니다. 모든 값은 가상의 값입니다. 오류 코드와 복구 방법은 [API 오류](/ko/reference/api-errors)를 참고하세요.

## 응답 읽기

모든 응답은 다음 순서로 읽으세요.

1. HTTP 상태를 읽으세요.
2. 본문이 해당 라우트의 형식인지 확인하세요.
3. 각 `project_id`가 요청한 프로젝트 범위 안에 있는지 확인하세요.
4. 표준 enum 값과 식별자는 바꾸지 마세요.
5. `next_action`은 클라이언트가 그 `code`와 `target`을 지원할 때만 따르세요.
6. `null`, 생략된 필드, 빈 목록은 서로 다른 상태로 다루세요.

`200 OK`에 빈 목록이 들어 있을 수 있습니다. 로그 목록이 비어 있으면 요청과 일치하는 로그가 없다는 뜻입니다. 이슈, 트레이스, 액션도 비어 있다는 뜻은 아닙니다. 타임스탬프가 가깝다는 이유로 시그널을 연결하지 마세요. 트레이스는 응답에 정확한 `trace_id`가 있을 때만 따라가세요.

`404`와 빈 목록은 서로 다른 결과입니다. 목록 라우트는 일치하는 레코드가 없으면 `[]`를 반환합니다. 상세 라우트는 읽을 수 있는 레코드를 찾지 못하면 `code: not_found`와 함께 `404`를 반환합니다. 어느 결과도 수집이 실패했다는 것을 증명하지는 않습니다.

## 보낼 값

응답의 쓸모는 보내는 텔레메트리에 달려 있습니다. 모든 엔벨로프나 SDK 설정에 다음 값을 넣으세요.

* `release`와 `environment`.
* 서비스, 액션, 메트릭의 고정된 이름.
* 로그, 스팬, 액션, 이슈가 같은 요청에 속할 때의 트레이스 컨텍스트.
* 표준 심각도 값: `info`, `warning`, `error`, `critical`.
* 디버깅에 도움이 되는 속성. 시크릿이나 불필요한 개인정보는 넣지 마세요.

[SDK 연동](/ko/guides/sdk-ingestion)과 [수집 데이터 관리하기](/ko/guides/safe-capture)을 참고하세요.

## 수집 응답

공개 SDK는 `POST /v1/events` 응답을 대신 읽습니다. 애플리케이션 코드에서 처리할 필요가 없습니다.

### SDK 배치 응답

SDK 배치가 성공하면 스트림별로 접수된 개수와 함께 `200 OK`를 반환합니다.

```json theme={null}
{
  "issues": 1,
  "logs": 2,
  "trace_spans": 3,
  "actions": 1,
  "metrics": 1,
  "already_accepted": 0,
  "context_updated": true,
  "next": "review accepted telemetry in logs, issues, traces, actions, metrics, or live feed",
  "next_action": {
    "code": "review_ingested_telemetry",
    "target": "telemetry_reads"
  }
}
```

| 필드 | 의미 |
| - | - |
| `issues`, `logs`, `trace_spans`, `actions`, `metrics` | LogBrew가 이 배치에서 접수한 이벤트입니다. |
| `already_accepted` | LogBrew가 이전에 같은 ID로 접수한 이벤트입니다. LogBrew는 이 이벤트를 다시 저장하지 않습니다. |
| `context_updated` | 배치가 릴리스나 환경 컨텍스트를 업데이트하면 `true`입니다. |

개수는 이 배치만 셉니다. LogBrew가 이미 접수한 배치를 재시도하면 모든 스트림이 `0`이고, `already_accepted`에는 이벤트 수가 들어갑니다. 재시도할 때는 원래 이벤트 ID를 유지하세요.

### 네이티브 엔벨로프 응답

`POST /api/telemetry/ingest`는 스트림별로 접수된 항목 수와 함께 `200 OK`를 반환합니다.

```json theme={null}
{
  "issues": 0,
  "logs": 1,
  "trace_spans": 0,
  "actions": 1,
  "already_accepted": 0,
  "next": "review accepted telemetry in logs, issues, traces, actions, or live feed",
  "next_action": {
    "code": "review_ingested_telemetry",
    "target": "telemetry_reads"
  }
}
```

네이티브 응답에는 `metrics`나 `context_updated` 필드가 없습니다. 이 필드를 추가하지 마세요. `already_accepted`는 LogBrew가 이 프로젝트에서 같은 `id`로 이전에 접수한 항목을 셉니다. [네이티브 엔벨로프 직접 보내기](/ko/guides/sdk-ingestion#required-envelope-fields)를 참고하세요.

### 수집 오류

`code: ingest_key_invalid`와 함께 `401`이 오면 수집 bearer 토큰이 해지되었거나, 만료되었거나, 해당 프로젝트에 유효하지 않다는 뜻입니다. `POST /v1/events`에서 bearer 토큰이 없거나 수집 키가 아니면 `code: unauthorized`와 함께 `401`을 반환합니다. 현재 프로젝트 범위의 수집 bearer 토큰을 사용하세요. 계정 bearer 토큰으로 재시도하지 마세요.

SDK 배치가 거부되면 `code: validation_failed`와 함께 `422`를 반환합니다.

```json theme={null}
{
  "error": "SDK event ids must be unique within a batch",
  "code": "validation_failed",
  "next": "send release/environment context plus issue, log, span, action, or metric events from a current LogBrew SDK",
  "next_action": {
    "code": "fix_request",
    "target": "request"
  }
}
```

다른 `error` 값으로는 `SDK event batch requires at least one event`와 `SDK event type is not supported by this ingest route`가 있습니다. SDK 버전과 전송 설정을 확인하세요. 빈 배치를 보내지 마세요. 검증을 통과시키려고 이벤트 유형이나 ID를 바꾸지 마세요.

`POST`가 아닌 메서드로 요청하면 `code: method_not_allowed`와 함께 `405`를 반환합니다. 읽을 수 없는 JSON 본문은 `code: invalid_json`을 반환합니다.

## 텔레메트리 목록 응답 형식

목록 라우트는 기본적으로 맨 JSON 배열을 반환합니다. 성공했지만 비어 있는 목록은 `[]`입니다.

`pagination=cursor`를 보내면 라우트가 엔벨로프를 반환합니다. 컬렉션 키는 시그널에 따라 정해집니다.

| 라우트 | 컬렉션 키 |
| - | - |
| `/api/logs` | `logs` |
| `/api/telemetry/issues` | `issues` |
| `/api/telemetry/issues/{issue_id}/events` | `events`, 최상위 `issue_id` 포함 |
| `/api/telemetry/actions` | `actions` |
| `/api/telemetry/metrics` | `metrics` |
| `/api/telemetry/releases` | `releases` |
| `/api/telemetry/traces` | `traces`, 최상위 `naming_quality` 포함 |

`next_cursor`는 이어서 읽는 데 필요한 필드를 담은 객체이며, 마지막 페이지에서는 `null`입니다. 행은 최신순입니다. [커서 기반 페이지네이션](/ko/reference/telemetry-api-filters#cursor-pagination)을 참고하세요.

### <span id="log-response">로그 응답</span>

`limit=1`로 요청한 로그 커서 페이지입니다.

```json theme={null}
{
  "logs": [
    {
      "id": "150d1288-bec2-49ec-a036-3f7ac55f1b2d",
      "project_id": "123e4567-e89b-12d3-a456-426614174000",
      "level": "warning",
      "severity": "warning",
      "triage": {
        "effective_severity": "warning",
        "ignored": false,
        "rule_applied": false
      },
      "source": "runtime",
      "message": "checkout retry scheduled",
      "attributes": { "route": "/checkout" },
      "occurred_at": "2026-06-01T12:00:00Z",
      "environment": "production",
      "release": "api@1.2.3",
      "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
      "span_id": "00f067aa0ba902b7",
      "service_name": "checkout-api",
      "sdk_name": "@logbrew/node",
      "sdk_version": "0.1.8",
      "next_action": {
        "code": "inspect_trace",
        "target": "trace_summary"
      }
    }
  ],
  "next_cursor": {
    "time": "2026-06-01T12:00:00Z",
    "id": "150d1288-bec2-49ec-a036-3f7ac55f1b2d"
  }
}
```

로그에는 호환용 `level`과 표시용 `severity`가 있습니다. 둘 다 표준 값을 담습니다. `triage`에는 심각도 규칙의 결과가 들어갑니다. [심각도 규칙 응답](#severity-rule-responses)을 참고하세요.

로그에 트레이스 링크가 없으면 `trace_id`와 `span_id`는 `null`입니다. 이때 `next_action`은 `inspect_related_logs`이고 대상은 `telemetry_logs`입니다. 로그, 이슈, 액션은 같은 `next_action` 조합 2개를 사용합니다.

`GET /api/logs/{log_id}`는 같은 형식으로 로그 1개를 반환합니다.

### <span id="issue-response">이슈 응답</span>

`GET /api/telemetry/issues/{issue_id}`는 그룹화된 이슈 1개를 반환합니다. 목록도 같은 객체를 반환합니다. 이 예시는 기본 응답 버전 1을 사용합니다.

```json theme={null}
{
  "id": "9b2b4b3a-bd4e-4f85-a0f6-48118f037c17",
  "project_id": "123e4567-e89b-12d3-a456-426614174000",
  "fingerprint": "payment-error",
  "severity": "error",
  "triage": {
    "effective_severity": "error",
    "ignored": false,
    "rule_applied": false
  },
  "title": "PaymentError",
  "message": "payment provider timed out",
  "stack_trace": null,
  "attributes": {},
  "environment": "production",
  "release": "checkout@1.2.3",
  "service_name": "checkout-api",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "span_id": "00f067aa0ba902b7",
  "next_action": {
    "code": "inspect_trace",
    "target": "trace_summary"
  },
  "status": "unresolved",
  "occurrence_count": 2,
  "first_seen_at": "2026-07-13T07:00:00Z",
  "last_seen_at": "2026-07-13T08:00:00Z"
}
```

버전 1에서 `status`는 `unresolved`, `resolved`, `ignored` 중 하나입니다. 보관된 이슈는 `triage.ignored`가 `true`입니다. `next_action` 조합과 그 뒤에 이어지는 조회는 [이슈 조사하기](/ko/guides/investigate-an-issue)를 참고하세요.

`first_seen_at`과 `last_seen_at`은 첫 발생 건과 가장 최근 발생 건의 시각입니다. 상태가 바뀌어도 이 값은 바뀌지 않습니다.

일부 필드는 LogBrew에 데이터가 있을 때만 나타납니다.

| 필드 | 나타나는 경우 |
| - | - |
| `span_id` | 이슈에 스팬 링크가 있을 때입니다. 없으면 필드를 생략합니다. |
| `diagnostics` | 최신 발생 건에 예외 유형이나 브레드크럼이 있을 때입니다. [이슈 발생 기록](#issue-occurrence-history)을 참고하세요. |
| `symbolication` | `GET /api/telemetry/issues/{issue_id}`에서만, 이슈에 릴리스 아티팩트 식별 정보가 있을 때입니다. [JavaScript 소스맵](/ko/guides/source-maps)을 참고하세요. |

`symbolication.status`는 `resolved`, `invalid_metadata`, `artifact_not_found`, `source_not_resolved`, `unavailable` 중 하나입니다. 목록, 상태 변경, 규칙 변경은 `symbolication`을 반환하지 않습니다.

`response_version=2`를 보내면 이슈의 필드는 같고 다음 항목이 바뀝니다.

| 필드 | 버전 2 값 |
| - | - |
| `status` | `unresolved`, `resolved`, `archived` 중 하나입니다. 버전 1은 `archived`를 `ignored`로 보여 줍니다. |
| `priority` | `high`, `medium`, `low` 중 하나입니다. |
| `priority_source` | 사람이 우선순위를 정했으면 `manual`입니다. 우선순위가 실제 적용된 심각도를 따르면 `derived`입니다. |

```json theme={null}
{
  "id": "9b2b4b3a-bd4e-4f85-a0f6-48118f037c17",
  "status": "archived",
  "severity": "error",
  "priority": "low",
  "priority_source": "manual"
}
```

이 예시는 일부 필드만 보여 줍니다. 파생된 우선순위는 `error`와 `critical`이면 `high`, `warning`이면 `medium`, `info`이면 `low`입니다. 이 값은 `triage.effective_severity`를 사용하므로 이슈 심각도 규칙으로 바뀔 수 있습니다.

`response_version=3`을 보내면 이슈에 버전 2의 모든 필드와 `assignee`가 들어갑니다. 이슈에 담당자가 없으면 `assignee`는 `null`입니다.

```json theme={null}
{
  "id": "9b2b4b3a-bd4e-4f85-a0f6-48118f037c17",
  "status": "unresolved",
  "priority": "high",
  "priority_source": "derived",
  "assignee": {
    "user_id": "5d6e7f80-1a2b-4c3d-8e4f-5a6b7c8d9e0f",
    "name": "Ada Example",
    "email": "ada@example.com"
  }
}
```

| 필드 | 의미 |
| - | - |
| `assignee.user_id` | 담당자의 사용자 ID입니다. 필터나 변경 요청에서 `assignee`로 보내세요. |
| `assignee.name` | 담당자의 이름입니다. 계정에 이름이 없으면 `null`입니다. |
| `assignee.email` | 담당자의 이메일 주소입니다. |

할당된 멤버가 더 이상 프로젝트를 읽을 수 없으면 이슈에 담당자가 표시되지 않습니다.

`response_version=4`에서는 `assignee`에 `type`도 있습니다. `user` 담당자에는 버전 3과 같이 `user_id`, `name`, `email`이 있습니다. `team` 담당자에는 `team_id`, `slug`, `name`이 있습니다. 버전 3은 팀 담당자를 `null`로 표시합니다. CLI는 버전 4를 요청합니다.

```json theme={null}
{
  "assignee": {
    "type": "team",
    "team_id": "7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
    "slug": "payments",
    "name": "Payments"
  }
}
```

다른 `response_version` 값은 `code: validation_failed`와 함께 `422`를 반환합니다. 커서 페이지는 `issues`의 모든 이슈에 같은 버전을 사용합니다.

### <span id="assignee-list-response">담당자 목록 응답</span>

`GET /api/telemetry/issues/assignees?project_id=<project_id>`는 프로젝트에서 이슈를 할당받을 수 있는 모든 계정을 반환합니다.

```json theme={null}
{
  "project_id": "123e4567-e89b-12d3-a456-426614174000",
  "assignees": [
    {
      "user_id": "5d6e7f80-1a2b-4c3d-8e4f-5a6b7c8d9e0f",
      "name": "Ada Example",
      "email": "ada@example.com"
    },
    {
      "user_id": "0f1e2d3c-4b5a-4968-8776-655443322110",
      "name": null,
      "email": "sam@example.com"
    }
  ]
}
```

`response_version=2`를 추가하면 `teams`도 받습니다. 프로젝트 조직의 모든 팀이 `team_id`, `slug`, `name`과 함께 들어 있습니다.

계정이 프로젝트를 읽을 수 없으면 라우트는 `code: not_found`와 함께 `404`를 반환합니다.

### <span id="action-response">액션 응답</span>

```json theme={null}
{
  "id": "d8ee3a6f-3918-440b-96cb-14166419399a",
  "project_id": "123e4567-e89b-12d3-a456-426614174000",
  "name": "checkout_failed",
  "distinct_id": "user_123",
  "session_id": "session_456",
  "occurred_at": "2026-06-01T12:00:00Z",
  "properties": { "surface": "checkout" },
  "environment": "production",
  "release": "checkout@1.2.3",
  "service_name": "checkout-api",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "span_id": "00f067aa0ba902b7",
  "next_action": {
    "code": "inspect_trace",
    "target": "trace_summary"
  }
}
```

`distinct_id`는 한 행위자나 세션을 따라갑니다. `distinct_id`와 `session_id`는 구조를 해석하지 말고 그대로 식별자로 다루세요. `session_id`와 `trace_id`는 값이 없으면 `null`입니다. `span_id`는 값이 없으면 생략됩니다. 액션에는 심각도가 없습니다. [제품 이벤트 수집](/ko/guides/product-events)을 참고하세요.

### 메트릭 응답

```json theme={null}
{
  "id": "6f1c2d3e-4a5b-4c6d-8e7f-901234567890",
  "event_id": "metric-1",
  "project_id": "123e4567-e89b-12d3-a456-426614174000",
  "name": "http.server.duration",
  "kind": "histogram",
  "value": 12.5,
  "unit": "ms",
  "temporality": "delta",
  "attributes": {},
  "occurred_at": "2026-07-13T08:00:00Z",
  "environment": "production",
  "release": "checkout@1.2.3",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "span_id": "00f067aa0ba902b7",
  "service_name": "checkout-api",
  "sdk_name": "@logbrew/node",
  "sdk_version": "0.1.8"
}
```

각 항목은 샘플 1개입니다. 샘플 수를 이벤트 수나 사용자 수로 쓰지 마세요. `unit`, `temporality`, `trace_id`, `span_id`는 값이 없으면 `null`입니다. `null` 값은 0이 아닙니다.

### 릴리스 응답

```json theme={null}
{
  "project_id": "123e4567-e89b-12d3-a456-426614174000",
  "release": "api@1.2.3",
  "environment": "production",
  "service_name": "checkout-api",
  "issue_count": 3,
  "log_count": 824,
  "trace_span_count": 421,
  "action_count": 19,
  "metric_count": 87,
  "first_seen_at": "2026-06-01T12:00:00Z",
  "last_seen_at": "2026-06-01T12:15:00Z",
  "trace_health": {
    "status": "errors_observed",
    "trace_count": 42,
    "error_trace_count": 3,
    "error_rate_basis_points": 714
  },
  "next_action": {
    "code": "inspect_release_issues",
    "target": "telemetry_issues"
  }
}
```

한 행은 프로젝트, 릴리스, 환경, 서비스 조합 1개를 다룹니다. 행의 개수는 그 행만 설명합니다.

`trace_health.status`는 `unknown`(행에 트레이스가 없을 때), `no_errors_observed`, `errors_observed` 중 하나입니다. `error_rate_basis_points`는 오류 스팬이 있는 트레이스의 비율이며 `0`에서 `10000` 사이입니다. 모든 요청에 대한 오류율이 아닙니다.

`next_action`은 다음 순서에서 데이터가 있는 첫 번째 시그널 유형을 가리킵니다.

| `code` | `target` |
| - | - |
| `inspect_release_issues` | `telemetry_issues` |
| `inspect_release_traces` | `trace_explorer` |
| `inspect_release_logs` | `telemetry_logs` |
| `inspect_release_actions` | `telemetry_actions` |
| `inspect_release_metrics` | `telemetry_metrics` |
| `review_release_telemetry` | `telemetry_reads` |

[릴리스](/ko/concepts/releases)를 참고하세요.

### 트레이스 검색 응답

`GET /api/telemetry/traces?pagination=cursor`는 트레이스마다 행 1개를 반환하고, 반환된 페이지에 대한 `naming_quality` 점검 결과를 함께 반환합니다.

```json theme={null}
{
  "traces": [
    {
      "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
      "project_ids": ["123e4567-e89b-12d3-a456-426614174000"],
      "root_span_name": "POST /checkout",
      "root_service_name": "checkout-api",
      "root_operation": "http.server",
      "span_count": 3,
      "error_span_count": 1,
      "service_count": 1,
      "started_at": "2026-06-01T12:00:00Z",
      "duration_ms": 45,
      "services": ["checkout-api"],
      "releases": ["checkout@1.2.3"],
      "environments": ["production"],
      "next_action": {
        "code": "inspect_trace",
        "target": "trace_summary"
      }
    }
  ],
  "next_cursor": null,
  "naming_quality": {
    "evaluated_traces": 1,
    "meaningful_name_traces": 1,
    "generic_name_traces": 0,
    "unmatched_route_traces": 0,
    "generic_operation_traces": 0,
    "truncated": false,
    "next_action": null
  }
}
```

`naming_quality`는 반환된 페이지만 다룹니다. 계정 전체의 점수가 아닙니다. `truncated: true`는 다음 페이지가 있다는 뜻입니다. 페이지에 일반적인 이름, 일치하지 않는 라우트, 일반적인 작업이 있으면 `next_action`은 `improve_trace_naming`이고 대상은 `sdk_configuration`입니다. `pagination=cursor`가 없으면 라우트는 트레이스를 맨 배열로 반환하고 `naming_quality`는 반환하지 않습니다.

`next_cursor`에는 `time`과 `trace_id`가 들어 있습니다. 이 값을 `cursor_time`과 `cursor_trace_id`로 보내세요.

`GET /api/telemetry/traces/{trace_id}`는 트레이스 1개의 스팬을 반환합니다. `GET /api/telemetry/traces/{trace_id}/summary`는 루트 스팬, 오류 스팬, 서비스, 소요 시간, 가장 느린 경로를 원시 스팬 속성 없이 반환합니다. 요약에서 `404`가 오면 전체 범위와 일치하는 트레이스가 없다는 뜻입니다. 필터를 빼고 재시도하지 마세요. [트레이스 스팬과 요약 응답](#trace-spans-and-summary-responses)을 참고하세요.

### 빈 목록과 없는 레코드

목록 조회는 일치하는 항목이 없을 수 있습니다. 상세 조회는 규칙이 다릅니다.

| 요청 | 성공 | 읽을 수 있는 레코드가 없을 때 |
| - | - | - |
| `GET /api/logs/{log_id}` | [로그](#log-response) 1개와 함께 `200` | `code: not_found`와 함께 `404` |
| `GET /api/telemetry/issues/{issue_id}` | [이슈](#issue-response) 1개와 함께 `200` | `code: not_found`와 함께 `404` |
| `GET /api/telemetry/issues/{issue_id}/events` | 발생 건과 함께 `200` | 첫 페이지에서 `code: not_found`와 함께 `404` |
| `GET /api/telemetry/traces/{trace_id}` | 스팬과 함께 `200` | `[]`와 함께 `200` |
| `GET /api/telemetry/traces/{trace_id}/summary` | 요약 1개와 함께 `200` | `code: not_found`와 함께 `404` |

로그가 없으면 다음 `404`를 반환합니다.

```json theme={null}
{
  "error": "log not found",
  "code": "not_found",
  "next": "check the resource id or filters and retry",
  "next_action": {
    "code": "check_resource",
    "target": "resource"
  }
}
```

이슈가 없으면 `"error": "issue not found"`를 사용합니다. 트레이스 요약이 없으면 `"error": "trace not found"`를 사용합니다. 알 수 없는 ID와 계정이 읽을 수 없는 ID는 같은 응답을 반환합니다.

계정이 읽을 수 없는 `project_id` 1개로 목록을 요청하면 빈 목록과 함께 `200`을 반환합니다. 데이터가 없다고 보고하기 전에 프로젝트 ID를 확인하세요.

### 거부된 프로젝트 선택

로그, 이슈, 액션, 메트릭, 릴리스, 트레이스 목록은 `project_id`로 프로젝트 1개를, 또는 `project_ids`로 프로젝트 2\~50개를 받습니다. 로그 속성 카탈로그도 같은 선택자를 받습니다. 이슈 발생 기록은 이 선택자를 받지 않습니다.

`project_id`와 `project_ids`를 함께 보내면 `422`를 반환합니다.

```json theme={null}
{
  "error": "project_id and project_ids are mutually exclusive",
  "code": "validation_failed",
  "next": "use project_id for one project, project_ids=uuid,uuid for two to fifty projects, or omit both for all readable projects",
  "next_action": {
    "code": "fix_request",
    "target": "request"
  }
}
```

`project_ids`에 잘못된 UUID나 중복된 ID가 있거나, ID가 2개보다 적거나 50개보다 많아도 `code: validation_failed`와 함께 `422`를 반환합니다.

`project_ids` 중 프로젝트 1개라도 계정이 읽을 수 없으면 요청 전체가 `404`를 반환합니다. 나머지 프로젝트를 부분 결과로 반환하지 않습니다.

```json theme={null}
{
  "error": "project selection not found",
  "code": "not_found",
  "next": "refresh readable projects and retry with only project IDs you can read",
  "next_action": {
    "code": "check_resource",
    "target": "resource"
  }
}
```

빈 목록이나 0개가 아니라 오류를 표시하세요. 프로젝트 목록을 다시 읽으세요. 프로젝트를 말없이 빼지 말고, 모든 프로젝트를 대상으로 재시도하지도 마세요. MCP 연결 같은 위임된 자격 증명은 허용 범위 밖의 프로젝트에 대해 `code: forbidden`과 함께 `403`을 반환합니다. [프로젝트 범위](/ko/reference/telemetry-api-filters#project-scope)를 참고하세요.

### <span id="cursor-errors">커서 오류</span>

로그, 이슈, 액션, 메트릭, 이슈 발생 건은 `cursor_time`과 `cursor_id`를 사용합니다. 커서가 거부되면 `code: validation_failed`와 함께 `422`를 반환합니다. 이 오류는 목록이 비어 있다는 뜻이 아닙니다.

| 요청 | `error` |
| - | - |
| `pagination=cursor`와 커서 필드 2개 중 1개만 보냄 | `cursor_time and cursor_id must be sent together` |
| `cursor_time`이 RFC3339 타임스탬프가 아님 | `cursor_time must be an RFC3339 timestamp` |
| `pagination=cursor` 없이 커서 필드를 보내거나, 다른 `pagination` 값을 보냄 | `pagination must be cursor when cursor pagination is requested` |

예시는 다음과 같습니다.

```json theme={null}
{
  "error": "cursor_time and cursor_id must be sent together",
  "code": "validation_failed",
  "next": "send pagination=cursor alone for the first page, then send cursor_time and cursor_id together from next_cursor",
  "next_action": {
    "code": "fix_request",
    "target": "request"
  }
}
```

이전 페이지의 `next_cursor`를 통째로 복사하세요. `cursor_time`을 현재 시각으로 바꾸지 마세요. 커서를 잃어버렸다면 `pagination=cursor`와 원래 필터로 처음부터 다시 시작하세요. 알 수 없는 쿼리 키나 유형이 잘못된 값은 `"error": "invalid query parameters"`와 함께 `422`를 반환합니다.

### 릴리스 커서 응답

릴리스 커서에는 필드가 5개 있습니다. 성공했지만 일치하는 항목이 없는 요청은 `200`을 반환합니다.

```json theme={null}
{
  "releases": [],
  "next_cursor": null
}
```

`next_cursor`가 `null`이 아니면 다음 요청에 값 5개를 모두 보내세요.

| `next_cursor` 필드 | 쿼리 매개변수 |
| - | - |
| `time` | `cursor_time` |
| `project_id` | `cursor_project_id` |
| `release` | `cursor_release` |
| `environment` | `cursor_environment` |
| `service_name` | `cursor_service_name` |

`cursor_project_id`는 위치를 표시합니다. `project_id`나 `project_ids`를 대신하지 않습니다. 원래 프로젝트와 필터 매개변수를 유지하세요. 각 값은 URL 인코딩하세요.

`pagination=cursor`와 함께 커서 일부만 보내거나 릴리스, 환경, 서비스 값이 비어 있으면 `"error": "all release cursor fields must be sent together"`와 함께 `422`를 반환합니다. 잘못된 `cursor_time`과 빠진 `pagination=cursor`는 [다른 커서](#cursor-errors)와 같은 오류를 반환합니다.

### 트레이스 검색 오류

다음 오류는 `GET /api/telemetry/traces`에 적용됩니다. 모두 `code: validation_failed`와 함께 `422`를 반환합니다. 거부된 요청은 빈 결과가 아닙니다.

| 요청 | `error` |
| - | - |
| `status`가 `error`나 `ok`가 아님 | `trace status must be error or ok` |
| `min_duration_ms`가 `0`보다 작음 | `minimum trace duration must not be negative` |
| 커서 필드를 1개만 보내거나 `cursor_trace_id`가 비어 있음 | `cursor_time and cursor_trace_id must be sent together` |
| `cursor_time`이 RFC3339 타임스탬프가 아님 | `cursor_time must be an RFC3339 timestamp` |
| `pagination=cursor` 없이 커서 필드를 보냄 | `pagination must be cursor when cursor pagination is requested` |

두 트레이스 상태를 모두 포함하려면 `status`를 생략하세요. 다음 페이지를 읽을 때는 `next_cursor.time`을 `cursor_time`으로, `next_cursor.trace_id`를 `cursor_trace_id`로 복사하세요. 트레이스 페이지에는 `cursor_id`를 보내지 마세요. [트레이스 필터](/ko/reference/telemetry-api-filters#trace-filters)를 참고하세요.

### <span id="issue-occurrence-history">이슈 발생 기록</span>

`GET /api/telemetry/issues/{issue_id}/events`는 그룹화된 이슈 1개의 발생 건을 읽습니다. 계정 bearer 토큰과 그룹화된 이슈 ID를 사용하세요. 이 라우트는 `pagination`, `cursor_time`, `cursor_id`, `limit`만 받습니다. 프로젝트, 시간, 릴리스, 환경, 심각도 필터는 받지 않습니다.

```bash theme={null}
curl --get "https://api.logbrew.co/api/telemetry/issues/$ISSUE_ID/events" \
  --header "Authorization: Bearer $LOGBREW_ACCOUNT_BEARER" \
  --data-urlencode "pagination=cursor" \
  --data-urlencode "limit=1"
```

발생 건 1개와 이어서 읽을 커서가 있는 첫 페이지입니다.

```json theme={null}
{
  "issue_id": "d1776d70-763c-47b5-a8f1-76f593221e2b",
  "events": [
    {
      "id": "db619770-1f7c-4bd5-8310-9f171df8af47",
      "issue_id": "d1776d70-763c-47b5-a8f1-76f593221e2b",
      "project_id": "123e4567-e89b-12d3-a456-426614174000",
      "severity": "error",
      "title": "Checkout failed",
      "message": "Checkout could not complete",
      "stack_trace": null,
      "attributes": {
        "exception": {
          "type": "CheckoutError",
          "mechanism": { "type": "javascript.error", "handled": false }
        },
        "breadcrumbs": [
          {
            "timestamp": "2026-06-01T11:54:59Z",
            "category": "checkout",
            "message": "Checkout started"
          }
        ]
      },
      "diagnostics": {
        "schema_version": 1,
        "exception": {
          "type": "CheckoutError",
          "mechanism": { "type": "javascript.error", "handled": false }
        },
        "breadcrumbs": [
          {
            "timestamp": "2026-06-01T11:54:59Z",
            "category": "checkout",
            "message": "Checkout started"
          }
        ],
        "breadcrumbs_truncated": false
      },
      "environment": "production",
      "release": "api@1.2.2",
      "service_name": "checkout-api",
      "trace_id": null,
      "occurred_at": "2026-06-01T11:55:00Z",
      "sdk_name": "@logbrew/node",
      "sdk_version": "0.1.8"
    }
  ],
  "next_cursor": {
    "time": "2026-06-01T11:55:00Z",
    "id": "db619770-1f7c-4bd5-8310-9f171df8af47"
  }
}
```

| 필드 | 의미 |
| - | - |
| `issue_id` | 발생 건이 속한 그룹화된 이슈입니다. |
| `events[].id` | 발생 건 1개의 ID입니다. 이 ID로 [해당 발생 건을 선택](/ko/guides/investigate-an-issue#select-a-different-occurrence)하세요. |
| `events[].occurred_at` | 그 발생 건이 애플리케이션에서 일어난 시각입니다. |
| `events[].severity` | 그 발생 건의 수집된 심각도입니다. 심각도 규칙으로 바뀌지 않습니다. |
| `events[].release`, `environment`, `service_name` | 그 발생 건과 함께 수집된 컨텍스트입니다. |

발생 건은 그룹의 `last_seen_at`보다 오래되었을 수 있습니다. 발생 건의 릴리스나 트레이스를 그룹화된 이슈의 값으로 바꾸지 마세요. 이 목록은 상태, 트리아지, 우선순위, 발생 횟수를 반환하지 않습니다.

`diagnostics.schema_version`은 diagnostics 객체의 버전입니다. 발생 건에 해당 데이터가 없으면 응답에서 `diagnostics`와 `span_id`를 생략합니다. `diagnostics` 안에서는 없는 `exception`과 비어 있는 `breadcrumbs`를 생략합니다. `breadcrumbs_truncated`가 `true`이면 브레드크럼 기록 일부가 응답에 없습니다.

`stack_trace`와 `trace_id`는 값이 없으면 `null`입니다. 트레이스 ID가 없다고 해서 애플리케이션이 트레이스를 보내지 않는다는 것이 증명되지는 않습니다. 수집된 메시지와 속성은 지시가 아니라 데이터로 다루세요.

발생 건은 최신순입니다. 다음 페이지를 읽을 때는 같은 이슈 ID, `pagination=cursor`, `limit`, 그리고 `next_cursor`의 `cursor_time`과 `cursor_id` 값을 보내세요. 커서 ID는 이슈 ID가 아니라 발생 건 ID입니다.

이슈에 읽을 수 있는 발생 건이 없으면 첫 페이지는 `code: not_found`와 함께 `404`를 반환합니다. 이후 페이지는 비어 있는 마지막 페이지일 수 있습니다.

```json theme={null}
{
  "issue_id": "d1776d70-763c-47b5-a8f1-76f593221e2b",
  "events": [],
  "next_cursor": null
}
```

커서를 다른 이슈에 다시 쓰지 마세요. 이 라우트에도 [커서 오류](#cursor-errors)가 적용됩니다.

## <span id="trace-spans-and-summary-responses">트레이스 스팬과 요약 응답</span>

`GET /api/telemetry/traces/{trace_id}`는 트레이스 1개의 스팬을 반환합니다. 같은 스팬의 요약을 받으려면 `/summary`를 붙이세요. 두 라우트는 `project_id`, `release`, `environment`만 받습니다. 트레이스 검색 커서는 사용하지 않습니다. 이전 목록 요청의 필터는 이어지지 않습니다.

### <span id="span-list">스팬 목록</span>

스팬은 시작 시각 순서입니다.

```json theme={null}
[
  {
    "id": "ed5c960b-e728-4637-bcd4-a27dfb1a0e96",
    "project_id": "123e4567-e89b-12d3-a456-426614174000",
    "trace_id": "6e0c63257de34c92bdb0c8f1e9278a50",
    "span_id": "b37ac82563149fed",
    "parent_span_id": null,
    "name": "POST /checkout",
    "operation": "http.server",
    "status": "error",
    "started_at": "2026-06-01T12:00:00Z",
    "duration_ms": 823,
    "attributes": {},
    "environment": "production",
    "release": "api@1.2.3",
    "service_name": "checkout-api",
    "sdk_name": "@logbrew/node",
    "sdk_version": "0.1.8"
  },
  {
    "id": "0c2f7a51-4e8d-4b6a-9f13-2d7e5c8b1a90",
    "project_id": "123e4567-e89b-12d3-a456-426614174000",
    "trace_id": "6e0c63257de34c92bdb0c8f1e9278a50",
    "span_id": "7a085853722dc6d2",
    "parent_span_id": "b37ac82563149fed",
    "name": "POST /v1/charges",
    "operation": "http.client",
    "status": "error",
    "started_at": "2026-06-01T12:00:00.100Z",
    "duration_ms": 640,
    "attributes": {},
    "environment": "production",
    "release": "api@1.2.3",
    "service_name": "checkout-api",
    "sdk_name": "@logbrew/node",
    "sdk_version": "0.1.8"
  }
]
```

`status: "error"`는 API 조회가 아니라 수집된 작업을 설명합니다. `parent_span_id: null`은 스팬에 부모 ID가 없다는 뜻입니다. `status: null`은 스팬이 상태를 보고하지 않았다는 뜻입니다.

### 트레이스 요약

같은 스팬 2개의 요약입니다.

```json theme={null}
{
  "trace_id": "6e0c63257de34c92bdb0c8f1e9278a50",
  "span_count": 2,
  "error_span_count": 2,
  "service_count": 1,
  "project_count": 1,
  "started_at": "2026-06-01T12:00:00Z",
  "duration_ms": 823,
  "root_span": {
    "span_id": "b37ac82563149fed",
    "parent_span_id": null,
    "name": "POST /checkout",
    "operation": "http.server",
    "status": "error",
    "started_at": "2026-06-01T12:00:00Z",
    "duration_ms": 823,
    "service_name": "checkout-api"
  },
  "slowest_child_span": {
    "span_id": "7a085853722dc6d2",
    "parent_span_id": "b37ac82563149fed",
    "name": "POST /v1/charges",
    "operation": "http.client",
    "status": "error",
    "started_at": "2026-06-01T12:00:00.100Z",
    "duration_ms": 640,
    "service_name": "checkout-api"
  },
  "slowest_path": [
    {
      "span_id": "b37ac82563149fed",
      "parent_span_id": null,
      "name": "POST /checkout",
      "operation": "http.server",
      "status": "error",
      "started_at": "2026-06-01T12:00:00Z",
      "duration_ms": 823,
      "service_name": "checkout-api"
    },
    {
      "span_id": "7a085853722dc6d2",
      "parent_span_id": "b37ac82563149fed",
      "name": "POST /v1/charges",
      "operation": "http.client",
      "status": "error",
      "started_at": "2026-06-01T12:00:00.100Z",
      "duration_ms": 640,
      "service_name": "checkout-api"
    }
  ],
  "error_spans": [
    {
      "span_id": "b37ac82563149fed",
      "parent_span_id": null,
      "name": "POST /checkout",
      "operation": "http.server",
      "status": "error",
      "started_at": "2026-06-01T12:00:00Z",
      "duration_ms": 823,
      "service_name": "checkout-api"
    },
    {
      "span_id": "7a085853722dc6d2",
      "parent_span_id": "b37ac82563149fed",
      "name": "POST /v1/charges",
      "operation": "http.client",
      "status": "error",
      "started_at": "2026-06-01T12:00:00.100Z",
      "duration_ms": 640,
      "service_name": "checkout-api"
    }
  ],
  "services": [
    {
      "service_name": "checkout-api",
      "span_count": 2,
      "error_span_count": 2,
      "max_duration_ms": 823
    }
  ],
  "releases": ["api@1.2.3"],
  "environments": ["production"]
}
```

| 필드 | 의미 |
| - | - |
| `duration_ms` | 가장 이른 스팬 시작부터 가장 늦은 스팬 종료까지의 시간입니다. 스팬 소요 시간의 합이 아닙니다. |
| `root_span` | 부모가 트레이스 안에 없는 스팬 중 가장 이른 스팬입니다. 출발점일 뿐 증명된 원인이 아닙니다. |
| `slowest_child_span` | 부모 ID가 있는 스팬 중 가장 긴 스팬이며, 없으면 `null`입니다. |
| `slowest_path` | 루트에서 `slowest_child_span`까지 알려진 부모 체인입니다. `slowest_child_span`이 `null`이면 `[]`입니다. |
| `error_spans` | 가장 긴 오류 스팬이며 최대 5개입니다. `error_span_count`는 이보다 클 수 있습니다. |

두 라우트 모두 커서나 잘림 필드를 반환하지 않습니다. 응답에 없는 `truncated` 값을 추가하지 마세요.

### 일치하는 스팬이 없을 때

트레이스 ID와 범위에 일치하는 스팬이 없으면 스팬 목록은 `[]`와 함께 `200`을 반환합니다. 요약은 `"error": "trace not found"`, `code: not_found`와 함께 `404`를 반환합니다. 트레이스 ID와 범위를 확인하세요. 결과가 나오게 하려고 필터를 빼지 마세요. 어느 결과도 애플리케이션이 텔레메트리를 보내지 않았다는 것을 증명하지는 않습니다.

## <span id="live-event-response-format">실시간 이벤트 응답 형식</span>

실시간 피드는 새 이벤트를 보냅니다. 클라이언트의 연결이 끊긴 동안 놓친 이벤트는 다시 보내지 않습니다. 기존 레코드는 목록 라우트로 읽으세요.

### 티켓 만들기

계정 bearer 토큰으로 `POST /api/feed/ticket`을 보내세요. 요청에는 본문이 없습니다.

```bash theme={null}
curl --request POST "https://api.logbrew.co/api/feed/ticket" \
  --header "Authorization: Bearer $LOGBREW_ACCOUNT_BEARER"
```

`200 OK` 응답의 `ticket` 필드에 티켓이 들어 있습니다.

```json theme={null}
{
  "ticket": "<feed-ticket>",
  "expires_in_seconds": 60,
  "next": "connect with GET /api/feed/live?ticket=<ticket> before the ticket expires",
  "next_action": {
    "code": "connect_live_feed",
    "target": "live_feed"
  }
}
```

티켓은 수명이 짧은 자격 증명입니다. 로그에 남기거나 에이전트 프롬프트에 넣지 마세요. `expires_in_seconds`가 끝나기 전에 연결을 여세요. 만료 시간은 연결을 시작할 수 있는 시점을 제한할 뿐, 연결이 열려 있는 시간을 제한하지 않습니다.

`401`은 계정 로그인이 유효하지 않다는 뜻입니다. 티켓을 요청하기 전에 다시 로그인하세요. `POST`가 아닌 메서드는 `code: method_not_allowed`와 함께 `405`를 반환합니다.

### 연결 열기

WebSocket 클라이언트를 `wss://api.logbrew.co/api/feed/live?ticket=<feed-ticket>`에 연결하세요. 티켓은 URL 인코딩하세요. URL에 계정 bearer 토큰이나 수집 bearer 토큰을 넣지 마세요. `401`은 티켓이나 계정 로그인이 유효하지 않다는 뜻입니다. 다시 로그인한 뒤 새 티켓을 만드세요.

| 응답 | 원인 | 다음 단계 |
| - | - | - |
| `"error": "feed ticket is required"`와 함께 `422` | `ticket` 쿼리 값이 없거나 비어 있습니다. | 티켓을 만들고 그 티켓으로 연결하세요. |
| `code: unauthorized`와 함께 `401` | 티켓이 만료되었거나 유효하지 않습니다. | 새 티켓을 만드세요. 같은 티켓으로 재시도하지 마세요. |
| `code: invalid_websocket_upgrade` | 요청이 올바른 WebSocket 업그레이드가 아닙니다. | WebSocket 클라이언트를 사용하세요. |
| `code: method_not_allowed`와 함께 `405` | 요청이 `GET`을 사용하지 않습니다. | 연결에는 `GET`을 사용하세요. |

오류가 난 뒤에는 잠시 기다렸다가 다시 연결하세요. 짧은 간격으로 계속 재연결하지 마세요.

### 메시지 읽기

각 메시지는 `{ "type": "...", "data": ... }` 엔벨로프입니다. `data`를 파싱하기 전에 `type`을 읽으세요. 클라이언트가 지원하지 않는 유형은 건너뛰세요.

| 이벤트 유형 | 데이터 페이로드 |
| - | - |
| `native_log` | [로그 응답](#log-response) 형식의 로그 데이터입니다. |
| `native_issue` | 버전 1 [이슈 응답](#issue-response) 형식의 그룹화된 이슈 데이터입니다. |
| `native_trace_span` | [스팬 목록](#span-list) 항목 형식의 스팬 1개입니다. |
| `native_action` | [액션 응답](#action-response) 형식의 제품 액션 데이터입니다. |
| `project_created`, `project_updated`, `project_catalog_changed` | 프로젝트 데이터입니다. 변경되면 프로젝트 목록을 다시 읽으세요. |
| `usage_updated`, `usage_limit_warning`, `usage_limit_blocked` | [계정 사용량](/ko/reference/account-api#account-usage) 형식의 계정 사용량입니다. |

```json theme={null}
{
  "type": "native_log",
  "data": {
    "id": "150d1288-bec2-49ec-a036-3f7ac55f1b2d",
    "project_id": "123e4567-e89b-12d3-a456-426614174000",
    "level": "warning",
    "severity": "warning",
    "triage": {
      "effective_severity": "warning",
      "ignored": false,
      "rule_applied": false
    },
    "source": "runtime",
    "message": "checkout retry scheduled",
    "attributes": { "route": "/checkout" },
    "occurred_at": "2026-06-01T12:00:00Z",
    "environment": "production",
    "release": "api@1.2.3",
    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
    "span_id": "00f067aa0ba902b7",
    "service_name": "checkout-api",
    "sdk_name": "@logbrew/node",
    "sdk_version": "0.1.8",
    "next_action": {
      "code": "inspect_trace",
      "target": "trace_summary"
    }
  }
}
```

`native_issue` 이벤트는 이슈 응답 버전 1을 사용합니다. 보관된 이슈는 `ignored`로 표시되고, 이벤트에 우선순위가 없습니다. 메트릭과 릴리스는 실시간 이벤트 유형이 아닙니다. 각각의 목록 라우트로 읽으세요. 티켓은 프로젝트를 선택하지 않습니다. 프로젝트 화면에 표시하기 전에 각 이벤트의 `project_id`를 확인하세요. `logbrew watch --json`이 이 피드를 사용합니다. [CLI 레퍼런스](/ko/reference/cli)를 참고하세요.

### 끊긴 연결 처리하기

| 종료 코드 | 이유 | 다음 단계 |
| - | - | - |
| `1013` | `live feed fell behind; reconnect to resync` | 잠시 기다린 뒤 새 티켓으로 다시 연결하세요. 놓친 레코드를 받으려면 목록 라우트를 다시 읽으세요. |
| `1008` | `live feed access changed; sign in again` | 재연결을 멈추세요. 새 티켓을 만들기 전에 다시 로그인하세요. |

다른 연결 끊김에는 LogBrew가 주는 이유가 없을 수 있습니다. 다시 연결한 뒤에는 목록 라우트를 다시 읽고 ID로 레코드를 맞춰 보세요.

## <span id="issue-status-response-examples">이슈 상태 응답 예시</span>

`{ "status": "resolved" }`로 `PATCH /api/telemetry/issues/{issue_id}`를 보내면 업데이트된 그룹화된 이슈와 함께 `200 OK`를 반환합니다. 형식은 [이슈 응답](#issue-response)과 같습니다. 버전은 `response_version` 쿼리 키로 선택합니다. 성공 플래그는 반환하지 않습니다.

본문에는 `status`, `priority`, `assignee` 중 1개 이상을 넣습니다. `status`에는 `unresolved`, `resolved`, `archived`를 쓸 수 있습니다. `ignored`는 `archived`의 이전 이름입니다. [이슈 1개 업데이트하기](/ko/reference/telemetry-api#update-one-issue)를 참고하세요.

성공을 표시하기 전에 반환된 `id`, `status`, `priority`를 확인하세요. 보낸 값으로 결과를 만들지 마세요. 상태를 바꿔도 발생 건은 삭제되지 않고 `first_seen_at`과 `last_seen_at`도 바뀌지 않습니다. `severity`나 심각도 규칙도 바뀌지 않습니다.

변경이 거부되면 `code: validation_failed`와 함께 `422`를 반환합니다.

```json theme={null}
{
  "error": "issue status must be unresolved, resolved, or archived",
  "code": "validation_failed",
  "next": "use status=unresolved, status=resolved, or status=archived",
  "next_action": {
    "code": "fix_request",
    "target": "request"
  }
}
```

| 결과 | 원인 |
| - | - |
| `422`, `issue update requires status, priority, or assignee` | 본문에 세 필드가 하나도 없습니다. |
| `422`, `issue priority must be high, medium, or low` | 지원하지 않는 우선순위 값입니다. |
| `422`, `issue assignee must be me, none, a user ID, or team:<team_id>` | 지원하지 않는 담당자 값입니다. |
| `422`, `issue assignee cannot read this project` | 담당자가 프로젝트에 접근할 수 없습니다. LogBrew는 아무것도 바꾸지 않습니다. |
| `422`, `issue assignee team is not a team of this project's organization` | 팀이 프로젝트의 조직에 속하지 않습니다. LogBrew는 아무것도 바꾸지 않습니다. |
| `code: forbidden`과 함께 `403` | 계정이 프로젝트의 이슈를 관리할 수 없습니다. |
| `code: not_found`와 함께 `404` | 계정이 이슈를 읽을 수 없거나 이슈가 없습니다. |

이미 요청한 값과 같은 필드는 바뀌지 않습니다. 시간 초과가 났거나 응답을 받지 못했다면 같은 요청을 다시 보내거나 먼저 이슈를 읽으세요. 현재 상태를 읽기 전에 다른 변경을 보내지 마세요. [쓰기 요청](/ko/reference/api-errors#writes)을 참고하세요.

`POST /api/telemetry/issues/bulk`는 요청한 이슈 ID마다 결과 1개를 요청 순서대로 담아 `200 OK`를 반환합니다. 찾은 이슈는 모두 버전 3 형식을 사용합니다. `not_found` 결과는 `issue: null`입니다.

```json theme={null}
{
  "project_id": "123e4567-e89b-12d3-a456-426614174000",
  "results": [
    {
      "issue_id": "9b2b4b3a-bd4e-4f85-a0f6-48118f037c17",
      "outcome": "updated",
      "issue": {
        "id": "9b2b4b3a-bd4e-4f85-a0f6-48118f037c17",
        "status": "archived",
        "priority": "high",
        "priority_source": "derived",
        "assignee": null
      }
    },
    {
      "issue_id": "3f1c2d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
      "outcome": "not_found",
      "issue": null
    }
  ]
}
```

이 예시는 이슈 필드 일부만 보여 줍니다. `outcome`은 `updated`, `unchanged`, `not_found` 중 하나입니다. 같은 요청을 반복하면 `unchanged`를 반환하고 아무것도 바꾸지 않습니다. [이슈 일괄 업데이트하기](/ko/reference/telemetry-api#update-issues-in-bulk)를 참고하세요.

다음 경우에는 일괄 요청 전체가 실패합니다.

| 결과 | 원인 |
| - | - |
| `422`, `issue_ids must name 1 to 100 issues` | 목록이 비어 있거나 ID가 100개보다 많습니다. |
| `422`, `issue_ids must not contain duplicates` | 같은 ID가 두 번 이상 나옵니다. |
| `code: invalid_json`과 함께 `422` | 본문에 알 수 없는 필드가 있습니다. |
| `code: forbidden`과 함께 `403` | 계정이 프로젝트의 이슈를 관리할 수 없습니다. |
| `404`, `project not found` | 계정이 프로젝트를 읽을 수 없습니다. |

## 분석 요청 범위

분석 라우트, 메트릭 시계열 라우트, 시그널 양 라우트는 프로젝트 1개와 기간 1개를 읽습니다. 모든 요청에는 `project_id`와 `since`가 필요합니다. `since`에는 `24h`나 `7d` 같은 짧은 기간 표기나 RFC3339 타임스탬프를 쓸 수 있습니다. `until`에는 RFC3339 타임스탬프를 쓸 수 있으며 기본값은 요청 시각입니다. 기간에는 `since`가 포함되고 `until`은 포함되지 않으며, 31일을 넘을 수 없습니다.

`service_name`, `release`, `environment`는 정확히 일치하는 값으로 거르는 선택 필터입니다. 각 값은 LogBrew가 앞뒤 공백을 없앤 뒤 1\~256자여야 합니다. 세그먼트 비교 라우트는 이 필터를 최상위가 아니라 각 세그먼트 안에 넣습니다.

계정이 프로젝트를 읽을 수 없으면 이 라우트는 개수가 0인 `200 OK`를 반환합니다. `404`는 반환하지 않습니다. 결과가 비어 있다고 프로젝트 접근 권한이 확인된 것은 아닙니다. `project_id`를 계정이 읽을 수 있는 프로젝트와 비교하세요. 해당 프로젝트가 포함되지 않은 MCP 연결로 요청하면 `403`과 `forbidden`을 반환합니다.

| 라우트 | `response_version` 값 | 기본값 |
| - | - | - |
| `GET /api/telemetry/analytics/overview` | `1`, `2`, `3` | `1` |
| `GET /api/telemetry/analytics/properties` | `1`, `2` | `1` |
| `POST /api/telemetry/analytics/segments/compare` | `1`, `2` | `1` |
| `POST /api/telemetry/analytics/retention` | `1`, `2` | `1` |
| `POST /api/telemetry/analytics/lifecycle` | `1`, `2` | `1` |
| `POST /api/telemetry/analytics/funnel` | 받지 않음 | 스키마 `1` |
| `POST /api/telemetry/analytics/paths` | 받지 않음 | 스키마 `1` |
| `GET /api/telemetry/metrics/series` | 받지 않음 | 스키마 `1` |
| `GET /api/telemetry/summary/volume` | `1` | `1` |

모든 응답은 `schema_version`, `query`, `purpose`로 시작합니다. `query`는 실제로 적용된 범위를 그대로 돌려줍니다. 보내지 않은 필터는 생략합니다. `purpose`는 라우트가 답하는 질문을 설명하는 일반 텍스트입니다.

분모가 0이면 비율을 생략합니다. 생략된 비율은 `0`도 아니고 `null`도 아닙니다. 비율을 믿기 전에 `coverage`를 읽으세요.

### <span id="shared-scope-errors">공통 범위 오류</span>

다음 오류는 HTTP `422`와 `validation_failed`를 사용합니다.

| 원인 | `error` |
| - | - |
| `project_id`가 없음 | `product analytics project_id is required` |
| `since`가 없음 | `product analytics since is required` |
| `since`가 기간 표기나 RFC3339 타임스탬프가 아님 | `invalid since value` |
| `until`이 RFC3339 타임스탬프가 아님 | `product analytics until must be an RFC3339 timestamp` |
| `since`가 `until`보다 이르지 않음 | `product analytics since must be earlier than until` |
| 기간이 31일보다 김 | `product analytics range must not exceed 31 days` |
| 필터가 비어 있거나 256자보다 김 | `product analytics context filters must be non-empty and at most 256 characters` |

예를 들어 `project_id` 없이 요청하면 다음을 반환합니다.

```json theme={null}
{
  "error": "product analytics project_id is required",
  "code": "validation_failed",
  "next": "choose one owned project and send its project_id",
  "next_action": { "code": "fix_request", "target": "request" }
}
```

메트릭 시계열 라우트와 시그널 양 라우트도 같은 범위 규칙을 따르지만 `error` 텍스트는 다릅니다. 해당 오류는 아래 각 섹션에 나와 있습니다.

알 수 없는 쿼리 키는 [잘못된 쿼리 매개변수](/ko/reference/api-errors#invalid-query-parameters) 응답과 함께 `422`를 반환합니다. 알 수 없는 JSON 필드, 빠진 필수 JSON 필드, 지원하지 않는 enum 값은 `invalid_json`을 반환합니다. [잘못된 JSON](/ko/reference/api-errors#invalid-json)을 참고하세요. 잘못된 HTTP 메서드로 요청하면 `405`와 `method_not_allowed`를 반환합니다. 이 응답의 `next` 텍스트에 지원하는 메서드와 필드가 나와 있습니다. [지원하지 않는 메서드](/ko/reference/api-errors#unsupported-method)를 참고하세요.

## 제품 분석 개요 응답

`GET /api/telemetry/analytics/overview`는 프로젝트 1개의 활동을 보여 줍니다. 액션 양, 사용자, 세션, 시계열, 상위 액션, 분류된 페이지 조회, 화면 조회, 상호작용이 들어 있습니다. 다른 분석 라우트에 쓸 정확한 이벤트 이름을 찾을 때 사용하세요.

`response_version=3`을 보내세요. 버전 1과 2도 같은 개수를 반환하지만 `estimation.method` 값이 이전 토큰입니다. 버전 1은 식별된 사용자를 셀 때 주체 종류를 확인하지 않습니다. 버전 2와 3은 종류가 `user`인 주체만 세고, 익명 주체 수와 `subject_coverage`를 추가합니다.

다음 요청은 1시간을 5분 단위 시간 구간으로 읽고, 각 상위 목록에 행 2개를 반환합니다.

```bash theme={null}
curl --get "https://api.logbrew.co/api/telemetry/analytics/overview" \
  --header "Authorization: Bearer $LOGBREW_ACCOUNT_BEARER" \
  --data-urlencode "project_id=<project_id>" \
  --data-urlencode "since=2026-06-01T11:00:00Z" \
  --data-urlencode "until=2026-06-01T12:00:00Z" \
  --data-urlencode "interval=5m" \
  --data-urlencode "top_limit=2" \
  --data-urlencode "response_version=3"
```

### 개요 성공

이 가상 결과에서 프로젝트에는 시간 구간 12개 중 2개에 걸쳐 액션 120개가 있습니다. 액션 20개는 익명 주체에서 나옵니다. 모든 액션에 세션 ID가 있습니다. 액션의 절반에 트레이스 ID가 있습니다.

```json theme={null}
{
  "schema_version": 3,
  "query": {
    "project_id": "11111111-1111-4111-8111-111111111111",
    "since": "2026-06-01T11:00:00Z",
    "until": "2026-06-01T12:00:00Z",
    "interval": "5m",
    "interval_seconds": 300,
    "top_limit": 2
  },
  "purpose": "Shows what people do in one project through action volume, active identified users, separately labeled anonymous subjects, explicit sessions, activity over time, top actions, and versioned page-view, screen-view, interaction, surface, and exact event-name breakdowns. Exact classified event names can be used as ordered funnel steps. It never infers people from anonymous events or relabels arbitrary unclassified telemetry as product behavior.",
  "analysis_level": "user_and_session",
  "summary": {
    "actions": 120,
    "active_identified_users": 18,
    "active_anonymous_subjects": 7,
    "sessions": 24,
    "distinct_action_names": 3,
    "actions_per_identified_user": 5.555555555555555,
    "actions_per_session": 5.0
  },
  "coverage": {
    "identified_actions": 100,
    "anonymous_actions": 20,
    "subject_coverage": {
      "index_version": 1,
      "identified_user_events": 100,
      "anonymous_subject_events": 20,
      "legacy_unknown_kind_events": 0,
      "missing_subject_events": 0,
      "historical_unindexed_events": 0
    },
    "sessionized_actions": 120,
    "traced_actions": 60,
    "identification_rate": 0.8333333333333334,
    "sessionization_rate": 1.0,
    "trace_link_rate": 0.5,
    "expected_buckets": 12,
    "returned_points": 2,
    "returned_top_actions": 2,
    "top_actions_truncated": true,
    "first_seen_at": "2026-06-01T11:00:10Z",
    "last_seen_at": "2026-06-01T11:09:50Z",
    "limitations": [
      "Typed anonymous subjects are counted separately and never contribute to active identified-user counts.",
      "Actions without a trace ID cannot link product behavior directly to trace investigation."
    ]
  },
  "estimation": {
    "unique_counts_are_approximate": true,
    "method": "approximate_distinct",
    "description": "Active identified-user, anonymous-subject, session, distinct action-name, distinct surface, and distinct exact event-name counts are approximate distinct counts. Event totals and capture-coverage counts are exact within the selected, fully evaluated query window."
  },
  "series": [
    {
      "bucket_start": "2026-06-01T11:00:00Z",
      "bucket_end": "2026-06-01T11:05:00Z",
      "actions": 50,
      "active_identified_users": 8,
      "sessions": 10,
      "identified_actions": 40,
      "sessionized_actions": 50,
      "traced_actions": 25
    },
    {
      "bucket_start": "2026-06-01T11:05:00Z",
      "bucket_end": "2026-06-01T11:10:00Z",
      "actions": 70,
      "active_identified_users": 12,
      "sessions": 14,
      "identified_actions": 60,
      "sessionized_actions": 70,
      "traced_actions": 35
    }
  ],
  "top_actions": [
    {
      "name": "checkout_started",
      "actions": 50,
      "active_identified_users": 12,
      "sessions": 14,
      "share_of_actions": 0.4166666666666667
    },
    {
      "name": "purchase_completed",
      "actions": 40,
      "active_identified_users": 9,
      "sessions": 10,
      "share_of_actions": 0.3333333333333333
    }
  ],
  "classified_activity": {
    "summary": {
      "events": 110,
      "page_views": 50,
      "screen_views": 20,
      "interactions": 40,
      "distinct_surfaces": 4,
      "distinct_event_names": 5,
      "active_identified_users": 17,
      "active_anonymous_subjects": 6,
      "sessions": 23
    },
    "coverage": {
      "classified_actions": 60,
      "unclassified_actions": 60,
      "action_classification_rate": 0.5,
      "surfaced_events": 110,
      "named_events": 110,
      "identified_events": 90,
      "subject_coverage": {
        "index_version": 1,
        "identified_user_events": 90,
        "anonymous_subject_events": 20,
        "legacy_unknown_kind_events": 0,
        "missing_subject_events": 0,
        "historical_unindexed_events": 0
      },
      "sessionized_events": 110,
      "traced_events": 55,
      "surface_rate": 1.0,
      "event_name_rate": 1.0,
      "identification_rate": 0.8181818181818182,
      "sessionization_rate": 1.0,
      "trace_link_rate": 0.5,
      "expected_buckets": 12,
      "returned_points": 2,
      "returned_top_surfaces": 2,
      "returned_top_events": 2,
      "top_surfaces_truncated": true,
      "top_events_truncated": true,
      "first_seen_at": "2026-06-01T11:00:20Z",
      "last_seen_at": "2026-06-01T11:09:40Z",
      "limitations": [
        "Only events carrying supported version-1 analytics annotations appear in classified activity; arbitrary actions and technical spans are not relabeled.",
        "Historical or unannotated actions contribute to the action summary but not to classified screen-view or interaction breakdowns.",
        "Classified events with typed anonymous subjects are counted separately and never contribute to identified-user counts.",
        "Classified events without a trace ID cannot link product behavior directly to trace investigation."
      ]
    },
    "series": [
      {
        "bucket_start": "2026-06-01T11:00:00Z",
        "bucket_end": "2026-06-01T11:05:00Z",
        "events": 45,
        "page_views": 20,
        "screen_views": 10,
        "interactions": 15
      },
      {
        "bucket_start": "2026-06-01T11:05:00Z",
        "bucket_end": "2026-06-01T11:10:00Z",
        "events": 65,
        "page_views": 30,
        "screen_views": 10,
        "interactions": 25
      }
    ],
    "top_surfaces": [
      {
        "kind": "page_view",
        "surface": "/checkout/:step",
        "events": 40,
        "active_identified_users": 12,
        "sessions": 14,
        "share_of_classified_events": 0.36363636363636365
      },
      {
        "kind": "screen_view",
        "surface": "Checkout",
        "events": 20,
        "active_identified_users": 9,
        "sessions": 10,
        "share_of_classified_events": 0.18181818181818182
      }
    ],
    "top_events": [
      {
        "kind": "page_view",
        "event_name": "/checkout/:step",
        "events": 40,
        "active_identified_users": 12,
        "sessions": 14,
        "share_of_classified_events": 0.36363636363636365
      },
      {
        "kind": "interaction",
        "event_name": "checkout_started",
        "events": 30,
        "active_identified_users": 10,
        "sessions": 12,
        "share_of_classified_events": 0.2727272727272727
      }
    ]
  },
  "next_action": {
    "code": "build_product_funnel",
    "target": "/api/telemetry/analytics/funnel",
    "reason": "Use two through eight exact keys from classified_activity.top_events to measure ordered conversion and drop-off for explicit sessions or opaque identified users."
  }
}
```

### 개요 읽기

`subject_coverage`의 다섯 값을 더하면 해당 섹션의 이벤트 합계가 됩니다. 사용자 수에 들어가는 값은 `identified_user_events`뿐입니다. `anonymous_actions`는 `summary.actions`에서 `identified_actions`를 뺀 값입니다.

`series`에는 활동이 있는 시간 구간만 들어 있습니다. `expected_buckets`는 기간 안에 있을 수 있는 시간 구간의 수입니다. 차트에 그 규칙을 밝히지 않았다면 빠진 시간 구간을 측정된 0으로 그리지 마세요. 시간 구간은 UTC 간격 경계에 맞춰집니다. 기간이 시간 구간 중간에서 시작하면 그 일부 구간도 포함합니다.

`classified_activity`는 페이지 조회, 화면 조회, 상호작용을 셉니다. 화면 조회와 상호작용은 액션이기도 합니다. 페이지 조회는 액션이 아닙니다. `summary.actions`와 `classified_activity.summary.events`를 더하지 마세요.

`classified_activity.top_events`의 `kind`와 `event_name`을 퍼널 단계, 재방문 선택자, 라이프사이클 선택자, 경로 기준 이벤트, 세그먼트 대상에 복사하세요. `top_events_truncated`가 `true`이면 양이 더 적은 이름이 있다는 뜻입니다. 더 보려면 `top_limit`을 최대 20까지 늘리세요.

`analysis_level`은 `no_data`, `event_only`, `user_level`, `user_and_session` 중 하나입니다. 프로젝트에 액션도 분류된 이벤트도 없으면 `next_action.code`는 `capture_product_activity`입니다. [제품 분석](/ko/concepts/product-analytics#use-the-analytics-api)을 참고하세요.

### <span id="overview-errors">개요 오류</span>

[공통 범위 오류](#shared-scope-errors) 외에도 개요는 다음 값에 대해 `422`와 `validation_failed`를 반환합니다.

| 값 | `error` |
| - | - |
| `response_version`이 `1`, `2`, `3`이 아님 | `product analytics response_version must be 1, 2, or 3` |
| `top_limit`이 `1`\~`20`이 아님 | `product analytics top_limit must be between 1 and 20` |
| `interval`이 `auto`, `1m`, `5m`, `15m`, `1h`, `6h`, `1d`가 아님 | `product analytics interval is not supported` |
| 직접 지정한 `interval`에 시간 구간이 500개보다 많이 필요함 | `product analytics interval would exceed 500 buckets` |

`interval`의 기본값은 `auto`입니다. 시간 구간이 300개 이하로 필요한 가장 작은 간격을 선택합니다. `top_limit`의 기본값은 `10`입니다.

## <span id="ordered-funnel-response">순서가 있는 퍼널 응답</span>

`POST /api/telemetry/analytics/funnel`은 단계 2\~8개를 순서대로 완료했는지 측정합니다. 개요의 `classified_activity.top_events`에서 정확한 `kind`와 `event_name`을 각각 복사하세요. 비슷한 액션 이름은 일치하지 않습니다. 다음 요청 본문은 `/pricing`을 본 뒤 1시간 안에 가입을 완료한 세션을 셉니다.

```json theme={null}
{
  "project_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
  "since": "2026-08-01T00:00:00Z",
  "until": "2026-08-02T00:00:00Z",
  "environment": "production",
  "analysis_unit": "session",
  "conversion_window_seconds": 3600,
  "steps": [
    { "kind": "page_view", "event_name": "/pricing" },
    { "kind": "interaction", "event_name": "signup_completed" }
  ]
}
```

`analysis_unit`은 `session` 또는 `identified_user`입니다. 기본값은 `session`입니다. 단계의 `kind`는 `page_view`, `screen_view`, `interaction` 중 하나입니다. 퍼널 라우트는 `response_version`을 받지 않습니다.

### 퍼널 성공

`200 OK` 응답입니다.

```json theme={null}
{
  "schema_version": 1,
  "query": {
    "project_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
    "since": "2026-08-01T00:00:00Z",
    "until": "2026-08-02T00:00:00Z",
    "environment": "production",
    "analysis_unit": "session",
    "conversion_window_seconds": 3600
  },
  "purpose": "Shows where explicit sessions or typed opaque user subjects enter, progress, and drop off across two through eight versioned page-view, screen-view, or interaction events. Steps must occur in order with strictly increasing timestamps inside the selected conversion window; raw session and subject identifiers are never returned.",
  "summary": {
    "candidate_units": 60,
    "entered_units": 50,
    "completed_units": 15,
    "entry_rate": 0.8333333333333334,
    "overall_conversion_rate": 0.3
  },
  "coverage": {
    "classified_events": 200,
    "named_events": 180,
    "unnamed_events": 20,
    "unit_identified_events": 160,
    "selected_step_events": 120,
    "usable_selected_step_events": 100,
    "excluded_selected_step_events": 20,
    "event_name_rate": 0.9,
    "selected_unit_coverage_rate": 0.8333333333333334,
    "limitations": [
      "Only supported version-1 classified events with exact derived names participate; arbitrary actions and technical spans are never relabeled.",
      "A unit reaches later steps only when timestamps strictly increase and the final match remains inside conversion_window_seconds from its first match.",
      "Historical or surface-less classified events without a derived event name cannot match exact funnel steps.",
      "Requested-step events without an explicit session ID are excluded from ordered unit counts.",
      "Session funnels count visits or app sessions, not unique people; use analysis_unit=identified_user only when typed user-subject coverage is sufficient."
    ]
  },
  "steps": [
    {
      "position": 1,
      "kind": "page_view",
      "event_name": "/pricing",
      "units": 50,
      "conversion_from_first": 1.0,
      "drop_off_to_next_units": 35,
      "drop_off_to_next_rate": 0.7
    },
    {
      "position": 2,
      "kind": "interaction",
      "event_name": "signup_completed",
      "units": 15,
      "conversion_from_previous": 0.3,
      "conversion_from_first": 0.3
    }
  ],
  "next_action": {
    "code": "compare_funnel_contexts",
    "target": "/api/telemetry/analytics/funnel",
    "reason": "Compare the same exact steps across bounded releases, environments, or services to locate material conversion changes."
  }
}
```

비율은 0에서 1 사이의 소수입니다. 비율을 믿기 전에 `coverage`를 읽으세요. `selected_unit_coverage_rate`는 선택된 단계 이벤트 중 LogBrew가 사용할 수 있었던 비율입니다. 응답은 원본 식별 정보를 반환하지 않습니다. [제품 분석](/ko/concepts/product-analytics#measure-a-funnel)을 참고하세요.

### 전환과 커버리지 읽기

`entry_rate`는 `entered_units`를 `candidate_units`로 나눈 값이며, 여기서는 60개 중 50개입니다. `overall_conversion_rate`는 `completed_units`를 `entered_units`로 나눈 값이며, 50개 중 15개, 즉 30%입니다. 15를 전체 후보로 나눈 값이 아닙니다. 이 개수는 단위가 왜 멈췄는지 설명하지 않습니다.

커버리지는 세션이 아니라 이벤트를 셉니다. 여기서는 이벤트 120개가 단계와 일치하고, 그중 20개에 세션 ID가 없습니다. 결과는 이 이벤트 20개를 세션 수에서 뺍니다. 이 20개를 실패한 세션 20개로 보지 마세요.

첫 단계에는 `conversion_from_previous`가 없습니다. 마지막 단계에는 `drop_off_to_next_units`와 `drop_off_to_next_rate`가 없습니다.

단위는 단계 시각이 엄격하게 증가할 때만 뒤 단계에 도달합니다. 타임스탬프가 같은 이벤트로는 연속한 두 단계를 충족할 수 없습니다. 마지막 단계는 첫 단계로부터 `conversion_window_seconds` 안에 일어나야 합니다.

세션 모드는 사람이 아니라 세션을 셉니다. `identified_user`는 종류가 `user`인 불투명 주체 ID가 있는 이벤트에만 사용하세요. 이 모드는 전환 기간 안에서 서로 다른 세션의 이벤트를 연결할 수 있습니다. 결과를 비교할 때는 같은 단위와 같은 단계를 유지하세요.

`conversion_window_seconds`의 기본값은 24시간과 기간 길이 중 짧은 쪽입니다. 최댓값은 기간 길이를 초 단위로 올림한 값입니다.

### 빈 퍼널 결과

성공했지만 비어 있는 응답에도 요청한 단계가 `units` 0과 함께 나옵니다. 후보 단위가 없으면 `summary`에서 `entry_rate`와 `overall_conversion_rate`를 생략합니다. 일치하는 이벤트가 없다는 이유로 라우트가 `404`를 반환하지는 않습니다.

원인을 찾으려면 `coverage`와 `next_action`을 읽으세요.

| `next_action.code` | 원인 |
| - | - |
| `capture_product_activity` | 범위 안에 분류된 이벤트가 없습니다. |
| `choose_captured_funnel_steps` | 요청한 단계와 일치하는 이벤트가 없습니다. |
| `sessionize_product_activity` | 단계 이벤트는 있지만 세션 ID가 있는 이벤트가 없습니다. |
| `identify_product_users` | 단계 이벤트는 있지만 종류가 `user`인 주체를 가진 이벤트가 없습니다. |
| `verify_funnel_entry` | 1단계에 들어온 단위가 없습니다. |
| `investigate_funnel_drop_off` | 들어온 단위는 있지만 모든 단계를 완료한 단위가 없습니다. |
| `compare_funnel_contexts` | 퍼널을 완료한 단위가 1개 이상 있습니다. |

### 거부된 퍼널 요청

[공통 범위 오류](#shared-scope-errors) 외에도 다음 요청은 `422`와 `validation_failed`를 반환합니다. 단계가 2개보다 적거나 8개보다 많을 때입니다.

```json theme={null}
{
  "error": "product analytics funnel requires two through eight ordered steps",
  "code": "validation_failed",
  "next": "send a steps array containing 2 through 8 exact classified event selectors",
  "next_action": { "code": "fix_request", "target": "request" }
}
```

`conversion_window_seconds`가 `0`이거나 기간 길이보다 클 때입니다.

```json theme={null}
{
  "error": "product analytics funnel conversion window must fit inside the query range",
  "code": "validation_failed",
  "next": "use conversion_window_seconds from 1 through the selected range length",
  "next_action": { "code": "fix_request", "target": "request" }
}
```

`event_name`이 비어 있거나, 256자보다 길거나, 제어 문자를 포함하면 `product analytics funnel event_name must be non-empty, control-free, and at most 256 characters`를 반환합니다. `interaction` 단계는 올바른 액션 이름이어야 합니다. 그렇지 않으면 오류는 `product analytics interaction event_name must be a bounded stable action name`입니다.

요청을 고친 뒤 다시 보내세요. 비어 있지 않은 결과를 얻으려고 단계를 빼거나 분석 단위를 바꾸지 마세요.

## 정확한 이벤트 분석과 자동 분석

재방문과 라이프사이클은 두 가지 요청 형식을 받습니다. 원하는 질문에 맞는 형식을 `response_version`으로 선택하세요.

| 분석 | `response_version` | 이벤트 선택자 |
| - | - | - |
| 정확한 이벤트의 재방문 | `1` 또는 생략 | `start_event`와 `return_event`를 모두 보내세요. |
| 정확한 이벤트 1개의 라이프사이클 | `1` 또는 생략 | `event`를 보내세요. |
| 자동 재방문 또는 자동 라이프사이클 | `2` | 이벤트 선택자를 보내지 마세요. |

정확한 선택자에는 `kind`와 `event_name`이 들어갑니다. `kind`는 `page_view`, `screen_view`, `interaction` 중 하나입니다. `event_name`은 `classified_activity.top_events`에서 복사하세요. 버튼 레이블을 보고 이벤트 이름을 추측하지 마세요.

두 형식 모두 사용자, 즉 종류가 `user`인 불투명 주체 ID만 셉니다. 익명 주체와 사용할 수 있는 주체 ID가 없는 이벤트는 사용자로 세지 않습니다. 응답은 주체 ID를 반환하지 않습니다.

고정 간격은 `hour`, `day`, `week`, `thirty_day`입니다. 모두 길이가 고정된 기간입니다. `thirty_day`는 달력상의 한 달이 아니라 30일입니다. `interval`을 생략하면 API는 48시간 이하의 기간에는 `hour`를, 더 긴 기간에는 `day`를 사용합니다.

### 정확한 이벤트 재방문 요청

다음 본문을 `POST /api/telemetry/analytics/retention`으로 보내세요. 사용자가 기간 안의 첫 결제 뒤에 결제를 다시 완료하는지 묻는 요청입니다.

```json theme={null}
{
  "project_id": "11111111-1111-4111-8111-111111111111",
  "since": "2026-06-01T00:00:00Z",
  "until": "2026-06-03T00:00:00Z",
  "response_version": 1,
  "interval": "day",
  "interval_count": 1,
  "mode": "return_on",
  "cohort_mode": "first_in_range",
  "start_event": { "kind": "interaction", "event_name": "checkout_completed" },
  "return_event": { "kind": "interaction", "event_name": "checkout_completed" }
}
```

기간 안에서 처음 일치하는 시작 이벤트가 각 사용자의 기준점이 됩니다. 재방문은 그 기준점보다 엄격하게 뒤에, 그리고 `until` 전에 일어나야 합니다. 두 선택자에 같은 이벤트를 써도 됩니다.

| 필드 | 값 | 기본값 |
| - | - | - |
| `interval_count` | 기간 `1`\~`31`개 | `10` |
| `mode` | `return_on`은 각 기간 안의 재방문만 셉니다. `return_on_or_after`는 각 기간의 시작 시점이나 그 뒤의 재방문을 셉니다. | `return_on` |
| `cohort_mode` | `first_in_range` | `first_in_range` |

### 정확한 이벤트 재방문 성공

이 가상 예시에서는 사용자 2명이 결제를 완료합니다. 첫 번째 사용자는 6월 1일 12:00에 결제를 완료하고 13:00에 다시 완료합니다. 두 번째 사용자는 6월 2일 12:00에 결제를 완료합니다. 두 번째 사용자는 6월 2일 13:00에 페이지를 보지만 결제를 다시 완료하지는 않습니다.

```json theme={null}
{
  "schema_version": 1,
  "query": {
    "project_id": "11111111-1111-4111-8111-111111111111",
    "since": "2026-06-01T00:00:00Z",
    "until": "2026-06-03T00:00:00Z",
    "start_event": { "kind": "interaction", "event_name": "checkout_completed" },
    "return_event": { "kind": "interaction", "event_name": "checkout_completed" },
    "interval": "day",
    "interval_seconds": 86400,
    "interval_count": 1,
    "mode": "return_on",
    "cohort_mode": "first_in_range"
  },
  "purpose": "Measures whether typed opaque user subjects return after their first matching start event inside the selected half-open query range. Returns must occur strictly after each subject's anchor. return_on measures one fixed subject-relative period; return_on_or_after measures from that period's threshold through the exclusive query upper bound. Eligibility-aware denominators prevent immature subjects from being reported as churned, cohorts are fixed buckets aligned to since, and raw subject identifiers are never returned.",
  "summary": {
    "cohort_subjects": 2,
    "subjects_returned_after_start": 1,
    "return_rate_after_start": 0.5,
    "non_empty_cohorts": 2,
    "periods_with_eligible_subjects": 1,
    "fully_observed_periods": 0
  },
  "coverage": {
    "classified_events": 4,
    "named_events": 4,
    "unnamed_events": 0,
    "identified_events": 4,
    "start_events": 3,
    "usable_start_events": 3,
    "excluded_start_events": 0,
    "return_events": 3,
    "usable_return_events": 3,
    "excluded_return_events": 0,
    "event_name_rate": 1.0,
    "start_identity_coverage_rate": 1.0,
    "return_identity_coverage_rate": 1.0,
    "limitations": [
      "Only application-supplied opaque subject IDs explicitly typed as users are counted. Anonymous, legacy-untyped, historical-unindexed, invalid, or absent subjects are excluded rather than inferred as people.",
      "first_in_range anchors each subject at its first matching start inside the selected range; it does not prove that event was the subject's first-ever historical start.",
      "Cohort buckets are fixed-width and aligned to the query's since value. hour, day, week, and thirty_day mean fixed elapsed durations; thirty_day is not a calendar month.",
      "Returns must match the exact selected kind and derived event name, occur strictly after the subject anchor, and remain before the exclusive query upper bound.",
      "This resource does not provide recurring-start cohorts, group retention, arbitrary property breakdowns, custom return ranges, or lifetime first-event lookup.",
      "return_on includes only subjects whose complete exact period ends by until; later subjects are excluded from that period's denominator."
    ]
  },
  "curve": [
    {
      "period": 0,
      "threshold_seconds": 0,
      "window_end_seconds": 86400,
      "eligible_subjects": 1,
      "retained_subjects": 1,
      "weighted_retention_rate": 1.0,
      "fully_observed_cohorts": 1,
      "fully_observed_cohort_average_rate": 1.0,
      "all_subjects_eligible": false
    }
  ],
  "cohorts": [
    {
      "cohort": 0,
      "started_at": "2026-06-01T00:00:00Z",
      "ended_at": "2026-06-02T00:00:00Z",
      "subjects": 1,
      "subjects_returned_after_start": 1,
      "periods": [
        {
          "period": 0,
          "eligible_subjects": 1,
          "retained_subjects": 1,
          "retention_rate": 1.0,
          "all_subjects_eligible": true
        }
      ]
    },
    {
      "cohort": 1,
      "started_at": "2026-06-02T00:00:00Z",
      "ended_at": "2026-06-03T00:00:00Z",
      "subjects": 1,
      "subjects_returned_after_start": 0,
      "periods": [
        {
          "period": 0,
          "eligible_subjects": 0,
          "retained_subjects": 0,
          "all_subjects_eligible": false
        }
      ]
    }
  ],
  "next_action": {
    "code": "extend_retention_observation_window",
    "target": "/api/telemetry/analytics/retention",
    "reason": "Move since earlier, until later, or request fewer periods when a fully observed long-term comparison is required."
  }
}
```

### 재방문 읽기

커버리지는 분류된 이벤트 4개를 모두 세지만, 각 결제 선택자와 일치하는 이벤트는 3개뿐입니다. 페이지 조회는 재방문으로 세지 않습니다. 사용자 2명 중 1명이 결제를 반복하므로 `return_rate_after_start`는 `0.5`입니다.

기준점 뒤로 하루 전체를 관측한 사용자는 첫 번째 사용자뿐입니다. 이 사용자는 하루 안에 돌아오므로 곡선의 비율은 `1.0`입니다. 두 번째 사용자는 12시간만 관측했습니다. 이 사용자는 기간 0의 대상이 아니므로 두 번째 코호트의 셀에는 `retention_rate`가 없습니다. 이 사용자를 이탈한 사용자로 세지 마세요.

`weighted_retention_rate`는 재방문한 사용자 전체를 대상 사용자 전체로 나눈 값입니다. `fully_observed_cohort_average_rate`는 모든 사용자가 대상인 코호트들의 비율을 단순 평균한 값입니다. 코호트는 `since`에서 시작하는 고정 시간 구간입니다. `cohorts`에는 사용자가 있는 코호트만 들어 있습니다.

`mode: "return_on_or_after"`를 쓰면 곡선에서 `window_end_seconds`를 생략합니다.

### 자동 재방문

`response_version: 2`를 보내고 `start_event`나 `return_event`는 보내지 마세요. 지원되는 모든 페이지 조회, 화면 조회, 상호작용이 시작과 재방문으로 집계됩니다. 이 버전은 이벤트 집합을 선택합니다. SDK 수집 형식을 선택하는 것이 아닙니다.

```json theme={null}
{
  "project_id": "11111111-1111-4111-8111-111111111111",
  "since": "2026-06-01T00:00:00Z",
  "until": "2026-06-03T00:00:00Z",
  "response_version": 2,
  "interval": "day",
  "interval_count": 1,
  "mode": "return_on",
  "cohort_mode": "first_in_range"
}
```

응답의 `summary`, `curve`, `cohorts`, `next_action` 형식은 정확한 이벤트 응답과 같습니다. 이 발췌는 바뀌는 필드를 보여 줍니다. 이 예시에서 같은 사용자 2명에게 이벤트가 3개 있고 페이지 조회는 없습니다.

```json theme={null}
{
  "schema_version": 2,
  "query": {
    "project_id": "11111111-1111-4111-8111-111111111111",
    "since": "2026-06-01T00:00:00Z",
    "until": "2026-06-03T00:00:00Z",
    "activity_scope": "all_classified_activity",
    "interval": "day",
    "interval_seconds": 86400,
    "interval_count": 1,
    "mode": "return_on",
    "cohort_mode": "first_in_range"
  },
  "purpose": "Measures whether typed opaque user subjects perform any later supported classified product activity after their first such activity inside the selected half-open range. It does not choose or infer a top event. Returns must occur strictly after the anchor, anonymous and unidentified subjects remain excluded from user counts, eligibility-aware denominators prevent immature subjects from being reported as churned, and raw identifiers are never returned.",
  "coverage": {
    "classified_events": 3,
    "named_events": 3,
    "unnamed_events": 0,
    "identified_events": 3,
    "start_events": 3,
    "usable_start_events": 3,
    "excluded_start_events": 0,
    "return_events": 3,
    "usable_return_events": 3,
    "excluded_return_events": 0,
    "event_name_rate": 1.0,
    "start_identity_coverage_rate": 1.0,
    "return_identity_coverage_rate": 1.0,
    "limitations": [
      "Only application-supplied opaque subject IDs explicitly typed as users are counted. Anonymous, legacy-untyped, historical-unindexed, invalid, or absent subjects are excluded rather than inferred as people.",
      "first_in_range anchors each subject at its first supported classified activity in the range; it does not prove that activity was the subject's first-ever product activity.",
      "Cohort buckets are fixed-width and aligned to the query's since value. hour, day, week, and thirty_day mean fixed elapsed durations; thirty_day is not a calendar month.",
      "Returns are any supported classified activity strictly after the subject anchor and before the exclusive query upper bound; no event is ranked, selected, or inferred.",
      "This resource does not provide recurring-start cohorts, group retention, arbitrary property breakdowns, custom return ranges, or lifetime first-event lookup.",
      "return_on includes only subjects whose complete exact period ends by until; later subjects are excluded from that period's denominator."
    ]
  }
}
```

자동 모드에서는 `start_events`와 `return_events`가 모두 `classified_events`와 같습니다. 이 값은 사용자 수가 아니라 이벤트 수입니다.

### 대상 사용자가 없는 재방문

성공한 결과에도 코호트 사용자가 없을 수 있습니다. 이 발췌는 코호트 없이 요청한 기간 1개를 보여 줍니다.

```json theme={null}
{
  "schema_version": 2,
  "summary": {
    "cohort_subjects": 0,
    "subjects_returned_after_start": 0,
    "non_empty_cohorts": 0,
    "periods_with_eligible_subjects": 0,
    "fully_observed_periods": 0
  },
  "curve": [
    {
      "period": 0,
      "threshold_seconds": 0,
      "window_end_seconds": 86400,
      "eligible_subjects": 0,
      "retained_subjects": 0,
      "fully_observed_cohorts": 0,
      "all_subjects_eligible": false
    }
  ],
  "cohorts": []
}
```

`coverage`와 `next_action`을 읽으세요. 분류된 이벤트가 없는 범위(`capture_product_activity`)와 사용자가 없는 이벤트(`identify_product_users` 또는 `identify_returning_users`)를 구분해 줍니다. 비율이 없다고 해서 측정된 0은 아닙니다.

### 재방문 오류

[공통 범위 오류](#shared-scope-errors) 외에도 다음 요청은 `422`와 `validation_failed`를 반환합니다.

| 원인 | `error` |
| - | - |
| 버전 1인데 선택자 2개가 모두 있지 않음, 버전 2인데 선택자가 있음, 또는 다른 버전 | `product analytics retention activity selection is contradictory` |
| `interval_count`가 `1`\~`31`이 아님 | `product analytics retention interval_count must be between 1 and 31` |
| 코호트 시간 구간 수에 `interval_count`를 곱한 값이 500보다 큼 | `product analytics retention interval and interval_count would exceed 500 cohort matrix cells` |

셀 한도는 사용자 수가 아니라 기간과 간격 수로 정해집니다. 30일 기간에 `interval: "hour"`를 쓰면 코호트 시간 구간이 720개입니다. `interval_count: 1`이면 셀이 720개로 500보다 많습니다.

```json theme={null}
{
  "error": "product analytics retention interval and interval_count would exceed 500 cohort matrix cells",
  "code": "validation_failed",
  "next": "choose a wider interval, a shorter time range, or fewer retention periods",
  "next_action": { "code": "fix_request", "target": "request" }
}
```

자동 재방문을 사용하려면 두 선택자를 모두 빼세요. 이 조회 요청을 고치려고 SDK를 바꾸지 마세요.

### 정확한 이벤트 라이프사이클 요청

다음 본문을 `POST /api/telemetry/analytics/lifecycle`로 보내세요. 프로젝트의 모든 활동이 아니라 이벤트 1개를 기준으로 사용자를 분류합니다.

```json theme={null}
{
  "project_id": "11111111-1111-4111-8111-111111111111",
  "since": "2026-06-01T00:00:00Z",
  "until": "2026-06-03T00:00:00Z",
  "response_version": 1,
  "interval": "day",
  "history_period_count": 2,
  "event": { "kind": "interaction", "event_name": "checkout_completed" }
}
```

`history_period_count`는 `since` 이전에서 LogBrew가 기록으로 읽는 완전한 기간의 수를 정합니다. `2`부터 `31`까지 쓸 수 있습니다. 기본값은 `2`입니다. 사용자가 다른 활동을 하는 동안에도 이 이벤트 기준으로는 휴면일 수 있습니다.

### 정확한 이벤트 라이프사이클 성공

이 가상 예시는 기록 기간 2일과 분석 기간 2일을 사용합니다. 알파벳은 예시 사용자를 구분할 뿐입니다. 응답은 사용자 ID를 반환하지 않습니다.

| 날짜(UTC) | 결제를 완료한 사용자 |
| - | - |
| 5월 30일, 기록 | B |
| 5월 31일, 기록 | A, D |
| 6월 1일, 분석 | A, B, C |
| 6월 2일, 분석 | A, B |

C는 6월 2일에 페이지도 봅니다. 이 이벤트는 결제 선택자와 일치하지 않습니다.

```json theme={null}
{
  "schema_version": 1,
  "query": {
    "project_id": "11111111-1111-4111-8111-111111111111",
    "since": "2026-06-01T00:00:00Z",
    "until": "2026-06-03T00:00:00Z",
    "history_since": "2026-05-30T00:00:00Z",
    "event": { "kind": "interaction", "event_name": "checkout_completed" },
    "interval": "day",
    "interval_seconds": 86400,
    "history_period_count": 2,
    "expected_buckets": 2
  },
  "purpose": "Classifies typed opaque user subjects for one exact product event in each fixed analysis period. new_within_observed_history means active now with no earlier matching event inside the bounded history window; returning means active now and in the immediately previous period; resurrected means active now, inactive in the previous period, and active earlier in observed history; dormant means active in the previous period but not now. These states are disjoint, incomplete buckets are marked, and raw subject identifiers are never returned.",
  "summary": {
    "observed_subjects": 4,
    "analysis_active_subjects": 3,
    "history_only_subjects": 1,
    "returned_buckets": 2,
    "fully_observed_buckets": 2,
    "latest_fully_observed_period": 1,
    "buckets_with_resurrection": 1,
    "buckets_with_dormancy": 2
  },
  "coverage": {
    "analysis_classified_events": 6,
    "analysis_named_events": 6,
    "analysis_unnamed_events": 0,
    "analysis_identified_events": 6,
    "selected_events": 5,
    "usable_selected_events": 5,
    "usable_selected_sessionized_events": 5,
    "usable_selected_trace_linked_events": 5,
    "excluded_selected_events": 0,
    "history_selected_events": 3,
    "usable_history_selected_events": 3,
    "excluded_history_selected_events": 0,
    "event_name_rate": 1.0,
    "selected_identity_coverage_rate": 1.0,
    "selected_sessionization_rate": 1.0,
    "selected_trace_link_rate": 1.0,
    "history_identity_coverage_rate": 1.0,
    "limitations": [
      "Only application-supplied opaque subject IDs explicitly typed as users are counted. Anonymous, legacy-untyped, historical-unindexed, invalid, or absent subjects are excluded rather than inferred as people.",
      "new_within_observed_history means no earlier selected event between history_since and the current bucket. It does not prove that a subject is a lifetime-new user.",
      "Observed history contains exactly 2 complete fixed periods before since; activity before history_since is intentionally unknown.",
      "Buckets are fixed-width and aligned to since. hour, day, week, and thirty_day mean fixed elapsed durations; thirty_day is not a calendar month.",
      "Returning, resurrected, and dormant compare only the selected exact kind and derived event name. They do not infer intent, identity merges, or causality.",
      "This resource does not provide arbitrary property breakdowns, reusable cohorts, group lifecycle, lifetime first-event lookup, or per-subject lists."
    ]
  },
  "buckets": [
    {
      "period": 0,
      "started_at": "2026-06-01T00:00:00Z",
      "ended_at": "2026-06-02T00:00:00Z",
      "fully_observed": true,
      "active_subjects": 3,
      "new_within_observed_history_subjects": 1,
      "returning_subjects": 1,
      "resurrected_subjects": 1,
      "dormant_subjects": 1,
      "previous_active_subjects": 2,
      "new_share_of_active": 0.3333333333333333,
      "returning_share_of_active": 0.3333333333333333,
      "resurrected_share_of_active": 0.3333333333333333,
      "dormant_share_of_previous_active": 0.5,
      "net_active_change": 1
    },
    {
      "period": 1,
      "started_at": "2026-06-02T00:00:00Z",
      "ended_at": "2026-06-03T00:00:00Z",
      "fully_observed": true,
      "active_subjects": 2,
      "new_within_observed_history_subjects": 0,
      "returning_subjects": 2,
      "resurrected_subjects": 0,
      "dormant_subjects": 1,
      "previous_active_subjects": 3,
      "new_share_of_active": 0.0,
      "returning_share_of_active": 1.0,
      "resurrected_share_of_active": 0.0,
      "dormant_share_of_previous_active": 0.3333333333333333,
      "net_active_change": -1
    }
  ],
  "next_action": {
    "code": "investigate_lifecycle_loss",
    "target": "/api/telemetry/analytics/paths",
    "reason": "The latest complete bucket lost more prior active subjects than it added through new-within-observed-history and resurrected activity; compare bounded journeys around the selected event."
  }
}
```

### 라이프사이클 읽기

6월 1일에 A는 계속 활동, B는 복귀, C는 신규, D는 휴면입니다. 6월 2일에 A와 B는 계속 활동이고, C는 페이지를 봤지만 결제 기준으로는 휴면입니다. 6월 2일의 순변화는 `2 - 3 = -1`입니다.

각 시간 구간에서 신규, 계속 활동, 복귀 사용자를 더하면 `active_subjects`가 됩니다. `returning_subjects`와 `dormant_subjects`를 더하면 `previous_active_subjects`가 됩니다. `net_active_change`는 `active_subjects`에서 `previous_active_subjects`를 뺀 값입니다.

고유 사용자 수를 구하려고 일별 활성 사용자 수를 더하지 마세요. 여기서 일별 수를 더하면 5이지만 `analysis_active_subjects`는 3입니다. 기록 기간과 분석 기간을 통틀어 서로 다른 사용자 4명이 나옵니다.

`until` 뒤에 끝나는 마지막 시간 구간의 `fully_observed`는 `false`입니다. 이 구간의 개수는 전체 기간을 관측할 수 있게 되면 바뀔 수 있습니다. `next_action`은 조사를 제안할 뿐 원인을 보여 주지 않습니다.

### 자동 라이프사이클

`response_version: 2`를 보내고 `event` 선택자는 보내지 마세요. 지원되는 모든 페이지 조회, 화면 조회, 상호작용이 활동으로 집계됩니다.

```json theme={null}
{
  "project_id": "11111111-1111-4111-8111-111111111111",
  "since": "2026-06-01T00:00:00Z",
  "until": "2026-06-03T00:00:00Z",
  "response_version": 2,
  "interval": "day",
  "history_period_count": 2
}
```

응답의 `summary`, `buckets`, `next_action` 형식은 정확한 이벤트 응답과 같습니다. 이 발췌는 바뀌는 필드를 보여 줍니다.

```json theme={null}
{
  "schema_version": 2,
  "query": {
    "project_id": "11111111-1111-4111-8111-111111111111",
    "since": "2026-06-01T00:00:00Z",
    "until": "2026-06-03T00:00:00Z",
    "history_since": "2026-05-30T00:00:00Z",
    "activity_scope": "all_classified_activity",
    "interval": "day",
    "interval_seconds": 86400,
    "history_period_count": 2,
    "expected_buckets": 2
  },
  "purpose": "Classifies typed opaque user subjects by any supported classified product activity in each fixed period. It does not choose or infer a top event. new_within_observed_history, returning, resurrected, and dormant remain disjoint; anonymous and unidentified subjects are excluded from user counts, incomplete buckets are marked, and raw identifiers are never returned."
}
```

자동 모드에서는 `coverage.selected_events`가 `coverage.analysis_classified_events`와 같습니다. `limitations`의 다섯 번째 항목은 `Returning, resurrected, and dormant compare any supported classified product activity; no event is ranked, selected, or inferred.`입니다.

### 사용자가 없는 라이프사이클

분석 기간과 기록 기간 모두에 사용할 수 있는 사용자가 없으면 `buckets`는 비어 있고 `summary`에서 `latest_fully_observed_period`를 생략합니다.

```json theme={null}
{
  "schema_version": 2,
  "summary": {
    "observed_subjects": 0,
    "analysis_active_subjects": 0,
    "history_only_subjects": 0,
    "returned_buckets": 0,
    "fully_observed_buckets": 0,
    "buckets_with_resurrection": 0,
    "buckets_with_dormancy": 0
  },
  "buckets": []
}
```

분석 기간에 활동이 없어도, 기록 기간에만 나오는 사용자가 있으면 휴면 사용자가 있는 시간 구간이 생길 수 있습니다.

### 라이프사이클 오류

[공통 범위 오류](#shared-scope-errors) 외에도 다음 요청은 `422`와 `validation_failed`를 반환합니다.

| 원인 | `error` |
| - | - |
| 버전 1인데 `event`가 없음, 버전 2인데 `event`가 있음, 또는 다른 버전 | `product analytics lifecycle activity selection is contradictory` |
| `history_period_count`가 `2`\~`31`이 아님 | `product analytics lifecycle history_period_count must be between 2 and 31` |
| 기록 기간의 합계가 62일보다 김 | `product analytics lifecycle history must not exceed 62 days` |
| 분석 시간 구간이 100개보다 많음 | `product analytics lifecycle interval would exceed 100 analysis buckets` |

예를 들어 `week` 기록 기간 10개에는 70일이 필요합니다.

```json theme={null}
{
  "error": "product analytics lifecycle history must not exceed 62 days",
  "code": "validation_failed",
  "next": "use fewer history periods or a narrower fixed interval",
  "next_action": { "code": "fix_request", "target": "request" }
}
```

### 선택한 분석 범위 읽기

정확한 이벤트 응답은 `schema_version: 1`입니다. 이 응답의 `query`는 선택자를 그대로 돌려주고 `activity_scope`를 생략합니다. 자동 응답은 `schema_version: 2`와 `query.activity_scope: "all_classified_activity"`를 가지며 선택자를 생략합니다. 수집된 활동이 바뀌지 않아도 선택자가 다르면 결과가 바뀔 수 있습니다.

## 메트릭 시계열 응답

`GET /api/telemetry/metrics/series`는 프로젝트 1개에서 정확한 메트릭 이름 1개를 읽습니다. 이름은 `GET /api/telemetry/metrics`에서 복사하세요. 다음 요청은 델타 카운터를 서비스별로 묶습니다.

```bash theme={null}
curl --get "https://api.logbrew.co/api/telemetry/metrics/series" \
  --header "Authorization: Bearer $LOGBREW_ACCOUNT_BEARER" \
  --data-urlencode "project_id=<project_id>" \
  --data-urlencode "name=jobs.completed" \
  --data-urlencode "since=2026-06-01T12:00:00Z" \
  --data-urlencode "until=2026-06-01T12:15:00Z" \
  --data-urlencode "interval=5m" \
  --data-urlencode "group_by=service_name"
```

### 메트릭 시계열 성공

이 가상 응답에는 델타 카운터 시계열 1개가 들어 있습니다. 첫 번째 시간 구간에 값이 `2`, `3`, `5`인 샘플 3개가 있습니다. 마지막 시간 구간에 값이 `1`, `4`인 샘플 2개가 있습니다. 가운데 시간 구간에는 샘플이 없으므로 응답에도 그 구간의 포인트가 없습니다.

```json theme={null}
{
  "schema_version": 1,
  "query": {
    "project_id": "11111111-1111-4111-8111-111111111111",
    "name": "jobs.completed",
    "since": "2026-06-01T12:00:00Z",
    "until": "2026-06-01T12:15:00Z",
    "interval": "5m",
    "interval_seconds": 300,
    "group_by": "service_name",
    "series_limit": 10
  },
  "purpose": "Shows how one metric changes over time without changing its meaning: gauges use sampled state, delta counters use change volume and rate, histograms use distributions, and cumulative streams stay raw when reset-safe change is ambiguous.",
  "coverage": {
    "samples": 5,
    "series": 1,
    "returned_series": 1,
    "points": 2,
    "expected_buckets_per_series": 3,
    "first_seen_at": "2026-06-01T12:00:10Z",
    "last_seen_at": "2026-06-01T12:14:50Z",
    "truncated": false
  },
  "series": [
    {
      "identity": {
        "kind": "counter",
        "unit": "{job}",
        "temporality": "delta",
        "group_by": "service_name",
        "group_value": "checkout-worker"
      },
      "status": "ready",
      "aggregation": {
        "code": "delta_sum",
        "description": "Primary value is the sum of recorded changes in each bucket; rate_per_second normalizes that change by bucket width."
      },
      "sample_count": 5,
      "points": [
        {
          "bucket_start": "2026-06-01T12:00:00Z",
          "bucket_end": "2026-06-01T12:05:00Z",
          "sample_count": 3,
          "value": 10.0,
          "last": 5.0,
          "min": 2.0,
          "max": 5.0,
          "average": 3.3333333333333335,
          "sum": 10.0,
          "rate_per_second": 0.03333333333333333,
          "trace_exemplars": ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
        },
        {
          "bucket_start": "2026-06-01T12:10:00Z",
          "bucket_end": "2026-06-01T12:15:00Z",
          "sample_count": 2,
          "value": 5.0,
          "last": 4.0,
          "min": 1.0,
          "max": 4.0,
          "average": 2.5,
          "sum": 5.0,
          "rate_per_second": 0.016666666666666666,
          "trace_exemplars": []
        }
      ]
    }
  ],
  "next_action": {
    "code": "inspect_metric_change",
    "target": "trace_summary",
    "reason": "Open a trace exemplar from an unusual bucket to connect the metric change to spans, errors, logs, and release context."
  }
}
```

### 메트릭 시계열 읽기

첫 번째 포인트는 샘플 10개가 아니라 완료된 작업 10개를 기록합니다. `rate_per_second`는 그 변화량을 300초 시간 구간으로 나눈 값입니다. `last`는 합계가 아니라 마지막으로 기록된 변화량입니다. [메트릭 집계의 의미](/ko/concepts/telemetry#read-the-metric-value)를 참고하세요.

시계열의 종류와 temporality에 따라 `aggregation.code`와 각 포인트의 통계가 정해집니다. 포인트는 해당하지 않는 통계를 생략합니다. 없는 통계는 0이 아닙니다.

| 종류와 temporality | `status` | `aggregation.code` | `value` | 다른 통계 |
| - | - | - | - | - |
| `gauge`, `instant` | `ready` | `gauge_last` | 마지막 샘플 | `last`, `min`, `max`, `average` |
| `counter`, `delta` | `ready` | `delta_sum` | 변화량의 합 | `last`, `min`, `max`, `average`, `sum`, `rate_per_second` |
| `histogram`, `delta` | `ready` | `distribution_p95` | p95 근사치 | `min`, `max`, `average`, `sum`, `p50`, `p95`, `p99` |
| 모든 종류, `cumulative` | `limited` | `raw_cumulative_last` | 마지막 원시 샘플 | `last`, `min`, `max` |
| 그 밖의 조합 | `limited` | `raw_last` | 마지막 원시 샘플 | `last`, `min`, `max` |

`limited`는 오류가 아니라 성공한 응답입니다. 여기에는 LogBrew가 원시 값만 보여 주는 이유를 설명하는 `aggregation.limitation`이 추가됩니다. `ready`라고 해서 수집이 완전하다는 것이 증명되지는 않습니다.

차트를 그리기 전에 `coverage`를 읽으세요.

* `samples`는 일치하는 모든 시계열의 샘플을 셉니다. 응답에 반환되지 않은 시계열도 포함합니다.
* `series`는 일치하는 시계열을 셉니다. `returned_series`와 `points`는 반환된 데이터를 셉니다.
* `series_limit` 때문에 양이 적은 시계열이 빠졌으면 `truncated`는 `true`입니다.
* `expected_buckets_per_series`는 기간 안에 있을 수 있는 시간 구간의 수입니다.

샘플이 없는 시간 구간에 포인트를 추가하지 마세요. 0을 넣거나 이전 값을 복사하지 마세요. 각 포인트는 `trace_exemplars`를 최대 3개 반환합니다. 트레이스 예시는 샘플 1개를 트레이스에 연결합니다. 시간 구간의 모든 샘플을 설명하지는 않습니다. 트레이스 예시가 있는 포인트가 없으면 `next_action.target`은 `metric_samples`입니다.

### 메트릭 시계열 범위와 오류

`project_id`, `name`, `since`는 필수입니다. `name`은 최대 200자입니다. `interval`은 `auto`, `1m`, `5m`, `15m`, `1h`, `6h`, `1d` 중 하나입니다. 기본값은 `auto`이며, 시간 구간이 300개 이하인 가장 작은 간격을 선택합니다. 직접 지정한 간격에는 시간 구간이 500개보다 많이 필요하면 안 됩니다.

`group_by`는 `none`, `service_name`, `release`, `environment` 중 하나입니다. 기본값은 `none`입니다. 그룹화하지 않아도 종류, 단위, temporality가 다르면 별도의 시계열로 나뉩니다. `series_limit`에는 `1`\~`20`을 쓸 수 있습니다. 기본값은 `10`입니다.

결과가 비어 있으면 `series: []`, `samples` 0과 함께 `200`을 반환합니다. `coverage` 객체에서 `first_seen_at`과 `last_seen_at`을 생략합니다. 그렇다고 애플리케이션이 그 메트릭을 보낸 적이 없다는 것이 증명되지는 않습니다. 프로젝트, 메트릭 이름, 기간을 확인하세요.

`name`을 생략하면 `422`를 반환합니다.

```json theme={null}
{
  "error": "metric series name is required",
  "code": "validation_failed",
  "next": "send the exact metric name returned by /api/telemetry/metrics",
  "next_action": { "code": "fix_request", "target": "request" }
}
```

7일 동안 1분 단위 시간 구간을 요청하면 `422`를 반환합니다.

```json theme={null}
{
  "error": "metric series interval would exceed 500 buckets",
  "code": "validation_failed",
  "next": "use interval=auto or select a wider interval",
  "next_action": { "code": "fix_request", "target": "request" }
}
```

다른 `422` 오류로는 `metric series group_by is not supported`, `metric series interval is not supported`, `metric series_limit must be between 1 and 20`이 있습니다.

## 경로 분석 응답

`POST /api/telemetry/analytics/paths`는 같은 세션에서 선택한 이벤트 1개의 앞이나 뒤에 있는, 이름이 있는 이벤트를 찾습니다. 선택한 이벤트가 기준 이벤트입니다. 기준 이벤트의 `kind`와 `event_name`은 `classified_activity.top_events`에서 복사하세요. 이 라우트는 `schema_version: 1`을 반환합니다. `response_version`이나 `analysis_unit`은 받지 않습니다.

```json theme={null}
{
  "project_id": "11111111-1111-4111-8111-111111111111",
  "since": "2026-06-01T11:00:00Z",
  "until": "2026-06-01T12:00:00Z",
  "direction": "following",
  "anchor": { "kind": "page_view", "event_name": "/checkout" },
  "depth": 2,
  "collapse_repeated": true,
  "path_limit": 2
}
```

`direction`과 `anchor`는 필수입니다.

### 경로 성공

이 가상 결과에서는 세션 4개에 기준 이벤트가 있습니다. 반환된 경로 2개는 그중 세션 3개를 나타냅니다. 순위가 낮은 경로 1개는 반환되지 않습니다.

```json theme={null}
{
  "schema_version": 1,
  "query": {
    "project_id": "11111111-1111-4111-8111-111111111111",
    "since": "2026-06-01T11:00:00Z",
    "until": "2026-06-01T12:00:00Z",
    "direction": "following",
    "anchor": { "kind": "page_view", "event_name": "/checkout" },
    "depth": 2,
    "collapse_repeated": true,
    "path_limit": 2
  },
  "purpose": "Shows the highest-volume named product-event sequences immediately before or after one exact version-1 page view, screen view, or interaction inside explicit sessions. Following paths use the earliest matching anchor in each session; preceding paths use the latest. Optional exact property predicates classify only the anchor, while surrounding events preserve the real session journey. Results aggregate exact bounded sequences and never return raw session or subject identifiers.",
  "summary": {
    "anchored_sessions": 4,
    "represented_sessions": 3,
    "unrepresented_sessions": 1,
    "returned_paths": 2,
    "paths_truncated": true
  },
  "coverage": {
    "classified_events": 12,
    "named_events": 12,
    "unnamed_events": 0,
    "sessionized_events": 10,
    "unsessionized_events": 2,
    "anchor_events": 5,
    "usable_anchor_events": 4,
    "excluded_anchor_events": 1,
    "traced_anchor_events": 1,
    "event_name_rate": 1.0,
    "sessionization_rate": 0.8333333333333334,
    "anchor_session_coverage_rate": 0.8,
    "anchor_trace_link_rate": 0.2,
    "ordered_event_cap_per_session": 1024,
    "limitations": [
      "Paths include only supported version-1 classified events with exact derived names; arbitrary actions, technical spans, and raw identity values are excluded.",
      "Paths stay inside application-supplied session boundaries and the selected half-open time range; activity beyond either boundary is not inferred.",
      "Each session retains at most 1024 direction-nearest named events before anchoring, so exceptionally noisy sessions can be omitted or truncated.",
      "Classified events without an explicit opaque session ID cannot participate in session paths.",
      "Trace exemplars prove only that a matching anchor carried the same trace ID. They do not prove root cause, and deeper investigation depends on retained spans for that trace.",
      "Some matching anchors lack trace IDs, so trace-linked path evidence covers only the reported traced_anchor_events population."
    ]
  },
  "paths": [
    {
      "rank": 1,
      "sessions": 2,
      "share_of_anchored_sessions": 0.5,
      "traced_sessions": 1,
      "trace_link_rate": 0.5,
      "trace_exemplars": ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"],
      "nodes": [
        { "relative_position": 0, "kind": "page_view", "event_name": "/checkout" },
        { "relative_position": 1, "kind": "interaction", "event_name": "checkout.submitted" },
        { "relative_position": 2, "kind": "interaction", "event_name": "checkout.completed" }
      ]
    },
    {
      "rank": 2,
      "sessions": 1,
      "share_of_anchored_sessions": 0.25,
      "traced_sessions": 0,
      "trace_link_rate": 0.0,
      "trace_exemplars": [],
      "nodes": [
        { "relative_position": 0, "kind": "page_view", "event_name": "/checkout" },
        { "relative_position": 1, "kind": "interaction", "event_name": "checkout.failed" }
      ]
    }
  ],
  "next_action": {
    "code": "inspect_path_trace",
    "target": "/api/telemetry/traces/{trace_id}/investigation",
    "reason": "Open an exemplar from the material path to connect the product journey to retained spans, issues, logs, actions, metrics, and release context as evidence rather than a root-cause claim."
  }
}
```

### 경로 읽기

`share_of_anchored_sessions`는 경로의 세션 수를 기준 이벤트가 있는 세션 4개 전체로 나눈 값입니다. 비율은 67%와 33%가 아니라 50%와 25%입니다. `unrepresented_sessions`는 기준 이벤트가 있는 세션 중 반환된 어느 경로에도 나타나지 않는 세션을 셉니다. `paths_truncated: true`는 결과가 완전하지 않다는 뜻입니다.

커버리지는 세션이 아니라 이벤트를 셉니다. 여기서는 이벤트 5개가 기준 이벤트와 일치하지만, 그중 1개에 세션 ID가 없습니다. 이 이벤트는 경로에 들어갈 수 없습니다. 각 경로는 트레이스 예시를 최대 3개 반환합니다. 직접 받은 응답에 있는 트레이스 ID를 사용하세요. 트레이스 링크는 사용자 액션이나 실패의 원인을 보여 주지 않습니다.

| 필드 | 의미 |
| - | - |
| `following` | 각 세션에서 가장 먼저 일치하는 기준 이벤트에서 시작합니다. 기준 이벤트의 위치는 `0`입니다. 뒤의 노드는 양수 위치를 가집니다. |
| `preceding` | 각 세션에서 가장 마지막에 일치하는 기준 이벤트에서 끝납니다. 앞의 노드는 음수 위치를 가집니다. 기준 이벤트의 위치는 `0`입니다. |
| `depth` | 기준 이벤트를 빼고 그 옆에 오는 노드 수입니다. `1`\~`8`이며 기본값은 `4`입니다. 경로의 노드가 이보다 적을 수 있습니다. |
| `collapse_repeated` | 종류와 이름이 같은 연속 노드를 하나로 합칩니다. 기본값은 `true`입니다. |
| `path_limit` | 반환할 경로의 최대 수이며, 세션 수 순으로 순위를 매깁니다. `1`\~`20`이며 기본값은 `10`입니다. |

두 방향 모두 노드는 시간 순서입니다. 경로는 세션 전체를 재생한 것이 아닙니다. LogBrew는 기준 이벤트를 선택하기 전에 세션마다 이름이 있는 이벤트를 최대 1024개 읽습니다. `following`에서는 가장 이른 이벤트를, `preceding`에서는 가장 늦은 이벤트를 읽습니다. 이 한도 밖에 있는 기준 이벤트는 빠질 수 있습니다.

### <span id="filter-the-anchor-by-property">속성으로 기준 이벤트 필터링하기</span>

`property_filters`를 최대 4개 보내세요. 각 필터에는 정확한 `key`와 `value`가 1개씩 들어갑니다. 키는 [속성 카탈로그](#analytics-property-catalog)에서 복사하세요. 필터는 기준 이벤트에만 적용되고 그 주변 이벤트에는 적용되지 않습니다. 값은 대소문자를 구분합니다. 필터가 있으면 `query`가 필터를 그대로 돌려주고, `coverage.anchor_property_filters`가 다음 값을 보고합니다.

| 필드 | 의미 |
| - | - |
| `context_events` | 속성 필터를 적용하기 전, 범위 안의 기준 이벤트입니다. |
| `property_ready_events` | 요청한 키를 모두 가진 기준 이벤트입니다. |
| `missing_property_events` | 요청한 키 중 하나 이상이 없는 기준 이벤트입니다. |
| `matching_events` | 값이 모든 필터와 일치하는 기준 이벤트입니다. |
| `nonmatching_value_events` | 키는 모두 있지만 값이 다른 기준 이벤트입니다. |
| `property_ready_rate` | `property_ready_events`를 `context_events`로 나눈 값입니다. |
| `match_rate` | `matching_events`를 `property_ready_events`로 나눈 값입니다. |

없는 키와 다른 값은 고치는 방법이 다릅니다. 둘을 합치지 마세요. 요청에 속성 필터가 없으면 이 객체를 생략합니다.

### 빈 경로와 거부된 요청

빈 `paths` 배열도 성공한 `200` 응답일 수 있습니다. `summary`, `coverage`, `next_action`을 읽으세요. 다음 코드로 원인을 구분합니다. `capture_product_activity`, `choose_captured_path_anchor`, `capture_anchor_properties`, `improve_anchor_property_coverage`, `verify_anchor_property_values`, `sessionize_product_activity`, `narrow_or_move_path_anchor`.

[공통 범위 오류](#shared-scope-errors) 외에도 다음 요청은 `422`와 `validation_failed`를 반환합니다.

| 원인 | `error` |
| - | - |
| `depth`가 `1`\~`8`이 아님 | `product analytics path depth must be between 1 and 8` |
| `path_limit`이 `1`\~`20`이 아님 | `product analytics path_limit must be between 1 and 20` |
| 속성 필터가 4개보다 많음 | `product analytics paths accept at most four exact anchor property filters` |
| 속성 카탈로그에 없는 키 | `product analytics path property keys must be supported privacy-safe keys` |
| 같은 키가 두 번 나옴 | `product analytics path property keys must be unique` |
| 빈 값, 256자보다 긴 값, 또는 제어 문자 | `product analytics path property values must be non-empty, control-free, and at most 256 characters` |

예를 들어 `depth: 0`은 다음을 반환합니다.

```json theme={null}
{
  "error": "product analytics path depth must be between 1 and 8",
  "code": "validation_failed",
  "next": "use depth from 1 through 8 adjacent named events",
  "next_action": { "code": "fix_request", "target": "request" }
}
```

## <span id="analytics-property-catalog">분석 속성 카탈로그</span>

`GET /api/telemetry/analytics/properties`는 프로젝트 1개에서 분류된 제품 이벤트에 있는 속성 키를 나열합니다. 이 키는 [세그먼트 비교](#analytics-segment-comparison)와 경로 기준 이벤트 필터에 사용하세요. 카탈로그는 키와 개수를 반환합니다. 속성 값이나 식별 정보는 절대 반환하지 않습니다.

### 사용할 수 있는 속성 키 읽기

`project_id`와 `since`를 보내세요. `until`, `service_name`, `release`, `environment`, `limit`, `response_version`도 보낼 수 있습니다. `limit`에는 키 `1`\~`50`개를 쓸 수 있습니다. 기본값은 `20`입니다. 공개 `estimation.method` 값을 받으려면 `response_version=2`를 보내세요.

```bash theme={null}
curl --get "https://api.logbrew.co/api/telemetry/analytics/properties" \
  --header "Authorization: Bearer $LOGBREW_ACCOUNT_BEARER" \
  --data-urlencode "project_id=<project_id>" \
  --data-urlencode "since=2026-06-01T00:00:00Z" \
  --data-urlencode "until=2026-06-02T00:00:00Z" \
  --data-urlencode "limit=20" \
  --data-urlencode "response_version=2"
```

### 속성 카탈로그 성공

이 가상 결과에는 분류된 이벤트 100개가 있습니다. 그중 5개는 현재 속성 인덱스가 생기기 전에 수집되었습니다.

```json theme={null}
{
  "schema_version": 2,
  "query": {
    "project_id": "11111111-1111-4111-8111-111111111111",
    "since": "2026-06-01T00:00:00Z",
    "until": "2026-06-02T00:00:00Z",
    "limit": 20
  },
  "purpose": "Discovers bounded privacy-safe runtime, framework, operating-system, device, application, and custom-tag keys captured on classified product events. It reports migration, capture, privacy-filtering, and per-key coverage without returning property values or identities, so humans and agents can build exact segment comparisons without guessing which dimensions exist.",
  "summary": {
    "classified_events": 100,
    "available_property_keys": 3,
    "returned_properties": 3
  },
  "coverage": {
    "indexed_events": 95,
    "unindexed_events": 5,
    "complete_index_events": 95,
    "incomplete_index_events": 0,
    "privacy_filtered_events": 4,
    "events_with_properties": 90,
    "index_coverage_rate": 0.95,
    "property_capture_rate": 0.9,
    "properties_truncated": false,
    "values_returned": false,
    "limitations": [
      "Property discovery returns keys and aggregate counts only; property values and identity columns are never returned.",
      "Property filters use exact case-sensitive bounded string equality and combine with logical AND inside each segment.",
      "Historical events written before the current property index are excluded from property descriptors and exact property filters.",
      "Direct-identity, credential-like, and network-address custom tag keys were intentionally excluded from the analytics property index."
    ]
  },
  "estimation": {
    "count_accuracy": "approximate",
    "method": "approximate_distinct",
    "description": "Available property-key and per-key distinct-value counts are approximate distinct counts. Classified-event, index-coverage, privacy-filtering, and per-key event counts are exact within the selected fully evaluated window."
  },
  "properties": [
    {
      "key": "resource.runtime.name",
      "source": "standard_context",
      "value_type": "string",
      "events": 90,
      "coverage_rate": 0.9,
      "distinct_values": 2
    },
    {
      "key": "resource.application.version",
      "source": "standard_context",
      "value_type": "string",
      "events": 70,
      "coverage_rate": 0.7,
      "distinct_values": 4
    },
    {
      "key": "tag.plan",
      "source": "custom_tag",
      "value_type": "string",
      "events": 60,
      "coverage_rate": 0.6,
      "distinct_values": 3
    }
  ],
  "next_action": {
    "code": "compare_property_segments",
    "target": "/api/telemetry/analytics/segments/compare",
    "reason": "Use exact returned keys with locally known values to compare behavior while preserving identity and property-value privacy."
  }
}
```

### 카탈로그 해석하기

`source`는 `resource.runtime.name` 같은 표준 컨텍스트 키이면 `standard_context`이고, `tag.`로 시작하는 키이면 `custom_tag`입니다. `value_type`은 항상 `string`입니다. `properties`는 `events`가 많은 순서로 정렬됩니다.

`tag.plan`은 분류된 이벤트 100개 중 60개에 있습니다. 서로 다른 값은 약 3개입니다. 키 설명에는 그 값이 나오지 않습니다. 애플리케이션에서 알고 있는 값을 사용하세요. 개수를 보고 값을 추측하지 마세요. 이벤트 1개에 키가 여러 개 있을 수 있으므로 키별 `events` 개수를 더하지 마세요.

| 필드 | 의미 |
| - | - |
| `summary.classified_events` | 범위 안의 분류된 이벤트입니다. 각 `coverage_rate`의 분모입니다. |
| `summary.available_property_keys` | 서로 다른 키의 추정 개수입니다. 반환된 행 수가 아닙니다. |
| `coverage.unindexed_events` | 현재 속성 인덱스가 생기기 전에 수집된 이벤트입니다. 이 이벤트는 키에 반영되지 않습니다. |
| `coverage.incomplete_index_events` | 컨텍스트 일부가 잘못되었거나 잘린 이벤트입니다. 이 이벤트의 유효한 키는 그대로 셉니다. |
| `coverage.privacy_filtered_events` | LogBrew가 민감한 사용자 지정 태그 키를 제거한 이벤트입니다. 키 개수가 아닙니다. |
| `coverage.properties_truncated` | 키가 `limit`보다 많으면 `true`입니다. 범위를 좁히거나 `limit`을 최대 50까지 늘리세요. |
| `coverage.values_returned` | 항상 `false`입니다. |

`properties` 배열이 비어 있다고 해서 프로젝트에 제품 이벤트가 없다는 것이 증명되지는 않습니다. `summary`, `coverage`, `next_action`을 읽으세요.

### 속성 카탈로그 오류

[공통 범위 오류](#shared-scope-errors) 외에도 `limit=0`이나 `limit=51`은 `422`를 반환합니다.

```json theme={null}
{
  "error": "product analytics property limit must be between 1 and 50",
  "code": "validation_failed",
  "next": "use limit=1 through limit=50",
  "next_action": { "code": "fix_request", "target": "request" }
}
```

`response_version`이 `1`이나 `2`가 아니면 `product analytics response_version must be 1 or 2`를 반환합니다. LogBrew는 다른 값보다 `response_version`을 먼저 확인합니다.

`POST` 요청은 `405`를 반환합니다.

```json theme={null}
{
  "error": "Method not allowed",
  "code": "method_not_allowed",
  "next": "send GET /api/telemetry/analytics/properties with required project_id and since plus optional until, service_name, release, environment, limit, or response_version query parameters",
  "next_action": { "code": "use_supported_method", "target": "api_method" }
}
```

## <span id="analytics-segment-comparison">분석 세그먼트 비교</span>

`POST /api/telemetry/analytics/segments/compare`는 프로젝트 1개에서 세그먼트 2\~4개에 걸쳐 세션이나 사용자가 정확한 이벤트 1개에 얼마나 자주 도달하는지 비교합니다. 세그먼트는 서비스, 릴리스, 환경, 속성 값으로 이벤트를 선택합니다. 첫 번째 세그먼트가 기준입니다.

### 비교 정의하기

계정 인증과 `Content-Type: application/json`으로 다음 JSON 본문을 보내세요. 여기서 `basic`과 `premium`은 예시 애플리케이션의 요금제 값이며 LogBrew 요금제가 아닙니다.

```json theme={null}
{
  "project_id": "11111111-1111-4111-8111-111111111111",
  "since": "2026-06-01T11:00:00Z",
  "until": "2026-06-01T12:00:00Z",
  "interval": "1h",
  "analysis_unit": "session",
  "response_version": 2,
  "target": { "kind": "interaction", "event_name": "checkout_completed" },
  "segments": [
    {
      "key": "basic",
      "label": "Basic plan",
      "service_name": "checkout-api",
      "environment": "production",
      "property_filters": [{ "key": "tag.plan", "value": "basic" }]
    },
    {
      "key": "premium",
      "label": "Premium plan",
      "service_name": "checkout-api",
      "environment": "production",
      "property_filters": [{ "key": "tag.plan", "value": "premium" }]
    }
  ]
}
```

`target`과 `segments`는 필수입니다. `analysis_unit`은 `session` 또는 `identified_user`입니다. 기본값은 `session`이며, 사람이 아니라 세션을 셉니다. `interval`에는 개요와 같은 값을 쓸 수 있습니다. 기본값은 `auto`입니다.

각 세그먼트에는 고유한 `key`, `label`, 그리고 다른 세그먼트와 다른 필터 조합이 필요합니다.

* `key`: 1\~32바이트입니다. 소문자 ASCII 문자나 숫자로 시작하세요. 소문자 ASCII 문자, 숫자, `_`, `-`만 사용하세요.
* `label`: 제어 문자 없이 1\~80자입니다.
* `service_name`, `release`, `environment`: 선택 사항입니다. 각 값은 제어 문자 없이 1\~256자입니다.
* `property_filters`: 필터를 0개부터 4개까지 쓸 수 있습니다. 한 세그먼트에서 각 키는 한 번만 쓸 수 있습니다. 이벤트는 세그먼트의 모든 필터와 일치해야 합니다. 값은 대소문자를 구분하고 1\~256자이며, LogBrew가 앞뒤 공백을 없앱니다.

필터가 없는 세그먼트는 프로젝트와 기간 안의 분류된 활동 전체를 선택합니다. 세그먼트는 서로 겹칠 수 있습니다. 레이블이나 키가 달라도 필터 조합이 같은 두 세그먼트는 허용되지 않습니다.

### 세그먼트 비교 성공

이 가상 결과에서 기준 세그먼트는 세션 50개 중 10개에서 결제에 도달합니다. premium 세그먼트는 세션 60개 중 18개에서 도달합니다.

```json theme={null}
{
  "schema_version": 2,
  "query": {
    "project_id": "11111111-1111-4111-8111-111111111111",
    "since": "2026-06-01T11:00:00Z",
    "until": "2026-06-01T12:00:00Z",
    "interval": "1h",
    "interval_seconds": 3600,
    "analysis_unit": "session",
    "target": { "kind": "interaction", "event_name": "checkout_completed" },
    "segments": [
      {
        "key": "basic",
        "label": "Basic plan",
        "service_name": "checkout-api",
        "release": null,
        "environment": "production",
        "property_filters": [{ "key": "tag.plan", "value": "basic" }]
      },
      {
        "key": "premium",
        "label": "Premium plan",
        "service_name": "checkout-api",
        "release": null,
        "environment": "production",
        "property_filters": [{ "key": "tag.plan", "value": "premium" }]
      }
    ]
  },
  "purpose": "Compares how often explicit sessions or typed opaque user subjects reach one exact versioned page-view, screen-view, or interaction outcome across two through four named service, release, environment, and exact safe-property segments. It distinguishes missing property capture from nonmatching values, returns time trends and descriptive differences from the first segment, and never returns identities or claims causality or statistical significance.",
  "summary": {
    "segment_count": 2,
    "baseline_segment_key": "basic",
    "segments_with_eligible_units": 2,
    "segments_with_target_events": 2,
    "segments_with_reached_units": 2
  },
  "confidence": {
    "interpretation": "descriptive_only",
    "unique_count_accuracy": "approximate",
    "estimation_method": "approximate_distinct",
    "causal_inference": "not_established",
    "statistical_significance": "not_tested",
    "segment_overlap": "possible",
    "limitations": [
      "Unique session or typed user-subject counts use bounded cardinality estimates; event and capture-coverage totals are exact within the fully evaluated window.",
      "Segments are evaluated independently and may overlap, so their totals must not be added or interpreted as a population partition.",
      "Differences and relative lift are descriptive only. They do not control for exposure, assignment, seasonality, selection bias, or other confounders.",
      "Bucket-level unique counts are recalculated inside each bucket and are not additive across the series.",
      "Session analysis compares visits or app sessions, not unique people.",
      "At least one segment has classified events without the selected unit ID; compare unit_coverage_rate before interpreting reach differences.",
      "Property predicates are exact case-sensitive string matches. Events without the current property index or every requested key are excluded from segment results and reported separately as missing_property_events.",
      "Direct-identity, credential-like, and network-address custom tag keys are excluded from the analytics property index and cannot be used for segmentation."
    ]
  },
  "segments": [
    {
      "key": "basic",
      "label": "Basic plan",
      "eligible_units": 50,
      "reached_units": 10,
      "reach_rate": 0.2,
      "usable_target_events_per_reached_unit": 1.8,
      "coverage": {
        "classified_events": 200,
        "unit_identified_events": 190,
        "excluded_events": 10,
        "unit_coverage_rate": 0.95,
        "target_events": 20,
        "usable_target_events": 18,
        "excluded_target_events": 2,
        "target_unit_coverage_rate": 0.9,
        "traced_target_events": 10,
        "target_trace_link_rate": 0.5,
        "property_filters": {
          "context_events": 500,
          "property_ready_events": 480,
          "missing_property_events": 20,
          "property_ready_rate": 0.96,
          "matching_events": 200,
          "nonmatching_value_events": 280,
          "match_rate": 0.4166666666666667
        }
      },
      "series": [
        {
          "bucket_start": "2026-06-01T11:00:00Z",
          "bucket_end": "2026-06-01T12:00:00Z",
          "classified_events": 200,
          "eligible_units": 50,
          "target_events": 20,
          "usable_target_events": 18,
          "reached_units": 10,
          "reach_rate": 0.2
        }
      ]
    },
    {
      "key": "premium",
      "label": "Premium plan",
      "eligible_units": 60,
      "reached_units": 18,
      "reach_rate": 0.3,
      "usable_target_events_per_reached_unit": 1.8888888888888888,
      "coverage": {
        "classified_events": 240,
        "unit_identified_events": 228,
        "excluded_events": 12,
        "unit_coverage_rate": 0.95,
        "target_events": 36,
        "usable_target_events": 34,
        "excluded_target_events": 2,
        "target_unit_coverage_rate": 0.9444444444444444,
        "traced_target_events": 18,
        "target_trace_link_rate": 0.5,
        "property_filters": {
          "context_events": 500,
          "property_ready_events": 480,
          "missing_property_events": 20,
          "property_ready_rate": 0.96,
          "matching_events": 240,
          "nonmatching_value_events": 240,
          "match_rate": 0.5
        }
      },
      "series": [
        {
          "bucket_start": "2026-06-01T11:00:00Z",
          "bucket_end": "2026-06-01T12:00:00Z",
          "classified_events": 240,
          "eligible_units": 60,
          "target_events": 36,
          "usable_target_events": 34,
          "reached_units": 18,
          "reach_rate": 0.3
        }
      ],
      "comparison_to_baseline": {
        "eligible_units_difference": 10,
        "reached_units_difference": 8,
        "target_events_difference": 16,
        "reach_rate_difference": 0.09999999999999998,
        "relative_reach_rate_lift": 0.4999999999999998
      }
    }
  ],
  "next_action": {
    "code": "investigate_segment_paths",
    "target": "/api/telemetry/analytics/paths",
    "reason": "Inspect paths around the target in the weakest segment, then use correlated traces to explain a material behavioral difference."
  }
}
```

### 비율과 차이 읽기

`eligible_units`는 세그먼트에서 분류된 활동이 있는 세션이나 사용자의 추정 수입니다. `reached_units`는 그중 대상 이벤트에 도달한 수의 추정치입니다. `reach_rate`는 `reached_units`를 `eligible_units`로 나눈 값입니다. 선택한 단위의 ID가 없는 이벤트는 이 단위 수에 들어가지 않습니다.

기준 비율은 20%입니다. premium 비율은 30%입니다. 차이는 10%포인트입니다. 상대 상승률은 10%가 아니라 50%입니다. 작은 소수점 오차는 부동소수점 연산 때문에 생깁니다. 표시할 때는 비율을 반올림하되, 계산에는 반환된 값을 사용하세요. 기준 세그먼트에는 `comparison_to_baseline`이 없습니다. 기준 도달률이 0이면 상대 상승률을 생략합니다.

`query`에서 보내지 않은 세그먼트 필터는 `null`입니다. 빈 `property_filters` 목록은 생략합니다.

`coverage.property_filters`에는 [경로 기준 이벤트 필터 커버리지](#filter-the-anchor-by-property)와 같은 필드가 있습니다. 여기서는 `matching_events`가 `classified_events`와 같습니다. 없는 키와 다른 값은 따로 다루세요. 세그먼트는 서로 겹칠 수 있고, 세션 1개가 여러 시간 구간에 나올 수 있습니다. 세그먼트나 시간 구간에 걸쳐 단위 수를 더하지 마세요.

이 비교는 현상을 기술할 뿐입니다. `confidence.causal_inference`는 `not_established`이고 `confidence.statistical_significance`는 `not_tested`입니다. 비율이 20% 대비 30%라고 해서 요금제 때문에 결제가 늘었다는 뜻은 아닙니다.

### 세그먼트 비교 오류

[공통 범위 오류](#shared-scope-errors) 외에도 다음 요청은 `422`와 `validation_failed`를 반환합니다.

| 원인 | `error` |
| - | - |
| 세그먼트가 2개보다 적거나 4개보다 많음 | `product analytics segment comparison requires two through four segments` |
| 두 세그먼트의 키가 같음 | `product analytics segment keys must be unique` |
| 두 세그먼트의 필터가 같음 | `product analytics segment filters must be unique` |
| 키가 허용된 1\~32바이트가 아님 | `product analytics segment keys must be 1 through 32 machine-safe bytes` |
| 레이블이 비어 있거나, 너무 길거나, 제어 문자가 있음 | `product analytics segment labels must be non-empty, control-free, and at most 80 characters` |
| 필터 값에 제어 문자가 있음 | `product analytics segment filters must be control-free` |
| 한 세그먼트에 속성 필터가 4개보다 많음 | `product analytics segments accept at most four exact property filters` |
| 속성 카탈로그에 없는 키 | `product analytics segment property keys must be supported privacy-safe keys` |
| 한 세그먼트에 같은 속성 키가 두 번 나옴 | `product analytics segment property keys must be unique within each segment` |
| 속성 값이 비어 있거나, 너무 길거나, 제어 문자가 있음 | `product analytics segment property values must be non-empty, control-free, and at most 256 characters` |
| `response_version`이 `1`이나 `2`가 아님 | `product analytics response_version must be 1 or 2` |

`interval` 오류는 [개요 오류](#overview-errors)와 같습니다.

예를 들어 키는 다르고 필터가 같은 세그먼트 2개는 다음을 반환합니다.

```json theme={null}
{
  "error": "product analytics segment filters must be unique",
  "code": "validation_failed",
  "next": "change at least one exact service_name, release, environment, or property filter",
  "next_action": { "code": "fix_request", "target": "request" }
}
```

레이블이 다르거나 속성 필터의 순서가 달라도 선택되는 이벤트는 바뀌지 않습니다. 검증 실패는 일치하는 이벤트가 없다는 근거가 아닙니다.

`GET` 요청은 `405`를 반환합니다.

```json theme={null}
{
  "error": "Method not allowed",
  "code": "method_not_allowed",
  "next": "send POST /api/telemetry/analytics/segments/compare with project_id, since, one exact target, two through four named segments, and optional until, interval, analysis_unit, property_filters, or response_version JSON fields",
  "next_action": { "code": "use_supported_method", "target": "api_method" }
}
```

## 시그널 양 응답

`GET /api/telemetry/summary/volume`은 프로젝트 1개와 기간 1개에 대해 보존된 시그널을 셉니다. `project_id`와 `since`가 필요합니다. `project_ids`는 받지 않습니다.

`until`은 미래 시각일 수 없습니다. 생략하면 요청 시각이 끝 시각이 됩니다. `24h` 같은 짧은 `since` 표기는 `until`부터 거꾸로 셉니다. `interval`은 `auto`, `1m`, `5m`, `15m`, `1h`, `6h`, `1d` 중 하나입니다. 기본값 `auto`는 시간 구간이 120개 이하인 가장 작은 간격을 선택합니다. 요청에 시간 구간이 500개보다 많이 필요하면 안 됩니다. `response_version`은 `1`만 있습니다.

### 시그널 개수

이 가상 응답은 1분 단위 시간 구간 2개를 다룹니다. 요청은 UTC 12:02에 `until` 없이 실행되었습니다. 이 계정의 보존 기간 정책은 30일입니다. 반환된 정책과 기준 시각을 읽으세요. 이 값이 내 계정에도 적용된다고 가정하지 마세요.

```json theme={null}
{
  "schema_version": 1,
  "query": {
    "project_id": "11111111-1111-4111-8111-111111111111",
    "requested_since": "2026-09-08T12:00:00Z",
    "since": "2026-09-08T12:00:00Z",
    "until": "2026-09-08T12:02:00Z",
    "interval": "1m",
    "interval_seconds": 60,
    "bucket_alignment": "query_relative_utc_half_open",
    "bucket_count": 2,
    "service_name": "checkout-api",
    "release": "api@1.2.3",
    "environment": "production"
  },
  "purpose": "Compares retained issue occurrences, log events, unique traces, action events, and metric points over one exact project and time scope. Every lane is independently scalable, uses explicit zero-filled half-open buckets, returns no source telemetry rows, and qualifies retention and sampling so different signal volumes are not mistaken for equivalent impact.",
  "series": [
    {
      "signal": "issues",
      "count_unit": "issue_occurrences",
      "total": 1,
      "peak": 1,
      "non_empty_buckets": 1,
      "points": [
        { "bucket_start": "2026-09-08T12:00:00Z", "bucket_end": "2026-09-08T12:01:00Z", "count": 1 },
        { "bucket_start": "2026-09-08T12:01:00Z", "bucket_end": "2026-09-08T12:02:00Z", "count": 0 }
      ]
    },
    {
      "signal": "logs",
      "count_unit": "log_events",
      "total": 3,
      "peak": 2,
      "non_empty_buckets": 2,
      "points": [
        { "bucket_start": "2026-09-08T12:00:00Z", "bucket_end": "2026-09-08T12:01:00Z", "count": 2 },
        { "bucket_start": "2026-09-08T12:01:00Z", "bucket_end": "2026-09-08T12:02:00Z", "count": 1 }
      ]
    },
    {
      "signal": "traces",
      "count_unit": "unique_traces_by_first_observed_span",
      "total": 1,
      "peak": 1,
      "non_empty_buckets": 1,
      "points": [
        { "bucket_start": "2026-09-08T12:00:00Z", "bucket_end": "2026-09-08T12:01:00Z", "count": 1 },
        { "bucket_start": "2026-09-08T12:01:00Z", "bucket_end": "2026-09-08T12:02:00Z", "count": 0 }
      ]
    },
    {
      "signal": "actions",
      "count_unit": "action_events",
      "total": 1,
      "peak": 1,
      "non_empty_buckets": 1,
      "points": [
        { "bucket_start": "2026-09-08T12:00:00Z", "bucket_end": "2026-09-08T12:01:00Z", "count": 1 },
        { "bucket_start": "2026-09-08T12:01:00Z", "bucket_end": "2026-09-08T12:02:00Z", "count": 0 }
      ]
    },
    {
      "signal": "metrics",
      "count_unit": "metric_points",
      "total": 2,
      "peak": 2,
      "non_empty_buckets": 1,
      "points": [
        { "bucket_start": "2026-09-08T12:00:00Z", "bucket_end": "2026-09-08T12:01:00Z", "count": 0 },
        { "bucket_start": "2026-09-08T12:01:00Z", "bucket_end": "2026-09-08T12:02:00Z", "count": 2 }
      ]
    }
  ],
  "coverage": {
    "storage_evaluation": "complete",
    "output_truncation": "not_truncated",
    "payload_shape": "aggregate_only",
    "count_precision": "exact",
    "integer_safety": "javascript_safe",
    "javascript_safe_integer_max": 9007199254740991,
    "returned_series": 5,
    "returned_points": 10,
    "max_buckets_per_series": 500,
    "retention": {
      "policy_days": 30,
      "cutoff": "2026-08-09T12:02:00Z",
      "window_clipped": false
    },
    "sampling": {
      "server": "not_applied",
      "upstream": "unknown"
    },
    "limitations": ["upstream_sampling_unknown", "release_volume_not_included"]
  }
}
```

### 각 개수를 해당 단위로 읽기

| 시그널 | `total`이 세는 것 | 이렇게 읽지 마세요 |
| - | - | - |
| `issues` | 보존된 이슈 발생 건 | 그룹화된 이슈 수나 영향을 받은 사용자 수 |
| `logs` | 보존된 로그 이벤트 | 요청 수 |
| `traces` | 이 기간에 처음 보존된 스팬을 기준으로 센 고유 트레이스 ID | 스팬 수나 전체 기간의 트레이스 시작 수 |
| `actions` | 보존된 액션 이벤트 | 사용자 수나 세션 수 |
| `metrics` | 보존된 메트릭 포인트 | 메트릭 값의 합계 |

`total`은 시계열의 시간 구간별 개수를 더한 값입니다. `peak`는 시간 구간별 개수 중 가장 큰 값입니다. `non_empty_buckets`는 개수가 0보다 큰 시간 구간을 셉니다. 시계열 5개는 항상 이 순서로 나옵니다. 서로 다른 시그널 개수를 하나의 영향 점수로 합치지 마세요.

시간 구간은 `query.since`에서 시작합니다. 각 시간 구간은 시작 시각을 포함하고 끝 시각을 포함하지 않습니다. 마지막 시간 구간은 간격보다 짧을 수 있습니다. 응답에는 릴리스 개수 시계열이 없습니다. `release_volume_not_included`를 릴리스 0개로 읽지 마세요.

### 비어 있거나 짧아진 기간

일치하는 시그널이 없어도 시계열 5개는 모두 남고, 포인트의 개수는 명시적으로 0입니다. 실패한 요청은 이와 다릅니다. 오류를 0으로 채운 차트로 바꾸지 마세요.

`query.requested_since`와 `query.since`를 비교하세요. 보존 기간 때문에 기간이 짧아지면 `coverage.retention.window_clipped`가 `true`이고 `limitations`에 `retention_window_clipped`가 들어갑니다. 요청한 기간이 하나도 남지 않으면 `query.bucket_count`가 `0`이고 각 시계열의 `points` 배열이 비어 있습니다. 그렇다고 그 기간에 이벤트가 없었다는 것이 증명되지는 않습니다.

보존 기간 한도가 적용되지 않으면 `policy_days`는 `null`이고 `cutoff`는 생략됩니다. `sampling.upstream`은 `storage_evaluation`이 `complete`일 때도 항상 `unknown`입니다. 응답이 애플리케이션에서 만든 모든 이벤트를 센다고 주장하지 마세요.

### 거부된 시그널 양 요청

다음 요청은 `422`와 `validation_failed`를 반환합니다.

| 원인 | `error` |
| - | - |
| `project_id`가 없음 | `signal volume project_id is required` |
| `since`가 없음 | `signal volume since is required` |
| `until`이 요청 시각보다 늦음 | `signal volume until must not be in the future` |
| 기간이 31일보다 김 | `signal volume range must not exceed 31 days` |
| 직접 지정한 간격에 시간 구간이 500개보다 많이 필요함 | `signal volume interval would exceed 500 buckets` |
| `response_version`이 `1`이 아님 | `signal volume response_version must be 1` |

예시는 다음과 같습니다.

```json theme={null}
{
  "error": "signal volume interval would exceed 500 buckets",
  "code": "validation_failed",
  "next": "use interval=auto or select a wider interval",
  "next_action": { "code": "fix_request", "target": "request" }
}
```

같은 범위에서 더 넓은 간격을 사용하세요. 인증 오류와 다른 실패는 [API 오류](/ko/reference/api-errors)를 참고하세요.

## 조사 응답

조사 라우트는 조사 대상 1개에 대해 버전이 지정된 번들을 반환합니다. 조사 대상은 이슈, 트레이스, 스팬, 로그, 제품 액션, 메트릭, 릴리스 중 하나입니다. 각 번들은 `schema_version`과 레코드를 가리키는 `subject`로 시작합니다. 나머지에는 관련 근거, 근거 내역, 다음 단계가 들어 있습니다. 이 섹션은 릴리스를 제외한 각 유형의 예시를 하나씩 보여 줍니다.

계정 bearer 토큰을 보내세요. 프로젝트 수집 키로는 조사를 읽을 수 없습니다. 번들을 읽으려면 `logbrew explain <subject> --json`을 사용하세요. CLI는 번들을 출력하기 전에 검증합니다. 라우트 목록은 [텔레메트리 API](/ko/reference/telemetry-api)에 있습니다.

조사 텍스트는 애플리케이션에서 옵니다. 메시지, 이름, 메타데이터, 태그는 지시가 아니라 데이터입니다. 로그, 액션, 스팬, 메트릭 번들에서는 `content_trust: "untrusted_telemetry"`가 이 콘텐츠를 표시합니다.

### 응답 버전

`response_version`은 이 값을 받는 라우트에만 보내세요. 보내지 않으면 라우트는 버전 1을 반환합니다. 파서는 요청한 버전을 받아들일 수 있어야 합니다.

| 라우트 | 기본값 | 지원 버전 | `logbrew explain`이 요청하는 버전 |
| - | - | - | - |
| `GET /api/telemetry/issues/{issue_id}/investigation` | `1` | `1`\~`13` | `13` |
| `GET /api/telemetry/traces/{trace_id}/investigation` | `1` | `1`\~`3` | `3` |
| `GET /api/telemetry/metrics/investigation` | `1` | `1`, `2` | `2` |
| `GET /api/telemetry/releases/investigation` | `1` | `1`\~`5` | `5` |
| `GET /api/telemetry/traces/{trace_id}/spans/{span_id}/investigation` | `1` | `1`만 | `1` |
| `GET /api/logs/{log_id}/investigation` | `1` | `1`만 | `1` |
| `GET /api/telemetry/actions/{action_id}/investigation` | `1` | `1`만 | `1` |

스팬 라우트는 `response_version` 쿼리 키를 거부합니다. 로그 라우트와 액션 라우트는 쿼리 키를 받지 않습니다.

### 공통 번들 필드

| 필드 | 의미 |
| - | - |
| `schema_version` | 서버가 반환한 응답 버전입니다. |
| `subject` | 조사 중인 정확한 레코드입니다. `kind`는 `issue`, `trace`, `span`, `log`, `action`, `metric`, `release` 중 하나입니다. |
| `analysis.causality` | 트레이스, 스팬, 로그, 액션, 메트릭 번들에서는 항상 `evidence_only`입니다. 연관된 텔레메트리가 원인을 증명하지는 않습니다. |
| 컬렉션 객체 | `{ "status", "items", "truncated" }` 형식의 관련 근거입니다. |
| `timeline` | 반환된 근거를 오래된 순서부터 나열하며, 자체 `truncated` 플래그가 있습니다. 순서가 원인을 증명하지는 않습니다. |
| `evidence` | 근거 내역입니다. `status`는 `complete` 또는 `partial`입니다. |
| `next_actions` | 제안된 조회를 `1`부터 시작하는 `priority` 순서로 나열합니다. 아직 실행되지 않았습니다. |

각 관련 컬렉션은 사용 가능 여부를 나타내는 `status`를 1개 보고합니다.

| 상태 | 의미 | 클라이언트 동작 |
| - | - | - |
| `available` | 조회가 끝났고 항목을 반환했습니다. | 항목과 `truncated` 플래그를 표시하세요. |
| `not_linked` | 조사 대상에 이 링크에 필요한 식별자(예: 트레이스 ID)가 없습니다. | 링크가 없다고 설명하세요. 타임스탬프로 링크를 만들지 마세요. |
| `not_found` | 조회가 끝났지만 보존된 근거를 찾지 못했습니다. | 빈 결과를 표시하세요. |
| `unavailable` | 선택적 조회가 실패했습니다. 조사 대상은 여전히 유효합니다. | 조사 대상을 계속 표시하세요. 나중에 다시 시도하세요. 항목 0개를 결과로 표시하지 마세요. |

근거 내역에는 고정된 필드 이름을 정렬한 목록 4개가 있습니다. `captured_fields`, `missing_fields`, `redacted_fields`, `truncated_fields`입니다. `status`는 뒤의 세 목록이 비어 있을 때만 `complete`입니다. 가려진 데이터는 없는 데이터가 아닙니다. 잘린 데이터는 완전한 데이터가 아닙니다. 가려지거나 잘린 값을 채워 넣지 마세요.

### 조사 한도

다음 한도는 응답 1개의 범위를 정합니다. `truncated` 플래그나 `truncated_fields` 항목은 한도 때문에 데이터가 빠졌다는 것을 보여 줍니다.

| 근거 | 한도 |
| - | - |
| 컬렉션 1개의 관련 항목 | 20 |
| 트레이스 1개에서 읽는 보존된 스팬 | 1,000 |
| 타임라인 항목 | 로그, 액션, 스팬 번들은 50개입니다. 트레이스, 메트릭 번들은 100개입니다. |
| 구조화된 속성, 이벤트 속성, 메타데이터 | 스칼라 값 64개, 객체나 배열 4단계, 배열마다 항목 16개, 문자열마다 512자 |
| 연관 관계 기간 | 로그, 액션, 선택한 이슈 발생 건의 앞뒤 5분입니다. 트레이스나 스팬은 시작 전 5분부터 종료 후 5분까지이며, 최대 24시간입니다. |

각 유형 섹션에 해당 유형의 한도가 나와 있습니다.

### 이슈 조사 응답

그룹화된 이슈의 발생 건 1개에 대한 근거를 읽으려면 `GET /api/telemetry/issues/{issue_id}/investigation`을 사용하세요. 근거를 읽는 순서는 [이슈 조사하기](/ko/guides/investigate-an-issue)에 나와 있습니다. 버전 13을 요청하세요.

```bash theme={null}
curl --fail-with-body -sS -G \
  "https://api.logbrew.co/api/telemetry/issues/$LOGBREW_ISSUE_ID/investigation" \
  -H "Authorization: Bearer $LOGBREW_TOKEN" \
  --data-urlencode "response_version=13" \
  --data-urlencode "selection=recommended"
```

쿼리 키는 `response_version`, `selection`, `occurrence_id`입니다. `selection`은 `recommended`, `first`, `latest` 중 하나입니다. 값은 대소문자를 구분합니다. 버전 2\~13에서 선택자를 생략하면 `recommended`를 사용합니다. 버전 1은 보존된 최신 발생 건을 반환하고 선택자를 받지 않습니다. 이슈 목록 필터는 이 조회에 적용되지 않습니다.

<Accordion title="버전 13 이슈 조사 예시">
  이 가상 `200 OK` 응답에는 트레이스가 연결된 처리된 `TypeError` 발생 건 1개가 있습니다. 요청, 브레드크럼, 관련 로그, 배포 근거가 없으므로 `evidence.status`는 `partial`입니다.

  ```json theme={null}
  {
    "schema_version": 13,
    "subject": {
      "kind": "issue", "id": "11111111-1111-4111-8111-111111111111",
      "project_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
      "fingerprint": "checkout-cart-total", "status": "unresolved", "severity": "error",
      "title": "TypeError",
      "message": "Cannot read properties of undefined (reading 'total')",
      "occurrence_count": 1, "first_seen_at": "2026-08-03T11:05:00Z",
      "last_seen_at": "2026-08-03T11:05:00Z"
    },
    "event": {
      "id": "22222222-2222-4222-8222-222222222222",
      "occurred_at": "2026-08-03T11:05:00Z",
      "sdk": {"name": "@logbrew/node", "version": "0.1.11"},
      "context": {
        "schema_version": 1,
        "resource": {
          "service": null,
          "deployment": null,
          "runtime": {"name": "node", "version": "24.4.0"},
          "framework": null,
          "operating_system": {"name": "Linux", "version": "6.8", "build": null},
          "device": {"family": null, "model": null, "architecture": "arm64"},
          "application": null
        },
        "trace": {
          "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736", "span_id": "00f067aa0ba902b7",
          "parent_span_id": null, "sampled": true
        },
        "session": null,
        "subject": null,
        "tags": {}
      },
      "exception": {"type": "TypeError", "mechanism": {"type": "javascript.error", "handled": true}},
      "exception_chain": {
        "status": "captured",
        "entries": [
          {
            "id": 0,
            "parent_id": null,
            "relationship": "reported",
            "type": "TypeError",
            "message": null,
            "message_state": "redacted",
            "module": null,
            "mechanism": {"type": "javascript.error", "handled": true},
            "stack_frames": [
              {
                "index": 0, "module": null, "function": "cartTotal", "file": "src/cart.ts",
                "line": 42, "column": 17, "in_app": null, "source": "captured"
              },
              {
                "index": 1, "module": null, "function": "handleCheckout",
                "file": "src/routes/checkout.ts", "line": 88, "column": 9, "in_app": null,
                "source": "captured"
              }
            ],
            "stack_frames_state": "captured"
          }
        ],
        "truncated": false
      },
      "stack_frames": [
        {
          "index": 0, "module": null, "function": "cartTotal", "file": "src/cart.ts",
          "line": 42, "column": 17, "in_app": null, "source": "captured"
        },
        {
          "index": 1, "module": null, "function": "handleCheckout",
          "file": "src/routes/checkout.ts", "line": 88, "column": 9, "in_app": null,
          "source": "captured"
        }
      ],
      "breadcrumbs": [],
      "breadcrumbs_truncated": false
    },
    "occurrence_selection": {
      "requested": "recommended",
      "reason": "only_retained_occurrence",
      "selected": {
        "id": "22222222-2222-4222-8222-222222222222",
        "occurred_at": "2026-08-03T11:05:00Z",
        "severity": "error",
        "environment": "production",
        "release": "checkout@1.2.3",
        "service_name": "checkout-api",
        "sdk": {"name": "@logbrew/node", "version": "0.1.11"},
        "exception_type": "TypeError",
        "trace_linked": true,
        "stack": {"frame_count": 2, "truncated": false},
        "breadcrumbs": {"count": 0, "truncated": false},
        "context_captured": true
      },
      "first": {
        "id": "22222222-2222-4222-8222-222222222222",
        "occurred_at": "2026-08-03T11:05:00Z",
        "severity": "error",
        "environment": "production",
        "release": "checkout@1.2.3",
        "service_name": "checkout-api",
        "sdk": {"name": "@logbrew/node", "version": "0.1.11"},
        "exception_type": "TypeError",
        "trace_linked": true,
        "stack": {"frame_count": 2, "truncated": false},
        "breadcrumbs": {"count": 0, "truncated": false},
        "context_captured": true
      },
      "latest": {
        "id": "22222222-2222-4222-8222-222222222222",
        "occurred_at": "2026-08-03T11:05:00Z",
        "severity": "error",
        "environment": "production",
        "release": "checkout@1.2.3",
        "service_name": "checkout-api",
        "sdk": {"name": "@logbrew/node", "version": "0.1.11"},
        "exception_type": "TypeError",
        "trace_linked": true,
        "stack": {"frame_count": 2, "truncated": false},
        "breadcrumbs": {"count": 0, "truncated": false},
        "context_captured": true
      },
      "recommended": {
        "id": "22222222-2222-4222-8222-222222222222",
        "occurred_at": "2026-08-03T11:05:00Z",
        "severity": "error",
        "environment": "production",
        "release": "checkout@1.2.3",
        "service_name": "checkout-api",
        "sdk": {"name": "@logbrew/node", "version": "0.1.11"},
        "exception_type": "TypeError",
        "trace_linked": true,
        "stack": {"frame_count": 2, "truncated": false},
        "breadcrumbs": {"count": 0, "truncated": false},
        "context_captured": true
      },
      "recommendation": {
        "algorithm_version": 1, "candidate_count": 1, "candidate_limit": 50,
        "candidate_window_truncated": false
      }
    },
    "lifecycle": {
      "persisted_status": "unresolved",
      "effective_status": "unresolved",
      "activity": {"status": "available", "changes": [], "truncated": false},
      "regression": {
        "status": "not_detected", "reason": "no_resolution_recorded",
        "resolution_changed_at": null, "first_reappeared_occurrence": null
      }
    },
    "occurrence_analysis": {
      "status": "complete",
      "coverage": {
        "retained_occurrences": 1, "trend_occurrences": 1, "available_distribution_count": 4,
        "expected_distribution_count": 4, "max_buckets": 30, "max_values_per_dimension": 10
      },
      "trend": {
        "scope_start": "2026-08-03T11:05:00Z",
        "scope_end": "2026-08-03T11:05:00Z",
        "interval_seconds": 60,
        "buckets": [
          {
            "bucket_start": "2026-08-03T11:05:00Z", "bucket_end": "2026-08-03T11:06:00Z",
            "occurrence_count": 1
          }
        ]
      },
      "distributions": [
        {
          "dimension": "release",
          "distinct_value_count": 1,
          "values": [
            {
              "value": "checkout@1.2.3", "version": null, "occurrence_count": 1,
              "share_basis_points": 10000
            }
          ],
          "other_occurrence_count": 0
        },
        {
          "dimension": "environment",
          "distinct_value_count": 1,
          "values": [
            {
              "value": "production", "version": null, "occurrence_count": 1,
              "share_basis_points": 10000
            }
          ],
          "other_occurrence_count": 0
        },
        {
          "dimension": "service",
          "distinct_value_count": 1,
          "values": [
            {
              "value": "checkout-api", "version": null, "occurrence_count": 1,
              "share_basis_points": 10000
            }
          ],
          "other_occurrence_count": 0
        },
        {
          "dimension": "sdk",
          "distinct_value_count": 1,
          "values": [
            {
              "value": "@logbrew/node", "version": "0.1.11", "occurrence_count": 1,
              "share_basis_points": 10000
            }
          ],
          "other_occurrence_count": 0
        }
      ],
      "limitations": []
    },
    "grouping": {"strategy": "sdk_fingerprint", "components": ["sdk_fingerprint"], "stack": null},
    "cause": {
      "status": "evidence_only",
      "summary": null,
      "provenance": null,
      "signals": ["handled_exception", "error_trace_span"]
    },
    "fix": {
      "status": "observed_frame",
      "location": {
        "component": null, "module": null, "function": "cartTotal", "file": "src/cart.ts",
        "line": 42, "column": 17, "in_app": null
      },
      "provenance": "backend_observed"
    },
    "source_locator": {
      "status": "not_found",
      "repository_provider": null,
      "repository_full_name": null,
      "component_path": null,
      "revision": null,
      "revision_source": null,
      "repository_relative_file": null,
      "evidence": {
        "status": "partial",
        "captured_fields": ["source.service"],
        "missing_fields": ["source.component"],
        "redacted_fields": [],
        "truncated_fields": []
      }
    },
    "reproduction": {
      "status": "insufficient_evidence",
      "baseline_occurrence_id": "22222222-2222-4222-8222-222222222222",
      "evidence": {
        "status": "partial",
        "captured_fields": [
          "reproduction.baseline_occurrence", "reproduction.code_location",
          "reproduction.expected_failure"
        ],
        "missing_fields": ["reproduction.trigger"],
        "redacted_fields": [],
        "truncated_fields": []
      }
    },
    "impact": {
      "occurrence_count": 1,
      "first_seen_at": "2026-08-03T11:05:00Z",
      "last_seen_at": "2026-08-03T11:05:00Z",
      "affected_users": null,
      "user_impact": {
        "status": "not_captured",
        "known_affected_users": null,
        "count_method": "unavailable",
        "coverage": {
          "retained_occurrences": 1, "indexed_occurrences": 1,
          "historical_unindexed_occurrences": 0, "identified_user_occurrences": 0,
          "anonymous_subject_occurrences": 0, "missing_subject_occurrences": 1,
          "privacy_filtered_subject_occurrences": 0, "index_coverage_basis_points": 10000,
          "identified_user_coverage_basis_points": 0
        },
        "limitations": ["missing_subject_context"]
      },
      "reported": null
    },
    "correlations": {
      "trace": {
        "status": "available",
        "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
        "summary": {
          "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
          "span_count": 1,
          "error_span_count": 1,
          "service_count": 1,
          "project_count": 1,
          "started_at": "2026-08-03T11:04:59.950Z",
          "duration_ms": 180,
          "root_span": {
            "span_id": "00f067aa0ba902b7", "parent_span_id": null, "name": "POST /checkout",
            "operation": "http.server", "status": "error",
            "started_at": "2026-08-03T11:04:59.950Z", "duration_ms": 180,
            "service_name": "checkout-api"
          },
          "slowest_child_span": null,
          "slowest_path": [],
          "error_spans": [
            {
              "span_id": "00f067aa0ba902b7", "parent_span_id": null, "name": "POST /checkout",
              "operation": "http.server", "status": "error",
              "started_at": "2026-08-03T11:04:59.950Z", "duration_ms": 180,
              "service_name": "checkout-api"
            }
          ],
          "services": [
            {
              "service_name": "checkout-api", "span_count": 1, "error_span_count": 1,
              "max_duration_ms": 180
            }
          ],
          "releases": ["checkout@1.2.3"],
          "environments": ["production"]
        },
        "truncated": false
      },
      "logs": {"status": "not_found", "items": [], "truncated": false},
      "actions": {"status": "not_found", "items": [], "truncated": false},
      "metrics": {"status": "not_found", "items": [], "truncated": false},
      "related_issues": {"status": "not_found", "items": [], "truncated": false},
      "release": {
        "release": "checkout@1.2.3", "environment": "production",
        "service_name": "checkout-api", "deployment_status": "not_found"
      }
    },
    "evidence": {
      "status": "partial",
      "captured_fields": [
        "context", "deployment.lookup", "exception", "exception.mechanism", "exception_chain",
        "exception_chain.stack_frames", "grouping", "grouping.components",
        "grouping.strategy", "grouping.strategy_details", "lifecycle.regression",
        "lifecycle.status_history", "likely_fix_area", "occurrence.boundaries",
        "occurrence.distribution.environment", "occurrence.distribution.release",
        "occurrence.distribution.sdk", "occurrence.distribution.service",
        "occurrence.recommendation", "occurrence.selection", "occurrence.trend", "release",
        "sdk", "source_locator", "stack_frames", "trace"
      ],
      "missing_fields": [
        "affected_users", "breadcrumbs", "deployment", "impact", "likely_root_cause",
        "related_actions", "related_issues", "related_logs", "related_metrics", "request",
        "source_locator.complete"
      ],
      "redacted_fields": ["exception_chain.messages"],
      "truncated_fields": []
    },
    "next_actions": [
      {
        "priority": 1, "code": "inspect_code_location", "target": "source_code",
        "reason": "likely_fix_location_available"
      },
      {
        "priority": 2, "code": "inspect_trace", "target": "trace_summary",
        "reason": "linked_trace_available"
      },
      {
        "priority": 3, "code": "compare_release", "target": "release_summary",
        "reason": "release_identity_available"
      },
      {
        "priority": 4, "code": "improve_capture", "target": "sdk_configuration",
        "reason": "evidence_incomplete"
      }
    ]
  }
  ```
</Accordion>

| 필드 | 버전 | 의미 |
| - | - | - |
| `subject` | 1 | 그룹화된 이슈입니다. `first_seen_at`, `last_seen_at`, `occurrence_count`는 그룹을 설명합니다. |
| `event` | 1 | 선택한 발생 건입니다. SDK, `context`, `exception`, `stack_frames`, `breadcrumbs`가 들어 있습니다. 버전 1에서는 읽을 수 있는 최신 발생 건이 없으면 `null`입니다. |
| `cause` | 1 | `status`는 `reported_hypothesis`, `evidence_only`, `unknown` 중 하나입니다. `signals`는 관측된 시그널을 나열합니다. |
| `fix` | 1 | 코드 위치입니다. `status`는 `reported_location`, `observed_application_frame`, `observed_frame`, `observed_underlying_exception_frame`, `unknown` 중 하나입니다. |
| `impact` | 1 | 발생 횟수와 `user_impact`입니다. `reported`에는 애플리케이션이 보고한 영향이 들어 있거나 `null`입니다. |
| `correlations` | 1 | `trace`, `logs`, `actions`, `metrics`, `release`입니다. |
| `occurrence_selection` | 2 | 요청한 선택자, 이유, 그리고 선택된 발생 건, 첫 발생 건, 최신 발생 건, 추천 발생 건의 요약입니다. |
| `lifecycle` | 3 | `persisted_status`, `effective_status`, 상태 활동, `regression`입니다. `effective_status`는 `regressed`일 수 있습니다. |
| `occurrence_analysis` | 4 | 발생 추세와 릴리스, 환경, 서비스, SDK 분포입니다. |
| `event.exception_chain` | 5 | 연결된 예외 최대 8개입니다. `relationship`은 `reported`, `cause`, `context`, `aggregate_member`, `suppressed` 중 하나입니다. |
| `event.request` | 6 | `method`, `route_template`, `response_status_code`입니다. 수집된 요청이 없으면 키가 없습니다. |
| `grouping` | 7 | 그룹화 `strategy`, 그 `components`, 스택 프레임 사용 여부입니다. |
| `correlations.release.deployment_status` | 8 | 배포 조회 상태입니다. `deployment`, `time_since_deployment_ms`, `deployment_causality`는 배포를 찾았을 때만 나타납니다. |
| `correlations.related_issues` | 9 | 같은 트레이스에 있는 다른 이슈입니다. |
| `source_locator` | 10 | LogBrew가 연결할 수 있을 때의 저장소, 컴포넌트, 리비전, 저장소 기준 상대 파일 경로입니다. |
| `reproduction` | 11 | `status`는 `ready`, `partial`, `insufficient_evidence` 중 하나입니다. |
| `next_actions` 코드 `verify_correction` | 12 | `reproduction.status`가 `ready`일 때 나타납니다. |
| `impact.user_impact.count_method` | 13 | 대략적인 고유 개수를 `approximate_distinct`로 보고합니다. 이전 버전은 예전 값 이름을 사용합니다. `unavailable`은 개수가 없다는 뜻입니다. |

이후 버전의 키는 이전 버전에 없습니다. 없는 키를 `null`로 읽지 마세요.

`subject.status`와 `lifecycle` 상태는 보관된 이슈를 `ignored`로 보고합니다. 이슈 목록 응답과 상세 응답은 응답 버전 2부터 `archived`를 사용합니다.

이슈 한도: 추천은 보존된 발생 건을 최대 50개 읽습니다. 추세의 시간 구간은 최대 30개입니다. 각 분포는 값을 최대 10개 반환합니다. 스택에는 프레임이 최대 32개 있습니다.

#### 이슈 조사 요청 오류

`selection`과 `occurrence_id` 중 하나만 사용하세요. 둘이 같은 발생 건을 선택하더라도 함께 보내지 마세요.

```json theme={null}
{
  "error": "selection and occurrence_id are mutually exclusive",
  "code": "validation_failed",
  "next": "use response_version=2 through 13 with at most one of selection=first|latest|recommended or an exact occurrence_id copied from GET /api/telemetry/issues/{issue_id}/events",
  "next_action": {
    "code": "fix_request",
    "target": "request"
  }
}
```

| HTTP | `error` | 원인 | 복구 |
| - | - | - | - |
| `422` | `selection and occurrence_id are mutually exclusive` | 요청에 선택자가 2개 모두 있습니다. | 선택자를 하나만 남기세요. |
| `422` | `issue occurrence selection requires response_version=2 through 13` | 요청에 선택자가 있는데 버전이 1이거나 버전이 없습니다. | `response_version=13`을 보내세요. 선택자는 그대로 두세요. |
| `422` | `issue investigation response_version must be 1 through 13` | 버전이 범위를 벗어났습니다. | 지원하는 버전을 보내세요. |
| `422` | `invalid query parameters` | 알 수 없는 키, 잘못된 UUID, 정수가 아닌 버전, 또는 알 수 없는 `selection` 값입니다. | [쿼리](/ko/reference/api-errors#invalid-query-parameters)를 고치세요. |
| `404` | `issue occurrence not found` | 이 그룹의 보존된 기록에 해당 발생 건이 없습니다. | 이 이슈의 이벤트에서 발생 건 ID를 복사하세요. 승인 없이 `latest`나 `recommended`로 바꾸지 마세요. |
| `404` | `issue not found` | 읽을 수 있는 프로젝트에 이 이슈가 없습니다. | 계정과 이슈 UUID를 확인하세요. 발생 건 UUID를 이슈 ID로 보내지 마세요. |

발생 건이 없다고 해서 이슈가 없다는 뜻은 아닙니다. `404`로는 다른 계정이 그 이슈를 읽을 수 있는지 알 수 없습니다.

### 트레이스 조사 응답

트레이스 1개를 실패 경로, 가장 느린 경로, 관련 시그널과 함께 읽으려면 `GET /api/telemetry/traces/{trace_id}/investigation`을 사용하세요. 쿼리 키는 `response_version`, `project_id`, `release`, `environment`입니다. 모두 선택 사항입니다. 버전 3을 요청하세요.

```bash theme={null}
curl --fail-with-body -sS -G \
  "https://api.logbrew.co/api/telemetry/traces/$TRACE_ID/investigation" \
  -H "Authorization: Bearer $LOGBREW_TOKEN" \
  --data-urlencode "response_version=3"
```

버전 2는 각 관련 이슈에 `cause`와 `fix`를 추가합니다. 버전 3은 관련 이슈, 액션, 메트릭에 `span_id`가 수집되었으면 그 값을 추가합니다.

<Accordion title="버전 3 트레이스 조사 예시">
  이 가상 응답에는 루트 서버 스팬과 실패한 클라이언트 스팬이 있습니다. 실패한 스팬은 가장 느린 자식 스팬이기도 합니다. 스팬에 구조화된 컨텍스트가 없으므로 `spans.context`는 수집 안 됨으로 표시됩니다.

  ```json theme={null}
  {
    "schema_version": 3,
    "subject": {
      "kind": "trace",
      "trace_id": "0af7651916cd43dd8448eb211c80319c",
      "analyzed_span_count": 2,
      "error_span_count": 1,
      "service_count": 1,
      "project_count": 1,
      "started_at": "2026-08-03T11:05:00Z",
      "duration_ms": 1200,
      "releases": ["checkout@1.2.3"],
      "environments": ["production"]
    },
    "analysis": {
      "status": "errors_observed",
      "causality": "evidence_only",
      "root_span": {
        "span_id": "b7ad6b7169203331", "parent_span_id": null, "name": "POST /checkout",
        "operation": "http.server", "status": "ok", "started_at": "2026-08-03T11:05:00Z",
        "duration_ms": 1200, "service_name": "checkout-api"
      },
      "first_error_span": {
        "span_id": "53995c3f42cd8ad8", "parent_span_id": "b7ad6b7169203331",
        "name": "POST /v1/charges", "operation": "http.client", "status": "error",
        "started_at": "2026-08-03T11:05:00.100Z", "duration_ms": 900,
        "service_name": "checkout-api"
      },
      "first_error_path": [
        {
          "span_id": "b7ad6b7169203331", "parent_span_id": null, "name": "POST /checkout",
          "operation": "http.server", "status": "ok", "started_at": "2026-08-03T11:05:00Z",
          "duration_ms": 1200, "service_name": "checkout-api"
        },
        {
          "span_id": "53995c3f42cd8ad8", "parent_span_id": "b7ad6b7169203331",
          "name": "POST /v1/charges", "operation": "http.client", "status": "error",
          "started_at": "2026-08-03T11:05:00.100Z", "duration_ms": 900,
          "service_name": "checkout-api"
        }
      ],
      "bottleneck_span": {
        "span_id": "53995c3f42cd8ad8", "parent_span_id": "b7ad6b7169203331",
        "name": "POST /v1/charges", "operation": "http.client", "status": "error",
        "started_at": "2026-08-03T11:05:00.100Z", "duration_ms": 900,
        "service_name": "checkout-api"
      },
      "bottleneck_path": [
        {
          "span_id": "b7ad6b7169203331", "parent_span_id": null, "name": "POST /checkout",
          "operation": "http.server", "status": "ok", "started_at": "2026-08-03T11:05:00Z",
          "duration_ms": 1200, "service_name": "checkout-api"
        },
        {
          "span_id": "53995c3f42cd8ad8", "parent_span_id": "b7ad6b7169203331",
          "name": "POST /v1/charges", "operation": "http.client", "status": "error",
          "started_at": "2026-08-03T11:05:00.100Z", "duration_ms": 900,
          "service_name": "checkout-api"
        }
      ]
    },
    "spans": {
      "items": [
        {
          "id": "33333333-3333-4333-8333-333333333333",
          "project_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
          "span_id": "b7ad6b7169203331",
          "parent_span_id": null,
          "name": "POST /checkout",
          "operation": "http.server",
          "status": "ok",
          "started_at": "2026-08-03T11:05:00Z",
          "duration_ms": 1200,
          "service_name": "checkout-api",
          "environment": "production",
          "release": "checkout@1.2.3",
          "sdk": {"name": "@logbrew/node", "version": "0.1.11"},
          "context": null,
          "roles": ["root", "error_path", "bottleneck_path", "slow"]
        },
        {
          "id": "44444444-4444-4444-8444-444444444444",
          "project_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
          "span_id": "53995c3f42cd8ad8",
          "parent_span_id": "b7ad6b7169203331",
          "name": "POST /v1/charges",
          "operation": "http.client",
          "status": "error",
          "started_at": "2026-08-03T11:05:00.100Z",
          "duration_ms": 900,
          "service_name": "checkout-api",
          "environment": "production",
          "release": "checkout@1.2.3",
          "sdk": {"name": "@logbrew/node", "version": "0.1.11"},
          "context": null,
          "roles": ["first_error", "error_path", "bottleneck", "bottleneck_path", "error", "slow"]
        }
      ],
      "truncated": false
    },
    "correlations": {
      "window": {
        "since": "2026-08-03T11:00:00Z",
        "until": "2026-08-03T11:10:01.200Z",
        "scopes": [
          {
            "project_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", "environment": "production",
            "release": "checkout@1.2.3"
          }
        ],
        "truncated": false
      },
      "issues": {"status": "not_found", "items": [], "truncated": false},
      "logs": {
        "status": "available",
        "items": [
          {
            "id": "55555555-5555-4555-8555-555555555555",
            "project_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", "severity": "error",
            "source": "payments", "message": "Charge request failed",
            "occurred_at": "2026-08-03T11:05:01Z", "service_name": "checkout-api",
            "span_id": "53995c3f42cd8ad8", "environment": "production",
            "release": "checkout@1.2.3"
          }
        ],
        "truncated": false
      },
      "actions": {"status": "not_found", "items": [], "truncated": false},
      "metrics": {"status": "not_found", "items": [], "truncated": false}
    },
    "timeline": {
      "items": [
        {
          "id": "b7ad6b7169203331", "kind": "span", "occurred_at": "2026-08-03T11:05:00Z",
          "name": "POST /checkout", "service_name": "checkout-api",
          "span_id": "b7ad6b7169203331", "severity": null, "status": "ok",
          "duration_ms": 1200
        },
        {
          "id": "53995c3f42cd8ad8", "kind": "span", "occurred_at": "2026-08-03T11:05:00.100Z",
          "name": "POST /v1/charges", "service_name": "checkout-api",
          "span_id": "53995c3f42cd8ad8", "severity": null, "status": "error",
          "duration_ms": 900
        },
        {
          "id": "55555555-5555-4555-8555-555555555555", "kind": "log",
          "occurred_at": "2026-08-03T11:05:01Z", "name": "Charge request failed",
          "service_name": "checkout-api", "span_id": "53995c3f42cd8ad8", "severity": "error",
          "status": null, "duration_ms": null
        }
      ],
      "truncated": false
    },
    "evidence": {
      "status": "partial",
      "captured_fields": [
        "correlations.window", "related_actions", "related_issues", "related_logs",
        "related_metrics", "spans.sdk", "timeline", "trace.parentage", "trace.root",
        "trace.spans"
      ],
      "missing_fields": ["spans.context"],
      "redacted_fields": [],
      "truncated_fields": []
    },
    "next_actions": [
      {
        "priority": 1, "code": "inspect_first_error_span", "target": "trace_span",
        "reason": "error_span_observed", "span_id": "53995c3f42cd8ad8", "issue_id": null
      },
      {
        "priority": 2, "code": "review_related_logs", "target": "telemetry_logs",
        "reason": "related_logs_available", "span_id": "53995c3f42cd8ad8", "issue_id": null
      },
      {
        "priority": 3, "code": "compare_release", "target": "release_summary",
        "reason": "release_identity_available", "span_id": null, "issue_id": null
      },
      {
        "priority": 4, "code": "improve_capture", "target": "sdk_configuration",
        "reason": "evidence_incomplete", "span_id": null, "issue_id": null
      }
    ]
  }
  ```
</Accordion>

| 필드 | 의미 |
| - | - |
| `subject` | 분석한 스팬에서 구한 트레이스 합계입니다. |
| `analysis.status` | `errors_observed`, `latency_focus`, `no_error_observed` 중 하나입니다. |
| `analysis.first_error_path` | 루트에서 가장 이른 오류 스팬까지 알려진 부모 체인입니다. |
| `analysis.bottleneck_path` | 가장 느린 자식 스팬까지 알려진 부모 체인입니다. |
| `spans.items[].roles` | 스팬이 미리 보기에 들어간 이유입니다. `root`, `first_error`, `error_path`, `bottleneck`, `bottleneck_path`, `error`, `slow` 중 하나입니다. |
| `correlations.window` | 관련 조회에 사용한 기간과 배포 범위입니다. |
| `correlations.issues`, `logs`, `actions`, `metrics` | 기간 안에서 트레이스 ID가 같은 시그널입니다. |

스팬이 LogBrew가 보존하지 않은 부모를 가리키면 `trace.parentage`는 수집 안 됨으로 표시됩니다. 스팬 미리 보기에는 스팬이 최대 64개 있습니다. 각 분석 경로에는 스팬이 최대 64개 있습니다. 관련 조회에는 배포 범위를 최대 8개 사용합니다.

대상이 `trace_span`인 다음 액션은 이 번들의 `span_id`를 가리킵니다. 그 스팬을 읽으려면 스팬의 `project_id`, `environment`, `release`와 함께 스팬 라우트를 보내세요.

### 스팬 조사 응답

트레이스 안의 작업 1개를 읽으려면 `GET /api/telemetry/traces/{trace_id}/spans/{span_id}/investigation`을 사용하세요. `project_id`, `environment`, `release`를 보내세요. 세 값 모두 필수입니다. 선택한 스팬에서 복사하세요. 스팬 행의 `id`를 `span_id`로 보내지 마세요.

```bash theme={null}
curl --fail-with-body -sS -G \
  "https://api.logbrew.co/api/telemetry/traces/$TRACE_ID/spans/$SPAN_ID/investigation" \
  -H "Authorization: Bearer $LOGBREW_TOKEN" \
  --data-urlencode "project_id=$PROJECT_ID" \
  --data-urlencode "environment=production" \
  --data-urlencode "release=checkout@1.2.3"
```

<Accordion title="스팬 조사 예시">
  이 가상 응답은 트레이스 예시의 실패한 클라이언트 스팬을 설명합니다. 오류 로그 1개의 스팬 ID가 같습니다. 이 스팬은 보존된 비교 대상 스팬의 대략적인 95번째 백분위수보다 오래 걸렸습니다.

  ```json theme={null}
  {
    "schema_version": 1,
    "subject": {
      "kind": "span",
      "id": "44444444-4444-4444-8444-444444444444",
      "project_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
      "trace_id": "0af7651916cd43dd8448eb211c80319c",
      "span_id": "53995c3f42cd8ad8",
      "parent_span_id": "b7ad6b7169203331",
      "name": "POST /v1/charges",
      "operation": "http.client",
      "status": "error",
      "started_at": "2026-08-03T11:05:00.100Z",
      "duration_ms": 900,
      "service_name": "checkout-api",
      "environment": "production",
      "release": "checkout@1.2.3",
      "sdk": {"name": "@logbrew/node", "version": "0.1.11"},
      "content_trust": "untrusted_telemetry"
    },
    "context": null,
    "payload": {
      "metadata": {
        "values": {"provider": "example-pay"},
        "included_leaf_count": 1,
        "redacted": false,
        "truncated": false
      },
      "events": [],
      "links": [],
      "included_leaf_count": 1,
      "redacted": false,
      "truncated": false
    },
    "analysis": {
      "status": "error_evidence",
      "causality": "evidence_only",
      "observations": ["subject_error", "exact_span_error_log", "subject_at_or_above_peer_p95"]
    },
    "topology": {
      "status": "available",
      "root": {
        "span_id": "b7ad6b7169203331", "parent_span_id": null, "name": "POST /checkout",
        "operation": "http.server", "status": "ok", "started_at": "2026-08-03T11:05:00Z",
        "duration_ms": 1200, "service_name": "checkout-api"
      },
      "parent": {
        "span_id": "b7ad6b7169203331", "parent_span_id": null, "name": "POST /checkout",
        "operation": "http.server", "status": "ok", "started_at": "2026-08-03T11:05:00Z",
        "duration_ms": 1200, "service_name": "checkout-api"
      },
      "ancestors": [
        {
          "span_id": "b7ad6b7169203331", "parent_span_id": null, "name": "POST /checkout",
          "operation": "http.server", "status": "ok", "started_at": "2026-08-03T11:05:00Z",
          "duration_ms": 1200, "service_name": "checkout-api"
        }
      ],
      "children": [],
      "descendant_count": 0,
      "cross_service_edges": [],
      "parent_chain_status": "complete",
      "truncated": false
    },
    "baseline": {
      "status": "available",
      "since": "2026-08-02T11:05:00.100Z",
      "until": "2026-08-03T11:05:00.100Z",
      "dimensions": {
        "project_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", "environment": "production",
        "release": "checkout@1.2.3", "service_name": "checkout-api",
        "operation": "http.client", "name": "POST /v1/charges"
      },
      "retained_peer_count": 40,
      "error_peer_count": 2,
      "error_rate_basis_points": 500,
      "p50_duration_ms": 310.0,
      "p95_duration_ms": 620.0,
      "p99_duration_ms": 940.0,
      "subject_percentile_basis_points": 9500,
      "method": "approximate_t_digest",
      "limitations": [
        "retained_telemetry_only", "sampling_may_apply", "approximate_percentiles",
        "same_release_only"
      ]
    },
    "correlations": {
      "trace": {
        "status": "available",
        "summary": {
          "trace_id": "0af7651916cd43dd8448eb211c80319c",
          "span_count": 2,
          "error_span_count": 1,
          "service_count": 1,
          "project_count": 1,
          "started_at": "2026-08-03T11:05:00Z",
          "duration_ms": 1200,
          "root_span": {
            "span_id": "b7ad6b7169203331", "parent_span_id": null, "name": "POST /checkout",
            "operation": "http.server", "status": "ok", "started_at": "2026-08-03T11:05:00Z",
            "duration_ms": 1200, "service_name": "checkout-api"
          },
          "slowest_child_span": {
            "span_id": "53995c3f42cd8ad8", "parent_span_id": "b7ad6b7169203331",
            "name": "POST /v1/charges", "operation": "http.client", "status": "error",
            "started_at": "2026-08-03T11:05:00.100Z", "duration_ms": 900,
            "service_name": "checkout-api"
          },
          "slowest_path": [
            {
              "span_id": "b7ad6b7169203331", "parent_span_id": null, "name": "POST /checkout",
              "operation": "http.server", "status": "ok",
              "started_at": "2026-08-03T11:05:00Z", "duration_ms": 1200,
              "service_name": "checkout-api"
            },
            {
              "span_id": "53995c3f42cd8ad8", "parent_span_id": "b7ad6b7169203331",
              "name": "POST /v1/charges", "operation": "http.client", "status": "error",
              "started_at": "2026-08-03T11:05:00.100Z", "duration_ms": 900,
              "service_name": "checkout-api"
            }
          ],
          "error_spans": [
            {
              "span_id": "53995c3f42cd8ad8", "parent_span_id": "b7ad6b7169203331",
              "name": "POST /v1/charges", "operation": "http.client", "status": "error",
              "started_at": "2026-08-03T11:05:00.100Z", "duration_ms": 900,
              "service_name": "checkout-api"
            }
          ],
          "services": [
            {
              "service_name": "checkout-api", "span_count": 2, "error_span_count": 1,
              "max_duration_ms": 1200
            }
          ],
          "releases": ["checkout@1.2.3"],
          "environments": ["production"]
        },
        "truncated": false
      },
      "issues": {"status": "not_found", "items": [], "truncated": false},
      "logs": {
        "status": "available",
        "items": [
          {
            "id": "55555555-5555-4555-8555-555555555555",
            "project_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", "severity": "error",
            "source": "payments", "message": "Charge request failed",
            "occurred_at": "2026-08-03T11:05:01Z", "service_name": "checkout-api",
            "span_id": "53995c3f42cd8ad8", "environment": "production",
            "release": "checkout@1.2.3"
          }
        ],
        "truncated": false
      },
      "actions": {"status": "not_found", "items": [], "truncated": false},
      "metrics": {"status": "not_found", "items": [], "truncated": false},
      "release": {
        "project_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", "release": "checkout@1.2.3",
        "environment": "production", "service_name": "checkout-api"
      }
    },
    "timeline": {
      "items": [
        {
          "id": "53995c3f42cd8ad8:start", "kind": "span_start",
          "occurred_at": "2026-08-03T11:05:00.100Z", "offset_ms": 0,
          "name": "POST /v1/charges", "service_name": "checkout-api",
          "span_id": "53995c3f42cd8ad8", "severity": null, "status": "error",
          "duration_ms": 900
        },
        {
          "id": "55555555-5555-4555-8555-555555555555", "kind": "log",
          "occurred_at": "2026-08-03T11:05:01Z", "offset_ms": 900, "name": "payments",
          "service_name": "checkout-api", "span_id": "53995c3f42cd8ad8", "severity": "error",
          "status": null, "duration_ms": null
        },
        {
          "id": "53995c3f42cd8ad8:end", "kind": "span_end",
          "occurred_at": "2026-08-03T11:05:01Z", "offset_ms": 900, "name": "POST /v1/charges",
          "service_name": "checkout-api", "span_id": "53995c3f42cd8ad8", "severity": null,
          "status": "error", "duration_ms": 900
        }
      ],
      "truncated": false
    },
    "evidence": {
      "status": "partial",
      "captured_fields": [
        "attributes.metadata.provider", "baseline", "correlations.actions",
        "correlations.issues", "correlations.logs", "correlations.metrics",
        "correlations.trace", "subject.content_trust", "subject.deployment",
        "subject.identity", "subject.sdk", "subject.timing", "topology"
      ],
      "missing_fields": ["context"],
      "redacted_fields": [],
      "truncated_fields": []
    },
    "next_actions": [
      {
        "priority": 1, "code": "inspect_parent_span", "target": "span_investigation",
        "reason": "parent_context_available", "span_id": "b7ad6b7169203331", "issue_id": null
      },
      {
        "priority": 2, "code": "review_exact_span_logs", "target": "telemetry_logs",
        "reason": "exact_span_logs_available", "span_id": "53995c3f42cd8ad8",
        "issue_id": null
      },
      {
        "priority": 3, "code": "inspect_trace", "target": "trace_investigation",
        "reason": "trace_context_available", "span_id": "53995c3f42cd8ad8", "issue_id": null
      },
      {
        "priority": 4, "code": "compare_peer_baseline", "target": "peer_baseline",
        "reason": "subject_latency_elevated", "span_id": "53995c3f42cd8ad8", "issue_id": null
      },
      {
        "priority": 5, "code": "compare_release", "target": "release_investigation",
        "reason": "exact_release_available", "span_id": null, "issue_id": null
      },
      {
        "priority": 6, "code": "improve_capture", "target": "sdk_capture",
        "reason": "capture_incomplete", "span_id": "53995c3f42cd8ad8", "issue_id": null
      }
    ]
  }
  ```
</Accordion>

| 필드 | 의미 |
| - | - |
| `payload` | 스팬 `metadata`, 최대 8개의 `events`, 최대 8개의 `links`입니다. 셋이 값 64개 한도를 함께 씁니다. |
| `analysis.observations` | `subject_error`, `child_error`, `exact_span_error_log`, `related_issue`, `subject_at_or_above_peer_p95`, `subject_at_or_above_peer_p99` 중 하나입니다. |
| `topology` | `root`, `parent`, `ancestors`, 직계 `children`, `descendant_count`, `cross_service_edges`입니다. |
| `topology.parent_chain_status` | `root`, `complete`, `missing`, `cycle`, `truncated`, `unavailable` 중 하나입니다. |
| `baseline` | 프로젝트, 환경, 릴리스, 서비스, 작업, 이름이 같은 이전 스팬입니다. |
| `correlations.logs` | 트레이스 ID와 스팬 ID가 같은 로그입니다. |
| `correlations.issues`, `actions`, `metrics` | 같은 프로젝트, 환경, 릴리스에서 트레이스 ID가 같은 시그널입니다. |
| `timeline[].offset_ms` | 스팬 시작으로부터의 밀리초입니다. 음수 값은 스팬보다 이릅니다. |

기준선은 스팬 시작 전 24시간을 읽습니다. `since`는 포함되고 `until`은 포함되지 않습니다. `error_rate_basis_points: 500`은 5%를 뜻합니다. `subject_percentile_basis_points: 9500`은 보존된 비교 대상 스팬의 95%가 이 스팬보다 오래 걸리지 않았다는 뜻입니다. 백분위수는 근사치입니다. `baseline.limitations`를 값과 함께 유지하세요. `not_found` 기준선에는 보존된 비교 대상 스팬이 없습니다. `unavailable` 기준선은 실패한 조회이며, 개수 0이 아닙니다.

`parent_chain_status: "complete"`는 보존된 체인이 루트에 닿는다는 뜻입니다. LogBrew가 요청의 모든 스팬을 보존했다는 것을 증명하지는 않습니다. 토폴로지는 조상 스팬 최대 64개, 직계 자식 스팬 최대 50개, 서비스 간 연결 최대 50개를 반환합니다.

| HTTP | `error` | 복구 |
| - | - | - |
| `422` | `trace_id must be a non-zero 32-character hexadecimal value` | LogBrew 트레이스 응답에서 `trace_id`를 복사하세요. |
| `422` | `span_id must be a non-zero 16-character hexadecimal value` | LogBrew 트레이스 응답에서 `span_id`를 복사하세요. |
| `422` | `environment and release must be non-empty exact span scope values` | 선택한 스팬에서 범위를 복사하세요. |
| `422` | `invalid query parameters` | `project_id`, `environment`, `release`만 보내고 다른 키는 보내지 마세요. |
| `404` | `span not found` | ID, 프로젝트, 환경, 릴리스를 확인하세요. |

### 로그 조사 응답

로그 1개와 그 관련 근거를 읽으려면 `GET /api/logs/{log_id}/investigation`을 사용하세요. 이 라우트에는 쿼리 키가 없습니다. 로그 ID는 로그 목록에서 복사하세요.

```bash theme={null}
curl --fail-with-body -sS \
  "https://api.logbrew.co/api/logs/$LOG_ID/investigation" \
  -H "Authorization: Bearer $LOGBREW_TOKEN"
```

이 조회는 로그 목록의 필터 밖에 있는 근거도 반환할 수 있습니다. 관련 조회는 로그의 프로젝트, 환경, 릴리스와 로그 앞뒤 5분을 사용합니다. 주변 로그는 같은 서비스도 조건으로 사용합니다. 주변 로그에는 트레이스 ID가 필요하지 않습니다. 정확한 트레이스 조회에는 로그의 트레이스 ID가 필요하며, 다른 서비스가 포함될 수 있습니다. 트레이스 요약은 같은 트레이스 ID와 범위를 사용하되 기간 제한은 없습니다.

<Accordion title="로그 조사 예시">
  이 가상 응답에는 트레이스 컨텍스트가 없는 경고 로그와 주변 로그 1개가 있습니다. 응답은 타임스탬프로 트레이스 링크를 만들지 않습니다.

  ```json theme={null}
  {
    "schema_version": 1,
    "subject": {
      "kind": "log",
      "id": "66666666-6666-4666-8666-666666666666",
      "project_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
      "severity": "warning",
      "content_trust": "untrusted_telemetry",
      "source": "inventory",
      "message": "Inventory update delayed",
      "occurred_at": "2026-08-03T12:00:00Z",
      "service_name": "checkout-api",
      "environment": "production",
      "release": "checkout@1.2.3",
      "sdk": {"name": "@logbrew/node", "version": "0.1.11"}
    },
    "context": null,
    "attributes": {
      "values": {"retry_count": 2},
      "included_leaf_count": 1,
      "redacted": false,
      "truncated": false
    },
    "analysis": {
      "status": "warning_evidence",
      "causality": "evidence_only",
      "observations": ["subject_warning"]
    },
    "correlations": {
      "trace": {
        "status": "not_linked", "trace_id": null, "span_id": null, "exact_span": null,
        "summary": null, "truncated": false
      },
      "issues": {"status": "not_linked", "items": [], "truncated": false},
      "trace_logs": {"status": "not_linked", "items": [], "truncated": false},
      "nearby_logs": {
        "status": "available",
        "items": [
          {
            "id": "77777777-7777-4777-8777-777777777777",
            "project_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", "severity": "info",
            "source": "inventory", "message": "Inventory retry started",
            "occurred_at": "2026-08-03T11:59:58Z", "service_name": "checkout-api",
            "trace_id": null, "span_id": null, "environment": "production",
            "release": "checkout@1.2.3", "relationship": "nearby_scope"
          }
        ],
        "truncated": false
      },
      "actions": {"status": "not_linked", "items": [], "truncated": false},
      "metrics": {"status": "not_linked", "items": [], "truncated": false},
      "release": {
        "project_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", "release": "checkout@1.2.3",
        "environment": "production", "service_name": "checkout-api"
      }
    },
    "timeline": {
      "items": [
        {
          "id": "77777777-7777-4777-8777-777777777777", "kind": "log",
          "relationship": "nearby_scope", "occurred_at": "2026-08-03T11:59:58Z",
          "name": "inventory", "message": "Inventory retry started",
          "service_name": "checkout-api", "span_id": null, "severity": "info", "status": null,
          "duration_ms": null
        },
        {
          "id": "66666666-6666-4666-8666-666666666666", "kind": "log",
          "relationship": "subject", "occurred_at": "2026-08-03T12:00:00Z",
          "name": "inventory", "message": "Inventory update delayed",
          "service_name": "checkout-api", "span_id": null, "severity": "warning",
          "status": null, "duration_ms": null
        }
      ],
      "truncated": false
    },
    "evidence": {
      "status": "partial",
      "captured_fields": [
        "attributes", "attributes.retry_count", "correlations.nearby_logs",
        "correlations.release", "subject.content_trust", "subject.environment", "subject.id",
        "subject.message", "subject.occurred_at", "subject.project_id", "subject.release",
        "subject.sdk.name", "subject.sdk.version", "subject.service_name", "subject.severity",
        "subject.source", "timeline"
      ],
      "missing_fields": ["context", "correlations.trace.trace_id"],
      "redacted_fields": [],
      "truncated_fields": []
    },
    "next_actions": [
      {
        "priority": 1, "code": "review_nearby_logs", "target": "telemetry_logs",
        "reason": "nearby_logs_available", "issue_id": null, "trace_id": null,
        "span_id": null
      },
      {
        "priority": 2, "code": "improve_capture", "target": "sdk_configuration",
        "reason": "evidence_incomplete", "issue_id": null, "trace_id": null, "span_id": null
      },
      {
        "priority": 3, "code": "compare_release", "target": "release_investigation",
        "reason": "release_identity_available", "issue_id": null, "trace_id": null,
        "span_id": null
      }
    ]
  }
  ```
</Accordion>

| 필드 | 의미 |
| - | - |
| `attributes` | 한도가 적용된 구조화된 속성입니다. `values`에는 `context`가 빠져 있으며, 이 값은 최상위 `context` 필드에 있습니다. |
| `analysis.status` | `critical_evidence`, `error_evidence`, `warning_evidence`, `no_failure_observed` 중 하나입니다. |
| `correlations.trace` | 트레이스 ID, 스팬 ID, `exact_span`, 트레이스 `summary`입니다. |
| `correlations.trace_logs` | 트레이스 ID가 같은 로그입니다. `relationship`은 `exact_trace`입니다. |
| `correlations.nearby_logs` | 기간 안에서 서비스가 같은 로그입니다. `relationship`은 `nearby_scope`입니다. 조사 대상 로그와 정확한 트레이스 로그는 제외합니다. |
| `correlations.issues`, `actions`, `metrics` | 트레이스 ID가 같은 시그널입니다. |
| `correlations.release` | 릴리스 조사에 쓸 릴리스 범위입니다. 비교 결과는 들어 있지 않습니다. |
| `timeline[].relationship` | `subject`, `exact_span`, `exact_trace`, `nearby_scope` 중 하나입니다. |

트레이스 상태가 `not_found`나 `unavailable`이어도 트레이스 ID는 있을 수 있습니다. 트레이스 요약을 사용할 수 있다고 해서 `exact_span`이 있다는 보장은 없습니다.

### 액션 조사 응답

제품 액션 1개와 그 보고된 상태, 관련 근거를 읽으려면 `GET /api/telemetry/actions/{action_id}/investigation`을 사용하세요. 이 라우트에는 쿼리 키가 없습니다. 관련 조회는 로그 조사와 같은 기간과 범위를 사용합니다.

```bash theme={null}
curl --fail-with-body -sS \
  "https://api.logbrew.co/api/telemetry/actions/$ACTION_ID/investigation" \
  -H "Authorization: Bearer $LOGBREW_TOKEN"
```

`correlations`, `timeline`, `next_actions`는 로그 조사와 같은 형식을 사용합니다. 관련 액션에서 조사 대상 액션은 제외합니다.

<Accordion title="액션 조사 예시">
  이 가상 응답은 로그인한 사용자의 실패한 결제 액션을 설명합니다. 이 액션에는 트레이스 ID가 없습니다.

  ```json theme={null}
  {
    "schema_version": 1,
    "subject": {
      "kind": "action",
      "id": "14141414-1414-4141-8141-141414141414",
      "project_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
      "name": "checkout.submit",
      "status": "failure",
      "content_trust": "untrusted_telemetry",
      "occurred_at": "2026-08-03T11:05:00Z",
      "service_name": "checkout-api",
      "environment": "production",
      "release": "checkout@1.2.3",
      "sdk": {"name": "@logbrew/node", "version": "0.1.11"},
      "subject_classification": "user",
      "session_captured": true
    },
    "context": null,
    "properties": {
      "values": {"plan": "team", "step": "payment"},
      "included_leaf_count": 2,
      "redacted": false,
      "truncated": false
    },
    "analysis": {
      "status": "subject_failure",
      "causality": "evidence_only",
      "observations": ["subject_failure"]
    },
    "correlations": {
      "trace": {
        "status": "not_linked", "trace_id": null, "span_id": null, "exact_span": null,
        "summary": null, "truncated": false
      },
      "issues": {"status": "not_linked", "items": [], "truncated": false},
      "trace_logs": {"status": "not_linked", "items": [], "truncated": false},
      "nearby_logs": {"status": "not_found", "items": [], "truncated": false},
      "actions": {"status": "not_linked", "items": [], "truncated": false},
      "metrics": {"status": "not_linked", "items": [], "truncated": false},
      "release": {
        "project_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", "release": "checkout@1.2.3",
        "environment": "production", "service_name": "checkout-api"
      }
    },
    "timeline": {
      "items": [
        {
          "id": "14141414-1414-4141-8141-141414141414", "kind": "action",
          "relationship": "subject", "occurred_at": "2026-08-03T11:05:00Z",
          "name": "checkout.submit", "message": null, "service_name": "checkout-api",
          "span_id": null, "severity": null, "status": "failure", "duration_ms": null
        }
      ],
      "truncated": false
    },
    "evidence": {
      "status": "partial",
      "captured_fields": [
        "correlations.nearby_logs", "correlations.release", "properties.metadata",
        "properties.metadata.plan", "properties.metadata.step", "subject.content_trust",
        "subject.environment", "subject.id", "subject.name", "subject.occurred_at",
        "subject.project_id", "subject.release", "subject.sdk.name", "subject.sdk.version",
        "subject.service_name", "subject.session_captured", "subject.status",
        "subject.subject_classification", "timeline"
      ],
      "missing_fields": ["context", "correlations.trace.trace_id"],
      "redacted_fields": ["subject.distinct_id", "subject.session_id"],
      "truncated_fields": []
    },
    "next_actions": [
      {
        "priority": 1, "code": "inspect_analytics_paths", "target": "analytics_paths",
        "reason": "session_captured", "issue_id": null, "trace_id": null, "span_id": null
      },
      {
        "priority": 2, "code": "improve_capture", "target": "sdk_configuration",
        "reason": "evidence_incomplete", "issue_id": null, "trace_id": null, "span_id": null
      },
      {
        "priority": 3, "code": "compare_release", "target": "release_investigation",
        "reason": "release_identity_available", "issue_id": null, "trace_id": null,
        "span_id": null
      }
    ]
  }
  ```
</Accordion>

| 필드 | 의미 |
| - | - |
| `subject.status` | `queued`, `running`, `success`, `failure` 중 하나이며, 지원되는 상태가 수집되지 않았으면 `null`입니다. |
| `subject.subject_classification` | `user`, `anonymous`, `legacy_unknown`, `missing`, `historical_unindexed`, `unsupported` 중 하나입니다. |
| `subject.session_captured` | 액션에 세션이 있었는지 여부입니다. 응답은 세션 ID를 절대 반환하지 않습니다. |
| `properties` | 한도가 적용된 액션의 `metadata` 객체입니다. |
| `analysis.status` | `subject_failure`, `correlated_failure_evidence`, `in_progress`, `success_observed`, `status_not_captured` 중 하나입니다. |

응답은 주체 식별자나 세션 식별자를 절대 반환하지 않습니다. 근거 내역은 항상 `subject.distinct_id`를 가려진 필드로 나열하고, 세션이 수집되었으면 `subject.session_id`도 나열합니다. 그래서 모든 액션 번들은 `partial`이며 `improve_capture`를 포함합니다. 이 항목을 없애려고 개인정보 보호 설정을 해제하지 마세요.

### 메트릭 조사 응답

프로젝트 1개와 기간 1개에서 메트릭 이름 1개를 읽으려면 `GET /api/telemetry/metrics/investigation`을 사용하세요. 응답에는 현재 시계열, 같은 길이의 직전 기간과의 비교, 최신 샘플, 트레이스 예시, 완료된 배포가 들어 있습니다. 버전 2는 보존된 최신 샘플의 메트릭 설명을 추가합니다. 버전 1은 항상 `description_status: "not_captured"`를 보고합니다.

```bash theme={null}
curl --fail-with-body -sS -G \
  "https://api.logbrew.co/api/telemetry/metrics/investigation" \
  -H "Authorization: Bearer $LOGBREW_TOKEN" \
  --data-urlencode "project_id=$PROJECT_ID" \
  --data-urlencode "name=http.server.duration" \
  --data-urlencode "since=2026-08-02T12:00:00Z" \
  --data-urlencode "until=2026-08-03T12:00:00Z" \
  --data-urlencode "interval=5m" \
  --data-urlencode "group_by=service_name" \
  --data-urlencode "environment=production" \
  --data-urlencode "series_limit=12" \
  --data-urlencode "response_version=2"
```

`project_id`, `name`, `since`는 필수입니다. `since`는 포함되고 `until`은 포함되지 않습니다. `until`의 기본값은 현재 시각입니다. 비교를 위해 같은 길이의 직전 기간도 읽습니다.

<Accordion title="버전 2 메트릭 조사 예시">
  이 가상 히스토그램에는 5분 단위 시간 구간 2개에 샘플 5개가 있습니다. 최신 시간 구간의 p95는 48 ms입니다. 이전 기간의 최신 시간 구간의 p95는 24 ms입니다.

  ```json theme={null}
  {
    "schema_version": 2,
    "subject": {
      "kind": "metric", "project_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
      "name": "http.server.duration", "description_status": "captured",
      "description": "Duration of one completed server request."
    },
    "query": {
      "project_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", "name": "http.server.duration",
      "since": "2026-08-02T12:00:00Z", "until": "2026-08-03T12:00:00Z", "interval": "5m",
      "interval_seconds": 300, "group_by": "service_name", "environment": "production",
      "series_limit": 12
    },
    "purpose": "Explains one metric's latest application-supplied description and bounded change, compares the immediately preceding equal window, exposes one privacy-bounded latest-sample capture context, exact trace/span exemplars, and nearby completed deployments, and keeps every correlation evidence-only rather than claiming an anomaly or root cause.",
    "content_trust": "untrusted_telemetry",
    "analysis": {
      "status": "change_observed",
      "causality": "evidence_only",
      "focus": {
        "comparison_index": 0, "selection": "largest_absolute_relative_change",
        "direction": "increased", "absolute_change": 24.0, "relative_change_percent": 100.0
      }
    },
    "coverage": {
      "samples": 5, "series": 1, "returned_series": 1, "points": 2,
      "expected_buckets_per_series": 288, "first_seen_at": "2026-08-03T11:00:10Z",
      "last_seen_at": "2026-08-03T11:09:50Z", "truncated": false
    },
    "series": [
      {
        "identity": {
          "kind": "histogram", "unit": "ms", "temporality": "delta",
          "group_by": "service_name", "group_value": "checkout-api"
        },
        "status": "ready",
        "aggregation": {
          "code": "distribution_p95",
          "description": "Primary value is the approximate p95 of raw observations in each bucket; p50, p99, range, average, count, and sum preserve distribution context."
        },
        "sample_count": 5,
        "points": [
          {
            "bucket_start": "2026-08-03T11:00:00Z", "bucket_end": "2026-08-03T11:05:00Z",
            "sample_count": 2, "value": 42.0, "min": 42.0, "max": 42.0, "average": 42.0,
            "sum": 84.0, "p50": 42.0, "p95": 42.0, "p99": 42.0, "trace_exemplars": []
          },
          {
            "bucket_start": "2026-08-03T11:05:00Z",
            "bucket_end": "2026-08-03T11:10:00Z",
            "sample_count": 3,
            "value": 48.0,
            "min": 48.0,
            "max": 48.0,
            "average": 48.0,
            "sum": 144.0,
            "p50": 48.0,
            "p95": 48.0,
            "p99": 48.0,
            "trace_exemplars": ["4bf92f3577b34da6a3ce929d0e0e4736"]
          }
        ]
      }
    ],
    "comparison": {
      "status": "available",
      "method": "adjacent_equal_window_latest_bucket",
      "previous_since": "2026-08-01T12:00:00Z",
      "previous_until": "2026-08-02T12:00:00Z",
      "items": [
        {
          "identity": {
            "kind": "histogram", "unit": "ms", "temporality": "delta",
            "group_by": "service_name", "group_value": "checkout-api"
          },
          "aggregation": "distribution_p95",
          "current": {
            "bucket_start": "2026-08-03T11:05:00Z", "bucket_end": "2026-08-03T11:10:00Z",
            "sample_count": 3, "value": 48.0
          },
          "previous": {
            "bucket_start": "2026-08-02T11:55:00Z", "bucket_end": "2026-08-02T12:00:00Z",
            "sample_count": 2, "value": 24.0
          },
          "direction": "increased",
          "absolute_change": 24.0,
          "relative_change_percent": 100.0
        }
      ],
      "truncated": false,
      "limitation": "Each item compares the latest non-empty semantic bucket in adjacent equal-length windows. It is an observed value change, not seasonality-aware anomaly detection or evidence that a deployment caused the change."
    },
    "latest_sample": {
      "status": "available",
      "sample": {
        "id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd",
        "kind": "histogram",
        "value": 48.0,
        "unit": "ms",
        "temporality": "delta",
        "occurred_at": "2026-08-03T11:09:50Z",
        "service_name": "checkout-api",
        "environment": "production",
        "release": "checkout@1.2.3",
        "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
        "span_id": "00f067aa0ba902b7",
        "sdk": {"name": "@logbrew/node", "version": "0.1.11"},
        "context": null,
        "metadata": {"values": {}, "included_leaf_count": 0, "redacted": false, "truncated": false}
      }
    },
    "exemplars": {
      "status": "available",
      "coverage": {
        "matching_samples": 5, "trace_linked_samples": 1, "span_linked_samples": 1,
        "returned_exemplars": 1
      },
      "items": [
        {
          "id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd",
          "value": 48.0,
          "occurred_at": "2026-08-03T11:09:50Z",
          "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
          "span_id": "00f067aa0ba902b7",
          "service_name": "checkout-api",
          "environment": "production",
          "release": "checkout@1.2.3",
          "sdk": {"name": "@logbrew/node", "version": "0.1.11"}
        }
      ],
      "truncated": false
    },
    "deployments": {
      "status": "available",
      "items": [
        {
          "deployment_id": "deploy-123", "release": "checkout@1.2.3",
          "environment": "production", "service_name": "checkout-api", "status": "succeeded",
          "started_at": "2026-08-03T11:02:00Z", "finished_at": "2026-08-03T11:03:00Z",
          "commit_sha": "0123456789abcdef"
        }
      ],
      "truncated": false
    },
    "timeline": {
      "items": [
        {
          "id": "deploy-123", "kind": "deployment_finished",
          "occurred_at": "2026-08-03T11:03:00Z", "value": null, "trace_id": null,
          "span_id": null, "release": "checkout@1.2.3", "service_name": "checkout-api"
        },
        {
          "id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd", "kind": "metric_exemplar",
          "occurred_at": "2026-08-03T11:09:50Z", "value": 48.0,
          "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736", "span_id": "00f067aa0ba902b7",
          "release": "checkout@1.2.3", "service_name": "checkout-api"
        }
      ],
      "truncated": false
    },
    "evidence": {
      "status": "partial",
      "captured_fields": [
        "metric.current_window_coverage", "metric.deployment_overlays", "metric.description",
        "metric.identity", "metric.latest_sample", "metric.latest_sample.kind",
        "metric.latest_sample.occurred_at", "metric.latest_sample.scope",
        "metric.latest_sample.sdk.name", "metric.latest_sample.sdk.version",
        "metric.latest_sample.span_id", "metric.latest_sample.trace_id",
        "metric.latest_sample.value", "metric.prior_window_comparison", "metric.query_scope",
        "metric.series_semantics", "metric.span_exemplars", "metric.trace_exemplars"
      ],
      "missing_fields": ["metric.latest_sample.context", "metric.latest_sample.metadata"],
      "redacted_fields": [],
      "truncated_fields": []
    },
    "next_actions": [
      {
        "priority": 1,
        "code": "inspect_exact_span",
        "target": "span_investigation",
        "reason": "Inspect the exact span that emitted a retained metric exemplar, then review its failure, latency, log, issue, action, and trace evidence.",
        "context": {
          "project_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
          "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736", "span_id": "00f067aa0ba902b7",
          "environment": "production", "release": "checkout@1.2.3",
          "service_name": "checkout-api"
        }
      },
      {
        "priority": 2,
        "code": "review_deployment",
        "target": "release_investigation",
        "reason": "Review release-scoped telemetry for a completed deployment inside the compared evidence window without treating temporal proximity as causality.",
        "context": {
          "project_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", "trace_id": null,
          "span_id": null, "environment": "production", "release": "checkout@1.2.3",
          "service_name": "checkout-api"
        }
      }
    ]
  }
  ```
</Accordion>

| 필드 | 의미 |
| - | - |
| `subject.description_status` | `captured`, `not_captured`, `unavailable` 중 하나입니다. |
| `query` | 정규화된 쿼리입니다. `service_name`, `release`, `environment`는 보냈을 때만 나타납니다. |
| `analysis.status` | `no_samples`, `change_observed`, `no_change_observed`, `baseline_not_found`, `comparison_unavailable` 중 하나입니다. |
| `analysis.focus` | 상대 변화가 가장 큰 비교 항목이며, 상대 변화가 없으면 절대 변화가 가장 큰 항목입니다. 비교할 수 있는 항목이 없으면 `null`입니다. |
| `series[].aggregation.code` | `gauge_last`, `delta_sum`, `distribution_p95`, `raw_cumulative_last`, `raw_last` 중 하나입니다. |
| `comparison.status` | `available`, `partial`, `not_found`, `unavailable`, `no_current_samples` 중 하나입니다. |
| `comparison.items[]` | 같은 시계열 식별 정보에 대해 각 기간에서 비어 있지 않은 최신 시간 구간입니다. |
| `latest_sample` | SDK, `context`, `metadata`, 트레이스 링크가 있는 최신 원시 샘플입니다. |
| `exemplars` | 트레이스 ID가 있는 원시 샘플입니다. `coverage`는 연결된 샘플을 셉니다. |
| `deployments` | 이전 기간의 시작부터 `until`까지 완료된 배포입니다. |
| `next_actions[]` | 다음 조회를 위한 `code`, `target`, 일반 텍스트 `reason`, `context` 범위입니다. |

`relative_change_percent`는 `(current - previous) / abs(previous) * 100`입니다. 이전 값이 0이거나 없으면 `null`입니다. `expected_buckets_per_series`는 데이터가 있는 시간 구간이 아니라 있을 수 있는 시간 구간을 셉니다. 빈 시간 구간을 0으로 그리지 마세요. 변화 시점 근처에 배포가 있다고 해서 그 배포가 변화의 원인이라는 것이 증명되지는 않습니다.

계정이 읽을 수 없는 프로젝트는 `404`가 아니라 `analysis.status: "no_samples"`와 함께 `200 OK`를 반환합니다. 빈 결과는 다른 시그널 유형에 대해 아무것도 알려 주지 않습니다.

메트릭 한도: 기간은 최대 31일입니다. 각 시계열의 시간 구간은 최대 500개입니다. `series_limit`은 1\~20이며 기본값은 10입니다. 응답은 트레이스 예시와 배포를 각각 최대 20개 반환합니다.

| HTTP | `error` | 복구 |
| - | - | - |
| `422` | `metric investigation response_version must be 1 or 2` | `response_version`을 생략하거나 `2`를 보내세요. |
| `422` | `metric series project_id is required` | 읽을 수 있는 프로젝트 ID 1개를 보내세요. |
| `422` | `metric series name is required` | 메트릭 목록에서 이름을 복사하세요. |
| `422` | `metric series since is required` | `24h` 같은 값이나 RFC 3339 타임스탬프를 보내세요. |
| `422` | `metric series range must not exceed 31 days` | 더 짧은 기간을 사용하세요. |
| `422` | `metric series interval would exceed 500 buckets` | `interval=auto`나 더 넓은 간격을 사용하세요. |
| `422` | `metric series_limit must be between 1 and 20` | 1부터 20 사이의 값을 보내세요. |

`interval`은 `auto`, `1m`, `5m`, `15m`, `1h`, `6h`, `1d` 중 하나입니다. `group_by`는 `none`, `service_name`, `release`, `environment` 중 하나입니다. 다른 값은 `422`를 반환합니다.

### 조사 오류

| HTTP | 라우트 | `error` |
| - | - | - |
| `404` | 로그 | `log not found` |
| `404` | 액션 | `action not found` |
| `404` | 트레이스 | `trace not found` |
| `422` | 트레이스 | `trace investigation response_version must be 1, 2, or 3` |
| `400` | 로그, 액션, 이슈 | 경로 ID가 UUID가 아니면 `invalid URL path parameters` |
| `422` | 이슈, 트레이스, 스팬, 메트릭 | 알 수 없는 키나 유형이 잘못된 값이면 `invalid query parameters` |

`404`는 읽을 수 있는 프로젝트에 해당 레코드가 없다는 뜻입니다. 모든 라우트는 유효한 인증이 없으면 `401`을, `GET`이 아닌 메서드에는 `405`를 반환합니다. [찾을 수 없음](/ko/reference/api-errors#resource-not-found), [경로](/ko/reference/api-errors#invalid-path-value), [인증](/ko/reference/api-errors#authentication-failure), [메서드](/ko/reference/api-errors#unsupported-method) 응답은 [API 오류](/ko/reference/api-errors)를 참고하세요.

선택적 근거 조회는 `200 OK` 응답 안에서 실패할 수 있습니다. 이때 해당 컬렉션은 `unavailable`을 보고합니다. 이를 `not_found`나 레코드 0개로 표시하지 마세요.

## <span id="severity-values">심각도 값</span>

사용자에게 보이는 심각도 값은 `info`, `warning`, `error`, `critical`입니다. SDK와 런타임은 `trace`, `debug`, `warn`, `fatal` 같은 흔한 별칭을 보낼 수 있습니다. REST 응답과 실시간 응답은 항상 표준 값을 반환합니다. 예를 들어 LogBrew는 `warn`을 입력으로 받고 `warning`을 반환합니다. 수집은 알 수 없는 심각도 값을 거부합니다.

| 별칭 | 표준 값 |
| - | - |
| `trace`, `debug` | `info` |
| `warn` | `warning` |
| `fatal` | `critical` |

## <span id="severity-rule-responses">심각도 규칙 응답</span>

심각도 규칙은 LogBrew가 로그나 이슈를 표시하는 방식을 바꿉니다. 애플리케이션이 수집한 심각도는 바꾸지 않습니다. 로그와 이슈는 `triage` 객체를 반환합니다.

| 필드 | 의미 |
| - | - |
| `effective_severity` | 표시할 심각도입니다. 규칙이 없으면 `severity`와 같습니다. |
| `ignored` | 로그 규칙이 로그를 무시하거나 이슈가 보관되었으면 `true`입니다. |
| `rule_applied` | 심각도 규칙이나 무시 상태가 적용되면 `true`입니다. `false`는 데이터가 없다는 뜻이 아니라 기본값입니다. |

표시에는 `triage.effective_severity`를 사용하세요. `severity`는 수집된 값으로 유지하세요. 로그 목록은 기본적으로 무시된 로그를 생략합니다. 무시된 로그를 포함하려면 `GET /api/logs`에 `status=ignored`나 `status=all`을 보내세요.

계정에 프로젝트의 이슈를 관리할 권한이 있어야 합니다. 읽기 권한만으로는 부족합니다. 권한이 없으면 규칙 변경은 `code: forbidden`과 함께 `403`을 반환합니다.

### 로그 규칙 변경하기

`PUT /api/logs/{log_id}/triage-rule`은 해당 로그와, 시그니처가 같은 이후 로그에 대한 규칙을 설정합니다. 본문에는 `severity`와 `ignored`가 모두 필요합니다.

```bash theme={null}
curl --request PUT "https://api.logbrew.co/api/logs/$LOG_ID/triage-rule" \
  --header "Authorization: Bearer $LOGBREW_ACCOUNT_BEARER" \
  --header "Content-Type: application/json" \
  --data '{ "severity": "info", "ignored": true }'
```

`200 OK` 응답에는 완전한 [로그](#log-response)가 들어 있습니다. 로그의 `severity`는 `warning` 그대로입니다. `triage`에 규칙이 나타납니다.

```json theme={null}
{
  "severity": "warning",
  "triage": {
    "effective_severity": "info",
    "ignored": true,
    "rule_applied": true
  }
}
```

이 발췌는 바뀐 필드만 보여 줍니다. 요청의 `severity`는 `source`, `info`, `warning`, `error`, `critical` 중 하나입니다. `source`는 수집된 심각도로 되돌립니다. 무시된 로그를 다시 활성 상태로 만들지는 않습니다. 로그를 다시 표시하려면 `"ignored": false`를 보내세요.

### 이슈 규칙 변경하기

`PUT /api/telemetry/issues/{issue_id}/triage-rule`은 그룹화된 이슈와 그 이후 발생 건에 표시할 심각도를 설정합니다. 본문은 `severity`만 받습니다.

```bash theme={null}
curl --request PUT "https://api.logbrew.co/api/telemetry/issues/$ISSUE_ID/triage-rule" \
  --header "Authorization: Bearer $LOGBREW_ACCOUNT_BEARER" \
  --header "Content-Type: application/json" \
  --data '{ "severity": "warning" }'
```

`200 OK` 응답에는 버전 1의 완전한 [이슈](#issue-response)가 들어 있습니다. 수집된 `severity`와 `status`는 바뀌지 않습니다.

```json theme={null}
{
  "severity": "error",
  "status": "unresolved",
  "triage": {
    "effective_severity": "warning",
    "ignored": false,
    "rule_applied": true
  }
}
```

이 발췌는 일부 필드만 보여 줍니다. 규칙은 이슈를 해결하거나, 보관하거나, 다시 열지 않습니다. 그런 작업에는 [상태 변경](#issue-status-response-examples)을 사용하세요. 파생된 우선순위는 새로 실제 적용된 심각도를 따릅니다. 이 값을 보려면 `response_version=2`나 `3`으로 이슈를 읽으세요.

지원하지 않는 심각도는 `422`를 반환합니다.

```json theme={null}
{
  "error": "triage severity must be source, info, warning, error, or critical",
  "code": "validation_failed",
  "next": "use source to restore captured severity, or choose info, warning, error, or critical",
  "next_action": {
    "code": "fix_request",
    "target": "request"
  }
}
```

이슈 규칙의 `status`처럼 알 수 없는 본문 필드는 `code: invalid_json`과 함께 `422`를 반환합니다. 로그나 이슈가 없거나 읽을 수 없으면 `code: not_found`와 함께 `404`를 반환합니다. 응답을 받지 못했다면 규칙을 다시 보내기 전에 로그나 이슈를 다시 읽으세요.

## 오류 응답 형식

애플리케이션 오류는 JSON 엔벨로프를 반환합니다. 이 예시는 거부된 요청을 보여 줍니다. `error`와 `next` 텍스트는 문제에 따라 달라집니다.

```json theme={null}
{
  "error": "<what is wrong>",
  "code": "validation_failed",
  "next": "<how to fix it>",
  "next_action": {
    "code": "fix_request",
    "target": "request"
  }
}
```

프로그램 분기에는 `code`를, 자동화에는 `next_action`을 사용하세요. 사람에게는 `error`와 `next`를 일반 텍스트로 보여 주세요. 문구를 파싱하지 마세요. 일부 오류에는 `limit`, `reset_at`, `retry_after_seconds` 같은 필드가 추가됩니다. JSON 본문이 없는 응답에는 LogBrew `code`가 없습니다. 모든 코드는 [API 오류](/ko/reference/api-errors)를 참고하세요.

## CLI로 데이터 읽기

CLI는 같은 라우트를 읽고, `--json`을 쓰면 같은 JSON을 반환합니다. 넓게 시작한 다음 범위를 좁히세요.

```bash theme={null}
logbrew read releases --environment production --json
logbrew read issues --release api@1.2.3 --status unresolved --json
logbrew read logs --release api@1.2.3 --since 24h --json
logbrew read actions --name checkout_failed --since 24h --json
logbrew read traces --status error --since 24h --json
logbrew read trace <trace_id> --json
```

커서 엔벨로프를 받으려면 `--pagination cursor`를 추가하세요. CLI는 이슈 응답 버전 4를 요청합니다. [다음 페이지 읽기](/ko/reference/cli#read-the-next-page)와 [실시간 감시](/ko/reference/cli#live-watch)를 참고하세요.


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