---
title: "Observability query API"
description: "Query projects, traces, services and releases, manage environment policy, run exports and privacy-request lookups, and read recent signals from a coding agent."
canonical_url: "https://noxtica.com/docs/api/reference/observability/"
markdown_url: "https://noxtica.com/docs/api/reference/observability/index.md"
last_updated: 2026-10-27
published: 2026-10-27
section: "server-apis"
section_title: "Server APIs"
openapi_release: "v0.4.0"
openapi_spec_version: "1.3.0-2026-10-27"
site: "Noxtica"
language: "en"
source: markdown
---

# Observability query API

Query projects, traces, services and releases, manage environment policy, run exports and privacy-request lookups, and read recent signals from a coding agent.

Two separate read surfaces.

**APM query API** (`/v1/apm/*`, `sk_` server API key, collector hosts). Availability: customer and general capture are not yet available, and an approved proof-of-concept is limited to synthetic, no-personal-data telemetry. At the time of writing a server API key created in the Backoffice cannot carry `read:apm`, `write:apm` or `read:privacy_request`, so those operations are not callable with a self-service key yet; `read:export` can be selected.

**Coding-agent read API** (`/v1/coding-agent/*`, `cak_` coding-agent key, on `https://ingest.noxtica.com`). Create a key under Integrations & API, Coding agent keys.

| Environment | Base URL |
| --- | --- |
| Production | `https://collect.noxtica.com` |
| Production | `https://ingest.noxtica.com` |

Credential: `ApiKeyAuth`, `CodingAgentKeyAuth`. See [Authentication](https://noxtica.com/docs/api/authentication/).

## List APM projects {#v1ApmListProjects}

`GET /v1/apm/projects`

Lists tenant-owned observability projects from PostgreSQL authority. Results are cursor-paginated and never cached.

**Authentication:** Bearer token (`ApiKeyAuth`), scope `read:apm`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | No | Minimum 1, maximum 200. Default `50`. |
| `cursor` | query | string | No | At least 1 character, at most 512 characters. |

### Responses

| Status | Meaning | Error codes | Content |
| --- | --- | --- | --- |
| `200` | Project page. |  | `application/json` ApmProjectList |
| `400` | Closed-input validation failed. | `invalid_request` | `application/json` ApmError |
| `401` | Missing, invalid, or ingest-only credential. An `sk_apm_` key is never accepted here. | `Unauthorized` | `application/json` ApmError |
| `403` | The server API key lacks the operation's required scope. | `API key does not have read:apm scope` | `application/json` ApmError |
| `429` | V1 read rate limit exceeded. | `Rate limit exceeded` | `application/json` ApmRateLimitError |
| `503` | Authoritative control-plane data is unavailable. |  | `application/json` ApmUnavailable |

**Response headers**

| Status | Header | Type | Description |
| --- | --- | --- | --- |
| `200` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `400` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `401` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `403` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `429` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `503` | `Cache-Control` | const | APM control/query responses are never cacheable. |

**Response fields** (`200`, `application/json`)

| Field | Type | Always returned | Description |
| --- | --- | --- | --- |
| `ok` | const | Yes | Always `true`. |
| `items` | array of ApmProject | Yes |  |
| `items[]` | object (ApmProject) | No |  |
| `items[].project_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `items[].display_name` | string | Yes |  |
| `items[].state` | string | Yes |  |
| `items[].region_id` | string | Yes |  |
| `items[].data_plane_id` | string | Yes |  |
| `items[].capture_enabled` | boolean | Yes |  |
| `items[].enabled_signals` | array of string | Yes | At most 2 items, unique items. |
| `items[].authority_epoch` | integer | Yes | Minimum 0. |
| `items[].erasure_epoch` | integer | Yes | Minimum 0. |
| `items[].created_at` | string (date-time) | Yes |  |
| `items[].updated_at` | string (date-time) | Yes |  |
| `count` | integer | Yes | Minimum 0, maximum 200. |
| `truncated` | boolean | Yes |  |
| `next_cursor` | string, nullable | Yes |  |
| `freshness` | const | Yes | Always `postgres_authority`. |
| `completeness` | string | Yes | Allowed values: `complete`, `truncated`. |

**Response example** (`200`)

```json
{
  "ok": true,
  "items": [
    {
      "project_id": "checkout",
      "display_name": "Checkout",
      "state": "active",
      "region_id": "us-central1",
      "data_plane_id": "primary",
      "capture_enabled": false,
      "enabled_signals": [
        "traces"
      ],
      "authority_epoch": 7,
      "erasure_epoch": 2,
      "created_at": "2026-08-01T00:00:00.000Z",
      "updated_at": "2026-09-01T00:00:00.000Z"
    }
  ],
  "count": 1,
  "truncated": false,
  "next_cursor": null,
  "freshness": "postgres_authority",
  "completeness": "complete"
}
```

### Example request

**curl**

```bash
curl "https://collect.noxtica.com/v1/apm/projects" \
  -H "Authorization: Bearer $NOXTICA_API_KEY"
```

**Node.js** (global `fetch`, Node 18 or later)

```js
const response = await fetch('https://collect.noxtica.com/v1/apm/projects', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
  },
});
console.log(response.status, await response.text());
```

**Python** (standard library)

```python
import os
import urllib.request

request = urllib.request.Request(
    "https://collect.noxtica.com/v1/apm/projects",
    headers={
        "Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
    },
    method="GET",
)
with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())
```

## List project environments {#v1ApmListProjectEnvironments}

`GET /v1/apm/projects/{projectId}/environments`

Lists environments belonging to one tenant-owned APM project from PostgreSQL authority.

**Authentication:** Bearer token (`ApiKeyAuth`), scope `read:apm`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `projectId` | path | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `limit` | query | integer | No | Minimum 1, maximum 200. Default `50`. |
| `cursor` | query | string | No | At least 1 character, at most 512 characters. |

### Responses

| Status | Meaning | Error codes | Content |
| --- | --- | --- | --- |
| `200` | Environment page. |  | `application/json` ApmEnvironmentList |
| `400` | Closed-input validation failed. | `invalid_request` | `application/json` ApmError |
| `401` | Missing, invalid, or ingest-only credential. An `sk_apm_` key is never accepted here. | `Unauthorized` | `application/json` ApmError |
| `403` | The server API key lacks the operation's required scope. | `API key does not have read:apm scope` | `application/json` ApmError |
| `404` | The tenant-scoped resource was not found. | `resource_not_found` | `application/json` ApmError |
| `429` | V1 read rate limit exceeded. | `Rate limit exceeded` | `application/json` ApmRateLimitError |
| `503` | Authoritative control-plane data is unavailable. |  | `application/json` ApmUnavailable |

**Response headers**

| Status | Header | Type | Description |
| --- | --- | --- | --- |
| `200` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `400` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `401` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `403` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `404` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `429` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `503` | `Cache-Control` | const | APM control/query responses are never cacheable. |

**Response fields** (`200`, `application/json`)

| Field | Type | Always returned | Description |
| --- | --- | --- | --- |
| `ok` | const | Yes | Always `true`. |
| `project_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `items` | array of ApmEnvironmentPolicy | Yes |  |
| `items[]` | object (ApmEnvironmentPolicy) | No |  |
| `items[].project_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `items[].environment_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `items[].display_name` | string | Yes |  |
| `items[].environment_kind` | string | Yes |  |
| `items[].state` | string | Yes |  |
| `items[].region_id` | string | Yes |  |
| `items[].data_plane_id` | string | Yes |  |
| `items[].capture_enabled` | boolean | Yes |  |
| `items[].allowed_signals` | array of string | Yes | At most 2 items, unique items. |
| `items[].sampling_policy_id` | one of ApmScopedId or any, nullable | Yes |  |
| `items[].retention_policy_version` | integer | Yes | Minimum 1. |
| `items[].privacy_policy_version` | integer | Yes | Minimum 1. |
| `items[].authority_epoch` | integer | Yes | Minimum 0. |
| `items[].erasure_epoch` | integer | Yes | Minimum 0. |
| `items[].quota_manifest_revision` | integer | Yes | Minimum 0. |
| `items[].parser_manifest_revision` | integer | Yes | Minimum 0. |
| `items[].created_at` | string (date-time) | Yes |  |
| `items[].updated_at` | string (date-time) | Yes |  |
| `count` | integer | Yes | Minimum 0, maximum 200. |
| `truncated` | boolean | Yes |  |
| `next_cursor` | string, nullable | Yes |  |
| `freshness` | const | Yes | Always `postgres_authority`. |
| `completeness` | string | Yes | Allowed values: `complete`, `truncated`. |

**Response example** (`200`)

```json
{
  "ok": true,
  "project_id": "checkout",
  "items": [
    {
      "project_id": "checkout",
      "environment_id": "prod",
      "display_name": "Production",
      "environment_kind": "production",
      "state": "active",
      "region_id": "us-central1",
      "data_plane_id": "primary",
      "capture_enabled": false,
      "allowed_signals": [
        "traces"
      ],
      "sampling_policy_id": "baseline",
      "retention_policy_version": 3,
      "privacy_policy_version": 2,
      "authority_epoch": 7,
      "erasure_epoch": 2,
      "quota_manifest_revision": 4,
      "parser_manifest_revision": 5,
      "created_at": "2026-08-01T00:00:00.000Z",
      "updated_at": "2026-09-01T00:00:00.000Z"
    }
  ],
  "count": 1,
  "truncated": false,
  "next_cursor": null,
  "freshness": "postgres_authority",
  "completeness": "complete"
}
```

### Example request

**curl**

```bash
curl "https://collect.noxtica.com/v1/apm/projects/example-projectId/environments" \
  -H "Authorization: Bearer $NOXTICA_API_KEY"
```

**Node.js** (global `fetch`, Node 18 or later)

```js
const response = await fetch('https://collect.noxtica.com/v1/apm/projects/example-projectId/environments', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
  },
});
console.log(response.status, await response.text());
```

**Python** (standard library)

```python
import os
import urllib.request

request = urllib.request.Request(
    "https://collect.noxtica.com/v1/apm/projects/example-projectId/environments",
    headers={
        "Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
    },
    method="GET",
)
with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())
```

## Read an environment policy {#v1ApmGetEnvironmentPolicy}

`GET /v1/apm/projects/{projectId}/environments/{environmentId}/policy`

Returns the authoritative environment policy plus bounded sampling and retention policy inventories.

**Authentication:** Bearer token (`ApiKeyAuth`), scope `read:apm`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `projectId` | path | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `environmentId` | path | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |

### Responses

| Status | Meaning | Error codes | Content |
| --- | --- | --- | --- |
| `200` | Policy snapshot. |  | `application/json` ApmPolicySnapshot |
| `400` | Closed-input validation failed. | `invalid_request` | `application/json` ApmError |
| `401` | Missing, invalid, or ingest-only credential. An `sk_apm_` key is never accepted here. | `Unauthorized` | `application/json` ApmError |
| `403` | The server API key lacks the operation's required scope. | `API key does not have read:apm scope` | `application/json` ApmError |
| `404` | The tenant-scoped resource was not found. | `resource_not_found` | `application/json` ApmError |
| `429` | V1 read rate limit exceeded. | `Rate limit exceeded` | `application/json` ApmRateLimitError |
| `503` | Authoritative control-plane data is unavailable. |  | `application/json` ApmUnavailable |

**Response headers**

| Status | Header | Type | Description |
| --- | --- | --- | --- |
| `200` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `400` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `401` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `403` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `404` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `429` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `503` | `Cache-Control` | const | APM control/query responses are never cacheable. |

**Response fields** (`200`, `application/json`)

| Field | Type | Always returned | Description |
| --- | --- | --- | --- |
| `ok` | const | Yes | Always `true`. |
| `project_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `environment_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `policy` | object (ApmEnvironmentPolicy) | Yes |  |
| `policy.project_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `policy.environment_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `policy.display_name` | string | Yes |  |
| `policy.environment_kind` | string | Yes |  |
| `policy.state` | string | Yes |  |
| `policy.region_id` | string | Yes |  |
| `policy.data_plane_id` | string | Yes |  |
| `policy.capture_enabled` | boolean | Yes |  |
| `policy.allowed_signals` | array of string | Yes | At most 2 items, unique items. |
| `policy.sampling_policy_id` | one of ApmScopedId or any, nullable | Yes |  |
| `policy.retention_policy_version` | integer | Yes | Minimum 1. |
| `policy.privacy_policy_version` | integer | Yes | Minimum 1. |
| `policy.authority_epoch` | integer | Yes | Minimum 0. |
| `policy.erasure_epoch` | integer | Yes | Minimum 0. |
| `policy.quota_manifest_revision` | integer | Yes | Minimum 0. |
| `policy.parser_manifest_revision` | integer | Yes | Minimum 0. |
| `policy.created_at` | string (date-time) | Yes |  |
| `policy.updated_at` | string (date-time) | Yes |  |
| `sampling_policies` | array of ApmSamplingPolicy | Yes | At most 100 items. |
| `sampling_policies[]` | object (ApmSamplingPolicy) | No |  |
| `sampling_policies[].policy_id` | string | Yes |  |
| `sampling_policies[].policy_version` | integer | Yes |  |
| `sampling_policies[].signal` | string | Yes |  |
| `sampling_policies[].schema_version` | integer | Yes |  |
| `sampling_policies[].sampling_class` | string | Yes |  |
| `sampling_policies[].mode` | string | Yes |  |
| `sampling_policies[].enabled` | boolean | Yes |  |
| `sampling_policies[].rate_ppm` | integer, nullable | Yes |  |
| `sampling_policies[].tail_window_seconds` | integer, nullable | Yes |  |
| `sampling_policies[].tail_budget_events` | integer, nullable | Yes |  |
| `sampling_policies[].effective_from` | string (date-time) | Yes |  |
| `sampling_policies[].effective_until` | string (date-time), nullable | Yes |  |
| `sampling_policy_count` | integer | Yes | Minimum 0, maximum 100. |
| `sampling_policies_truncated` | boolean | Yes |  |
| `retention_policies` | array of ApmRetentionPolicy | Yes | At most 100 items. |
| `retention_policies[]` | object (ApmRetentionPolicy) | No |  |
| `retention_policies[].signal` | string | Yes |  |
| `retention_policies[].retention_class` | string | Yes |  |
| `retention_policies[].policy_version` | integer | Yes |  |
| `retention_policies[].duration_seconds` | integer, nullable | Yes |  |
| `retention_policies[].minimum_seconds` | integer | Yes |  |
| `retention_policies[].maximum_seconds` | integer | Yes |  |
| `retention_policies[].region_id` | string | Yes |  |
| `retention_policies[].deletion_mode` | string | Yes |  |
| `retention_policies[].legal_hold_state` | string | Yes |  |
| `retention_policies[].enabled` | boolean | Yes |  |
| `retention_policies[].effective_from` | string (date-time) | Yes |  |
| `retention_policies[].effective_until` | string (date-time), nullable | Yes |  |
| `retention_policy_count` | integer | Yes | Minimum 0, maximum 100. |
| `retention_policies_truncated` | boolean | Yes |  |
| `freshness` | object (ApmAuthorityFreshness) | Yes |  |
| `freshness.source` | const | Yes | Always `postgres_authority`. |
| `freshness.updated_at` | string (date-time) | Yes |  |
| `completeness` | object | Yes |  |
| `completeness.sampling_policies` | string | Yes | Allowed values: `complete`, `truncated`. |
| `completeness.retention_policies` | string | Yes | Allowed values: `complete`, `truncated`. |

**Response example** (`200`)

```json
{
  "ok": true,
  "project_id": "checkout",
  "environment_id": "prod",
  "policy": {
    "project_id": "checkout",
    "environment_id": "prod",
    "display_name": "Production",
    "environment_kind": "production",
    "state": "active",
    "region_id": "us-central1",
    "data_plane_id": "primary",
    "capture_enabled": false,
    "allowed_signals": [
      "traces"
    ],
    "sampling_policy_id": "baseline",
    "retention_policy_version": 3,
    "privacy_policy_version": 2,
    "authority_epoch": 7,
    "erasure_epoch": 2,
    "quota_manifest_revision": 4,
    "parser_manifest_revision": 5,
    "created_at": "2026-08-01T00:00:00.000Z",
    "updated_at": "2026-09-01T00:00:00.000Z"
  },
  "sampling_policies": [],
  "sampling_policy_count": 0,
  "sampling_policies_truncated": false,
  "retention_policies": [],
  "retention_policy_count": 0,
  "retention_policies_truncated": false,
  "freshness": {
    "source": "postgres_authority",
    "updated_at": "2026-09-01T00:00:00.000Z"
  },
  "completeness": {
    "sampling_policies": "complete",
    "retention_policies": "complete"
  }
}
```

### Example request

**curl**

```bash
curl "https://collect.noxtica.com/v1/apm/projects/example-projectId/environments/example-environmentId/policy" \
  -H "Authorization: Bearer $NOXTICA_API_KEY"
```

**Node.js** (global `fetch`, Node 18 or later)

```js
const response = await fetch('https://collect.noxtica.com/v1/apm/projects/example-projectId/environments/example-environmentId/policy', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
  },
});
console.log(response.status, await response.text());
```

**Python** (standard library)

```python
import os
import urllib.request

request = urllib.request.Request(
    "https://collect.noxtica.com/v1/apm/projects/example-projectId/environments/example-environmentId/policy",
    headers={
        "Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
    },
    method="GET",
)
with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())
```

## Update an environment policy {#v1ApmUpdateEnvironmentPolicy}

`PUT /v1/apm/projects/{projectId}/environments/{environmentId}/policy`

Updates a closed allowlist using optimistic authority-epoch concurrency. While capture rollout is OFF, `capture_enabled: true` is unavailable and returns 409 `capture_unavailable`; clients may only leave it absent or set it false.

**Authentication:** Bearer token (`ApiKeyAuth`), scope `write:apm`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `projectId` | path | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `environmentId` | path | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |

### Request body

Content type `application/json`, required.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `expected_authority_epoch` | integer | Yes | Minimum 1. |
| `capture_enabled` | const | No | `true` is unavailable while capture rollout is OFF. Always `false`. |
| `allowed_signals` | array of string | No | At most 2 items, unique items. |
| `sampling_policy_id` | one of ApmScopedId or any, nullable | No |  |
| `retention_policy_version` | integer | No | Minimum 1. |
| `privacy_policy_version` | integer | No | Minimum 1. |
| `region_id` | string | No | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `data_plane_id` | string | No | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |

### Responses

| Status | Meaning | Error codes | Content |
| --- | --- | --- | --- |
| `200` | Updated authoritative policy. |  | `application/json` ApmPolicyUpdateResult |
| `400` | Closed-input validation failed. | `invalid_request` | `application/json` ApmError |
| `401` | Missing, invalid, or ingest-only credential. An `sk_apm_` key is never accepted here. | `Unauthorized` | `application/json` ApmError |
| `403` | The server API key lacks the operation's required scope. | `API key does not have read:apm scope` | `application/json` ApmError |
| `404` | The tenant-scoped resource was not found. | `resource_not_found` | `application/json` ApmError |
| `409` | Capture is unavailable or the expected authority epoch is stale. | `capture_unavailable` | `application/json` ApmConflict |
| `503` | Authoritative control-plane data is unavailable. |  | `application/json` ApmUnavailable |

**Response headers**

| Status | Header | Type | Description |
| --- | --- | --- | --- |
| `200` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `400` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `401` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `403` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `404` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `409` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `503` | `Cache-Control` | const | APM control/query responses are never cacheable. |

**Response fields** (`200`, `application/json`)

| Field | Type | Always returned | Description |
| --- | --- | --- | --- |
| `ok` | const | Yes | Always `true`. |
| `project_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `environment_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `policy` | object (ApmEnvironmentPolicy) | Yes |  |
| `policy.project_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `policy.environment_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `policy.display_name` | string | Yes |  |
| `policy.environment_kind` | string | Yes |  |
| `policy.state` | string | Yes |  |
| `policy.region_id` | string | Yes |  |
| `policy.data_plane_id` | string | Yes |  |
| `policy.capture_enabled` | boolean | Yes |  |
| `policy.allowed_signals` | array of string | Yes | At most 2 items, unique items. |
| `policy.sampling_policy_id` | one of ApmScopedId or any, nullable | Yes |  |
| `policy.retention_policy_version` | integer | Yes | Minimum 1. |
| `policy.privacy_policy_version` | integer | Yes | Minimum 1. |
| `policy.authority_epoch` | integer | Yes | Minimum 0. |
| `policy.erasure_epoch` | integer | Yes | Minimum 0. |
| `policy.quota_manifest_revision` | integer | Yes | Minimum 0. |
| `policy.parser_manifest_revision` | integer | Yes | Minimum 0. |
| `policy.created_at` | string (date-time) | Yes |  |
| `policy.updated_at` | string (date-time) | Yes |  |
| `count` | const | Yes | Always `1`. |
| `truncated` | const | Yes | Always `false`. |
| `freshness` | object (ApmAuthorityFreshness) | Yes |  |
| `freshness.source` | const | Yes | Always `postgres_authority`. |
| `freshness.updated_at` | string (date-time) | Yes |  |
| `completeness` | const | Yes | Always `complete`. |

**Response example** (`200`)

```json
{
  "ok": true,
  "project_id": "checkout",
  "environment_id": "prod",
  "policy": {
    "project_id": "checkout",
    "environment_id": "prod",
    "display_name": "Production",
    "environment_kind": "production",
    "state": "active",
    "region_id": "us-central1",
    "data_plane_id": "primary",
    "capture_enabled": false,
    "allowed_signals": [
      "traces"
    ],
    "sampling_policy_id": "baseline",
    "retention_policy_version": 3,
    "privacy_policy_version": 2,
    "authority_epoch": 8,
    "erasure_epoch": 2,
    "quota_manifest_revision": 4,
    "parser_manifest_revision": 5,
    "created_at": "2026-08-01T00:00:00.000Z",
    "updated_at": "2026-09-21T00:00:00.000Z"
  },
  "count": 1,
  "truncated": false,
  "freshness": {
    "source": "postgres_authority",
    "updated_at": "2026-09-21T00:00:00.000Z"
  },
  "completeness": "complete"
}
```

### Example request

**curl**

```bash
curl -X PUT "https://collect.noxtica.com/v1/apm/projects/example-projectId/environments/example-environmentId/policy" \
  -H "Authorization: Bearer $NOXTICA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "expected_authority_epoch": 7,
  "capture_enabled": false,
  "allowed_signals": [
    "traces"
  ],
  "sampling_policy_id": "baseline"
}'
```

**Node.js** (global `fetch`, Node 18 or later)

```js
const response = await fetch('https://collect.noxtica.com/v1/apm/projects/example-projectId/environments/example-environmentId/policy', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    expected_authority_epoch: 7,
    capture_enabled: false,
    allowed_signals: [
      'traces',
    ],
    sampling_policy_id: 'baseline',
  }),
});
console.log(response.status, await response.text());
```

**Python** (standard library)

```python
import json
import os
import urllib.request

request = urllib.request.Request(
    "https://collect.noxtica.com/v1/apm/projects/example-projectId/environments/example-environmentId/policy",
    data=json.dumps({
        "expected_authority_epoch": 7,
        "capture_enabled": False,
        "allowed_signals": [
            "traces",
        ],
        "sampling_policy_id": "baseline",
    }).encode(),
    headers={
        "Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
        "Content-Type": "application/json",
    },
    method="PUT",
)
with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())
```

## Search traces {#v1ApmSearchTraces}

`GET /v1/apm/traces`

Searches the warehouse within one explicit project/environment and a range no longer than 31 days. Rows expose only the query allowlist; completeness and freshness are not inferred.

**Authentication:** Bearer token (`ApiKeyAuth`), scope `read:apm`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `project_id` | query | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `environment_id` | query | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `from` | query | string (date-time) | Yes | Inclusive RFC 3339 range start. The interval to `to` must not exceed 31 days. |
| `to` | query | string (date-time) | Yes | Exclusive RFC 3339 range end, after `from`; the interval must not exceed 31 days. |
| `limit` | query | integer | No | Minimum 1, maximum 200. Default `50`. |
| `cursor` | query | string | No | At least 1 character, at most 512 characters. |
| `service_name` | query | string | No | At least 1 character, at most 256 characters. Pattern `^[A-Za-z0-9._:/-]+$`. |
| `release_id` | query | string | No | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `status` | query | string | No | Allowed values: `ok`, `error`. |

### Responses

| Status | Meaning | Error codes | Content |
| --- | --- | --- | --- |
| `200` | Trace summary page. |  | `application/json` ApmTraceSearchResult |
| `400` | Closed-input validation failed. | `invalid_request` | `application/json` ApmError |
| `401` | Missing, invalid, or ingest-only credential. An `sk_apm_` key is never accepted here. | `Unauthorized` | `application/json` ApmError |
| `403` | The server API key lacks the operation's required scope. | `API key does not have read:apm scope` | `application/json` ApmError |
| `404` | The tenant-scoped resource was not found. | `resource_not_found` | `application/json` ApmError |
| `429` | V1 read rate limit exceeded. | `Rate limit exceeded` | `application/json` ApmRateLimitError |
| `500` | Unexpected internal trace-query failure. | `internal_error` | `application/json` ApmError |
| `503` | Warehouse query or mandatory access audit is unavailable. |  | `application/json` ApmUnavailable |

**Response headers**

| Status | Header | Type | Description |
| --- | --- | --- | --- |
| `200` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `400` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `401` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `403` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `404` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `429` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `500` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `503` | `Cache-Control` | const | APM control/query responses are never cacheable. |

**Response fields** (`200`, `application/json`)

| Field | Type | Always returned | Description |
| --- | --- | --- | --- |
| `ok` | const | Yes | Always `true`. |
| `available` | const | Yes | Always `true`. |
| `project_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `environment_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `items` | array of ApmTraceSummary | Yes | At most 200 items. |
| `items[]` | object (ApmTraceSummary) | No |  |
| `items[].traceId` | string | Yes | Pattern `^(?!0{32}$)[0-9a-f]{32}$`. |
| `items[].rootSpanName` | string, nullable | No |  |
| `items[].rootServiceName` | string, nullable | No |  |
| `items[].startTime` | string (date-time), nullable | No |  |
| `items[].endTime` | string (date-time), nullable | No |  |
| `items[].durationMs` | number, nullable | No |  |
| `items[].spanCount` | integer, nullable | No | Minimum 0. |
| `items[].errorCount` | integer, nullable | No | Minimum 0. |
| `items[].status` | string, nullable | No | Allowed values: `ok`, `error`, `null`. |
| `items[].releaseId` | string, nullable | No |  |
| `items[].buildId` | string, nullable | No |  |
| `items[].samplingClass` | string, nullable | No |  |
| `items[].headSamplingDecision` | string, nullable | No |  |
| `items[].tailSamplingDecision` | string, nullable | No |  |
| `items[].samplingReason` | string, nullable | No |  |
| `items[].retentionDecision` | string, nullable | No |  |
| `items[].headState` | string, nullable | No |  |
| `items[].tailState` | string, nullable | No |  |
| `items[].partial` | boolean, nullable | No |  |
| `items[].late` | boolean, nullable | No |  |
| `items[].completeness` | string, nullable | Yes |  |
| `items[].samplingPolicyId` | string, nullable | No |  |
| `items[].retentionPolicyVersion` | integer, nullable | No |  |
| `items[].freshnessWatermark` | string (date-time), nullable | No |  |
| `items[].ingestedAt` | string (date-time), nullable | No |  |
| `items[].schemaVersion` | integer, nullable | No |  |
| `count` | integer | Yes | Minimum 0, maximum 200. |
| `truncated` | boolean | Yes |  |
| `next_cursor` | string, nullable | Yes |  |
| `partial` | boolean | Yes |  |
| `completeness` | string | Yes |  |
| `freshness` | object (ApmFreshness) | Yes |  |
| `freshness.watermark` | string (date-time), nullable | Yes |  |
| `freshness.ingested_at` | string (date-time), nullable | Yes |  |

**Response example** (`200`)

```json
{
  "ok": true,
  "available": true,
  "project_id": "checkout",
  "environment_id": "prod",
  "items": [
    {
      "traceId": "0123456789abcdef0123456789abcdef",
      "startTime": "2026-09-20T12:00:00.000Z",
      "partial": false,
      "completeness": "complete",
      "freshnessWatermark": "2026-09-20T12:01:00.000Z",
      "ingestedAt": "2026-09-20T12:01:01.000Z"
    }
  ],
  "count": 1,
  "truncated": false,
  "next_cursor": null,
  "partial": false,
  "completeness": "complete",
  "freshness": {
    "watermark": "2026-09-20T12:01:00.000Z",
    "ingested_at": "2026-09-20T12:01:01.000Z"
  }
}
```

### Example request

**curl**

```bash
curl "https://collect.noxtica.com/v1/apm/traces?project_id=example-project_id&environment_id=example-environment_id&from=2026-01-01T00%3A00%3A00Z&to=2026-01-01T00%3A00%3A00Z" \
  -H "Authorization: Bearer $NOXTICA_API_KEY"
```

**Node.js** (global `fetch`, Node 18 or later)

```js
const response = await fetch('https://collect.noxtica.com/v1/apm/traces?project_id=example-project_id&environment_id=example-environment_id&from=2026-01-01T00%3A00%3A00Z&to=2026-01-01T00%3A00%3A00Z', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
  },
});
console.log(response.status, await response.text());
```

**Python** (standard library)

```python
import os
import urllib.request

request = urllib.request.Request(
    "https://collect.noxtica.com/v1/apm/traces?project_id=example-project_id&environment_id=example-environment_id&from=2026-01-01T00%3A00%3A00Z&to=2026-01-01T00%3A00%3A00Z",
    headers={
        "Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
    },
    method="GET",
)
with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())
```

## Read trace detail {#v1ApmGetTrace}

`GET /v1/apm/traces/{traceId}`

Returns a trace summary and a bounded timeline. Parent-edge completeness is explicitly false in this launch contract.

**Authentication:** Bearer token (`ApiKeyAuth`), scope `read:apm`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `traceId` | path | string | Yes | Pattern `^(?!0{32}$)[0-9a-f]{32}$`. |
| `project_id` | query | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `environment_id` | query | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `from` | query | string (date-time) | Yes | Inclusive RFC 3339 range start. The interval to `to` must not exceed 31 days. |
| `to` | query | string (date-time) | Yes | Exclusive RFC 3339 range end, after `from`; the interval must not exceed 31 days. |
| `limit` | query | integer | No | Minimum 1, maximum 200. Default `50`. |

### Responses

| Status | Meaning | Error codes | Content |
| --- | --- | --- | --- |
| `200` | Trace detail and timeline. |  | `application/json` ApmTraceDetailResult |
| `400` | Closed-input validation failed. | `invalid_request` | `application/json` ApmError |
| `401` | Missing, invalid, or ingest-only credential. An `sk_apm_` key is never accepted here. | `Unauthorized` | `application/json` ApmError |
| `403` | The server API key lacks the operation's required scope. | `API key does not have read:apm scope` | `application/json` ApmError |
| `404` | The tenant-scoped resource was not found. | `resource_not_found` | `application/json` ApmError |
| `429` | V1 read rate limit exceeded. | `Rate limit exceeded` | `application/json` ApmRateLimitError |
| `500` | Unexpected internal trace-query failure. | `internal_error` | `application/json` ApmError |
| `503` | Warehouse query or mandatory access audit is unavailable. |  | `application/json` ApmUnavailable |

**Response headers**

| Status | Header | Type | Description |
| --- | --- | --- | --- |
| `200` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `400` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `401` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `403` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `404` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `429` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `500` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `503` | `Cache-Control` | const | APM control/query responses are never cacheable. |

**Response fields** (`200`, `application/json`)

| Field | Type | Always returned | Description |
| --- | --- | --- | --- |
| `ok` | const | Yes | Always `true`. |
| `available` | const | Yes | Always `true`. |
| `project_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `environment_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `summary` | object (ApmTraceSummary) | Yes |  |
| `summary.traceId` | string | Yes | Pattern `^(?!0{32}$)[0-9a-f]{32}$`. |
| `summary.rootSpanName` | string, nullable | No |  |
| `summary.rootServiceName` | string, nullable | No |  |
| `summary.startTime` | string (date-time), nullable | No |  |
| `summary.endTime` | string (date-time), nullable | No |  |
| `summary.durationMs` | number, nullable | No |  |
| `summary.spanCount` | integer, nullable | No | Minimum 0. |
| `summary.errorCount` | integer, nullable | No | Minimum 0. |
| `summary.status` | string, nullable | No | Allowed values: `ok`, `error`, `null`. |
| `summary.releaseId` | string, nullable | No |  |
| `summary.buildId` | string, nullable | No |  |
| `summary.samplingClass` | string, nullable | No |  |
| `summary.headSamplingDecision` | string, nullable | No |  |
| `summary.tailSamplingDecision` | string, nullable | No |  |
| `summary.samplingReason` | string, nullable | No |  |
| `summary.retentionDecision` | string, nullable | No |  |
| `summary.headState` | string, nullable | No |  |
| `summary.tailState` | string, nullable | No |  |
| `summary.partial` | boolean, nullable | No |  |
| `summary.late` | boolean, nullable | No |  |
| `summary.completeness` | string, nullable | Yes |  |
| `summary.samplingPolicyId` | string, nullable | No |  |
| `summary.retentionPolicyVersion` | integer, nullable | No |  |
| `summary.freshnessWatermark` | string (date-time), nullable | No |  |
| `summary.ingestedAt` | string (date-time), nullable | No |  |
| `summary.schemaVersion` | integer, nullable | No |  |
| `timeline` | array of ApmTraceSpan | Yes | At most 200 items. |
| `timeline[]` | object (ApmTraceSpan) | No |  |
| `timeline[].traceId` | string | Yes | Pattern `^(?!0{32}$)[0-9a-f]{32}$`. |
| `timeline[].spanId` | string | Yes | Pattern `^(?!0{16}$)[0-9a-f]{16}$`. |
| `timeline[].parentSpanId` | string, nullable | No |  |
| `timeline[].spanName` | string, nullable | No |  |
| `timeline[].serviceName` | string, nullable | No |  |
| `timeline[].spanKind` | string, nullable | No |  |
| `timeline[].startTime` | string (date-time), nullable | No |  |
| `timeline[].endTime` | string (date-time), nullable | No |  |
| `timeline[].durationMs` | number, nullable | No |  |
| `timeline[].statusCode` | string, nullable | No |  |
| `timeline[].statusMessage` | string, nullable | No |  |
| `timeline[].releaseId` | string, nullable | No |  |
| `timeline[].buildId` | string, nullable | No |  |
| `timeline[].claimedRumLinkId` | string, nullable | No |  |
| `timeline[].confirmedRumLinkId` | string, nullable | No |  |
| `timeline[].correlationState` | string | No | Allowed values: `confirmed`, `claimed`, `unavailable`. |
| `timeline[].completeness` | string, nullable | Yes |  |
| `timeline[].eventTime` | string (date-time), nullable | No |  |
| `timeline[].ingestedAt` | string (date-time), nullable | No |  |
| `count` | integer | Yes | Minimum 0, maximum 200. |
| `truncated` | boolean | Yes |  |
| `partial` | boolean | Yes |  |
| `completeness` | object | Yes |  |
| `completeness.summary` | string | Yes |  |
| `completeness.timeline` | string | Yes |  |
| `freshness` | object (ApmFreshness) | Yes |  |
| `freshness.watermark` | string (date-time), nullable | Yes |  |
| `freshness.ingested_at` | string (date-time), nullable | Yes |  |
| `parent_edges_complete` | const | Yes | Always `false`. |

**Response example** (`200`)

```json
{
  "ok": true,
  "available": true,
  "project_id": "checkout",
  "environment_id": "prod",
  "summary": {
    "traceId": "0123456789abcdef0123456789abcdef",
    "startTime": "2026-09-20T12:00:00.000Z",
    "partial": false,
    "completeness": "complete",
    "freshnessWatermark": "2026-09-20T12:01:00.000Z",
    "ingestedAt": "2026-09-20T12:01:01.000Z"
  },
  "timeline": [
    {
      "traceId": "0123456789abcdef0123456789abcdef",
      "spanId": "aaaaaaaaaaaaaaaa",
      "parentSpanId": "bbbbbbbbbbbbbbbb",
      "completeness": "partial",
      "ingestedAt": "2026-09-20T12:01:01.000Z"
    }
  ],
  "count": 1,
  "truncated": false,
  "partial": true,
  "completeness": {
    "summary": "complete",
    "timeline": "partial"
  },
  "freshness": {
    "watermark": "2026-09-20T12:01:00.000Z",
    "ingested_at": "2026-09-20T12:01:01.000Z"
  },
  "parent_edges_complete": false
}
```

### Example request

**curl**

```bash
curl "https://collect.noxtica.com/v1/apm/traces/example-traceId?project_id=example-project_id&environment_id=example-environment_id&from=2026-01-01T00%3A00%3A00Z&to=2026-01-01T00%3A00%3A00Z" \
  -H "Authorization: Bearer $NOXTICA_API_KEY"
```

**Node.js** (global `fetch`, Node 18 or later)

```js
const response = await fetch('https://collect.noxtica.com/v1/apm/traces/example-traceId?project_id=example-project_id&environment_id=example-environment_id&from=2026-01-01T00%3A00%3A00Z&to=2026-01-01T00%3A00%3A00Z', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
  },
});
console.log(response.status, await response.text());
```

**Python** (standard library)

```python
import os
import urllib.request

request = urllib.request.Request(
    "https://collect.noxtica.com/v1/apm/traces/example-traceId?project_id=example-project_id&environment_id=example-environment_id&from=2026-01-01T00%3A00%3A00Z&to=2026-01-01T00%3A00%3A00Z",
    headers={
        "Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
    },
    method="GET",
)
with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())
```

## List service health {#v1ApmListServices}

`GET /v1/apm/services`

Returns bounded aggregate health rows for one project/environment and a range no longer than 31 days.

**Authentication:** Bearer token (`ApiKeyAuth`), scope `read:apm`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `project_id` | query | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `environment_id` | query | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `from` | query | string (date-time) | Yes | Inclusive RFC 3339 range start. The interval to `to` must not exceed 31 days. |
| `to` | query | string (date-time) | Yes | Exclusive RFC 3339 range end, after `from`; the interval must not exceed 31 days. |
| `limit` | query | integer | No | Minimum 1, maximum 200. Default `50`. |

### Responses

| Status | Meaning | Error codes | Content |
| --- | --- | --- | --- |
| `200` | Service health page. |  | `application/json` ApmServiceListResult |
| `400` | Closed-input validation failed. | `invalid_request` | `application/json` ApmError |
| `401` | Missing, invalid, or ingest-only credential. An `sk_apm_` key is never accepted here. | `Unauthorized` | `application/json` ApmError |
| `403` | The server API key lacks the operation's required scope. | `API key does not have read:apm scope` | `application/json` ApmError |
| `404` | The tenant-scoped resource was not found. | `resource_not_found` | `application/json` ApmError |
| `429` | V1 read rate limit exceeded. | `Rate limit exceeded` | `application/json` ApmRateLimitError |
| `503` | Warehouse query or mandatory access audit is unavailable. |  | `application/json` ApmUnavailable |

**Response headers**

| Status | Header | Type | Description |
| --- | --- | --- | --- |
| `200` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `400` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `401` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `403` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `404` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `429` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `503` | `Cache-Control` | const | APM control/query responses are never cacheable. |

**Response fields** (`200`, `application/json`)

| Field | Type | Always returned | Description |
| --- | --- | --- | --- |
| `ok` | const | Yes | Always `true`. |
| `available` | const | Yes | Always `true`. |
| `project_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `environment_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `items` | array of ApmServiceHealth | Yes | At most 200 items. |
| `items[]` | object (ApmServiceHealth) | No |  |
| `items[].service_name` | string, nullable | Yes |  |
| `items[].trace_count` | number, nullable | Yes |  |
| `items[].error_trace_count` | number, nullable | Yes |  |
| `items[].error_rate` | number, nullable | Yes |  |
| `items[].p50_duration_ms` | number, nullable | Yes |  |
| `items[].p95_duration_ms` | number, nullable | Yes |  |
| `items[].completeness` | string | Yes | Allowed values: `partial`, `unknown`. |
| `items[].freshness_watermark` | string (date-time), nullable | Yes |  |
| `count` | integer | Yes | Minimum 0, maximum 200. |
| `truncated` | boolean | Yes |  |
| `partial` | boolean | Yes |  |
| `completeness` | string | Yes | Allowed values: `partial`, `unknown`. |
| `freshness_watermark` | string (date-time), nullable | Yes |  |

**Response example** (`200`)

```json
{
  "ok": true,
  "available": true,
  "project_id": "checkout",
  "environment_id": "prod",
  "items": [
    {
      "service_name": "checkout-api",
      "trace_count": 120,
      "error_trace_count": 3,
      "error_rate": 0.025,
      "p50_duration_ms": 42,
      "p95_duration_ms": 310,
      "completeness": "unknown",
      "freshness_watermark": "2026-09-20T12:01:00.000Z"
    }
  ],
  "count": 1,
  "truncated": false,
  "partial": false,
  "completeness": "unknown",
  "freshness_watermark": "2026-09-20T12:01:00.000Z"
}
```

### Example request

**curl**

```bash
curl "https://collect.noxtica.com/v1/apm/services?project_id=example-project_id&environment_id=example-environment_id&from=2026-01-01T00%3A00%3A00Z&to=2026-01-01T00%3A00%3A00Z" \
  -H "Authorization: Bearer $NOXTICA_API_KEY"
```

**Node.js** (global `fetch`, Node 18 or later)

```js
const response = await fetch('https://collect.noxtica.com/v1/apm/services?project_id=example-project_id&environment_id=example-environment_id&from=2026-01-01T00%3A00%3A00Z&to=2026-01-01T00%3A00%3A00Z', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
  },
});
console.log(response.status, await response.text());
```

**Python** (standard library)

```python
import os
import urllib.request

request = urllib.request.Request(
    "https://collect.noxtica.com/v1/apm/services?project_id=example-project_id&environment_id=example-environment_id&from=2026-01-01T00%3A00%3A00Z&to=2026-01-01T00%3A00%3A00Z",
    headers={
        "Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
    },
    method="GET",
)
with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())
```

## Read service health and dependencies {#v1ApmGetServiceHealth}

`GET /v1/apm/services/{serviceName}/health`

Returns health plus dependency aggregates. Either warehouse subquery may be unavailable; `partial`, availability booleans, and `reason` report that honestly.

**Authentication:** Bearer token (`ApiKeyAuth`), scope `read:apm`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `serviceName` | path | string | Yes | At least 1 character, at most 256 characters. Pattern `^[A-Za-z0-9._:/-]+$`. |
| `project_id` | query | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `environment_id` | query | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `from` | query | string (date-time) | Yes | Inclusive RFC 3339 range start. The interval to `to` must not exceed 31 days. |
| `to` | query | string (date-time) | Yes | Exclusive RFC 3339 range end, after `from`; the interval must not exceed 31 days. |
| `limit` | query | integer | No | Minimum 1, maximum 200. Default `50`. |

### Responses

| Status | Meaning | Error codes | Content |
| --- | --- | --- | --- |
| `200` | Service health and dependencies. |  | `application/json` ApmServiceHealthResult |
| `400` | Closed-input validation failed. | `invalid_request` | `application/json` ApmError |
| `401` | Missing, invalid, or ingest-only credential. An `sk_apm_` key is never accepted here. | `Unauthorized` | `application/json` ApmError |
| `403` | The server API key lacks the operation's required scope. | `API key does not have read:apm scope` | `application/json` ApmError |
| `404` | The tenant-scoped resource was not found. | `resource_not_found` | `application/json` ApmError |
| `429` | V1 read rate limit exceeded. | `Rate limit exceeded` | `application/json` ApmRateLimitError |
| `503` | Warehouse query or mandatory access audit is unavailable. |  | `application/json` ApmUnavailable |

**Response headers**

| Status | Header | Type | Description |
| --- | --- | --- | --- |
| `200` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `400` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `401` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `403` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `404` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `429` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `503` | `Cache-Control` | const | APM control/query responses are never cacheable. |

**Response fields** (`200`, `application/json`)

| Field | Type | Always returned | Description |
| --- | --- | --- | --- |
| `ok` | const | Yes | Always `true`. |
| `available` | const | Yes | Always `true`. |
| `partial` | boolean | Yes |  |
| `reason` | string, nullable | Yes | Allowed values: `partial_warehouse_unavailable`, `source_incomplete`, `null`. |
| `project_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `environment_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `service_name` | string | Yes |  |
| `health` | one of ApmServiceHealth or any, nullable | Yes |  |
| `health{ApmServiceHealth}` | object (ApmServiceHealth) | No |  |
| `health{ApmServiceHealth}.service_name` | string, nullable | Yes |  |
| `health{ApmServiceHealth}.trace_count` | number, nullable | Yes |  |
| `health{ApmServiceHealth}.error_trace_count` | number, nullable | Yes |  |
| `health{ApmServiceHealth}.error_rate` | number, nullable | Yes |  |
| `health{ApmServiceHealth}.p50_duration_ms` | number, nullable | Yes |  |
| `health{ApmServiceHealth}.p95_duration_ms` | number, nullable | Yes |  |
| `health{ApmServiceHealth}.completeness` | string | Yes | Allowed values: `partial`, `unknown`. |
| `health{ApmServiceHealth}.freshness_watermark` | string (date-time), nullable | Yes |  |
| `health_available` | boolean | Yes |  |
| `items` | array of ApmDependencyHealth | Yes | At most 200 items. |
| `items[]` | object (ApmDependencyHealth) | No |  |
| `items[].source_service` | string, nullable | Yes |  |
| `items[].target_service` | string, nullable | Yes |  |
| `items[].protocol` | string, nullable | Yes |  |
| `items[].request_count` | number, nullable | Yes |  |
| `items[].error_count` | number, nullable | Yes |  |
| `items[].p50_duration_ms` | number, nullable | Yes |  |
| `items[].p95_duration_ms` | number, nullable | Yes |  |
| `items[].p99_duration_ms` | number, nullable | Yes |  |
| `items[].freshness_watermark` | string (date-time), nullable | Yes |  |
| `count` | integer | Yes | Minimum 0, maximum 200. |
| `dependencies_available` | boolean | Yes |  |
| `truncated` | boolean | Yes |  |
| `completeness` | string | Yes | Allowed values: `partial`, `unknown`. |
| `freshness_watermark` | string (date-time), nullable | Yes |  |

**Response example** (`200`)

```json
{
  "ok": true,
  "available": true,
  "partial": false,
  "reason": null,
  "project_id": "checkout",
  "environment_id": "prod",
  "service_name": "checkout-api",
  "health": {
    "service_name": "checkout-api",
    "trace_count": 120,
    "error_trace_count": 3,
    "error_rate": 0.025,
    "p50_duration_ms": 42,
    "p95_duration_ms": 310,
    "completeness": "unknown",
    "freshness_watermark": "2026-09-20T12:01:00.000Z"
  },
  "health_available": true,
  "items": [
    {
      "source_service": "checkout-api",
      "target_service": "payments",
      "protocol": "http",
      "request_count": 50,
      "error_count": 1,
      "p50_duration_ms": 25,
      "p95_duration_ms": 80,
      "p99_duration_ms": 140,
      "freshness_watermark": "2026-09-20T12:01:00.000Z"
    }
  ],
  "count": 1,
  "dependencies_available": true,
  "truncated": false,
  "completeness": "unknown",
  "freshness_watermark": "2026-09-20T12:01:00.000Z"
}
```

### Example request

**curl**

```bash
curl "https://collect.noxtica.com/v1/apm/services/example-serviceName/health?project_id=example-project_id&environment_id=example-environment_id&from=2026-01-01T00%3A00%3A00Z&to=2026-01-01T00%3A00%3A00Z" \
  -H "Authorization: Bearer $NOXTICA_API_KEY"
```

**Node.js** (global `fetch`, Node 18 or later)

```js
const response = await fetch('https://collect.noxtica.com/v1/apm/services/example-serviceName/health?project_id=example-project_id&environment_id=example-environment_id&from=2026-01-01T00%3A00%3A00Z&to=2026-01-01T00%3A00%3A00Z', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
  },
});
console.log(response.status, await response.text());
```

**Python** (standard library)

```python
import os
import urllib.request

request = urllib.request.Request(
    "https://collect.noxtica.com/v1/apm/services/example-serviceName/health?project_id=example-project_id&environment_id=example-environment_id&from=2026-01-01T00%3A00%3A00Z&to=2026-01-01T00%3A00%3A00Z",
    headers={
        "Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
    },
    method="GET",
)
with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())
```

## Read release health {#v1ApmGetReleaseHealth}

`GET /v1/apm/releases/{releaseId}/health`

Returns bounded aggregate health rows for one release within the required project/environment and time range.

**Authentication:** Bearer token (`ApiKeyAuth`), scope `read:apm`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `releaseId` | path | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `project_id` | query | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `environment_id` | query | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `from` | query | string (date-time) | Yes | Inclusive RFC 3339 range start. The interval to `to` must not exceed 31 days. |
| `to` | query | string (date-time) | Yes | Exclusive RFC 3339 range end, after `from`; the interval must not exceed 31 days. |
| `limit` | query | integer | No | Minimum 1, maximum 200. Default `50`. |

### Responses

| Status | Meaning | Error codes | Content |
| --- | --- | --- | --- |
| `200` | Release health page. |  | `application/json` ApmReleaseHealthResult |
| `400` | Closed-input validation failed. | `invalid_request` | `application/json` ApmError |
| `401` | Missing, invalid, or ingest-only credential. An `sk_apm_` key is never accepted here. | `Unauthorized` | `application/json` ApmError |
| `403` | The server API key lacks the operation's required scope. | `API key does not have read:apm scope` | `application/json` ApmError |
| `404` | The tenant-scoped resource was not found. | `resource_not_found` | `application/json` ApmError |
| `429` | V1 read rate limit exceeded. | `Rate limit exceeded` | `application/json` ApmRateLimitError |
| `503` | Warehouse query or mandatory access audit is unavailable. |  | `application/json` ApmUnavailable |

**Response headers**

| Status | Header | Type | Description |
| --- | --- | --- | --- |
| `200` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `400` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `401` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `403` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `404` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `429` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `503` | `Cache-Control` | const | APM control/query responses are never cacheable. |

**Response fields** (`200`, `application/json`)

| Field | Type | Always returned | Description |
| --- | --- | --- | --- |
| `ok` | const | Yes | Always `true`. |
| `available` | const | Yes | Always `true`. |
| `project_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `environment_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `release_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `items` | array of ApmReleaseHealth | Yes | At most 200 items. |
| `items[]` | object (ApmReleaseHealth) | No |  |
| `items[].release_id` | string, nullable | Yes |  |
| `items[].build_id` | string, nullable | Yes |  |
| `items[].trace_count` | number, nullable | Yes |  |
| `items[].error_trace_count` | number, nullable | Yes |  |
| `items[].error_rate` | number, nullable | Yes |  |
| `items[].p50_duration_ms` | number, nullable | Yes |  |
| `items[].p95_duration_ms` | number, nullable | Yes |  |
| `items[].completeness` | string | Yes | Allowed values: `partial`, `unknown`. |
| `items[].freshness_watermark` | string (date-time), nullable | Yes |  |
| `count` | integer | Yes | Minimum 0, maximum 200. |
| `truncated` | boolean | Yes |  |
| `partial` | boolean | Yes |  |
| `completeness` | string | Yes | Allowed values: `partial`, `unknown`. |
| `freshness_watermark` | string (date-time), nullable | Yes |  |

**Response example** (`200`)

```json
{
  "ok": true,
  "available": true,
  "project_id": "checkout",
  "environment_id": "prod",
  "release_id": "2026.09.20",
  "items": [
    {
      "release_id": "2026.09.20",
      "build_id": "build-42",
      "trace_count": 100,
      "error_trace_count": 2,
      "error_rate": 0.02,
      "p50_duration_ms": 40,
      "p95_duration_ms": 300,
      "completeness": "unknown",
      "freshness_watermark": "2026-09-20T12:01:00.000Z"
    }
  ],
  "count": 1,
  "truncated": false,
  "partial": false,
  "completeness": "unknown",
  "freshness_watermark": "2026-09-20T12:01:00.000Z"
}
```

### Example request

**curl**

```bash
curl "https://collect.noxtica.com/v1/apm/releases/example-releaseId/health?project_id=example-project_id&environment_id=example-environment_id&from=2026-01-01T00%3A00%3A00Z&to=2026-01-01T00%3A00%3A00Z" \
  -H "Authorization: Bearer $NOXTICA_API_KEY"
```

**Node.js** (global `fetch`, Node 18 or later)

```js
const response = await fetch('https://collect.noxtica.com/v1/apm/releases/example-releaseId/health?project_id=example-project_id&environment_id=example-environment_id&from=2026-01-01T00%3A00%3A00Z&to=2026-01-01T00%3A00%3A00Z', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
  },
});
console.log(response.status, await response.text());
```

**Python** (standard library)

```python
import os
import urllib.request

request = urllib.request.Request(
    "https://collect.noxtica.com/v1/apm/releases/example-releaseId/health?project_id=example-project_id&environment_id=example-environment_id&from=2026-01-01T00%3A00%3A00Z&to=2026-01-01T00%3A00%3A00Z",
    headers={
        "Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
    },
    method="GET",
)
with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())
```

## Create an asynchronous APM export {#v1ApmCreateExport}

`POST /v1/apm/exports`

Creates a tenant-scoped manifest for a range no longer than 31 days. This endpoint never returns storage references, credentials, or a download URL.

**Authentication:** Bearer token (`ApiKeyAuth`), scope `write:apm`.

### Request body

Content type `application/json`, required.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `project_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `environment_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `from` | string (date-time) | Yes | Range start; interval must not exceed 31 days. |
| `to` | string (date-time) | Yes | Range end after `from`; interval must not exceed 31 days. |
| `fact_names` | array of string | Yes | At least 1 item, at most 10 items, unique items. |
| `purpose` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9][A-Za-z0-9 ._:/-]{0,127}$`. |
| `expires_in_seconds` | integer | No | Minimum 60, maximum 604800. Default `3600`. |

### Responses

| Status | Meaning | Error codes | Content |
| --- | --- | --- | --- |
| `201` | Pending export manifest. |  | `application/json` ApmExportStatus |
| `400` | Closed-input validation failed. | `invalid_request` | `application/json` ApmError |
| `401` | Missing, invalid, or ingest-only credential. An `sk_apm_` key is never accepted here. | `Unauthorized` | `application/json` ApmError |
| `403` | The server API key lacks the operation's required scope. | `API key does not have read:apm scope` | `application/json` ApmError |
| `404` | The tenant-scoped resource was not found. | `resource_not_found` | `application/json` ApmError |
| `503` | Authoritative control-plane data is unavailable. |  | `application/json` ApmUnavailable |

**Response headers**

| Status | Header | Type | Description |
| --- | --- | --- | --- |
| `201` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `400` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `401` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `403` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `404` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `503` | `Cache-Control` | const | APM control/query responses are never cacheable. |

**Response fields** (`201`, `application/json`)

| Field | Type | Always returned | Description |
| --- | --- | --- | --- |
| `ok` | const | Yes | Always `true`. |
| `project_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `environment_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `export_manifest_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `purpose` | string | Yes |  |
| `fact_names` | array of string | Yes |  |
| `state` | string | Yes | Asynchronous manifest state reported by the runtime; consumers must tolerate future states. |
| `row_count` | integer, nullable | Yes | Minimum 0. |
| `byte_count` | integer, nullable | Yes | Minimum 0. |
| `created_at` | string (date-time), nullable | Yes |  |
| `expires_at` | string (date-time), nullable | Yes |  |
| `completed_at` | string (date-time), nullable | Yes |  |

**Response example** (`201`)

```json
{
  "ok": true,
  "project_id": "checkout",
  "environment_id": "prod",
  "export_manifest_id": "550e8400-e29b-41d4-a716-446655440000",
  "purpose": "incident_review",
  "fact_names": [
    "apm_spans",
    "apm_trace_summaries"
  ],
  "state": "pending",
  "row_count": null,
  "byte_count": null,
  "created_at": "2026-09-21T00:00:00.000Z",
  "expires_at": "2026-09-21T01:00:00.000Z",
  "completed_at": null
}
```

### Example request

**curl**

```bash
curl -X POST "https://collect.noxtica.com/v1/apm/exports" \
  -H "Authorization: Bearer $NOXTICA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "project_id": "checkout",
  "environment_id": "prod",
  "from": "2026-09-20T00:00:00.000Z",
  "to": "2026-09-21T00:00:00.000Z",
  "fact_names": [
    "apm_spans",
    "apm_trace_summaries"
  ],
  "purpose": "incident_review",
  "expires_in_seconds": 3600
}'
```

**Node.js** (global `fetch`, Node 18 or later)

```js
const response = await fetch('https://collect.noxtica.com/v1/apm/exports', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    project_id: 'checkout',
    environment_id: 'prod',
    from: '2026-09-20T00:00:00.000Z',
    to: '2026-09-21T00:00:00.000Z',
    fact_names: [
      'apm_spans',
      'apm_trace_summaries',
    ],
    purpose: 'incident_review',
    expires_in_seconds: 3600,
  }),
});
console.log(response.status, await response.text());
```

**Python** (standard library)

```python
import json
import os
import urllib.request

request = urllib.request.Request(
    "https://collect.noxtica.com/v1/apm/exports",
    data=json.dumps({
        "project_id": "checkout",
        "environment_id": "prod",
        "from": "2026-09-20T00:00:00.000Z",
        "to": "2026-09-21T00:00:00.000Z",
        "fact_names": [
            "apm_spans",
            "apm_trace_summaries",
        ],
        "purpose": "incident_review",
        "expires_in_seconds": 3600,
    }).encode(),
    headers={
        "Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
        "Content-Type": "application/json",
    },
    method="POST",
)
with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())
```

## Read asynchronous export status {#v1ApmGetExport}

`GET /v1/apm/exports/{exportId}`

Returns the safe manifest projection and its real asynchronous state; no object reference or signing credential is exposed.

**Authentication:** Bearer token (`ApiKeyAuth`), scope `read:export`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `exportId` | path | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |

### Responses

| Status | Meaning | Error codes | Content |
| --- | --- | --- | --- |
| `200` | Export status. |  | `application/json` ApmExportStatus |
| `400` | Closed-input validation failed. | `invalid_request` | `application/json` ApmError |
| `401` | Missing, invalid, or ingest-only credential. An `sk_apm_` key is never accepted here. | `Unauthorized` | `application/json` ApmError |
| `403` | The server API key lacks the operation's required scope. | `API key does not have read:apm scope` | `application/json` ApmError |
| `404` | The tenant-scoped resource was not found. | `resource_not_found` | `application/json` ApmError |
| `429` | V1 read rate limit exceeded. | `Rate limit exceeded` | `application/json` ApmRateLimitError |
| `503` | Authoritative control-plane data is unavailable. |  | `application/json` ApmUnavailable |

**Response headers**

| Status | Header | Type | Description |
| --- | --- | --- | --- |
| `200` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `400` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `401` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `403` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `404` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `429` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `503` | `Cache-Control` | const | APM control/query responses are never cacheable. |

**Response fields** (`200`, `application/json`)

| Field | Type | Always returned | Description |
| --- | --- | --- | --- |
| `ok` | const | Yes | Always `true`. |
| `project_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `environment_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `export_manifest_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `purpose` | string | Yes |  |
| `fact_names` | array of string | Yes |  |
| `state` | string | Yes | Asynchronous manifest state reported by the runtime; consumers must tolerate future states. |
| `row_count` | integer, nullable | Yes | Minimum 0. |
| `byte_count` | integer, nullable | Yes | Minimum 0. |
| `created_at` | string (date-time), nullable | Yes |  |
| `expires_at` | string (date-time), nullable | Yes |  |
| `completed_at` | string (date-time), nullable | Yes |  |

**Response example** (`200`)

```json
{
  "ok": true,
  "project_id": "checkout",
  "environment_id": "prod",
  "export_manifest_id": "550e8400-e29b-41d4-a716-446655440000",
  "purpose": "incident_review",
  "fact_names": [
    "apm_spans"
  ],
  "state": "running",
  "row_count": null,
  "byte_count": null,
  "created_at": "2026-09-21T00:00:00.000Z",
  "expires_at": "2026-09-21T01:00:00.000Z",
  "completed_at": null
}
```

### Example request

**curl**

```bash
curl "https://collect.noxtica.com/v1/apm/exports/example-exportId" \
  -H "Authorization: Bearer $NOXTICA_API_KEY"
```

**Node.js** (global `fetch`, Node 18 or later)

```js
const response = await fetch('https://collect.noxtica.com/v1/apm/exports/example-exportId', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
  },
});
console.log(response.status, await response.text());
```

**Python** (standard library)

```python
import os
import urllib.request

request = urllib.request.Request(
    "https://collect.noxtica.com/v1/apm/exports/example-exportId",
    headers={
        "Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
    },
    method="GET",
)
with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())
```

## Request an export download grant {#v1ApmCreateExportDownloadGrant}

`POST /v1/apm/exports/{exportId}/download-grant`

Reports readiness only. Collector signing authority is intentionally unavailable, so this launch contract never returns a URL, object reference, bearer token, or credential.

**Authentication:** Bearer token (`ApiKeyAuth`), scope `read:export`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `exportId` | path | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |

### Request body

Content type `application/json`, optional.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `expires_in_seconds` | integer | No | Minimum 1, maximum 300. Default `60`. |

### Responses

| Status | Meaning | Error codes | Content |
| --- | --- | --- | --- |
| `400` | Closed-input validation failed. | `invalid_request` | `application/json` ApmError |
| `401` | Missing, invalid, or ingest-only credential. An `sk_apm_` key is never accepted here. | `Unauthorized` | `application/json` ApmError |
| `403` | The server API key lacks the operation's required scope. | `API key does not have read:apm scope` | `application/json` ApmError |
| `404` | The tenant-scoped resource was not found. | `resource_not_found` | `application/json` ApmError |
| `409` | Export is not ready or failed. |  | `application/json` ApmGrantUnavailable |
| `410` | Export expired or was deleted. |  | `application/json` ApmGrantUnavailable |
| `429` | V1 read rate limit exceeded. | `Rate limit exceeded` | `application/json` ApmRateLimitError |
| `503` | Signing authority or control-plane storage is unavailable. |  | `application/json` |

**Response headers**

| Status | Header | Type | Description |
| --- | --- | --- | --- |
| `400` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `401` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `403` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `404` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `409` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `410` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `429` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `503` | `Cache-Control` | const | APM control/query responses are never cacheable. |

### Example request

**curl**

```bash
curl -X POST "https://collect.noxtica.com/v1/apm/exports/example-exportId/download-grant" \
  -H "Authorization: Bearer $NOXTICA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "expires_in_seconds": 60
}'
```

**Node.js** (global `fetch`, Node 18 or later)

```js
const response = await fetch('https://collect.noxtica.com/v1/apm/exports/example-exportId/download-grant', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    expires_in_seconds: 60,
  }),
});
console.log(response.status, await response.text());
```

**Python** (standard library)

```python
import json
import os
import urllib.request

request = urllib.request.Request(
    "https://collect.noxtica.com/v1/apm/exports/example-exportId/download-grant",
    data=json.dumps({
        "expires_in_seconds": 60,
    }).encode(),
    headers={
        "Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
        "Content-Type": "application/json",
    },
    method="POST",
)
with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())
```

## Read subject export status {#v1ApmGetPrivacyExport}

`GET /v1/apm/privacy/subjects/{subjectHandle}/exports/{requestId}`

Hashes the opaque subject handle before repository access and returns only the safe export projection. The cleartext handle and its hash are never returned.

**Authentication:** Bearer token (`ApiKeyAuth`), scope `read:privacy_request`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `subjectHandle` | path | string | Yes | Opaque subject handle; hashed before authentication, logging, storage, or repository access. At least 16 characters, at most 512 characters. Pattern `^[A-Za-z0-9._~:/+-]+$`. |
| `requestId` | path | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |

### Responses

| Status | Meaning | Error codes | Content |
| --- | --- | --- | --- |
| `200` | Subject export status. |  | `application/json` ApmPrivacyExportStatus |
| `400` | Closed-input validation failed. | `invalid_request` | `application/json` ApmError |
| `401` | Missing, invalid, or ingest-only credential. An `sk_apm_` key is never accepted here. | `Unauthorized` | `application/json` ApmError |
| `403` | The server API key lacks the operation's required scope. | `API key does not have read:apm scope` | `application/json` ApmError |
| `404` | The tenant-scoped resource was not found. | `resource_not_found` | `application/json` ApmError |
| `429` | V1 read rate limit exceeded. | `Rate limit exceeded` | `application/json` ApmRateLimitError |
| `503` | Authoritative control-plane data is unavailable. |  | `application/json` ApmUnavailable |

**Response headers**

| Status | Header | Type | Description |
| --- | --- | --- | --- |
| `200` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `400` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `401` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `403` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `404` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `429` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `503` | `Cache-Control` | const | APM control/query responses are never cacheable. |

**Response fields** (`200`, `application/json`)

| Field | Type | Always returned | Description |
| --- | --- | --- | --- |
| `ok` | const | Yes | Always `true`. |
| `privacy_request_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `project_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `environment_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `export_manifest_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `purpose` | string | Yes |  |
| `fact_names` | array of string | Yes |  |
| `state` | string | Yes | Asynchronous manifest state reported by the runtime; consumers must tolerate future states. |
| `row_count` | integer, nullable | Yes | Minimum 0. |
| `byte_count` | integer, nullable | Yes | Minimum 0. |
| `created_at` | string (date-time), nullable | Yes |  |
| `expires_at` | string (date-time), nullable | Yes |  |
| `completed_at` | string (date-time), nullable | Yes |  |

**Response example** (`200`)

```json
{
  "ok": true,
  "privacy_request_id": "dsar-42",
  "project_id": "checkout",
  "environment_id": "prod",
  "export_manifest_id": "550e8400-e29b-41d4-a716-446655440000",
  "purpose": "privacy_subject_export",
  "fact_names": [
    "apm_spans"
  ],
  "state": "pending",
  "row_count": null,
  "byte_count": null,
  "created_at": "2026-09-21T00:00:00.000Z",
  "expires_at": "2026-09-21T01:00:00.000Z",
  "completed_at": null
}
```

### Example request

**curl**

```bash
curl "https://collect.noxtica.com/v1/apm/privacy/subjects/example-subjectHandle/exports/example-requestId" \
  -H "Authorization: Bearer $NOXTICA_API_KEY"
```

**Node.js** (global `fetch`, Node 18 or later)

```js
const response = await fetch('https://collect.noxtica.com/v1/apm/privacy/subjects/example-subjectHandle/exports/example-requestId', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
  },
});
console.log(response.status, await response.text());
```

**Python** (standard library)

```python
import os
import urllib.request

request = urllib.request.Request(
    "https://collect.noxtica.com/v1/apm/privacy/subjects/example-subjectHandle/exports/example-requestId",
    headers={
        "Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
    },
    method="GET",
)
with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())
```

## Read subject erasure status {#v1ApmGetPrivacyErasure}

`GET /v1/apm/privacy/subjects/{subjectHandle}/erasures/{requestId}`

Returns aggregate deletion progress only. Subject identifiers, per-store object references, SQL, credentials, and PII are never exposed.

**Authentication:** Bearer token (`ApiKeyAuth`), scope `read:privacy_request`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `subjectHandle` | path | string | Yes | Opaque subject handle; hashed before authentication, logging, storage, or repository access. At least 16 characters, at most 512 characters. Pattern `^[A-Za-z0-9._~:/+-]+$`. |
| `requestId` | path | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |

### Responses

| Status | Meaning | Error codes | Content |
| --- | --- | --- | --- |
| `200` | Subject erasure status. |  | `application/json` ApmErasureStatus |
| `400` | Closed-input validation failed. | `invalid_request` | `application/json` ApmError |
| `401` | Missing, invalid, or ingest-only credential. An `sk_apm_` key is never accepted here. | `Unauthorized` | `application/json` ApmError |
| `403` | The server API key lacks the operation's required scope. | `API key does not have read:apm scope` | `application/json` ApmError |
| `404` | The tenant-scoped resource was not found. | `resource_not_found` | `application/json` ApmError |
| `429` | V1 read rate limit exceeded. | `Rate limit exceeded` | `application/json` ApmRateLimitError |
| `503` | Authoritative control-plane data is unavailable. |  | `application/json` ApmUnavailable |

**Response headers**

| Status | Header | Type | Description |
| --- | --- | --- | --- |
| `200` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `400` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `401` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `403` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `404` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `429` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `503` | `Cache-Control` | const | APM control/query responses are never cacheable. |

**Response fields** (`200`, `application/json`)

| Field | Type | Always returned | Description |
| --- | --- | --- | --- |
| `ok` | const | Yes | Always `true`. |
| `privacy_request_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `project_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `environment_id` | string | Yes | At least 1 character, at most 128 characters. Pattern `^[A-Za-z0-9._:-]+$`. |
| `state` | string | Yes |  |
| `attempts` | integer | Yes | Minimum 0. |
| `created_at` | string (date-time), nullable | Yes |  |
| `updated_at` | string (date-time), nullable | Yes |  |
| `completed_at` | string (date-time), nullable | Yes |  |
| `stores` | object | Yes |  |
| `stores.count` | integer | Yes | Minimum 0. |
| `stores.pending` | integer | Yes | Minimum 0. |
| `stores.running` | integer | Yes | Minimum 0. |
| `stores.succeeded` | integer | Yes | Minimum 0. |
| `stores.failed` | integer | Yes | Minimum 0. |
| `deleted` | object | Yes |  |
| `deleted.rows` | integer | Yes | Minimum 0. |
| `deleted.objects` | integer | Yes | Minimum 0. |
| `deleted.bytes` | integer | Yes | Minimum 0. |

**Response example** (`200`)

```json
{
  "ok": true,
  "privacy_request_id": "dsar-42",
  "project_id": "checkout",
  "environment_id": "prod",
  "state": "running",
  "attempts": 1,
  "created_at": "2026-09-21T00:00:00.000Z",
  "updated_at": "2026-09-21T00:05:00.000Z",
  "completed_at": null,
  "stores": {
    "count": 3,
    "pending": 1,
    "running": 1,
    "succeeded": 1,
    "failed": 0
  },
  "deleted": {
    "rows": 120,
    "objects": 2,
    "bytes": 4096
  }
}
```

### Example request

**curl**

```bash
curl "https://collect.noxtica.com/v1/apm/privacy/subjects/example-subjectHandle/erasures/example-requestId" \
  -H "Authorization: Bearer $NOXTICA_API_KEY"
```

**Node.js** (global `fetch`, Node 18 or later)

```js
const response = await fetch('https://collect.noxtica.com/v1/apm/privacy/subjects/example-subjectHandle/erasures/example-requestId', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
  },
});
console.log(response.status, await response.text());
```

**Python** (standard library)

```python
import os
import urllib.request

request = urllib.request.Request(
    "https://collect.noxtica.com/v1/apm/privacy/subjects/example-subjectHandle/erasures/example-requestId",
    headers={
        "Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
    },
    method="GET",
)
with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())
```

## List recent findings for a coding agent {#codingAgentListFindings}

`GET /v1/coding-agent/findings`

Reads recent raw signals for your organisation, narrowed to the scopes the coding-agent key
holds. Results from every scope the key holds are merged: this route has no single required
scope, and a scope narrows what is read instead of denying the call. At the time of writing
the scopes `rum_errors`, `apm_traces`, `apm_errors`, `apm_metrics` and `logs` have a findings
source; the other scopes a key can carry (`profiles`, `fingerprint_bot_signals`,
`kyc_risk_summaries`) add nothing to this route. The `signal` field names the scope a row
came from. An empty array is a normal answer; this route never answers `404`.

`since` defaults to 24 hours ago and `limit` to 50 (maximum 200). Create a coding-agent
key in the Backoffice under Integrations & API, Coding agent keys; keys start with `cak_`.

**Authentication:** Bearer token (`CodingAgentKeyAuth`).

**Base URL:** `https://ingest.noxtica.com` (Production).

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `since` | query | string (date-time) | No | Only signals observed at or after this RFC 3339 instant. Defaults to 24 hours ago. |
| `limit` | query | integer | No | Maximum number of signals to return. Defaults to 50; values above 200 are clamped to 200. Minimum 1, maximum 200. Default `50`. |

### Responses

| Status | Meaning | Error codes | Content |
| --- | --- | --- | --- |
| `200` | A (possibly empty) array of signals. |  | `application/json` |
| `400` | `schema_invalid` — `since` or `limit` could not be parsed. | `schema_invalid` | `application/json` CodingAgentError |
| `401` | `coding_agent_auth_missing` (no `Authorization` header) or `coding_agent_auth_invalid` (a malformed, unknown, revoked or inactive coding-agent key). | `coding_agent_auth_invalid`, `coding_agent_auth_missing` | `application/json` CodingAgentError |
| `429` | `coding_agent_rate_limited` — the per-key request limit was exceeded. Wait `Retry-After` seconds before retrying. | `coding_agent_rate_limited` | `application/json` CodingAgentError |
| `502` | `coding_agent_query_failed` — the read query failed. Retry with backoff. | `coding_agent_query_failed` | `application/json` CodingAgentError |
| `503` | `coding_agent_authority_unavailable` (the key could not be checked) or `coding_agent_bq_unavailable` (the read store is not available). Retry with backoff. | `coding_agent_authority_unavailable`, `coding_agent_bq_unavailable` | `application/json` CodingAgentError |

**Response headers**

| Status | Header | Type | Description |
| --- | --- | --- | --- |
| `429` | `Retry-After` | integer | Seconds until the key may call again. |

**Response fields** (`200`, `application/json`)

| Field | Type | Always returned | Description |
| --- | --- | --- | --- |
| `[]` | object (CodingAgentSignal) | No |  |
| `[].signal` | string | Yes | The key scope the row came from |
| `[].reference_id` | string | Yes | The id of the source record (event |
| `[].excerpt` | string | Yes | A short human-readable summary of the record. |
| `[].observed_at` | string (date-time) | Yes |  |
| `[].metric_value` | number | No | Present when the record carries a numeric value. |

**Response example** (`200`)

```json
[
  {
    "signal": "rum_errors",
    "reference_id": "evt_8f1c2a",
    "excerpt": "error /checkout",
    "observed_at": "2026-10-27T09:58:12Z"
  }
]
```

### Example request

**curl**

```bash
curl "https://ingest.noxtica.com/v1/coding-agent/findings" \
  -H "Authorization: Bearer $NOXTICA_CODING_AGENT_KEY"
```

**Node.js** (global `fetch`, Node 18 or later)

```js
const response = await fetch('https://ingest.noxtica.com/v1/coding-agent/findings', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.NOXTICA_CODING_AGENT_KEY}`,
  },
});
console.log(response.status, await response.text());
```

**Python** (standard library)

```python
import os
import urllib.request

request = urllib.request.Request(
    "https://ingest.noxtica.com/v1/coding-agent/findings",
    headers={
        "Authorization": f"Bearer {os.environ['NOXTICA_CODING_AGENT_KEY']}",
    },
    method="GET",
)
with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())
```

## Read one trace for a coding agent {#codingAgentGetTrace}

`GET /v1/coding-agent/traces/{trace_id}`

Returns the spans of one trace (a narrow projection: attributes, events and links are never
returned) from roughly the last 30 days. Requires the `apm_traces` scope on the key;
without it the route answers `403 coding_agent_scope_denied`. An unknown trace id, or one
that belongs to another organisation, is a normal `200` with an empty `spans` array: this
route never answers `404`.

**Authentication:** Bearer token (`CodingAgentKeyAuth`).

**Base URL:** `https://ingest.noxtica.com` (Production).

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `trace_id` | path | string | Yes | The trace id. |

### Responses

| Status | Meaning | Error codes | Content |
| --- | --- | --- | --- |
| `200` | The trace; `spans` is empty when the id is unknown. |  | `application/json` CodingAgentTrace |
| `401` | `coding_agent_auth_missing` (no `Authorization` header) or `coding_agent_auth_invalid` (a malformed, unknown, revoked or inactive coding-agent key). | `coding_agent_auth_invalid`, `coding_agent_auth_missing` | `application/json` CodingAgentError |
| `403` | `coding_agent_scope_denied` — the key is valid but lacks the scope this route requires. | `coding_agent_scope_denied` | `application/json` CodingAgentError |
| `429` | `coding_agent_rate_limited` — the per-key request limit was exceeded. Wait `Retry-After` seconds before retrying. | `coding_agent_rate_limited` | `application/json` CodingAgentError |
| `502` | `coding_agent_query_failed` — the read query failed. Retry with backoff. | `coding_agent_query_failed` | `application/json` CodingAgentError |
| `503` | `coding_agent_authority_unavailable` (the key could not be checked) or `coding_agent_bq_unavailable` (the read store is not available). Retry with backoff. | `coding_agent_authority_unavailable`, `coding_agent_bq_unavailable` | `application/json` CodingAgentError |

**Response headers**

| Status | Header | Type | Description |
| --- | --- | --- | --- |
| `429` | `Retry-After` | integer | Seconds until the key may call again. |

**Response fields** (`200`, `application/json`)

| Field | Type | Always returned | Description |
| --- | --- | --- | --- |
| `trace_id` | string | Yes |  |
| `spans` | array of CodingAgentSpan | Yes |  |
| `spans[]` | object (CodingAgentSpan) | No |  |
| `spans[].span_id` | string | Yes |  |
| `spans[].parent_span_id` | string | No |  |
| `spans[].service_name` | string | Yes |  |
| `spans[].operation_name` | string | Yes |  |
| `spans[].start_time` | string (date-time) | Yes |  |
| `spans[].end_time` | string (date-time) | Yes |  |
| `spans[].duration_ns` | integer (int64) | Yes |  |
| `spans[].status_code` | string | Yes |  |
| `spans[].status_message` | string | No |  |

**Response example** (`200`)

```json
{
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "spans": [
    {
      "span_id": "00f067aa0ba902b7",
      "service_name": "checkout-api",
      "operation_name": "GET /health",
      "start_time": "2026-10-27T09:58:00Z",
      "end_time": "2026-10-27T09:58:00.010Z",
      "duration_ns": 10000000,
      "status_code": "STATUS_CODE_OK"
    }
  ]
}
```

### Example request

**curl**

```bash
curl "https://ingest.noxtica.com/v1/coding-agent/traces/example-trace_id" \
  -H "Authorization: Bearer $NOXTICA_CODING_AGENT_KEY"
```

**Node.js** (global `fetch`, Node 18 or later)

```js
const response = await fetch('https://ingest.noxtica.com/v1/coding-agent/traces/example-trace_id', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.NOXTICA_CODING_AGENT_KEY}`,
  },
});
console.log(response.status, await response.text());
```

**Python** (standard library)

```python
import os
import urllib.request

request = urllib.request.Request(
    "https://ingest.noxtica.com/v1/coding-agent/traces/example-trace_id",
    headers={
        "Authorization": f"Bearer {os.environ['NOXTICA_CODING_AGENT_KEY']}",
    },
    method="GET",
)
with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())
```

## Read one error group for a coding agent {#codingAgentGetErrorGroup}

`GET /v1/coding-agent/errors/{error_group_id}`

Returns one error group with its aggregate counts and a bounded list of recent occurrences
(a narrow projection: stack frames are never returned) from roughly the last 30 days.
Requires the `apm_errors` scope on the key; without it the route answers
`403 coding_agent_scope_denied`. `404 coding_agent_error_group_not_found` means the group is
unknown or belongs to another organisation; the two cases are indistinguishable by design.

**Authentication:** Bearer token (`CodingAgentKeyAuth`).

**Base URL:** `https://ingest.noxtica.com` (Production).

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `error_group_id` | path | string | Yes | The error group id. |

### Responses

| Status | Meaning | Error codes | Content |
| --- | --- | --- | --- |
| `200` | The error group. |  | `application/json` CodingAgentErrorGroup |
| `401` | `coding_agent_auth_missing` (no `Authorization` header) or `coding_agent_auth_invalid` (a malformed, unknown, revoked or inactive coding-agent key). | `coding_agent_auth_invalid`, `coding_agent_auth_missing` | `application/json` CodingAgentError |
| `403` | `coding_agent_scope_denied` — the key is valid but lacks the scope this route requires. | `coding_agent_scope_denied` | `application/json` CodingAgentError |
| `404` | `coding_agent_error_group_not_found` — the group is unknown or belongs to another organisation. | `coding_agent_error_group_not_found` | `application/json` CodingAgentError |
| `429` | `coding_agent_rate_limited` — the per-key request limit was exceeded. Wait `Retry-After` seconds before retrying. | `coding_agent_rate_limited` | `application/json` CodingAgentError |
| `502` | `coding_agent_query_failed` — the read query failed. Retry with backoff. | `coding_agent_query_failed` | `application/json` CodingAgentError |
| `503` | `coding_agent_authority_unavailable` (the key could not be checked) or `coding_agent_bq_unavailable` (the read store is not available). Retry with backoff. | `coding_agent_authority_unavailable`, `coding_agent_bq_unavailable` | `application/json` CodingAgentError |

**Response headers**

| Status | Header | Type | Description |
| --- | --- | --- | --- |
| `429` | `Retry-After` | integer | Seconds until the key may call again. |

**Response fields** (`200`, `application/json`)

| Field | Type | Always returned | Description |
| --- | --- | --- | --- |
| `error_group_id` | string | Yes |  |
| `first_seen` | string (date-time) | Yes |  |
| `last_seen` | string (date-time) | Yes |  |
| `occurrence_count` | integer (int64) | Yes |  |
| `affected_release_ids` | array of string | Yes |  |
| `occurrences` | array of CodingAgentErrorOccurrence | Yes |  |
| `occurrences[]` | object (CodingAgentErrorOccurrence) | No |  |
| `occurrences[].occurrence_id` | string | Yes |  |
| `occurrences[].exception_type` | string | Yes |  |
| `occurrences[].exception_message` | string | No |  |
| `occurrences[].observed_at` | string (date-time) | Yes |  |
| `occurrences[].release_id` | string | No |  |

**Response example** (`200`)

```json
{
  "error_group_id": "eg_5d41402abc4b2a76",
  "first_seen": "2026-10-26T08:00:00Z",
  "last_seen": "2026-10-27T09:58:12Z",
  "occurrence_count": 42,
  "affected_release_ids": [
    "2026.10.26"
  ],
  "occurrences": [
    {
      "occurrence_id": "occ_91",
      "exception_type": "TypeError",
      "exception_message": "Cannot read properties of undefined",
      "observed_at": "2026-10-27T09:58:12Z",
      "release_id": "2026.10.26"
    }
  ]
}
```

### Example request

**curl**

```bash
curl "https://ingest.noxtica.com/v1/coding-agent/errors/example-error_group_id" \
  -H "Authorization: Bearer $NOXTICA_CODING_AGENT_KEY"
```

**Node.js** (global `fetch`, Node 18 or later)

```js
const response = await fetch('https://ingest.noxtica.com/v1/coding-agent/errors/example-error_group_id', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.NOXTICA_CODING_AGENT_KEY}`,
  },
});
console.log(response.status, await response.text());
```

**Python** (standard library)

```python
import os
import urllib.request

request = urllib.request.Request(
    "https://ingest.noxtica.com/v1/coding-agent/errors/example-error_group_id",
    headers={
        "Authorization": f"Bearer {os.environ['NOXTICA_CODING_AGENT_KEY']}",
    },
    method="GET",
)
with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())
```

_This page is generated from the Noxtica OpenAPI specification release `v0.4.0`: `v1.yaml` version `1.3.0-2026-10-27` (SHA-256 `3fd15257a022`). Do not edit it by hand._
