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

# Format des réponses de l’API et exemples JSON

> Lisez les réponses de l’API LogBrew : listes de télémétrie, pages avec curseur, événements en direct, modifications, succès et erreurs.

Cette page montre un exemple pour chaque famille de réponses de
l’[API de télémétrie](/fr/reference/telemetry-api). Toutes les valeurs sont fictives. Pour
les codes d’erreur et la reprise, consultez [Erreurs de l’API](/fr/reference/api-errors).

## Lire une réponse

Lisez chaque réponse dans cet ordre :

1. Lisez le statut HTTP.
2. Vérifiez que le corps a la forme définie par la route.
3. Vérifiez que chaque `project_id` fait partie du périmètre de projets demandé.
4. Conservez telles quelles les valeurs d’énumération canoniques et les identifiants.
5. Suivez `next_action` uniquement si le client prend en charge son `code` et
   sa `target`.
6. Traitez `null`, un champ omis et une liste vide comme des états différents.

Une réponse `200 OK` peut contenir une liste vide. Une liste de logs vide signifie
qu’aucun log ne correspond à la requête. Elle ne signifie pas que les problèmes,
les traces ou les actions sont vides aussi. Ne reliez pas des signaux parce que
leurs horodatages sont proches. Suivez une trace uniquement si la réponse
contient son `trace_id` exact.

Une réponse `404` et une liste vide sont deux résultats différents. Une route de liste renvoie `[]`
quand aucun enregistrement ne correspond. Une route de détail renvoie `404` avec `code: not_found`
quand elle ne trouve aucun enregistrement lisible. Aucun de ces résultats ne prouve que l’ingestion
a échoué.

## Données à envoyer

Les réponses ne valent que ce que vaut la télémétrie que vous envoyez. Envoyez ces valeurs
dans chaque enveloppe ou configuration du SDK :

* `release` et `environment`.
* Des noms stables pour les services, les actions et les métriques.
* Le contexte de trace quand des logs, des spans, des actions et des problèmes
  appartiennent à la même requête.
* Les valeurs de gravité canoniques : `info`, `warning`, `error` et `critical`.
* Des attributs utiles au débogage, sans secrets ni données personnelles inutiles.

Consultez [Intégration du SDK](/fr/guides/sdk-ingestion) et
[Contrôler les données capturées](/fr/guides/safe-capture).

## Réponse d’ingestion

Les SDK publics lisent la réponse de `POST /v1/events` à votre place. Le code de
l’application n’a pas à la traiter.

### Réponse à un lot du SDK

Un lot du SDK accepté renvoie `200 OK` avec le nombre de signaux acceptés pour
chaque flux :

```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"
  }
}
```

| Champ | Signification |
| - | - |
| `issues`, `logs`, `trace_spans`, `actions`, `metrics` | Les signaux de ce lot que LogBrew a acceptés. |
| `already_accepted` | Les signaux dont LogBrew a déjà accepté l’ID. LogBrew ne les stocke pas de nouveau. |
| `context_updated` | `true` quand le lot a mis à jour le contexte de version ou d’environnement. |

Les nombres portent uniquement sur ce lot. Si vous renvoyez un lot que LogBrew a
déjà accepté, la réponse contient `0` pour chaque flux et le nombre de signaux dans
`already_accepted`. Gardez les ID d’origine des signaux quand vous réessayez.

### Réponse à une enveloppe native

`POST /api/telemetry/ingest` renvoie `200 OK` avec le nombre d’éléments acceptés
pour chaque flux :

```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"
  }
}
```

La réponse native n’a pas de champ `metrics` ni `context_updated`. Ne les ajoutez
pas. `already_accepted` compte les éléments dont LogBrew a déjà accepté l’`id` pour
ce projet. Consultez l’[enveloppe native directe](/fr/guides/sdk-ingestion#required-envelope-fields).

### Erreurs d’ingestion

Une réponse `401` avec `code: ingest_key_invalid` signifie que le jeton bearer d’ingestion est
révoqué, expiré ou non valide pour le projet. Sur `POST /v1/events`, un jeton bearer
absent, ou un jeton bearer qui n’est pas une clé d’ingestion, renvoie `401` avec
`code: unauthorized`. Utilisez le jeton bearer d’ingestion actuel du projet. Ne
réessayez pas avec un jeton bearer de compte.

Un lot du SDK refusé renvoie `422` avec `code: validation_failed` :

```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"
  }
}
```

Les autres valeurs de `error` incluent `SDK event batch requires at least one event` et
`SDK event type is not supported by this ingest route`. Vérifiez la version du SDK
et la configuration du transport. N’envoyez pas de lot vide. Ne modifiez pas
les types ou les ID des signaux dans le seul but de faire passer la validation.

Une requête avec une autre méthode que `POST` renvoie `405` avec
`code: method_not_allowed`. Un corps JSON illisible renvoie `code: invalid_json`.

## Format des réponses de liste de télémétrie

Par défaut, les routes de liste renvoient un tableau JSON brut. Une liste vide
réussie vaut `[]`.

Avec `pagination=cursor`, la route renvoie une enveloppe. La clé de la collection
correspond au signal :

| Route | Clé de la collection |
| - | - |
| `/api/logs` | `logs` |
| `/api/telemetry/issues` | `issues` |
| `/api/telemetry/issues/{issue_id}/events` | `events`, avec un `issue_id` au premier niveau |
| `/api/telemetry/actions` | `actions` |
| `/api/telemetry/metrics` | `metrics` |
| `/api/telemetry/releases` | `releases` |
| `/api/telemetry/traces` | `traces`, avec un `naming_quality` au premier niveau |

`next_cursor` est un objet qui contient les champs de continuation, ou `null` sur la dernière
page. Les lignes vont de la plus récente à la plus ancienne. Consultez la [pagination par curseur](/fr/reference/telemetry-api-filters#cursor-pagination).

### <span id="log-response">Réponse de log</span>

Une page de logs avec curseur et `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"
  }
}
```

Les logs contiennent `level` pour la compatibilité et `severity` pour l’affichage. Les deux
contiennent la valeur canonique. `triage` contient le résultat d’une règle de gravité. Consultez
les [réponses des règles de gravité](#severity-rule-responses).

`trace_id` et `span_id` valent `null` quand le log n’a pas de lien de trace. Dans ce cas,
`next_action` vaut `inspect_related_logs` avec la cible `telemetry_logs`. Les logs,
les problèmes et les actions utilisent les deux mêmes paires `next_action`.

`GET /api/logs/{log_id}` renvoie un seul log, sous la même forme.

### <span id="issue-response">Réponse de problème</span>

`GET /api/telemetry/issues/{issue_id}` renvoie un problème groupé. La liste
renvoie les mêmes objets. Cet exemple utilise la version de réponse par défaut, la version 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"
}
```

En version 1, `status` vaut `unresolved`, `resolved` ou `ignored`. Pour un
problème archivé, `triage.ignored` vaut `true`. Pour les paires `next_action` et
les lectures qui en découlent, consultez
[Investiguer un problème](/fr/guides/investigate-an-issue).

`first_seen_at` et `last_seen_at` sont les heures de la première et de la plus
récente occurrence. Elles ne changent pas quand le statut change.

Certains champs n’apparaissent que si LogBrew dispose des données :

| Champ | Quand il apparaît |
| - | - |
| `span_id` | Le problème a un lien de span. Sinon, le champ est omis. |
| `diagnostics` | La dernière occurrence a un type d’exception ou des Breadcrumbs. Consultez l’[historique des occurrences d’un problème](#issue-occurrence-history). |
| `symbolication` | Uniquement sur `GET /api/telemetry/issues/{issue_id}`, quand le problème a une identité d’artefact de version. Consultez [Source maps JavaScript](/fr/guides/source-maps). |

`symbolication.status` vaut `resolved`, `invalid_metadata`,
`artifact_not_found`, `source_not_resolved` ou `unavailable`. La liste,
les changements de statut et les changements de règle ne renvoient pas `symbolication`.

Avec `response_version=2`, le problème a les mêmes champs, avec ces changements :

| Champ | Valeur en version 2 |
| - | - |
| `status` | `unresolved`, `resolved` ou `archived`. La version 1 affiche `archived` sous la forme `ignored`. |
| `priority` | `high`, `medium` ou `low`. |
| `priority_source` | `manual` quand une personne a défini la priorité. `derived` quand la priorité suit la gravité effective. |

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

Cet exemple ne montre que certains champs. Une priorité dérivée vaut `high` pour `error`
et `critical`, `medium` pour `warning` et `low` pour `info`. Elle utilise
`triage.effective_severity` : une règle de gravité de problème peut donc la modifier.

Avec `response_version=3`, le problème a tous les champs de la version 2, plus `assignee`.
`assignee` vaut `null` quand le problème n’a pas de responsable :

```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"
  }
}
```

| Champ | Signification |
| - | - |
| `assignee.user_id` | ID utilisateur du responsable. Envoyez-le comme `assignee` dans un filtre ou une modification. |
| `assignee.name` | Nom du responsable, ou `null` quand le compte n’a pas de nom. |
| `assignee.email` | Adresse e-mail du responsable. |

Un problème n’affiche aucun responsable quand le membre attribué ne peut plus lire
le projet.

Avec `response_version=4`, `assignee` a aussi un `type`. Un responsable `user`
a `user_id`, `name` et `email`, comme dans la version 3. Un responsable `team`
a `team_id`, `slug` et `name`. La version 3 affiche une équipe responsable
comme `null`. La CLI demande la version 4 :

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

Toute autre valeur de `response_version` renvoie `422` avec `code: validation_failed`. Une
page avec curseur utilise la même version pour chaque problème de `issues`.

### <span id="assignee-list-response">Réponse de la liste des responsables</span>

`GET /api/telemetry/issues/assignees?project_id=<project_id>` renvoie chaque
compte à qui l’on peut attribuer des problèmes dans le projet :

```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"
    }
  ]
}
```

Ajoutez `response_version=2` pour obtenir aussi `teams` : chaque équipe de
l’organisation du projet, avec `team_id`, `slug` et `name`.

Si le compte ne peut pas lire le projet, la route renvoie `404` avec
`code: not_found`.

### <span id="action-response">Réponse d’action</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` suit un même acteur ou une même session. Traitez `distinct_id` et
`session_id` comme des identifiants opaques. `session_id` et `trace_id` valent `null`
quand ils manquent. `span_id` est omis quand il manque. Une action n’a
pas de gravité. Consultez [Capture d’événements produit](/fr/guides/product-events).

### Réponse de métrique

```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"
}
```

Chaque élément est un échantillon. N’utilisez pas le nombre d’échantillons comme un nombre
d’événements ou d’utilisateurs. `unit`, `temporality`, `trace_id` et `span_id` valent `null` quand ils
manquent. Une valeur `null` n’est pas zéro.

### Réponse de version

```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"
  }
}
```

Une ligne couvre un projet, une version, un environnement et un service. Ses nombres
décrivent uniquement cette ligne.

`trace_health.status` vaut `unknown` quand la ligne n’a pas de trace, sinon
`no_errors_observed` ou `errors_observed`. `error_rate_basis_points` est la
part des traces qui ont un span en erreur, de `0` à `10000`. Ce n’est pas un taux
d’erreur de toutes les requêtes.

`next_action` désigne le premier type de signal qui a des données, dans cet ordre :

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

Consultez [Versions](/fr/concepts/releases).

### Réponse de la découverte des traces

`GET /api/telemetry/traces?pagination=cursor` renvoie une ligne pour chaque trace,
plus un reçu `naming_quality` pour la page renvoyée :

```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` couvre uniquement la page renvoyée. Ce n’est pas un score pour
tout le compte. `truncated: true` signifie qu’une autre page existe. Quand la page contient un
nom générique, une route non reconnue ou une opération générique, `next_action` vaut
`improve_trace_naming` avec la cible `sdk_configuration`. Sans
`pagination=cursor`, la route renvoie un tableau brut de traces, sans
`naming_quality`.

`next_cursor` contient `time` et `trace_id`. Envoyez-les comme `cursor_time` et
`cursor_trace_id`.

`GET /api/telemetry/traces/{trace_id}` renvoie les spans d’une trace.
`GET /api/telemetry/traces/{trace_id}/summary` renvoie le span racine, les spans
en erreur, les services, la durée et le chemin le plus lent, sans les attributs bruts des spans. Une
réponse `404` du résumé signifie qu’aucune trace ne correspond au périmètre complet. Ne réessayez pas
sans les filtres. Consultez les [réponses des spans et du résumé de trace](#trace-spans-and-summary-responses).

### Listes vides et enregistrements introuvables

Une lecture de liste peut ne renvoyer aucune correspondance. Une lecture de détail suit un autre contrat :

| Requête | Succès | Aucun enregistrement lisible |
| - | - | - |
| `GET /api/logs/{log_id}` | `200` avec un [log](#log-response) | `404` avec `code: not_found` |
| `GET /api/telemetry/issues/{issue_id}` | `200` avec un [problème](#issue-response) | `404` avec `code: not_found` |
| `GET /api/telemetry/issues/{issue_id}/events` | `200` avec des occurrences | `404` avec `code: not_found` sur la première page |
| `GET /api/telemetry/traces/{trace_id}` | `200` avec des spans | `200` avec `[]` |
| `GET /api/telemetry/traces/{trace_id}/summary` | `200` avec un résumé | `404` avec `code: not_found` |

Un log introuvable renvoie cette réponse `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"
  }
}
```

Un problème introuvable utilise `"error": "issue not found"`. Un résumé de trace introuvable utilise
`"error": "trace not found"`. Un ID inconnu et un ID que le compte ne peut pas
lire renvoient la même réponse.

Une liste avec un `project_id` que le compte ne peut pas lire renvoie `200` avec
une liste vide. Vérifiez l’ID du projet avant d’annoncer qu’aucune donnée n’existe.

### Sélections de projets refusées

Les listes de logs, de problèmes, d’actions, de métriques, de versions et de traces acceptent un projet
avec `project_id`, ou de 2 à 50 projets avec `project_ids`. Le catalogue des attributs
de logs accepte les mêmes sélecteurs. L’historique des occurrences d’un problème ne les
accepte pas.

Une requête qui contient à la fois `project_id` et `project_ids` renvoie `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` avec un UUID invalide, un ID répété, ou moins de 2 ou plus de
50 ID renvoie aussi `422` avec `code: validation_failed`.

Si le compte ne peut pas lire l’un des projets d’un ensemble `project_ids`, toute la
requête renvoie `404`. Elle ne renvoie pas les autres projets comme résultat
partiel :

```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"
  }
}
```

Affichez l’erreur, pas une liste vide ni un nombre égal à zéro. Relisez la liste
des projets. Ne retirez pas un projet sans le signaler, et ne réessayez pas sur tous
les projets. Un identifiant délégué, comme une connexion MCP, renvoie `403` avec
`code: forbidden` pour un projet hors de son autorisation. Consultez le
[périmètre de projets](/fr/reference/telemetry-api-filters#project-scope).

### <span id="cursor-errors">Erreurs de curseur</span>

Les logs, les problèmes, les actions, les métriques et les occurrences de problème utilisent `cursor_time` et
`cursor_id`. Un curseur refusé renvoie `422` avec `code: validation_failed`.
Ces erreurs ne signifient pas que la liste est vide.

| Requête | `error` |
| - | - |
| `pagination=cursor` avec un seul des deux champs du curseur | `cursor_time and cursor_id must be sent together` |
| `cursor_time` qui n’est pas un horodatage RFC3339 | `cursor_time must be an RFC3339 timestamp` |
| Champs du curseur sans `pagination=cursor`, ou avec une autre valeur de `pagination` | `pagination must be cursor when cursor pagination is requested` |

Par exemple :

```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"
  }
}
```

Copiez le `next_cursor` complet de la page précédente. Ne remplacez pas
`cursor_time` par l’heure actuelle. Si vous avez perdu le curseur, recommencez avec
`pagination=cursor` et les filtres d’origine. Un paramètre de requête inconnu ou une valeur
du mauvais type renvoie `422` avec `"error": "invalid query parameters"`.

### Réponses du curseur de versions

Un curseur de versions a 5 champs. Une requête réussie sans correspondance renvoie
`200` :

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

Quand `next_cursor` ne vaut pas `null`, envoyez les 5 valeurs dans la requête suivante :

| Champ de `next_cursor` | Paramètre de requête |
| - | - |
| `time` | `cursor_time` |
| `project_id` | `cursor_project_id` |
| `release` | `cursor_release` |
| `environment` | `cursor_environment` |
| `service_name` | `cursor_service_name` |

`cursor_project_id` marque une position. Il ne remplace pas `project_id` ni
`project_ids`. Gardez les paramètres de projet et de filtre d’origine. Encodez
chaque valeur pour l’URL.

Avec `pagination=cursor`, un curseur incomplet ou une valeur vide de version, d’environnement
ou de service renvoie `422` avec
`"error": "all release cursor fields must be sent together"`. Un
`cursor_time` invalide et un `pagination=cursor` absent renvoient les mêmes erreurs que les
[autres curseurs](#cursor-errors).

### Erreurs de la découverte des traces

Ces erreurs s’appliquent à `GET /api/telemetry/traces`. Chacune renvoie `422` avec
`code: validation_failed`. Une requête refusée n’est pas un résultat vide.

| Requête | `error` |
| - | - |
| `status` autre que `error` ou `ok` | `trace status must be error or ok` |
| `min_duration_ms` inférieur à `0` | `minimum trace duration must not be negative` |
| Un seul champ du curseur, ou un `cursor_trace_id` vide | `cursor_time and cursor_trace_id must be sent together` |
| `cursor_time` qui n’est pas un horodatage RFC3339 | `cursor_time must be an RFC3339 timestamp` |
| Champs du curseur sans `pagination=cursor` | `pagination must be cursor when cursor pagination is requested` |

Pour inclure les deux états de trace, omettez `status`. Pour la page suivante, copiez
`next_cursor.time` dans `cursor_time` et `next_cursor.trace_id` dans
`cursor_trace_id`. N’envoyez pas `cursor_id` pour une page de traces. Consultez les
[filtres de traces](/fr/reference/telemetry-api-filters#trace-filters).

### <span id="issue-occurrence-history">Historique des occurrences d’un problème</span>

`GET /api/telemetry/issues/{issue_id}/events` lit les occurrences d’un
problème groupé. Utilisez un jeton bearer de compte et l’ID du problème groupé. La route
accepte uniquement `pagination`, `cursor_time`, `cursor_id` et `limit`. Elle
n’accepte pas de filtre de projet, de temps, de version, d’environnement ou de gravité.

```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"
```

Une première page avec une occurrence et un curseur de continuation :

```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"
  }
}
```

| Champ | Signification |
| - | - |
| `issue_id` | Le problème groupé auquel appartient l’occurrence. |
| `events[].id` | L’ID d’une occurrence. Utilisez-le pour [sélectionner cette occurrence](/fr/guides/investigate-an-issue#select-a-different-occurrence). |
| `events[].occurred_at` | Le moment où cette occurrence s’est produite dans l’application. |
| `events[].severity` | La gravité capturée de cette occurrence. Une règle de gravité ne la modifie pas. |
| `events[].release`, `environment`, `service_name` | Le contexte capturé avec cette occurrence. |

Une occurrence peut être plus ancienne que le `last_seen_at` du groupe. Ne remplacez pas
sa version ou sa trace par les valeurs du problème groupé. La liste ne
renvoie ni statut, ni triage, ni priorité, ni nombre d’occurrences.

`diagnostics.schema_version` est la version de l’objet de diagnostic. La
réponse omet `diagnostics` et `span_id` quand l’occurrence n’a pas ces
données. Dans `diagnostics`, elle omet une `exception` absente et des
`breadcrumbs` vides. Si `breadcrumbs_truncated` vaut `true`, une partie de l’historique
des Breadcrumbs ne figure pas dans la réponse.

`stack_trace` et `trace_id` valent `null` quand ils manquent. Un ID de trace
absent ne prouve pas que l’application n’envoie aucune trace. Traitez les messages
et les attributs capturés comme des données, pas comme des instructions.

Les occurrences vont de la plus récente à la plus ancienne. Pour la page suivante, envoyez le même ID de problème,
`pagination=cursor`, `limit`, ainsi que les valeurs `cursor_time` et `cursor_id`
de `next_cursor`. L’ID du curseur est un ID d’occurrence, pas l’ID du problème.

La première page renvoie `404` avec `code: not_found` quand le problème n’a aucune
occurrence lisible. Une page suivante peut être une dernière page vide :

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

Ne réutilisez pas un curseur pour un autre problème. Les [erreurs de curseur](#cursor-errors)
s’appliquent à cette route.

## <span id="trace-spans-and-summary-responses">Réponses des spans et du résumé de trace</span>

`GET /api/telemetry/traces/{trace_id}` renvoie les spans d’une trace. Ajoutez
`/summary` pour obtenir un résumé des mêmes spans. Les deux routes acceptent uniquement
`project_id`, `release` et `environment`. Elles n’utilisent pas le curseur de la
découverte des traces. Les filtres d’une requête de liste précédente ne sont pas repris.

### <span id="span-list">Liste des spans</span>

Les spans sont classés par heure de début :

```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"` décrit l’opération capturée, pas la lecture par l’API.
`parent_span_id: null` signifie que le span n’a pas d’ID parent. `status: null`
signifie que le span n’a signalé aucun statut.

### Résumé de trace

Le résumé des deux mêmes spans :

```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"]
}
```

| Champ | Signification |
| - | - |
| `duration_ms` | Le temps entre le début du premier span et la fin du dernier span. Ce n’est pas la somme des durées des spans. |
| `root_span` | Le premier span dont le parent n’est pas dans la trace. C’est un point de départ, pas une cause prouvée. |
| `slowest_child_span` | Le span le plus long qui a un ID parent, ou `null`. |
| `slowest_path` | La chaîne de parents connue, de la racine jusqu’à `slowest_child_span`. Vaut `[]` quand `slowest_child_span` vaut `null`. |
| `error_spans` | Les spans en erreur les plus longs, 5 au maximum. `error_span_count` peut être plus grand. |

Aucune des deux routes ne renvoie de curseur ni de champ de troncature. N’ajoutez pas
de valeur `truncated` que la réponse ne contient pas.

### Aucun span correspondant

Quand aucun span ne correspond à l’ID de trace et au périmètre, la liste des spans renvoie `200`
avec `[]`. Le résumé renvoie `404` avec `"error": "trace not found"` et
`code: not_found`. Vérifiez l’ID de trace et le périmètre. Ne retirez pas les filtres
pour faire apparaître un résultat. Aucun de ces résultats ne prouve que l’application n’a envoyé
aucune télémétrie.

## <span id="live-event-response-format">Format des événements en direct</span>

Le flux en direct envoie les nouveaux événements. Il ne rejoue pas les événements que le client
a manqués pendant une déconnexion. Utilisez les routes de liste pour lire les enregistrements existants.

### Créer un ticket

Envoyez `POST /api/feed/ticket` avec un jeton bearer de compte. La requête n’a pas de corps.

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

Une réponse `200 OK` contient le ticket dans le champ `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"
  }
}
```

Le ticket est un identifiant de courte durée. Ne l’écrivez pas dans les logs et ne le mettez pas
dans le prompt d’un agent. Ouvrez la connexion avant la fin de `expires_in_seconds`. L’expiration
limite le moment où la connexion peut commencer, pas la durée pendant laquelle elle reste ouverte.

Une réponse `401` signifie que la connexion au compte n’est pas valide. Connectez-vous de nouveau avant de
demander un ticket. Une autre méthode que `POST` renvoie `405` avec
`code: method_not_allowed`.

### Ouvrir la connexion

Connectez un client WebSocket à
`wss://api.logbrew.co/api/feed/live?ticket=<feed-ticket>`. Encodez le
ticket pour l’URL. Ne mettez ni jeton bearer de compte ni jeton bearer d’ingestion dans l’URL. Une réponse `401`
signifie que le ticket ou la connexion au compte n’est pas valide. Créez un nouveau ticket
après vous être connecté de nouveau.

| Réponse | Cause | Étape suivante |
| - | - | - |
| `422` avec `"error": "feed ticket is required"` | La valeur de requête `ticket` est absente ou vide. | Créez un ticket et ouvrez la connexion avec. |
| `401` avec `code: unauthorized` | Le ticket a expiré ou n’est pas valide. | Créez un nouveau ticket. Ne réessayez pas avec le même ticket. |
| `code: invalid_websocket_upgrade` | La requête n’est pas une mise à niveau WebSocket valide. | Utilisez un client WebSocket. |
| `405` avec `code: method_not_allowed` | La requête n’utilise pas `GET`. | Utilisez `GET` pour la connexion. |

Après une erreur, attendez avant de rouvrir la connexion. Ne rouvrez pas la connexion en boucle serrée.

### Lire les messages

Chaque message est une enveloppe `{ "type": "...", "data": ... }`. Lisez `type`
avant d’analyser `data`. Ignorez les types que le client ne prend pas en charge.

| Type d’événement | Contenu de `data` |
| - | - |
| `native_log` | Données de log, sous la forme de la [réponse de log](#log-response). |
| `native_issue` | Données du problème groupé, sous la forme de la [réponse de problème](#issue-response) en version 1. |
| `native_trace_span` | Un span, sous la forme d’un élément de la [liste des spans](#span-list). |
| `native_action` | Données d’action produit, sous la forme de la [réponse d’action](#action-response). |
| `project_created`, `project_updated`, `project_catalog_changed` | Données de projet. Relisez la liste des projets quand elle change. |
| `usage_updated`, `usage_limit_warning`, `usage_limit_blocked` | Utilisation du compte, sous la forme de l’[utilisation du compte](/fr/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"
    }
  }
}
```

Un événement `native_issue` utilise la version 1 de la réponse de problème : un problème archivé
affiche `ignored`, et l’événement n’a pas de priorité. Les métriques et les versions ne sont pas
des types d’événements en direct. Lisez-les avec leurs routes de liste. Le ticket ne
sélectionne pas de projet. Vérifiez le `project_id` de chaque événement avant
de l’afficher dans la vue d’un projet. `logbrew watch --json` utilise ce flux. Consultez la
[référence de la CLI](/fr/reference/cli).

### Gérer une connexion fermée

| Code de fermeture | Motif | Étape suivante |
| - | - | - |
| `1013` | `live feed fell behind; reconnect to resync` | Attendez, puis rouvrez la connexion avec un nouveau ticket. Relisez les routes de liste pour récupérer les enregistrements manqués. |
| `1008` | `live feed access changed; sign in again` | Arrêtez de rouvrir la connexion. Connectez-vous de nouveau avant de créer un nouveau ticket. |

D’autres déconnexions peuvent n’avoir aucun motif LogBrew. Après avoir rouvert la connexion, relisez les
routes de liste et rapprochez les enregistrements par ID.

## <span id="issue-status-response-examples">Exemples de réponses de statut de problème</span>

`PATCH /api/telemetry/issues/{issue_id}` avec `{ "status": "resolved" }`
renvoie `200 OK` avec le problème groupé mis à jour, sous la même forme que la
[réponse de problème](#issue-response). Le paramètre de requête `response_version` choisit
la version. La route ne renvoie pas d’indicateur de succès.

Le corps définit au moins un des champs `status`, `priority` et `assignee`. `status`
accepte `unresolved`, `resolved` ou `archived`. `ignored` est un ancien nom
de `archived`. Consultez [Mettre à jour un problème](/fr/reference/telemetry-api#update-one-issue).

Vérifiez les valeurs renvoyées de `id`, `status` et `priority` avant d’afficher un succès. Ne
construisez pas le résultat à partir des valeurs envoyées. Un changement de statut ne supprime pas
d’occurrences et ne modifie ni `first_seen_at` ni `last_seen_at`. Il ne modifie pas
`severity` ni une règle de gravité.

Une modification refusée renvoie `422` avec `code: validation_failed` :

```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"
  }
}
```

| Résultat | Cause |
| - | - |
| `422`, `issue update requires status, priority, or assignee` | Le corps ne contient aucun des 3 champs. |
| `422`, `issue priority must be high, medium, or low` | La valeur de priorité n’est pas prise en charge. |
| `422`, `issue assignee must be me, none, a user ID, or team:<team_id>` | La valeur du responsable n’est pas prise en charge. |
| `422`, `issue assignee cannot read this project` | Le responsable n’a pas accès au projet. LogBrew ne modifie rien. |
| `422`, `issue assignee team is not a team of this project's organization` | L’équipe n’appartient pas à l’organisation du projet. LogBrew ne modifie rien. |
| `403` avec `code: forbidden` | Le compte ne peut pas gérer les problèmes du projet. |
| `404` avec `code: not_found` | Le compte ne peut pas lire le problème, ou le problème n’existe pas. |

Un champ qui a déjà la valeur demandée ne change pas. Après un délai dépassé
ou une réponse perdue, renvoyez la même requête, ou lisez d’abord le problème. N’envoyez
pas une autre modification avant d’avoir lu l’état actuel. Consultez
[Écritures](/fr/reference/api-errors#writes).

`POST /api/telemetry/issues/bulk` renvoie `200 OK` avec un résultat pour chaque
ID de problème demandé, dans l’ordre de la requête. Chaque problème trouvé utilise la forme
de la version 3. Un résultat `not_found` contient `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
    }
  ]
}
```

Cet exemple ne montre que certains champs du problème. `outcome` vaut `updated`,
`unchanged` ou `not_found`. Une requête répétée renvoie `unchanged` et
ne modifie rien. Consultez
[Mettre à jour des problèmes en masse](/fr/reference/telemetry-api#update-issues-in-bulk).

Une requête en masse échoue en entier dans ces cas :

| Résultat | Cause |
| - | - |
| `422`, `issue_ids must name 1 to 100 issues` | La liste est vide ou contient plus de 100 ID. |
| `422`, `issue_ids must not contain duplicates` | Un ID apparaît plus d’une fois. |
| `422` avec `code: invalid_json` | Le corps contient un champ inconnu. |
| `403` avec `code: forbidden` | Le compte ne peut pas gérer les problèmes du projet. |
| `404`, `project not found` | Le compte ne peut pas lire le projet. |

## Périmètre des requêtes d’analyse

Les routes d’analyse, la route des séries de métriques et la route du volume de signaux
lisent un projet sur une période. Chaque requête exige `project_id` et
`since`. `since` accepte une durée compacte comme `24h` ou `7d`, ou un
horodatage RFC3339. `until` accepte un horodatage RFC3339 et vaut par défaut l’heure de la
requête. La période inclut `since`, exclut `until` et ne peut pas dépasser
31 jours.

`service_name`, `release` et `environment` sont des filtres exacts facultatifs. Chaque
valeur doit compter de 1 à 256 caractères une fois que LogBrew a retiré les espaces. La route de
comparaison de segments place ces filtres dans chaque segment, pas au premier niveau.

Si le compte ne peut pas lire le projet, ces routes renvoient `200 OK` avec
des nombres à zéro. Elles ne renvoient pas `404`. Un résultat vide ne confirme pas
l’accès au projet. Comparez `project_id` avec les projets que le compte peut
lire. Une requête passée par une connexion MCP qui n’inclut pas le projet renvoie
`403` et `forbidden`.

| Route | Valeurs de `response_version` | Par défaut |
| - | - | - |
| `GET /api/telemetry/analytics/overview` | `1`, `2` ou `3` | `1` |
| `GET /api/telemetry/analytics/properties` | `1` ou `2` | `1` |
| `POST /api/telemetry/analytics/segments/compare` | `1` ou `2` | `1` |
| `POST /api/telemetry/analytics/retention` | `1` ou `2` | `1` |
| `POST /api/telemetry/analytics/lifecycle` | `1` ou `2` | `1` |
| `POST /api/telemetry/analytics/funnel` | Non acceptée | Schéma `1` |
| `POST /api/telemetry/analytics/paths` | Non acceptée | Schéma `1` |
| `GET /api/telemetry/metrics/series` | Non acceptée | Schéma `1` |
| `GET /api/telemetry/summary/volume` | `1` | `1` |

Chaque réponse commence par `schema_version`, `query` et `purpose`. `query`
reprend le périmètre effectif. Il omet un filtre que vous n’avez pas envoyé. `purpose`
est un texte simple qui explique à quelle question la route répond.

Un taux est omis quand son dénominateur vaut zéro. Un taux omis ne vaut ni `0`
ni `null`. Lisez `coverage` avant de vous fier à un taux.

### <span id="shared-scope-errors">Erreurs de périmètre communes</span>

Ces erreurs utilisent HTTP `422` et `validation_failed` :

| Cause | `error` |
| - | - |
| Pas de `project_id` | `product analytics project_id is required` |
| Pas de `since` | `product analytics since is required` |
| `since` n’est ni une durée ni un horodatage RFC3339 | `invalid since value` |
| `until` n’est pas un horodatage RFC3339 | `product analytics until must be an RFC3339 timestamp` |
| `since` n’est pas antérieur à `until` | `product analytics since must be earlier than until` |
| La période dépasse 31 jours | `product analytics range must not exceed 31 days` |
| Un filtre vide, ou un filtre de plus de 256 caractères | `product analytics context filters must be non-empty and at most 256 characters` |

Par exemple, une requête sans `project_id` renvoie :

```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" }
}
```

Les routes des séries de métriques et du volume de signaux appliquent les mêmes règles de périmètre, avec
leur propre texte `error`. Leurs sections ci-dessous listent ces erreurs.

Un paramètre de requête inconnu renvoie `422` avec la réponse
[paramètres de requête invalides](/fr/reference/api-errors#invalid-query-parameters).
Un champ JSON inconnu, un champ JSON obligatoire absent ou une
valeur d’énumération non prise en charge renvoie `invalid_json`. Consultez
[JSON invalide](/fr/reference/api-errors#invalid-json). Une requête avec la mauvaise
méthode HTTP renvoie `405` et `method_not_allowed`. Son texte `next` indique la
méthode et les champs pris en charge. Consultez
[Méthode non prise en charge](/fr/reference/api-errors#unsupported-method).

## Réponse de la vue d’ensemble des analyses produit

`GET /api/telemetry/analytics/overview` montre l’activité d’un projet :
volume d’actions, utilisateurs, sessions, une série temporelle, les principales actions, et les
vues de page, vues d’écran et interactions classifiées. Utilisez-la pour trouver les noms exacts
des événements à utiliser dans les autres routes d’analyse.

Envoyez `response_version=3`. Les versions 1 et 2 renvoient les mêmes nombres, mais leur
valeur `estimation.method` utilise un ancien nom. La version 1 ne vérifie pas le
type de sujet quand elle compte les utilisateurs identifiés. Les versions 2 et 3 comptent uniquement
les sujets de type `user`, et elles ajoutent les nombres de sujets anonymes et
`subject_coverage`.

Cette requête lit une heure en intervalles de cinq minutes et renvoie deux lignes dans
chaque classement :

```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"
```

### Vue d’ensemble réussie

Dans ce résultat fictif, le projet a 120 actions réparties dans deux des douze
intervalles. Vingt actions viennent de sujets anonymes. Chaque action a un
ID de session. La moitié des actions ont un ID de trace.

```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."
  }
}
```

### Lire la vue d’ensemble

La somme des cinq nombres de `subject_coverage` est égale au total d’événements
de leur section. Seul `identified_user_events` compte pour les utilisateurs.
`anonymous_actions` vaut `summary.actions` moins `identified_actions`.

`series` contient uniquement les intervalles qui ont de l’activité. `expected_buckets` est le
nombre d’intervalles possibles dans la période. N’affichez pas un intervalle absent comme un
zéro mesuré, sauf si votre graphique indique cette règle. Les intervalles s’alignent sur les
limites d’intervalle UTC. Une période qui commence au milieu d’un intervalle inclut cet
intervalle incomplet.

`classified_activity` compte les vues de page, les vues d’écran et les interactions.
Les vues d’écran et les interactions sont aussi des actions. Les vues de page ne le sont pas.
N’additionnez pas `summary.actions` et `classified_activity.summary.events`.

Copiez `kind` et `event_name` depuis `classified_activity.top_events` dans les étapes
d’entonnoir, les sélecteurs de rétention, les sélecteurs de cycle de vie, les ancres de parcours et les cibles
de segment. La valeur `true` dans `top_events_truncated` signifie que des noms moins fréquents
existent. Augmentez `top_limit` jusqu’à 20 pour en voir plus.

`analysis_level` vaut `no_data`, `event_only`, `user_level` ou
`user_and_session`. Si le projet n’a ni action ni événement classifié,
`next_action.code` vaut `capture_product_activity`. Consultez
[Analyses produit](/fr/concepts/product-analytics#use-the-analytics-api).

### <span id="overview-errors">Erreurs de la vue d’ensemble</span>

En plus des [erreurs de périmètre communes](#shared-scope-errors), la vue d’ensemble renvoie
`422` et `validation_failed` pour ces valeurs :

| Valeur | `error` |
| - | - |
| `response_version` autre que `1`, `2` ou `3` | `product analytics response_version must be 1, 2, or 3` |
| `top_limit` hors de `1` à `20` | `product analytics top_limit must be between 1 and 20` |
| `interval` autre que `auto`, `1m`, `5m`, `15m`, `1h`, `6h` ou `1d` | `product analytics interval is not supported` |
| Un `interval` explicite qui exige plus de 500 intervalles | `product analytics interval would exceed 500 buckets` |

La valeur par défaut de `interval` est `auto`. Elle choisit la plus petite durée d’intervalle qui donne
au plus 300 intervalles. La valeur par défaut de `top_limit` est `10`.

## <span id="ordered-funnel-response">Réponse d’entonnoir ordonné</span>

`POST /api/telemetry/analytics/funnel` mesure la progression ordonnée à travers 2 à
8 étapes. Copiez chaque `kind` et `event_name` exacts depuis
`classified_activity.top_events` dans la vue d’ensemble. Un nom d’action similaire ne
correspond pas. Ce corps de requête compte les sessions qui consultent `/pricing` puis
terminent l’inscription en moins d’une heure :

```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` vaut `session` ou `identified_user`. La valeur par défaut est `session`.
Le `kind` d’une étape vaut `page_view`, `screen_view` ou `interaction`. La route d’entonnoir
n’accepte pas `response_version`.

### Entonnoir réussi

Une réponse `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."
  }
}
```

Les taux sont des fractions de 0 à 1. Lisez `coverage` avant de vous fier à un taux.
`selected_unit_coverage_rate` est la part des événements d’étape sélectionnés que
LogBrew a pu utiliser. La réponse ne renvoie pas d’identités brutes. Consultez
[Analyses produit](/fr/concepts/product-analytics#measure-a-funnel).

### Lire la conversion et la couverture

`entry_rate` vaut `entered_units` divisé par `candidate_units` : 50 sur 60.
`overall_conversion_rate` vaut `completed_units` divisé par `entered_units` :
15 sur 50, soit 30 %. Ce n’est pas 15 divisé par tous les candidats. Ces nombres
n’expliquent pas pourquoi des unités se sont arrêtées.

La couverture compte des événements, pas des sessions. Ici, 120 événements correspondent à une étape, et 20
d’entre eux n’ont pas d’ID de session. Le résultat exclut ces 20 événements des
nombres de sessions. Ne les traitez pas comme 20 sessions en échec.

La première étape n’a pas de `conversion_from_previous`. La dernière étape n’a ni
`drop_off_to_next_units` ni `drop_off_to_next_rate`.

Une unité n’atteint une étape suivante que si les heures des étapes sont strictement croissantes.
Des événements avec le même horodatage ne peuvent pas valider deux étapes consécutives. La dernière
étape doit se produire dans les `conversion_window_seconds` qui suivent la première étape.

Le mode session compte des sessions, pas des personnes. Utilisez `identified_user` uniquement pour les événements
qui portent un ID de sujet opaque de type `user`. Ce mode peut relier des événements de
sessions différentes dans la fenêtre de conversion. Gardez la même unité et les
mêmes étapes quand vous comparez des résultats.

La valeur par défaut de `conversion_window_seconds` est 24 heures ou la durée de la
période, selon la plus courte. Le maximum est la durée de la période en secondes
entières, arrondie au supérieur.

### Résultats d’entonnoir vides

Une réponse vide réussie liste quand même les étapes demandées, avec des
`units` à zéro. S’il n’y a aucune unité candidate, `summary` omet `entry_rate` et
`overall_conversion_rate`. La route ne renvoie pas `404` parce qu’aucun événement
ne correspond.

Lisez `coverage` et `next_action` pour trouver la cause :

| `next_action.code` | Cause |
| - | - |
| `capture_product_activity` | Aucun événement classifié n’existe dans le périmètre. |
| `choose_captured_funnel_steps` | Aucun événement ne correspond à une étape demandée. |
| `sessionize_product_activity` | Des événements d’étape existent, mais aucun n’a d’ID de session. |
| `identify_product_users` | Des événements d’étape existent, mais aucun n’a de sujet de type `user`. |
| `verify_funnel_entry` | Aucune unité n’est entrée à l’étape 1. |
| `investigate_funnel_drop_off` | Des unités sont entrées, mais aucune n’a terminé toutes les étapes. |
| `compare_funnel_contexts` | Au moins une unité a terminé l’entonnoir. |

### Requêtes d’entonnoir refusées

En plus des [erreurs de périmètre communes](#shared-scope-errors), ces requêtes renvoient
`422` et `validation_failed`. Moins de 2 ou plus de 8 étapes :

```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" }
}
```

Un `conversion_window_seconds` de `0`, ou une valeur supérieure à la durée de la période :

```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" }
}
```

Un `event_name` vide, de plus de 256 caractères ou qui contient un
caractère de contrôle renvoie
`product analytics funnel event_name must be non-empty, control-free, and at most 256 characters`.
Une étape `interaction` doit aussi avoir un nom d’action valide. Sinon, l’erreur
est `product analytics interaction event_name must be a bounded stable action name`.

Corrigez la requête avant de la renvoyer. Ne retirez pas d’étapes et ne changez pas
l’unité d’analyse dans le seul but d’obtenir un résultat non vide.

## Analyse par événement exact et analyse automatique

La rétention et le cycle de vie acceptent deux formes de requête. Utilisez `response_version` pour
choisir la forme qui répond à votre question :

| Analyse | `response_version` | Sélecteurs d’événements |
| - | - | - |
| Rétention sur des événements exacts | `1`, ou omis | Envoyez à la fois `start_event` et `return_event`. |
| Cycle de vie sur un événement exact | `1`, ou omis | Envoyez `event`. |
| Rétention ou cycle de vie automatiques | `2` | N’envoyez aucun sélecteur d’événement. |

Un sélecteur exact contient `kind` et `event_name`. `kind` vaut `page_view`,
`screen_view` ou `interaction`. Copiez `event_name` depuis
`classified_activity.top_events`. Ne devinez pas un nom d’événement à partir du libellé
d’un bouton.

Les deux formes comptent uniquement des utilisateurs : des ID de sujet opaques de type `user`. Les sujets
anonymes et les événements sans ID de sujet utilisable ne sont pas comptés comme utilisateurs.
Les réponses ne renvoient jamais d’ID de sujet.

Les intervalles fixes sont `hour`, `day`, `week` et `thirty_day`. Ce sont des durées
fixes. `thirty_day` dure 30 jours, pas un mois civil. Si vous omettez
`interval`, l’API utilise `hour` pour une période de 48 heures ou moins, et `day` pour
une période plus longue.

### Requête de rétention par événement exact

Envoyez ce corps à `POST /api/telemetry/analytics/retention`. Il demande si
les utilisateurs finalisent un autre paiement après leur premier paiement dans la période :

```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" }
}
```

Le premier événement de départ correspondant dans la période sert de point d’ancrage à chaque utilisateur. Un retour doit
se produire strictement après ce point d’ancrage et avant `until`. Les deux sélecteurs peuvent désigner le
même événement.

| Champ | Valeurs | Par défaut |
| - | - | - |
| `interval_count` | De `1` à `31` intervalles | `10` |
| `mode` | `return_on` compte un retour dans chaque intervalle exact. `return_on_or_after` compte un retour au début de chaque intervalle ou après. | `return_on` |
| `cohort_mode` | `first_in_range` | `first_in_range` |

### Rétention par événement exact réussie

Dans cet exemple fictif, deux utilisateurs finalisent un paiement. Le premier utilisateur
finalise un paiement le 1er juin à 12 h 00, puis de nouveau à 13 h 00. Le second utilisateur
finalise un paiement le 2 juin à 12 h 00. Le 2 juin à 13 h 00, le second utilisateur
consulte une page mais ne finalise pas d’autre paiement.

```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."
  }
}
```

### Lire la rétention

La couverture compte les quatre événements classifiés, mais seuls trois correspondent à chaque
sélecteur de paiement. La vue de page ne compte pas comme un retour. L’un des deux
utilisateurs finalise un autre paiement : `return_rate_after_start` vaut donc `0.5`.

Seul le premier utilisateur a une journée complète d’observation après le point d’ancrage. Cet utilisateur
revient dans la journée : le taux de la courbe vaut donc `1.0`. Le second utilisateur n’a que
12 heures d’observation. Il n’est pas éligible pour l’intervalle 0 : la cellule de la seconde
cohorte omet donc `retention_rate`. Ne comptez pas cet utilisateur comme perdu.

`weighted_retention_rate` divise l’ensemble des utilisateurs revenus par l’ensemble des utilisateurs éligibles.
`fully_observed_cohort_average_rate` est la moyenne simple des taux des
cohortes dont tous les utilisateurs sont éligibles. Les cohortes sont des intervalles fixes qui commencent
à `since`. `cohorts` contient uniquement les cohortes qui ont des utilisateurs.

Avec `mode: "return_on_or_after"`, la courbe omet `window_end_seconds`.

### Rétention automatique

Envoyez `response_version: 2`, sans `start_event` ni `return_event`. Chaque
vue de page, vue d’écran et interaction prise en charge compte comme départ et comme
retour. Cette version sélectionne un ensemble d’événements. Elle ne sélectionne pas de format
de capture du 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"
}
```

La réponse a les mêmes formes `summary`, `curve`, `cohorts` et `next_action`
que la réponse par événement exact. Cet extrait montre les champs qui changent.
Dans cet exemple, les deux mêmes utilisateurs ont trois événements et aucune vue de page :

```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."
    ]
  }
}
```

En mode automatique, `start_events` et `return_events` sont tous deux égaux à
`classified_events`. Ces nombres comptent des événements, pas des utilisateurs.

### Rétention sans utilisateur éligible

Un résultat réussi peut n’avoir aucun utilisateur dans les cohortes. Cet extrait montre un
intervalle demandé sans cohorte :

```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": []
}
```

Lisez `coverage` et `next_action`. Ils distinguent un périmètre sans événement
classifié (`capture_product_activity`) d’événements sans utilisateur
(`identify_product_users` ou `identify_returning_users`). Un taux absent n’est
pas un zéro mesuré.

### Erreurs de rétention

En plus des [erreurs de périmètre communes](#shared-scope-errors), ces requêtes renvoient
`422` et `validation_failed` :

| Cause | `error` |
| - | - |
| Version 1 sans les deux sélecteurs, version 2 avec un sélecteur, ou autre version | `product analytics retention activity selection is contradictory` |
| `interval_count` hors de `1` à `31` | `product analytics retention interval_count must be between 1 and 31` |
| Le nombre d’intervalles de cohorte multiplié par `interval_count` dépasse 500 | `product analytics retention interval and interval_count would exceed 500 cohort matrix cells` |

La limite de cellules dépend de la période et des intervalles, pas du nombre d’utilisateurs. Une
période de 30 jours avec `interval: "hour"` compte 720 intervalles de cohorte. Avec
`interval_count: 1`, cela donne 720 cellules, soit plus de 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" }
}
```

Pour utiliser la rétention automatique, retirez les deux sélecteurs. Ne modifiez pas le SDK pour
corriger cette requête de lecture.

### Requête de cycle de vie par événement exact

Envoyez ce corps à `POST /api/telemetry/analytics/lifecycle`. Il classe
les utilisateurs selon un seul événement, pas selon toute l’activité du projet :

```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` définit le nombre d’intervalles complets avant `since` que LogBrew
lit comme historique. Il accepte de `2` à `31`. La valeur par défaut est `2`. Un utilisateur peut être
inactif pour cet événement tout en ayant d’autres activités.

### Cycle de vie par événement exact réussi

Cet exemple fictif utilise deux jours d’historique et deux jours d’analyse. Les
lettres servent seulement à identifier les utilisateurs de l’exemple. La réponse ne renvoie pas d’ID utilisateur.

| Date (UTC) | Utilisateurs qui finalisent un paiement |
| - | - |
| 30 mai, historique | B |
| 31 mai, historique | A et D |
| 1er juin, analyse | A, B et C |
| 2 juin, analyse | A et B |

C consulte aussi une page le 2 juin. Cet événement ne correspond pas au sélecteur
de paiement.

```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."
  }
}
```

### Lire le cycle de vie

Le 1er juin, A est récurrent, B est réactivé, C est nouveau et D est inactif. Le
2 juin, A et B sont récurrents, et C est inactif pour le paiement malgré la vue
de page. La variation nette du 2 juin est `2 - 3 = -1`.

Dans chaque intervalle, la somme des utilisateurs nouveaux, récurrents et réactivés est égale à
`active_subjects`. `returning_subjects` plus `dormant_subjects` est égal à
`previous_active_subjects`. `net_active_change` vaut `active_subjects` moins
`previous_active_subjects`.

N’additionnez pas les nombres d’actifs quotidiens pour obtenir des utilisateurs distincts. Ici, la somme des nombres quotidiens
vaut 5, mais `analysis_active_subjects` vaut 3. Quatre utilisateurs distincts apparaissent
sur l’ensemble de l’historique et de l’analyse.

`fully_observed` vaut `false` pour un dernier intervalle qui se termine après `until`. Ses
nombres peuvent changer quand l’intervalle complet devient observable. La `next_action`
suggère une investigation. Elle n’indique pas de cause.

### Cycle de vie automatique

Envoyez `response_version: 2`, sans sélecteur `event`. Chaque vue de page,
vue d’écran et interaction prise en charge compte comme activité :

```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
}
```

La réponse a les mêmes formes `summary`, `buckets` et `next_action` que
la réponse par événement exact. Cet extrait montre les champs qui changent :

```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."
}
```

En mode automatique, `coverage.selected_events` est égal à
`coverage.analysis_classified_events`. La cinquième entrée de `limitations` est
`Returning, resurrected, and dormant compare any supported classified product activity; no event is ranked, selected, or inferred.`

### Cycle de vie sans utilisateur

Si ni la période d’analyse ni l’historique ne contiennent d’utilisateur utilisable, `buckets`
est vide et `summary` omet `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": []
}
```

Des utilisateurs qui n’apparaissent que dans l’historique peuvent quand même produire des intervalles avec des
utilisateurs inactifs quand la période d’analyse n’a aucune activité.

### Erreurs de cycle de vie

En plus des [erreurs de périmètre communes](#shared-scope-errors), ces requêtes renvoient
`422` et `validation_failed` :

| Cause | `error` |
| - | - |
| Version 1 sans `event`, version 2 avec `event`, ou autre version | `product analytics lifecycle activity selection is contradictory` |
| `history_period_count` hors de `2` à `31` | `product analytics lifecycle history_period_count must be between 2 and 31` |
| Intervalles d’historique de plus de 62 jours au total | `product analytics lifecycle history must not exceed 62 days` |
| Plus de 100 intervalles d’analyse | `product analytics lifecycle interval would exceed 100 analysis buckets` |

Par exemple, dix intervalles d’historique `week` couvrent 70 jours :

```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" }
}
```

### Lire le périmètre d’analyse sélectionné

Les réponses par événement exact ont `schema_version: 1`. Leur `query` reprend les
sélecteurs et omet `activity_scope`. Les réponses automatiques ont
`schema_version: 2` et `query.activity_scope: "all_classified_activity"`.
Elles omettent les sélecteurs. Un autre sélecteur peut changer le résultat sans
aucun changement dans l’activité capturée.

## Réponse de série de métrique

`GET /api/telemetry/metrics/series` lit un nom de métrique exact dans un
projet. Copiez le nom depuis `GET /api/telemetry/metrics`. Cette requête regroupe
un compteur delta par service :

```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"
```

### Série de métrique réussie

Cette réponse fictive contient une série de compteur delta. Trois échantillons de
valeurs `2`, `3` et `5` se trouvent dans le premier intervalle. Deux échantillons de valeurs
`1` et `4` se trouvent dans le dernier intervalle. Aucun échantillon ne se trouve dans l’intervalle du milieu :
la réponse n’a donc pas de point pour lui.

```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."
  }
}
```

### Lire une série de métrique

Le premier point enregistre 10 tâches terminées, pas 10 échantillons. `rate_per_second`
divise cette variation par l’intervalle de 300 secondes. `last` est la dernière variation
enregistrée, pas la somme. Consultez la
[signification des agrégations de métriques](/fr/concepts/telemetry#read-the-metric-value).

Le type et la temporalité d’une série déterminent son `aggregation.code` et les
statistiques de chaque point. Un point omet une statistique qui ne s’applique pas. Une
statistique absente n’est pas zéro.

| Type et temporalité | `status` | `aggregation.code` | `value` | Autres statistiques |
| - | - | - | - | - |
| `gauge`, `instant` | `ready` | `gauge_last` | Dernier échantillon | `last`, `min`, `max`, `average` |
| `counter`, `delta` | `ready` | `delta_sum` | Somme des variations | `last`, `min`, `max`, `average`, `sum`, `rate_per_second` |
| `histogram`, `delta` | `ready` | `distribution_p95` | p95 approximatif | `min`, `max`, `average`, `sum`, `p50`, `p95`, `p99` |
| Tout type, `cumulative` | `limited` | `raw_cumulative_last` | Dernier échantillon brut | `last`, `min`, `max` |
| Toute autre paire | `limited` | `raw_last` | Dernier échantillon brut | `last`, `min`, `max` |

`limited` est une réponse réussie, pas une erreur. Elle ajoute
`aggregation.limitation`, qui explique pourquoi LogBrew n’affiche que des valeurs brutes.
`ready` ne prouve pas que la capture est complète.

Lisez `coverage` avant de tracer un graphique :

* `samples` compte les échantillons de toutes les séries correspondantes, y compris les séries que
  la réponse ne renvoie pas.
* `series` compte les séries correspondantes. `returned_series` et `points` comptent les
  données renvoyées.
* `truncated` vaut `true` quand `series_limit` a retiré des séries de plus faible volume.
* `expected_buckets_per_series` est le nombre d’intervalles possibles dans la période.

N’ajoutez pas de point pour un intervalle sans échantillon. N’insérez pas de zéro et ne copiez pas
la valeur précédente. Chaque point renvoie jusqu’à 3 `trace_exemplars`. Un exemplaire
de trace relie un échantillon à une trace. Il n’explique pas tous les échantillons de
l’intervalle. Si aucun point n’a d’exemplaire de trace, `next_action.target` vaut
`metric_samples`.

### Périmètre et erreurs des séries de métriques

`project_id`, `name` et `since` sont obligatoires. `name` compte au plus 200
caractères. `interval` vaut `auto`, `1m`, `5m`, `15m`, `1h`, `6h` ou `1d`.
La valeur par défaut est `auto`, qui choisit la plus petite durée d’intervalle donnant au plus 300
intervalles. Une durée d’intervalle explicite ne peut pas exiger plus de 500 intervalles.

`group_by` vaut `none`, `service_name`, `release` ou `environment`. La valeur par défaut
est `none`. Sans regroupement, les types, unités et temporalités différents restent dans
des séries séparées. `series_limit` accepte de `1` à `20`. La valeur par défaut est `10`.

Un résultat vide renvoie `200` avec `series: []` et des `samples` à zéro. L’objet
`coverage` omet `first_seen_at` et `last_seen_at`. Cela ne
prouve pas que l’application n’a jamais envoyé la métrique. Vérifiez le projet, le
nom de la métrique et la période.

Omettre `name` renvoie `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" }
}
```

Des intervalles d’une minute sur sept jours renvoient `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" }
}
```

Les autres erreurs `422` sont `metric series group_by is not supported`,
`metric series interval is not supported` et
`metric series_limit must be between 1 and 20`.

## Réponse d’analyse des parcours

`POST /api/telemetry/analytics/paths` trouve les événements nommés avant ou après
un événement sélectionné dans la même session. L’événement sélectionné est l’ancre.
Copiez son `kind` et son `event_name` depuis `classified_activity.top_events`. La
route renvoie `schema_version: 1`. Elle n’accepte ni `response_version` ni
`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` et `anchor` sont obligatoires.

### Parcours réussis

Dans ce résultat fictif, quatre sessions contiennent l’ancre. Les deux parcours
renvoyés représentent trois de ces sessions. Un parcours moins bien classé n’est pas
renvoyé.

```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."
  }
}
```

### Lire les parcours

`share_of_anchored_sessions` divise les sessions d’un parcours par l’ensemble des quatre
sessions ancrées. Les parts sont de 50 % et 25 %, pas de 67 % et 33 %.
`unrepresented_sessions` compte les sessions ancrées qu’aucun parcours renvoyé ne
représente. `paths_truncated: true` signifie que le résultat est incomplet.

La couverture compte des événements, pas des sessions. Ici, cinq événements correspondent à l’ancre, mais
l’un d’eux n’a pas d’ID de session. Cet événement ne peut entrer dans aucun parcours. Chaque parcours renvoie jusqu’à
3 exemplaires de trace. Utilisez un ID de trace tiré de votre propre réponse. Un lien de trace ne
montre pas ce qui a causé une action utilisateur ou un échec.

| Champ | Signification |
| - | - |
| `following` | Commence à la première ancre correspondante de chaque session. L’ancre a la position `0`. Les nœuds suivants ont des positions positives. |
| `preceding` | Se termine à la dernière ancre correspondante de chaque session. Les nœuds précédents ont des positions négatives. L’ancre a la position `0`. |
| `depth` | Nombre de nœuds à côté de l’ancre, sans compter l’ancre. De `1` à `8`. La valeur par défaut est `4`. Un parcours peut avoir moins de nœuds. |
| `collapse_repeated` | Fusionne les nœuds consécutifs de même type et de même nom. La valeur par défaut est `true`. |
| `path_limit` | Nombre maximal de parcours renvoyés, classés par nombre de sessions. De `1` à `20`. La valeur par défaut est `10`. |

Les nœuds sont dans l’ordre chronologique dans les deux directions. Un parcours n’est pas une relecture
complète de la session. LogBrew lit au plus 1024 événements nommés par session avant de choisir
l’ancre : les premiers événements pour `following` et les derniers pour
`preceding`. Une ancre hors de cette limite peut être omise.

### <span id="filter-the-anchor-by-property">Filtrer l’ancre par propriété</span>

Envoyez jusqu’à quatre `property_filters`, chacun avec une `key` et une `value` exactes.
Copiez la clé depuis le [catalogue de propriétés](#analytics-property-catalog). Les
filtres s’appliquent uniquement à l’ancre, pas aux événements qui l’entourent. Les valeurs sont
sensibles à la casse. Quand des filtres sont présents, `query` les reprend, et
`coverage.anchor_property_filters` indique :

| Champ | Signification |
| - | - |
| `context_events` | Événements d’ancre dans le périmètre, avant les filtres de propriété. |
| `property_ready_events` | Événements d’ancre qui ont toutes les clés demandées. |
| `missing_property_events` | Événements d’ancre auxquels il manque au moins une clé demandée. |
| `matching_events` | Événements d’ancre dont les valeurs correspondent à tous les filtres. |
| `nonmatching_value_events` | Événements d’ancre qui ont toutes les clés, mais une valeur différente. |
| `property_ready_rate` | `property_ready_events` divisé par `context_events`. |
| `match_rate` | `matching_events` divisé par `property_ready_events`. |

Les clés manquantes et les valeurs différentes demandent des corrections différentes. Ne les additionnez
pas. L’objet est omis quand la requête n’a aucun filtre de propriété.

### Parcours vides et requêtes refusées

Un tableau `paths` vide peut être une réponse `200` réussie. Lisez `summary`,
`coverage` et `next_action`. Ces codes distinguent les causes :
`capture_product_activity`, `choose_captured_path_anchor`,
`capture_anchor_properties`, `improve_anchor_property_coverage`,
`verify_anchor_property_values`, `sessionize_product_activity` et
`narrow_or_move_path_anchor`.

En plus des [erreurs de périmètre communes](#shared-scope-errors), ces requêtes renvoient
`422` et `validation_failed` :

| Cause | `error` |
| - | - |
| `depth` hors de `1` à `8` | `product analytics path depth must be between 1 and 8` |
| `path_limit` hors de `1` à `20` | `product analytics path_limit must be between 1 and 20` |
| Plus de quatre filtres de propriété | `product analytics paths accept at most four exact anchor property filters` |
| Une clé absente du catalogue de propriétés | `product analytics path property keys must be supported privacy-safe keys` |
| La même clé deux fois | `product analytics path property keys must be unique` |
| Une valeur vide, une valeur de plus de 256 caractères ou un caractère de contrôle | `product analytics path property values must be non-empty, control-free, and at most 256 characters` |

Par exemple, `depth: 0` renvoie :

```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">Catalogue des propriétés d’analyse</span>

`GET /api/telemetry/analytics/properties` liste les clés de propriété des
événements produit classifiés d’un projet. Utilisez ces clés dans les
[comparaisons de segments](#analytics-segment-comparison) et les filtres d’ancre de parcours.
Le catalogue renvoie des clés et des nombres. Il ne renvoie jamais de valeurs de propriété ni
d’identités.

### Lire les clés de propriété disponibles

Envoyez `project_id` et `since`. Vous pouvez aussi envoyer `until`, `service_name`,
`release`, `environment`, `limit` et `response_version`. `limit` accepte
de `1` à `50` clés. La valeur par défaut est `20`. Envoyez `response_version=2` pour obtenir la
valeur publique de `estimation.method`.

```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"
```

### Catalogue de propriétés réussi

Dans ce résultat fictif, il existe 100 événements classifiés. Cinq d’entre eux ont été
capturés avant l’index de propriétés actuel.

```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."
  }
}
```

### Interpréter le catalogue

`source` vaut `standard_context` pour une clé de contexte standard, comme
`resource.runtime.name`, ou `custom_tag` pour une clé qui commence par `tag.`.
`value_type` vaut toujours `string`. `properties` est classé par `events`, du plus grand
au plus petit.

La clé `tag.plan` figure sur 60 des 100 événements classifiés. Elle a environ 3 valeurs distinctes.
La description de la clé ne montre pas ces valeurs. Utilisez des valeurs que vous connaissez dans votre
application. Ne devinez pas une valeur à partir d’un nombre. Un événement peut avoir plusieurs
clés : n’additionnez donc pas les nombres `events` de chaque clé.

| Champ | Signification |
| - | - |
| `summary.classified_events` | Événements classifiés dans le périmètre. C’est le dénominateur de chaque `coverage_rate`. |
| `summary.available_property_keys` | Nombre estimé de clés distinctes, pas le nombre de lignes renvoyées. |
| `coverage.unindexed_events` | Événements capturés avant l’index de propriétés actuel. Ils ne peuvent pas fournir de clés. |
| `coverage.incomplete_index_events` | Événements dont une partie du contexte est mal formée ou tronquée. Leurs clés valides comptent quand même. |
| `coverage.privacy_filtered_events` | Événements dont LogBrew a retiré des clés de tags personnalisés sensibles. Ce n’est pas un nombre de clés. |
| `coverage.properties_truncated` | `true` quand il existe plus de clés que `limit`. Réduisez le périmètre, ou augmentez `limit` jusqu’à 50. |
| `coverage.values_returned` | Toujours `false`. |

Un tableau `properties` vide ne prouve pas que le projet n’a aucun événement
produit. Lisez `summary`, `coverage` et `next_action`.

### Erreurs du catalogue de propriétés

En plus des [erreurs de périmètre communes](#shared-scope-errors), `limit=0` ou
`limit=51` renvoie `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" }
}
```

Une valeur de `response_version` autre que `1` ou `2` renvoie
`product analytics response_version must be 1 or 2`. LogBrew vérifie
`response_version` avant les autres valeurs.

Une requête `POST` renvoie `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">Comparaison de segments d’analyse</span>

`POST /api/telemetry/analytics/segments/compare` compare la fréquence à laquelle des sessions
ou des utilisateurs atteignent un événement exact, sur 2 à 4 segments d’un même projet. Un
segment sélectionne des événements selon le service, la version, l’environnement et des valeurs de propriété.
Le premier segment sert de référence.

### Définir la comparaison

Envoyez ce corps JSON avec une authentification de compte et
`Content-Type: application/json`. Ici, `basic` et `premium` sont des valeurs de forfait
de l’application d’exemple, pas des forfaits 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` et `segments` sont obligatoires. `analysis_unit` vaut `session` ou
`identified_user`. La valeur par défaut est `session`, qui compte des sessions, pas
des personnes. `interval` accepte les mêmes valeurs que la vue d’ensemble. La valeur par défaut est
`auto`.

Chaque segment exige une `key` unique, un `label` et un ensemble de filtres différent :

* `key` : de 1 à 32 octets. Commencez par une lettre ASCII minuscule ou un chiffre. Utilisez
  uniquement des lettres ASCII minuscules, des chiffres, `_` et `-`.
* `label` : de 1 à 80 caractères, sans caractère de contrôle.
* `service_name`, `release`, `environment` : facultatifs. Chaque valeur compte de 1 à 256
  caractères, sans caractère de contrôle.
* `property_filters` : de 0 à 4 filtres. Chaque clé peut apparaître une fois par segment.
  Un événement doit correspondre à tous les filtres du segment. Les valeurs sont sensibles à la casse,
  comptent de 1 à 256 caractères, et LogBrew retire les espaces au début et à la fin.

Un segment sans filtre sélectionne toute l’activité classifiée du projet sur la
période. Les segments peuvent se chevaucher. Un label ou une clé différents ne rendent pas valides deux
ensembles de filtres identiques.

### Comparaison de segments réussie

Dans ce résultat fictif, le segment de référence atteint le paiement dans 10 sessions sur 50.
Le segment premium l’atteint dans 18 sessions sur 60.

```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."
  }
}
```

### Lire les taux et les écarts

`eligible_units` estime les sessions ou les utilisateurs qui ont une activité classifiée dans
le segment. `reached_units` estime combien d’entre eux ont atteint la cible.
`reach_rate` vaut `reached_units` divisé par `eligible_units`. Les événements sans
l’ID de l’unité sélectionnée ne comptent pas dans ces unités.

Le taux du segment de référence est de 20 %. Le taux du segment premium est de 30 %. L’écart est de 10
points de pourcentage. La hausse relative est de 50 %, pas de 10 %. De petites erreurs décimales
viennent de l’arithmétique à virgule flottante. Arrondissez les taux pour l’affichage, mais calculez
avec les valeurs renvoyées. Le segment de référence n’a pas de
`comparison_to_baseline`. La hausse relative est omise quand le taux d’atteinte du segment de
référence vaut zéro.

Dans `query`, un filtre de segment que vous n’avez pas envoyé vaut `null`. Une liste
`property_filters` vide est omise.

`coverage.property_filters` a les mêmes champs que la
[couverture des filtres d’ancre de parcours](#filter-the-anchor-by-property). Ici,
`matching_events` est égal à `classified_events`. Gardez séparées les clés manquantes et
les valeurs différentes. Les segments peuvent se chevaucher, et une session peut apparaître dans
plusieurs intervalles. N’additionnez pas les nombres d’unités entre segments ou entre intervalles.

La comparaison est descriptive. `confidence.causal_inference` vaut
`not_established` et `confidence.statistical_significance` vaut `not_tested`.
Un taux de 30 % contre 20 % ne montre pas qu’un forfait a entraîné plus de paiements.

### Erreurs de comparaison de segments

En plus des [erreurs de périmètre communes](#shared-scope-errors), ces requêtes renvoient
`422` et `validation_failed` :

| Cause | `error` |
| - | - |
| Moins de 2 ou plus de 4 segments | `product analytics segment comparison requires two through four segments` |
| Deux segments avec la même clé | `product analytics segment keys must be unique` |
| Deux segments avec les mêmes filtres | `product analytics segment filters must be unique` |
| Une clé qui ne compte pas de 1 à 32 octets autorisés | `product analytics segment keys must be 1 through 32 machine-safe bytes` |
| Un label vide, trop long ou qui contient un caractère de contrôle | `product analytics segment labels must be non-empty, control-free, and at most 80 characters` |
| Une valeur de filtre avec un caractère de contrôle | `product analytics segment filters must be control-free` |
| Plus de quatre filtres de propriété dans un segment | `product analytics segments accept at most four exact property filters` |
| Une clé absente du catalogue de propriétés | `product analytics segment property keys must be supported privacy-safe keys` |
| La même clé de propriété deux fois dans un segment | `product analytics segment property keys must be unique within each segment` |
| Une valeur de propriété vide, trop longue ou qui contient un caractère de contrôle | `product analytics segment property values must be non-empty, control-free, and at most 256 characters` |
| Une valeur de `response_version` autre que `1` ou `2` | `product analytics response_version must be 1 or 2` |

Les erreurs de `interval` sont les mêmes que les [erreurs de la vue d’ensemble](#overview-errors).

Par exemple, deux segments avec des clés différentes et des filtres identiques renvoient :

```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" }
}
```

Un autre label ou un autre ordre des filtres de propriété ne change pas
les événements sélectionnés. Un échec de validation n’est pas une preuve qu’aucun événement
ne correspond.

Une requête `GET` renvoie `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" }
}
```

## Réponse du volume de signaux

`GET /api/telemetry/summary/volume` compte les signaux conservés pour un projet
sur une période. La route exige `project_id` et `since`. Elle n’accepte pas
`project_ids`.

`until` ne peut pas être dans le futur. Si vous l’omettez, la fin est l’heure de la requête.
Un `since` compact comme `24h` compte à rebours à partir de `until`. `interval` vaut
`auto`, `1m`, `5m`, `15m`, `1h`, `6h` ou `1d`. La valeur par défaut `auto` choisit
la plus petite durée d’intervalle qui donne au plus 120 intervalles. Une requête ne peut pas exiger plus
de 500 intervalles. La seule valeur de `response_version` est `1`.

### Nombres de signaux

Cette réponse fictive couvre deux intervalles d’une minute. La requête a été envoyée à
12 h 02 UTC, sans `until`. Le compte a une politique de conservation de 30 jours. Lisez
la politique et la date limite renvoyées. Ne supposez pas que ces valeurs s’appliquent à votre
compte.

```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"]
  }
}
```

### Lire chaque nombre dans sa propre unité

| Signal | Ce que compte `total` | À ne pas lire comme |
| - | - | - |
| `issues` | Occurrences de problèmes conservées | Des problèmes groupés ou des utilisateurs touchés |
| `logs` | Logs conservés | Des requêtes |
| `traces` | ID de trace uniques, comptés à leur premier span conservé dans cette période | Des spans, ou les vrais débuts des traces |
| `actions` | Actions conservées | Des utilisateurs ou des sessions |
| `metrics` | Points de métrique conservés | La somme des valeurs des métriques |

`total` est la somme des nombres des intervalles de la série. `peak` est le plus grand
nombre d’un intervalle. `non_empty_buckets` compte les intervalles dont le nombre est supérieur à zéro. Les
cinq séries apparaissent toujours dans cet ordre. Ne combinez pas des nombres de signaux différents
en un seul score d’impact.

Les intervalles commencent à `query.since`. Un intervalle inclut son début et exclut sa
fin. Le dernier intervalle peut être plus court que la durée d’intervalle. La réponse n’a pas de
série de nombre de versions. Ne lisez pas `release_volume_not_included` comme zéro
version.

### Périodes vides ou raccourcies

Si aucun signal ne correspond, les cinq séries restent présentes, et leurs points ont des nombres
explicitement à zéro. Une requête en échec est un cas différent. Ne remplacez pas une erreur par un
graphique rempli de zéros.

Comparez `query.requested_since` avec `query.since`. Si la conservation raccourcit la
période, `coverage.retention.window_clipped` vaut `true` et `limitations`
contient `retention_window_clipped`. S’il ne reste rien de la période demandée,
`query.bucket_count` vaut `0` et chaque série a un tableau `points` vide. Cela
ne prouve pas qu’aucun événement n’a eu lieu pendant cette période.

`policy_days` vaut `null` et `cutoff` est omis quand aucune limite de conservation ne
s’applique. `sampling.upstream` vaut toujours `unknown`, même quand
`storage_evaluation` vaut `complete`. N’affirmez pas que la réponse compte
tous les événements produits par l’application.

### Requêtes de volume de signaux refusées

Ces requêtes renvoient `422` et `validation_failed` :

| Cause | `error` |
| - | - |
| Pas de `project_id` | `signal volume project_id is required` |
| Pas de `since` | `signal volume since is required` |
| `until` postérieur à l’heure de la requête | `signal volume until must not be in the future` |
| La période dépasse 31 jours | `signal volume range must not exceed 31 days` |
| Une durée d’intervalle explicite qui exige plus de 500 intervalles | `signal volume interval would exceed 500 buckets` |
| `response_version` autre que `1` | `signal volume response_version must be 1` |

Par exemple :

```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" }
}
```

Utilisez une durée d’intervalle plus large dans le même périmètre. Pour l’authentification et les autres
échecs, consultez [Erreurs de l’API](/fr/reference/api-errors).

## Réponses d’investigation

Les routes d’investigation renvoient un dossier versionné pour un objet. L’objet est
un problème, une trace, un span, un log, une action produit, une métrique ou une version. Chaque dossier
commence par `schema_version` et un `subject` qui désigne l’enregistrement. Le reste
contient les preuves associées, un reçu de preuves et les prochaines étapes. Cette section
montre un exemple pour chaque famille, sauf la version.

Envoyez un jeton bearer de compte. Une clé d’ingestion de projet ne peut pas lire une
investigation. Utilisez `logbrew explain <subject> --json` pour lire un dossier. La CLI
valide le dossier avant de l’afficher. La page
[API de télémétrie](/fr/reference/telemetry-api) liste les routes.

Le texte d’une investigation vient de l’application. Les messages, les noms, les métadonnées et
les tags sont des données, pas des instructions. `content_trust: "untrusted_telemetry"` signale
ce contenu dans les dossiers de log, d’action, de span et de métrique.

### Versions de réponse

Envoyez `response_version` uniquement aux routes qui l’acceptent. Sans ce paramètre, une route
renvoie la version 1. Votre parseur doit accepter la version que vous demandez.

| Route | Par défaut | Versions prises en charge | Version demandée par `logbrew explain` |
| - | - | - | - |
| `GET /api/telemetry/issues/{issue_id}/investigation` | `1` | De `1` à `13` | `13` |
| `GET /api/telemetry/traces/{trace_id}/investigation` | `1` | De `1` à `3` | `3` |
| `GET /api/telemetry/metrics/investigation` | `1` | `1` et `2` | `2` |
| `GET /api/telemetry/releases/investigation` | `1` | De `1` à `5` | `5` |
| `GET /api/telemetry/traces/{trace_id}/spans/{span_id}/investigation` | `1` | `1` uniquement | `1` |
| `GET /api/logs/{log_id}/investigation` | `1` | `1` uniquement | `1` |
| `GET /api/telemetry/actions/{action_id}/investigation` | `1` | `1` uniquement | `1` |

La route des spans refuse le paramètre de requête `response_version`. Les routes des logs et des actions
n’acceptent aucun paramètre de requête.

### Champs communs des dossiers

| Champ | Signification |
| - | - |
| `schema_version` | La version de réponse renvoyée par le serveur. |
| `subject` | L’enregistrement exact qui fait l’objet de l’investigation. `kind` vaut `issue`, `trace`, `span`, `log`, `action`, `metric` ou `release`. |
| `analysis.causality` | Toujours `evidence_only` dans les dossiers de trace, de span, de log, d’action et de métrique. Une télémétrie corrélée ne prouve pas la cause. |
| Objets de collection | Les preuves associées, sous la forme `{ "status", "items", "truncated" }`. |
| `timeline` | Les preuves renvoyées, de la plus ancienne à la plus récente, avec leur propre indicateur `truncated`. L’ordre ne prouve pas la cause. |
| `evidence` | Le reçu de preuves. `status` vaut `complete` ou `partial`. |
| `next_actions` | Les lectures suggérées, dans l’ordre de `priority`, à partir de `1`. Elles n’ont pas été exécutées. |

Chaque collection associée indique un `status` de disponibilité :

| Statut | Signification | Comportement du client |
| - | - | - |
| `available` | La lecture a abouti et a renvoyé des éléments. | Affichez les éléments et l’indicateur `truncated`. |
| `not_linked` | L’objet n’a pas d’identifiant pour ce lien, par exemple un ID de trace. | Expliquez que le lien manque. N’en construisez pas un à partir des horodatages. |
| `not_found` | La lecture a abouti et n’a trouvé aucune preuve conservée. | Affichez un résultat vide. |
| `unavailable` | La lecture facultative a échoué. L’objet reste valide. | Gardez l’objet visible. Réessayez plus tard. N’affichez pas zéro élément comme résultat. |

Le reçu de preuves contient quatre listes ordonnées de noms de champs stables :
`captured_fields`, `missing_fields`, `redacted_fields` et
`truncated_fields`. `status` vaut `complete` uniquement quand les trois dernières listes sont
vides. Une donnée masquée n’est pas une donnée manquante. Une donnée tronquée n’est pas une donnée complète.
Ne complétez pas une valeur masquée ou tronquée.

### Limites des investigations

Ces limites bornent une réponse. Un indicateur `truncated` ou une entrée de `truncated_fields`
montre qu’une limite a omis des données.

| Preuves | Limite |
| - | - |
| Éléments associés dans une collection | 20 |
| Spans conservés lus pour une trace | 1 000 |
| Éléments de la chronologie | 50 pour les dossiers de log, d’action et de span. 100 pour les dossiers de trace et de métrique. |
| Attributs structurés, propriétés et métadonnées | 64 valeurs scalaires, 4 niveaux d’objets ou de tableaux, 16 éléments par tableau, 512 caractères par chaîne |
| Période de corrélation | 5 minutes avant et après un log, une action ou l’occurrence de problème sélectionnée. Pour une trace ou un span, 5 minutes avant le début et après la fin, 24 heures au maximum. |

La section de chaque famille liste ses propres limites.

### Réponse d’investigation d’un problème

Utilisez `GET /api/telemetry/issues/{issue_id}/investigation` pour lire les preuves
d’une occurrence d’un problème groupé. Le guide
[Investiguer un problème](/fr/guides/investigate-an-issue) montre comment lire
les preuves dans l’ordre. Demandez la version 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"
```

Les paramètres de requête sont `response_version`, `selection` et `occurrence_id`.
`selection` vaut `recommended`, `first` ou `latest`. Les valeurs sont
sensibles à la casse. Dans les versions 2 à 13, un sélecteur omis utilise `recommended`.
La version 1 renvoie la dernière occurrence conservée et n’accepte aucun sélecteur.
Les filtres de la liste des problèmes ne s’appliquent pas à cette lecture.

<Accordion title="Exemple d’investigation de problème en version 13">
  Cette réponse `200 OK` fictive contient une occurrence `TypeError` gérée, avec une
  trace liée. Elle n’a ni requête, ni Breadcrumbs, ni logs associés, ni preuve de
  déploiement : `evidence.status` vaut donc `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>

| Champ | Version | Signification |
| - | - | - |
| `subject` | 1 | Le problème groupé. `first_seen_at`, `last_seen_at` et `occurrence_count` décrivent le groupe. |
| `event` | 1 | L’occurrence sélectionnée : SDK, `context`, `exception`, `stack_frames` et `breadcrumbs`. En version 1, `null` quand aucune dernière occurrence n’est lisible. |
| `cause` | 1 | `status` vaut `reported_hypothesis`, `evidence_only` ou `unknown`. `signals` liste les signaux observés. |
| `fix` | 1 | Un emplacement dans le code. `status` vaut `reported_location`, `observed_application_frame`, `observed_frame`, `observed_underlying_exception_frame` ou `unknown`. |
| `impact` | 1 | Les nombres d’occurrences et `user_impact`. `reported` contient l’impact signalé par l’application, ou `null`. |
| `correlations` | 1 | `trace`, `logs`, `actions`, `metrics` et `release`. |
| `occurrence_selection` | 2 | Le sélecteur demandé, le motif, et les résumés des occurrences sélectionnée, première, dernière et recommandée. |
| `lifecycle` | 3 | `persisted_status`, `effective_status`, l’activité de statut et `regression`. `effective_status` peut valoir `regressed`. |
| `occurrence_analysis` | 4 | La tendance des occurrences et leur répartition par version, environnement, service et SDK. |
| `event.exception_chain` | 5 | Jusqu’à 8 exceptions liées. `relationship` vaut `reported`, `cause`, `context`, `aggregate_member` ou `suppressed`. |
| `event.request` | 6 | `method`, `route_template` et `response_status_code`. La clé est absente quand aucune requête n’a été capturée. |
| `grouping` | 7 | La `strategy` de regroupement, ses `components` et l’utilisation des frames de pile. |
| `correlations.release.deployment_status` | 8 | L’état de la recherche du déploiement. `deployment`, `time_since_deployment_ms` et `deployment_causality` n’apparaissent que si un déploiement a été trouvé. |
| `correlations.related_issues` | 9 | Les autres problèmes de la même trace. |
| `source_locator` | 10 | Le dépôt, le composant, la révision et le fichier relatif au dépôt, quand LogBrew peut les relier. |
| `reproduction` | 11 | `status` vaut `ready`, `partial` ou `insufficient_evidence`. |
| `next_actions` avec le code `verify_correction` | 12 | Apparaît quand `reproduction.status` vaut `ready`. |
| `impact.user_impact.count_method` | 13 | Indique un nombre distinct approximatif sous la forme `approximate_distinct`. Les versions antérieures utilisent un ancien nom de valeur. `unavailable` signifie qu’il n’y a pas de nombre. |

Les clés d’une version ultérieure sont absentes des versions antérieures. Ne lisez pas une
clé absente comme `null`.

`subject.status` et les statuts de `lifecycle` indiquent un problème archivé sous la forme
`ignored`. Les réponses de liste et de détail des problèmes utilisent `archived` à partir de la version
de réponse 2.

Limites des problèmes : la recommandation lit au plus 50 occurrences conservées. La
tendance a au plus 30 intervalles. Chaque répartition renvoie au plus 10 valeurs. Une
pile a au plus 32 frames.

#### Erreurs de requête d’investigation d’un problème

Utilisez soit `selection`, soit `occurrence_id`. N’envoyez pas les deux, même quand ils
sélectionnent la même occurrence :

```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` | Cause | Correction |
| - | - | - | - |
| `422` | `selection and occurrence_id are mutually exclusive` | La requête contient les deux sélecteurs. | Gardez un seul sélecteur. |
| `422` | `issue occurrence selection requires response_version=2 through 13` | La requête contient un sélecteur avec la version 1, ou sans version. | Envoyez `response_version=13`. Gardez le sélecteur. |
| `422` | `issue investigation response_version must be 1 through 13` | La version est hors limites. | Envoyez une version prise en charge. |
| `422` | `invalid query parameters` | Un paramètre inconnu, un UUID invalide, une version qui n’est pas un entier ou une valeur de `selection` inconnue. | Corrigez la [requête](/fr/reference/api-errors#invalid-query-parameters). |
| `404` | `issue occurrence not found` | L’occurrence ne fait pas partie de l’historique conservé de ce groupe. | Copiez l’ID d’occurrence depuis les événements de ce problème. N’utilisez pas `latest` ou `recommended` à la place sans accord. |
| `404` | `issue not found` | Aucun projet lisible ne contient ce problème. | Vérifiez le compte et l’UUID du problème. N’envoyez pas un UUID d’occurrence comme ID de problème. |

Une occurrence introuvable ne signifie pas que le problème est introuvable. Une réponse `404`
n’indique pas si un autre compte peut lire le problème.

### Réponse d’investigation d’une trace

Utilisez `GET /api/telemetry/traces/{trace_id}/investigation` pour lire une trace
avec son chemin d’échec, son chemin le plus lent et ses signaux associés. Les paramètres de requête sont
`response_version`, `project_id`, `release` et `environment`. Tous sont
facultatifs. Demandez la version 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"
```

La version 2 ajoute `cause` et `fix` à chaque problème associé. La version 3 ajoute
`span_id` aux problèmes, actions et métriques associés, quand il a été capturé.

<Accordion title="Exemple d’investigation de trace en version 3">
  Cette réponse fictive contient un span serveur racine et un span client en échec. Le
  span en échec est aussi le span enfant le plus lent. Les spans n’ont pas de contexte
  structuré : `spans.context` est donc absent.

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

| Champ | Signification |
| - | - |
| `subject` | Les totaux de la trace, calculés à partir des spans analysés. |
| `analysis.status` | `errors_observed`, `latency_focus` ou `no_error_observed`. |
| `analysis.first_error_path` | La chaîne de parents connue, de la racine jusqu’au premier span en erreur. |
| `analysis.bottleneck_path` | La chaîne de parents connue jusqu’au span enfant le plus lent. |
| `spans.items[].roles` | La raison pour laquelle le span figure dans l’aperçu : `root`, `first_error`, `error_path`, `bottleneck`, `bottleneck_path`, `error` ou `slow`. |
| `correlations.window` | La période et les périmètres de déploiement utilisés pour les lectures associées. |
| `correlations.issues`, `logs`, `actions`, `metrics` | Les signaux qui ont le même ID de trace dans la période. |

`trace.parentage` est absent quand un span désigne un parent que LogBrew n’a pas
conservé. L’aperçu des spans contient au plus 64 spans. Chaque chemin d’analyse contient au plus
64 spans. Les lectures associées utilisent au plus 8 périmètres de déploiement.

Une prochaine étape avec la cible `trace_span` désigne un `span_id` de ce dossier. Pour
lire ce span, appelez la route des spans avec le `project_id`,
l’`environment` et la `release` de ce span.

### Réponse d’investigation d’un span

Utilisez `GET /api/telemetry/traces/{trace_id}/spans/{span_id}/investigation` pour
lire une opération d’une trace. Envoyez `project_id`, `environment` et `release`.
Les trois sont obligatoires. Copiez-les depuis le span sélectionné. N’envoyez pas l’`id`
de la ligne du span comme `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="Exemple d’investigation de span">
  Cette réponse fictive décrit le span client en échec de l’exemple de
  trace. Un log d’erreur a le même ID de span. Le span a duré plus longtemps que le
  95e centile approximatif de ses spans comparables conservés.

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

| Champ | Signification |
| - | - |
| `payload` | Les `metadata` du span, jusqu’à 8 `events` et jusqu’à 8 `links`. Ils partagent un budget de 64 valeurs. |
| `analysis.observations` | `subject_error`, `child_error`, `exact_span_error_log`, `related_issue`, `subject_at_or_above_peer_p95` ou `subject_at_or_above_peer_p99`. |
| `topology` | `root`, `parent`, `ancestors`, les `children` directs, `descendant_count` et `cross_service_edges`. |
| `topology.parent_chain_status` | `root`, `complete`, `missing`, `cycle`, `truncated` ou `unavailable`. |
| `baseline` | Les spans antérieurs qui ont le même projet, environnement, version, service, opération et nom. |
| `correlations.logs` | Les logs qui ont le même ID de trace et le même ID de span. |
| `correlations.issues`, `actions`, `metrics` | Les signaux qui ont le même ID de trace dans le même projet, le même environnement et la même version. |
| `timeline[].offset_ms` | Millisecondes depuis le début du span. Une valeur négative est antérieure au span. |

La référence lit les 24 heures qui précèdent le début du span. `since` est inclus
et `until` est exclu. `error_rate_basis_points: 500` signifie 5 %.
`subject_percentile_basis_points: 9500` signifie que 95 % des spans comparables conservés
n’ont pas duré plus longtemps que ce span. Les centiles sont approximatifs. Gardez
`baseline.limitations` avec les valeurs. Une référence `not_found` n’a aucun span comparable
conservé. Une référence `unavailable` est une lecture en échec, pas un nombre égal à zéro.

`parent_chain_status: "complete"` signifie que la chaîne conservée atteint une
racine. Cela ne prouve pas que LogBrew a conservé tous les spans de la requête.
La topologie renvoie au plus 64 ancêtres, 50 enfants directs et 50
liens entre services.

| HTTP | `error` | Correction |
| - | - | - |
| `422` | `trace_id must be a non-zero 32-character hexadecimal value` | Copiez `trace_id` depuis une réponse de trace LogBrew. |
| `422` | `span_id must be a non-zero 16-character hexadecimal value` | Copiez `span_id` depuis une réponse de trace LogBrew. |
| `422` | `environment and release must be non-empty exact span scope values` | Copiez le périmètre depuis le span sélectionné. |
| `422` | `invalid query parameters` | Envoyez `project_id`, `environment` et `release`, et aucun autre paramètre. |
| `404` | `span not found` | Vérifiez les ID, le projet, l’environnement et la version. |

### Réponse d’investigation d’un log

Utilisez `GET /api/logs/{log_id}/investigation` pour lire un log et ses preuves
associées. Cette route n’accepte aucun paramètre de requête. Copiez l’ID du log depuis une liste de logs.

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

La lecture peut renvoyer des preuves hors des filtres de la liste des logs. Les lectures
associées utilisent le projet, l’environnement et la version du log, et les 5 minutes avant
et après le log. Les logs proches utilisent aussi le même service. Ils n’ont pas besoin d’un
ID de trace. Les lectures par trace exacte exigent l’ID de trace du log et peuvent inclure d’autres
services. Le résumé de trace utilise le même ID de trace et le même périmètre, sans la
période.

<Accordion title="Exemple d’investigation de log">
  Cette réponse fictive contient un log d’avertissement sans contexte de trace et un
  log proche. La réponse ne construit pas de lien de trace à partir des horodatages.

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

| Champ | Signification |
| - | - |
| `attributes` | Les attributs structurés, dans les limites fixées. `values` exclut `context`, qui se trouve dans le champ `context` de premier niveau. |
| `analysis.status` | `critical_evidence`, `error_evidence`, `warning_evidence` ou `no_failure_observed`. |
| `correlations.trace` | L’ID de trace, l’ID de span, `exact_span` et le `summary` de la trace. |
| `correlations.trace_logs` | Les logs qui ont le même ID de trace. `relationship` vaut `exact_trace`. |
| `correlations.nearby_logs` | Les logs du même service dans la période. `relationship` vaut `nearby_scope`. Exclut l’objet de l’investigation et les logs de la trace exacte. |
| `correlations.issues`, `actions`, `metrics` | Les signaux qui ont le même ID de trace. |
| `correlations.release` | Le périmètre de version pour une investigation de version. Il ne contient pas de comparaison. |
| `timeline[].relationship` | `subject`, `exact_span`, `exact_trace` ou `nearby_scope`. |

Un ID de trace peut être présent quand le statut de la trace vaut `not_found` ou
`unavailable`. Un résumé de trace disponible ne garantit pas un `exact_span`.

### Réponse d’investigation d’une action

Utilisez `GET /api/telemetry/actions/{action_id}/investigation` pour lire une action
produit, son statut signalé et ses preuves associées. Cette route n’accepte aucun paramètre de
requête. Les lectures associées utilisent la même période et les mêmes périmètres qu’une investigation de log.

```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` et `next_actions` utilisent les formes de l’investigation
de log. Les actions associées excluent l’action qui fait l’objet de l’investigation.

<Accordion title="Exemple d’investigation d’action">
  Cette réponse fictive décrit une action de paiement en échec d’un utilisateur
  connecté. L’action n’a pas d’ID de trace.

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

| Champ | Signification |
| - | - |
| `subject.status` | `queued`, `running`, `success`, `failure`, ou `null` quand aucun statut pris en charge n’a été capturé. |
| `subject.subject_classification` | `user`, `anonymous`, `legacy_unknown`, `missing`, `historical_unindexed` ou `unsupported`. |
| `subject.session_captured` | Indique si l’action avait une session. La réponse ne renvoie jamais l’ID de session. |
| `properties` | L’objet `metadata` de l’action, dans les limites fixées. |
| `analysis.status` | `subject_failure`, `correlated_failure_evidence`, `in_progress`, `success_observed` ou `status_not_captured`. |

La réponse ne renvoie jamais d’identifiant de sujet ou de session. Le reçu
de preuves liste toujours `subject.distinct_id` comme masqué, ainsi que
`subject.session_id` quand une session a été capturée. C’est pourquoi chaque dossier
d’action est `partial` et inclut `improve_capture`. Ne retirez pas les protections de
confidentialité pour y répondre.

### Réponse d’investigation d’une métrique

Utilisez `GET /api/telemetry/metrics/investigation` pour lire un nom de métrique dans un
projet sur une période. La réponse contient la série actuelle, une comparaison
avec la période précédente de même durée, le dernier échantillon, les exemplaires
de trace et les déploiements terminés. La version 2 ajoute la description de la métrique
tirée du dernier échantillon conservé. La version 1 indique toujours
`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` et `since` sont obligatoires. `since` est inclus et
`until` est exclu. `until` vaut par défaut l’heure actuelle. La comparaison lit aussi la
période précédente de même durée.

<Accordion title="Exemple d’investigation de métrique en version 2">
  Cet histogramme fictif a 5 échantillons répartis dans deux intervalles de 5 minutes. Le dernier
  intervalle a un p95 de 48 ms. Le dernier intervalle de la période précédente a un
  p95 de 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>

| Champ | Signification |
| - | - |
| `subject.description_status` | `captured`, `not_captured` ou `unavailable`. |
| `query` | La requête normalisée. `service_name`, `release` et `environment` n’apparaissent que s’ils ont été envoyés. |
| `analysis.status` | `no_samples`, `change_observed`, `no_change_observed`, `baseline_not_found` ou `comparison_unavailable`. |
| `analysis.focus` | L’élément de comparaison qui a la plus forte variation relative, ou la plus forte variation absolue quand aucune variation relative n’existe. `null` quand aucun élément n’est comparable. |
| `series[].aggregation.code` | `gauge_last`, `delta_sum`, `distribution_p95`, `raw_cumulative_last` ou `raw_last`. |
| `comparison.status` | `available`, `partial`, `not_found`, `unavailable` ou `no_current_samples`. |
| `comparison.items[]` | Le dernier intervalle non vide de chaque période, pour la même identité de série. |
| `latest_sample` | Le dernier échantillon brut, avec le SDK, `context`, `metadata` et le lien de trace. |
| `exemplars` | Les échantillons bruts qui ont un ID de trace. `coverage` compte les échantillons liés. |
| `deployments` | Les déploiements terminés entre le début de la période précédente et `until`. |
| `next_actions[]` | `code`, `target`, une `reason` en texte simple et un périmètre `context` pour la lecture suivante. |

`relative_change_percent` vaut `(current - previous) / abs(previous) * 100`.
Il vaut `null` quand la valeur précédente est nulle ou absente. `expected_buckets_per_series`
compte les intervalles possibles, pas les intervalles qui ont des données. N’affichez pas les intervalles vides comme
des zéros. Un déploiement proche d’une variation ne prouve pas qu’il a causé cette variation.

Un projet que le compte ne peut pas lire renvoie `200 OK` avec
`analysis.status: "no_samples"`, pas `404`. Un résultat vide ne décrit pas
les autres types de signaux.

Limites des métriques : la période dure au plus 31 jours. Chaque série a au plus 500
intervalles. `series_limit` va de 1 à 20, 10 par défaut. La réponse renvoie au plus
20 exemplaires et 20 déploiements.

| HTTP | `error` | Correction |
| - | - | - |
| `422` | `metric investigation response_version must be 1 or 2` | Omettez `response_version` ou envoyez `2`. |
| `422` | `metric series project_id is required` | Envoyez l’ID d’un projet lisible. |
| `422` | `metric series name is required` | Copiez le nom depuis la liste des métriques. |
| `422` | `metric series since is required` | Envoyez une valeur comme `24h` ou un horodatage RFC 3339. |
| `422` | `metric series range must not exceed 31 days` | Utilisez une période plus courte. |
| `422` | `metric series interval would exceed 500 buckets` | Utilisez `interval=auto` ou une durée d’intervalle plus large. |
| `422` | `metric series_limit must be between 1 and 20` | Envoyez une valeur de 1 à 20. |

`interval` vaut `auto`, `1m`, `5m`, `15m`, `1h`, `6h` ou `1d`. `group_by` vaut
`none`, `service_name`, `release` ou `environment`. Les autres valeurs renvoient
`422`.

### Erreurs d’investigation

| HTTP | Route | `error` |
| - | - | - |
| `404` | Log | `log not found` |
| `404` | Action | `action not found` |
| `404` | Trace | `trace not found` |
| `422` | Trace | `trace investigation response_version must be 1, 2, or 3` |
| `400` | Log, action, problème | `invalid URL path parameters` quand l’ID du chemin n’est pas un UUID |
| `422` | Problème, trace, span, métrique | `invalid query parameters` pour un paramètre inconnu ou une valeur du mauvais type |

Une réponse `404` signifie qu’aucun projet lisible ne contient l’enregistrement. Chaque route renvoie aussi
`401` sans authentification valide et `405` pour une autre méthode que `GET`.
Consultez [Erreurs de l’API](/fr/reference/api-errors) pour les réponses
[ressource introuvable](/fr/reference/api-errors#resource-not-found),
[chemin invalide](/fr/reference/api-errors#invalid-path-value),
[échec d’authentification](/fr/reference/api-errors#authentication-failure) et
[méthode non prise en charge](/fr/reference/api-errors#unsupported-method).

Une lecture facultative de preuves peut échouer au sein d’une réponse `200 OK`. La collection
indique alors `unavailable`. Ne l’affichez pas comme `not_found` ni comme zéro enregistrement.

## <span id="severity-values">Valeurs de gravité</span>

Les valeurs de gravité visibles par l’utilisateur sont `info`, `warning`, `error` et `critical`.
Les SDK et les runtimes peuvent envoyer des alias courants comme `trace`, `debug`, `warn` ou
`fatal`. Les réponses REST et en direct renvoient toujours la valeur canonique. Par
exemple, LogBrew accepte `warn` en entrée et renvoie `warning`. L’ingestion refuse
les valeurs de gravité inconnues.

| Alias | Valeur canonique |
| - | - |
| `trace`, `debug` | `info` |
| `warn` | `warning` |
| `fatal` | `critical` |

## <span id="severity-rule-responses">Réponses des règles de gravité</span>

Une règle de gravité change la façon dont LogBrew affiche un log ou un problème. Elle ne
change pas la gravité capturée par l’application. Les logs et les problèmes renvoient un
objet `triage` :

| Champ | Signification |
| - | - |
| `effective_severity` | La gravité à afficher. Sans règle, elle est égale à `severity`. |
| `ignored` | `true` quand une règle de log ignore le log, ou quand le problème est archivé. |
| `rule_applied` | `true` quand une règle de gravité ou l’état ignoré s’applique. `false` est la valeur par défaut, pas une donnée manquante. |

Utilisez `triage.effective_severity` pour l’affichage. Gardez `severity` comme valeur
capturée. Par défaut, la liste des logs omet les logs ignorés. Envoyez `status=ignored` ou
`status=all` sur `GET /api/logs` pour les inclure.

Le compte doit avoir l’autorisation de gérer les problèmes du projet. L’accès en lecture
ne suffit pas. Sans cette autorisation, un changement de règle renvoie `403` avec
`code: forbidden`.

### Modifier une règle de log

`PUT /api/logs/{log_id}/triage-rule` définit la règle pour ce log et pour les logs
suivants qui ont la même signature. Le corps exige à la fois `severity` et `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 }'
```

Une réponse `200 OK` contient le [log](#log-response) complet. Sa valeur
`severity` reste `warning`. Son objet `triage` montre la règle :

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

Cet extrait ne montre que les champs modifiés. Dans la requête, `severity` vaut
`source`, `info`, `warning`, `error` ou `critical`. `source` rétablit la
gravité capturée. Cette valeur ne réactive pas un log ignoré. Pour afficher de nouveau le
log, envoyez `"ignored": false`.

### Modifier une règle de problème

`PUT /api/telemetry/issues/{issue_id}/triage-rule` définit la gravité affichée
d’un problème groupé et de ses occurrences suivantes. Le corps accepte uniquement
`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" }'
```

Une réponse `200 OK` contient le [problème](#issue-response) complet en
version 1. Les valeurs capturées de `severity` et de `status` ne changent pas :

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

Cet extrait ne montre que certains champs. Une règle ne résout pas, n’archive pas et ne
rouvre pas un problème. Utilisez pour cela le [changement de statut](#issue-status-response-examples).
Une priorité dérivée suit la nouvelle gravité effective. Lisez le problème
avec `response_version=2` ou `3` pour la voir.

Une gravité non prise en charge renvoie `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"
  }
}
```

Un champ de corps inconnu, comme `status` dans une règle de problème, renvoie `422` avec
`code: invalid_json`. Un log ou un problème introuvable ou illisible renvoie `404` avec
`code: not_found`. Si la réponse est perdue, relisez le log ou le problème
avant de renvoyer la règle.

## Format des réponses d’erreur

Les erreurs applicatives renvoient une enveloppe JSON. Cet exemple montre une requête
refusée. Le texte de `error` et de `next` dépend de l’erreur :

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

Utilisez `code` pour les branches du programme et `next_action` pour l’automatisation. Montrez `error`
et `next` aux personnes sous forme de texte simple. N’analysez pas leur formulation. Certaines erreurs
ajoutent des champs, comme `limit`, `reset_at` ou `retry_after_seconds`. Une réponse
sans corps JSON n’a pas de `code` LogBrew. Consultez
[Erreurs de l’API](/fr/reference/api-errors) pour tous les codes.

## Lire les données avec la CLI

La CLI lit les mêmes routes et renvoie le même JSON avec `--json`. Commencez
large, puis affinez :

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

Ajoutez `--pagination cursor` pour obtenir l’enveloppe avec curseur. La CLI demande la version 4
de la réponse de problème. Consultez [Lire la page suivante](/fr/reference/cli#read-the-next-page)
et [Suivi en direct](/fr/reference/cli#live-watch).


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