---
title: "Telemetry ingestion API"
description: "Send OpenTelemetry traces, metrics, logs, profiles and deployment markers with an ingestion key."
canonical_url: "https://noxtica.com/docs/api/reference/ingest/"
markdown_url: "https://noxtica.com/docs/api/reference/ingest/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
---

# Telemetry ingestion API

Send OpenTelemetry traces, metrics, logs, profiles and deployment markers with an ingestion key.

Point any OpenTelemetry SDK or Collector at `https://ingest.noxtica.com` (production) with `OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf` and the header `Authorization=Bearer <ingestion key>`. Ingestion keys (`sk_apm_...`) are created per project and environment under Integrations & API, Ingestion API keys, each with an allowlist of signals; a server API key (`sk_...`) is never accepted here. The same paths are also served by the collector hosts.

Browser real-user-monitoring and session-replay data is sent by the Noxtica browser integration with its own short-lived client tokens, not with an ingestion key, and is not a server API; the host-agent protocol of the Noxtica host agent is likewise internal to the agent.

| Environment | Base URL |
| --- | --- |
| Production ingest host | `https://ingest.noxtica.com` |
| Production collector host (same path and credential) | `https://collect.noxtica.com` |

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

## Ingest an OTLP/HTTP trace export {#v1ApmIngestTraces}

`POST /v1/traces`

OTLP/HTTP trace ingestion. Any OpenTelemetry SDK or Collector OTLP/HTTP exporter can send to it: the body is a proto3-JSON `ExportTraceServiceRequest` (`application/json`) or its protobuf encoding (`application/x-protobuf`), and any other Content-Type is rejected with `415`. `Content-Encoding: gzip` or `deflate` is accepted. The collector hosts reject a wire body above 1 MiB, a decompressed body above 4 MiB, and a request with more than 500 spans, so size your exporter batches accordingly.

Authenticate with an ingestion key (`Authorization: Bearer sk_apm_...`) whose allowed signals include `traces`; a server API key is never accepted.

**Hosts.** Send the request to `https://ingest.noxtica.com` (production): this is the host the Backoffice shows next to an ingestion key. The same path and credential are also served by the collector hosts listed under `servers`.

Per-key and per-project rate limits apply; a limited request answers `429` with a `Retry-After` header. The limit values differ between host implementations and are not part of this contract.

**Errors.** Error bodies are JSON. The collector hosts answer a `google.rpc.Status`-shaped body (`code` is the gRPC status code and `details[0].reason` the reason); the dedicated ingest hosts answer `{"code": <HTTP status>, "message": "...", "error": "<reason>"}`. The reason strings (for example `apm_auth_invalid`, `apm_rate_limited`) are the same on both, so branch on the HTTP status and the reason, never on `code`. Success bodies follow the OTLP wire contract and use the request's own media type.

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

**Base URL:** `https://ingest.noxtica.com` (Production ingest host), `https://collect.noxtica.com` (Production collector host (same path and credential)).

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Content-Encoding` | header | string | No | Defaults to `identity` (uncompressed) when absent. Any other value is rejected with 415. Allowed values: `identity`, `gzip`, `deflate`. |

### Request body

Content type `application/json`, required.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `resourceSpans` | array of OtlpResourceSpans | No |  |
| `resourceSpans[]` | object (OtlpResourceSpans) | No |  |
| `resourceSpans[].resource` | object | No |  |
| `resourceSpans[].resource.attributes` | array of OtlpKeyValue | No |  |
| `resourceSpans[].resource.attributes[]` | object (OtlpKeyValue) | No |  |
| `resourceSpans[].resource.attributes[].key` | string | No |  |
| `resourceSpans[].resource.attributes[].value` | object (OtlpAnyValue) | No | Proto3-JSON mapping of `opentelemetry.proto.common.v1.AnyValue` — exactly one property is present per the proto3 JSON oneof convention. |
| `resourceSpans[].resource.droppedAttributesCount` | integer | No | Minimum 0. |
| `resourceSpans[].scopeSpans` | array of OtlpScopeSpans | No |  |
| `resourceSpans[].scopeSpans[]` | object (OtlpScopeSpans) | No |  |
| `resourceSpans[].scopeSpans[].scope` | object | No |  |
| `resourceSpans[].scopeSpans[].scope.name` | string | No |  |
| `resourceSpans[].scopeSpans[].scope.version` | string | No |  |
| `resourceSpans[].scopeSpans[].scope.attributes` | array of OtlpKeyValue | No |  |
| `resourceSpans[].scopeSpans[].scope.droppedAttributesCount` | integer | No | Minimum 0. |
| `resourceSpans[].scopeSpans[].spans` | array of OtlpSpan | No |  |
| `resourceSpans[].scopeSpans[].spans[]` | object (OtlpSpan) | No | A single decoded span may still be dropped during post-decode validation — see `partialSuccess` on `OtlpTraceIngestResult` — so no property is `required` here; the wire format itself never rejects a well-formed but incomplete span. |
| `resourceSpans[].scopeSpans[].schemaUrl` | string | No |  |
| `resourceSpans[].schemaUrl` | string | No |  |

Content type `application/x-protobuf`, required.

Type: string (binary).

### Responses

| Status | Meaning | Error codes | Content |
| --- | --- | --- | --- |
| `200` | Export accepted. An empty object means every span was accepted; `partialSuccess` reports spans dropped during validation without failing the whole request. |  | `application/json` OtlpTraceIngestResult |
| `400` | `apm_malformed_otlp_json`/`apm_malformed_otlp_protobuf` — the body does not decode as a valid `ExportTraceServiceRequest` in its declared Content-Type. | `apm_malformed_otlp_json` | `application/json` OtlpStatus |
| `401` | Missing or invalid `sk_apm_` bearer credential, or the credential's `effective_at`/`expires_at` window excludes now. A tenant `ApiKeyAuth` `sk_` key is never accepted here. |  | `application/json` OtlpStatus |
| `403` | The credential is active but not authorized for trace ingest. |  | `application/json` OtlpStatus |
| `413` | The wire or decompressed body exceeds its byte cap, or the request carries more than 500 spans. |  | `application/json` OtlpStatus |
| `415` | Content-Type is not `application/json`, or Content-Encoding is set to a value other than `identity`, `gzip`, or `deflate`. |  | `application/json` OtlpStatus |
| `429` | Trace ingest rate limit exceeded (600 requests / 60s per `tenantId:projectId`). |  | `application/json` OtlpStatus |
| `503` | The tenant authority scope could not be confirmed, or the durable ingest queue is unavailable. |  | `application/json` OtlpStatus |

**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. |
| `401` | `WWW-Authenticate` | string | On the collector hosts always `Bearer realm="noxtica-otlp", charset="UTF-8"`; the ingest hosts do not send it. |
| `403` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `413` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `415` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `429` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `429` | `Retry-After` | integer | Seconds until the bucket allows another request. |
| `503` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `503` | `Retry-After` | integer | Always `5` on this status. |

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

| Field | Type | Always returned | Description |
| --- | --- | --- | --- |
| `partialSuccess` | object | No |  |
| `partialSuccess.rejectedSpans` | string | Yes | Decimal count of rejected spans |
| `partialSuccess.errorMessage` | string | No |  |

**Response example** (`200`)

```json
{}
```

### Example request

**curl**

```bash
curl -X POST "https://ingest.noxtica.com/v1/traces" \
  -H "Authorization: Bearer $NOXTICA_INGEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "resourceSpans": [
    {
      "resource": {
        "attributes": [
          {
            "key": "service.name",
            "value": {
              "stringValue": "checkout-api"
            }
          }
        ]
      },
      "scopeSpans": [
        {
          "scope": {
            "name": "@noxtica/otel-shim",
            "version": "1.0.0"
          },
          "spans": [
            {
              "traceId": "AAAAAAAAAAAAAAAAAAAAAA==",
              "spanId": "AAAAAAAAAAA=",
              "name": "GET /health",
              "kind": "SPAN_KIND_SERVER",
              "startTimeUnixNano": "1700000000000000000",
              "endTimeUnixNano": "1700000000010000000",
              "status": {
                "code": "STATUS_CODE_OK"
              }
            }
          ]
        }
      ]
    }
  ]
}'
```

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

```js
const response = await fetch('https://ingest.noxtica.com/v1/traces', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.NOXTICA_INGEST_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    resourceSpans: [
      {
        resource: {
          attributes: [
            {
              key: 'service.name',
              value: {
                stringValue: 'checkout-api',
              },
            },
          ],
        },
        scopeSpans: [
          {
            scope: {
              name: '@noxtica/otel-shim',
              version: '1.0.0',
            },
            spans: [
              {
                traceId: 'AAAAAAAAAAAAAAAAAAAAAA==',
                spanId: 'AAAAAAAAAAA=',
                name: 'GET /health',
                kind: 'SPAN_KIND_SERVER',
                startTimeUnixNano: '1700000000000000000',
                endTimeUnixNano: '1700000000010000000',
                status: {
                  code: 'STATUS_CODE_OK',
                },
              },
            ],
          },
        ],
      },
    ],
  }),
});
console.log(response.status, await response.text());
```

**Python** (standard library)

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

request = urllib.request.Request(
    "https://ingest.noxtica.com/v1/traces",
    data=json.dumps({
        "resourceSpans": [
            {
                "resource": {
                    "attributes": [
                        {
                            "key": "service.name",
                            "value": {
                                "stringValue": "checkout-api",
                            },
                        },
                    ],
                },
                "scopeSpans": [
                    {
                        "scope": {
                            "name": "@noxtica/otel-shim",
                            "version": "1.0.0",
                        },
                        "spans": [
                            {
                                "traceId": "AAAAAAAAAAAAAAAAAAAAAA==",
                                "spanId": "AAAAAAAAAAA=",
                                "name": "GET /health",
                                "kind": "SPAN_KIND_SERVER",
                                "startTimeUnixNano": "1700000000000000000",
                                "endTimeUnixNano": "1700000000010000000",
                                "status": {
                                    "code": "STATUS_CODE_OK",
                                },
                            },
                        ],
                    },
                ],
            },
        ],
    }).encode(),
    headers={
        "Authorization": f"Bearer {os.environ['NOXTICA_INGEST_KEY']}",
        "Content-Type": "application/json",
    },
    method="POST",
)
with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())
```

## Ingest an OTLP/HTTP metrics export {#v1ApmIngestMetrics}

`POST /v1/metrics`

OTLP/HTTP metrics ingestion: a proto3-JSON `ExportMetricsServiceRequest` (`application/json`) or its protobuf encoding (`application/x-protobuf`); any other Content-Type is rejected with `415`. `Content-Encoding: gzip` or `deflate` is accepted. The collector hosts reject a wire body above 1 MiB, a decompressed body above 4 MiB, and a request with more than 500 data points across every metric's `gauge`, `sum`, `histogram`, `exponentialHistogram` and `summary` arrays.

Authenticate with an ingestion key (`Authorization: Bearer sk_apm_...`) whose allowed signals include `metrics`.

**Hosts.** Send the request to `https://ingest.noxtica.com` (production): this is the host the Backoffice shows next to an ingestion key. The same path and credential are also served by the collector hosts listed under `servers`.

Per-key and per-project rate limits apply; a limited request answers `429` with a `Retry-After` header. The limit values differ between host implementations and are not part of this contract.

**Errors.** Error bodies are JSON. The collector hosts answer a `google.rpc.Status`-shaped body (`code` is the gRPC status code and `details[0].reason` the reason); the dedicated ingest hosts answer `{"code": <HTTP status>, "message": "...", "error": "<reason>"}`. The reason strings (for example `apm_auth_invalid`, `apm_rate_limited`) are the same on both, so branch on the HTTP status and the reason, never on `code`. Success bodies follow the OTLP wire contract and use the request's own media type.

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

**Base URL:** `https://ingest.noxtica.com` (Production ingest host), `https://collect.noxtica.com` (Production collector host (same path and credential)).

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Content-Encoding` | header | string | No | Defaults to `identity` (uncompressed) when absent. Any other value is rejected with 415. Allowed values: `identity`, `gzip`, `deflate`. |

### Request body

Content type `application/json`, required.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `resourceMetrics` | array of object | No |  |
| `resourceMetrics[]` | object | No | One proto3-JSON `ResourceMetrics` entry (`resource`, `scopeMetrics[]`, `schemaUrl`) — see the OTLP metrics proto3-JSON schema for the full nested shape. |

Content type `application/x-protobuf`, required.

Type: string (binary).

### Responses

| Status | Meaning | Error codes | Content |
| --- | --- | --- | --- |
| `200` | Export accepted. An empty object means every data point was accepted; `partialSuccess` reports data points dropped during validation without failing the whole request. |  | `application/json` OtlpMetricsIngestResult |
| `400` | `apm_malformed_metrics_payload` — the body does not decode as a valid `ExportMetricsServiceRequest` in its declared Content-Type. | `apm_malformed_metrics_payload` | `application/json` OtlpStatus |
| `401` | Missing or invalid `sk_apm_` bearer credential, or the credential's `effective_at`/`expires_at` window excludes now. A tenant `ApiKeyAuth` `sk_` key is never accepted here. |  | `application/json` OtlpStatus |
| `403` | The credential is active but not authorized for metrics ingest. |  | `application/json` OtlpStatus |
| `413` | The wire or decompressed body exceeds its byte cap, or the request carries more than 500 data points. |  | `application/json` OtlpStatus |
| `415` | Content-Type is not `application/json` or `application/x-protobuf`, or Content-Encoding is set to a value other than `identity`, `gzip`, or `deflate`. |  | `application/json` OtlpStatus |
| `429` | Metrics ingest rate limit exceeded (600 requests / 60s per `tenantId:projectId`). |  | `application/json` OtlpStatus |
| `503` | The tenant authority scope could not be confirmed, or the durable ingest queue is unavailable. |  | `application/json` OtlpStatus |

**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. |
| `401` | `WWW-Authenticate` | string | On the collector hosts always `Bearer realm="noxtica-otlp", charset="UTF-8"`; the ingest hosts do not send it. |
| `403` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `413` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `415` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `429` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `429` | `Retry-After` | integer | Seconds until the bucket allows another request. |
| `503` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `503` | `Retry-After` | integer | Always `5` on this status. |

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

| Field | Type | Always returned | Description |
| --- | --- | --- | --- |
| `partialSuccess` | object | No |  |
| `partialSuccess.rejectedDataPoints` | string | Yes | Decimal count of rejected data points |
| `partialSuccess.errorMessage` | string | No |  |

**Response example** (`200`)

```json
{}
```

### Example request

**curl**

```bash
curl -X POST "https://ingest.noxtica.com/v1/metrics" \
  -H "Authorization: Bearer $NOXTICA_INGEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "resourceMetrics": [
    {
      "resource": {
        "attributes": [
          {
            "key": "service.name",
            "value": {
              "stringValue": "checkout-api"
            }
          }
        ]
      },
      "scopeMetrics": [
        {
          "scope": {
            "name": "@noxtica/otel-shim",
            "version": "1.0.0"
          },
          "metrics": [
            {
              "name": "http.server.duration",
              "unit": "ms",
              "gauge": {
                "dataPoints": [
                  {
                    "timeUnixNano": "1700000000000000000",
                    "asDouble": 12.5
                  }
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}'
```

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

```js
const response = await fetch('https://ingest.noxtica.com/v1/metrics', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.NOXTICA_INGEST_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    resourceMetrics: [
      {
        resource: {
          attributes: [
            {
              key: 'service.name',
              value: {
                stringValue: 'checkout-api',
              },
            },
          ],
        },
        scopeMetrics: [
          {
            scope: {
              name: '@noxtica/otel-shim',
              version: '1.0.0',
            },
            metrics: [
              {
                name: 'http.server.duration',
                unit: 'ms',
                gauge: {
                  dataPoints: [
                    {
                      timeUnixNano: '1700000000000000000',
                      asDouble: 12.5,
                    },
                  ],
                },
              },
            ],
          },
        ],
      },
    ],
  }),
});
console.log(response.status, await response.text());
```

**Python** (standard library)

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

request = urllib.request.Request(
    "https://ingest.noxtica.com/v1/metrics",
    data=json.dumps({
        "resourceMetrics": [
            {
                "resource": {
                    "attributes": [
                        {
                            "key": "service.name",
                            "value": {
                                "stringValue": "checkout-api",
                            },
                        },
                    ],
                },
                "scopeMetrics": [
                    {
                        "scope": {
                            "name": "@noxtica/otel-shim",
                            "version": "1.0.0",
                        },
                        "metrics": [
                            {
                                "name": "http.server.duration",
                                "unit": "ms",
                                "gauge": {
                                    "dataPoints": [
                                        {
                                            "timeUnixNano": "1700000000000000000",
                                            "asDouble": 12.5,
                                        },
                                    ],
                                },
                            },
                        ],
                    },
                ],
            },
        ],
    }).encode(),
    headers={
        "Authorization": f"Bearer {os.environ['NOXTICA_INGEST_KEY']}",
        "Content-Type": "application/json",
    },
    method="POST",
)
with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())
```

## Ingest an OTLP/HTTP logs export {#v1ApmIngestLogs}

`POST /v1/logs`

OTLP/HTTP logs ingestion: a proto3-JSON `ExportLogsServiceRequest` (`application/json`) or its protobuf encoding (`application/x-protobuf`); any other Content-Type is rejected with `415`. `Content-Encoding: gzip` or `deflate` is accepted. The collector hosts reject a wire body above 1 MiB, a decompressed body above 4 MiB, and a request with more than 500 log records across every `resourceLogs[].scopeLogs[].logRecords[]`.

Authenticate with an ingestion key (`Authorization: Bearer sk_apm_...`) whose allowed signals include `logs`.

**Hosts.** Send the request to `https://ingest.noxtica.com` (production): this is the host the Backoffice shows next to an ingestion key. The same path and credential are also served by the collector hosts listed under `servers`.

Per-key and per-project rate limits apply; a limited request answers `429` with a `Retry-After` header. The limit values differ between host implementations and are not part of this contract.

**Errors.** Error bodies are JSON. The collector hosts answer a `google.rpc.Status`-shaped body (`code` is the gRPC status code and `details[0].reason` the reason); the dedicated ingest hosts answer `{"code": <HTTP status>, "message": "...", "error": "<reason>"}`. The reason strings (for example `apm_auth_invalid`, `apm_rate_limited`) are the same on both, so branch on the HTTP status and the reason, never on `code`. Success bodies follow the OTLP wire contract and use the request's own media type.

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

**Base URL:** `https://ingest.noxtica.com` (Production ingest host), `https://collect.noxtica.com` (Production collector host (same path and credential)).

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Content-Encoding` | header | string | No | Defaults to `identity` (uncompressed) when absent. Any other value is rejected with 415. Allowed values: `identity`, `gzip`, `deflate`. |

### Request body

Content type `application/json`, required.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `resourceLogs` | array of object | No |  |
| `resourceLogs[]` | object | No | One proto3-JSON `ResourceLogs` entry (`resource`, `scopeLogs[]`, `schemaUrl`) — see the OTLP logs proto3-JSON schema for the full nested shape. |

Content type `application/x-protobuf`, required.

Type: string (binary).

### Responses

| Status | Meaning | Error codes | Content |
| --- | --- | --- | --- |
| `200` | Export accepted. An empty object means every log record was accepted; `partialSuccess` reports log records dropped during validation without failing the whole request. |  | `application/json` OtlpLogsIngestResult |
| `400` | `apm_malformed_logs_payload` — the body does not decode as a valid `ExportLogsServiceRequest` in its declared Content-Type. | `apm_malformed_logs_payload` | `application/json` OtlpStatus |
| `401` | Missing or invalid `sk_apm_` bearer credential, or the credential's `effective_at`/`expires_at` window excludes now. A tenant `ApiKeyAuth` `sk_` key is never accepted here. |  | `application/json` OtlpStatus |
| `403` | The credential is active but not authorized for logs ingest. |  | `application/json` OtlpStatus |
| `413` | The wire or decompressed body exceeds its byte cap, or the request carries more than 500 log records. |  | `application/json` OtlpStatus |
| `415` | Content-Type is not `application/json` or `application/x-protobuf`, or Content-Encoding is set to a value other than `identity`, `gzip`, or `deflate`. |  | `application/json` OtlpStatus |
| `429` | Logs ingest rate limit exceeded (600 requests / 60s per `tenantId:projectId`). |  | `application/json` OtlpStatus |
| `503` | The tenant authority scope could not be confirmed, or the durable ingest queue is unavailable. |  | `application/json` OtlpStatus |

**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. |
| `401` | `WWW-Authenticate` | string | On the collector hosts always `Bearer realm="noxtica-otlp", charset="UTF-8"`; the ingest hosts do not send it. |
| `403` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `413` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `415` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `429` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `429` | `Retry-After` | integer | Seconds until the bucket allows another request. |
| `503` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `503` | `Retry-After` | integer | Always `5` on this status. |

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

| Field | Type | Always returned | Description |
| --- | --- | --- | --- |
| `partialSuccess` | object | No |  |
| `partialSuccess.rejectedLogRecords` | string | Yes | Decimal count of rejected log records |
| `partialSuccess.errorMessage` | string | No |  |

**Response example** (`200`)

```json
{}
```

### Example request

**curl**

```bash
curl -X POST "https://ingest.noxtica.com/v1/logs" \
  -H "Authorization: Bearer $NOXTICA_INGEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "resourceLogs": [
    {
      "resource": {
        "attributes": [
          {
            "key": "service.name",
            "value": {
              "stringValue": "checkout-api"
            }
          }
        ]
      },
      "scopeLogs": [
        {
          "scope": {
            "name": "@noxtica/otel-shim",
            "version": "1.0.0"
          },
          "logRecords": [
            {
              "timeUnixNano": "1700000000000000000",
              "severityNumber": 9,
              "severityText": "INFO",
              "body": {
                "stringValue": "order placed"
              }
            }
          ]
        }
      ]
    }
  ]
}'
```

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

```js
const response = await fetch('https://ingest.noxtica.com/v1/logs', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.NOXTICA_INGEST_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    resourceLogs: [
      {
        resource: {
          attributes: [
            {
              key: 'service.name',
              value: {
                stringValue: 'checkout-api',
              },
            },
          ],
        },
        scopeLogs: [
          {
            scope: {
              name: '@noxtica/otel-shim',
              version: '1.0.0',
            },
            logRecords: [
              {
                timeUnixNano: '1700000000000000000',
                severityNumber: 9,
                severityText: 'INFO',
                body: {
                  stringValue: 'order placed',
                },
              },
            ],
          },
        ],
      },
    ],
  }),
});
console.log(response.status, await response.text());
```

**Python** (standard library)

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

request = urllib.request.Request(
    "https://ingest.noxtica.com/v1/logs",
    data=json.dumps({
        "resourceLogs": [
            {
                "resource": {
                    "attributes": [
                        {
                            "key": "service.name",
                            "value": {
                                "stringValue": "checkout-api",
                            },
                        },
                    ],
                },
                "scopeLogs": [
                    {
                        "scope": {
                            "name": "@noxtica/otel-shim",
                            "version": "1.0.0",
                        },
                        "logRecords": [
                            {
                                "timeUnixNano": "1700000000000000000",
                                "severityNumber": 9,
                                "severityText": "INFO",
                                "body": {
                                    "stringValue": "order placed",
                                },
                            },
                        ],
                    },
                ],
            },
        ],
    }).encode(),
    headers={
        "Authorization": f"Bearer {os.environ['NOXTICA_INGEST_KEY']}",
        "Content-Type": "application/json",
    },
    method="POST",
)
with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())
```

## Ingest an OTLP/HTTP JSON profiles export {#v1ApmIngestProfiles}

`POST /v1/profiles`

OTLP/HTTP JSON profiles ingestion, a proto3-JSON `ProfilesData` (`opentelemetry.proto.profiles.v1development`; profiling is still an OpenTelemetry development signal). Unlike traces, metrics and logs, `application/json` is the only media type on the collector hosts: protobuf is rejected with `415`. `Content-Encoding: gzip` or `deflate` is accepted. The collector hosts reject a wire body above 2 MiB, a decompressed body above 8 MiB, and a request with more than 20 profiles across every `resourceProfiles[].scopeProfiles[].profiles[]`. The dedicated ingest hosts additionally accept the multipart upload format sent by the Noxtica host agent; that format is internal to the agent and not part of this contract.

Each accepted profile's raw bytes are stored before the row is queued, so if profile storage is unavailable the whole request fails with `503 apm_profile_storage_unavailable` even though authentication and validation succeeded.

Authenticate with an ingestion key (`Authorization: Bearer sk_apm_...`) whose allowed signals include `profiles`.

**Hosts.** Send the request to `https://ingest.noxtica.com` (production): this is the host the Backoffice shows next to an ingestion key. The same path and credential are also served by the collector hosts listed under `servers`.

Per-key and per-project rate limits apply; a limited request answers `429` with a `Retry-After` header. The limit values differ between host implementations and are not part of this contract.

**Errors.** Error bodies are JSON. The collector hosts answer a `google.rpc.Status`-shaped body (`code` is the gRPC status code and `details[0].reason` the reason); the dedicated ingest hosts answer `{"code": <HTTP status>, "message": "...", "error": "<reason>"}`. The reason strings (for example `apm_auth_invalid`, `apm_rate_limited`) are the same on both, so branch on the HTTP status and the reason, never on `code`. Success bodies follow the OTLP wire contract and use the request's own media type.

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

**Base URL:** `https://ingest.noxtica.com` (Production ingest host), `https://collect.noxtica.com` (Production collector host (same path and credential)).

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Content-Encoding` | header | string | No | Defaults to `identity` (uncompressed) when absent. Any other value is rejected with 415. Allowed values: `identity`, `gzip`, `deflate`. |

### Request body

Content type `application/json`, required.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `resourceProfiles` | array of object | No |  |
| `resourceProfiles[]` | object | No | One proto3-JSON `ResourceProfiles` entry (`resource`, `scopeProfiles[]`) — see the OTLP profiles proto3-JSON schema for the full nested shape. |

### Responses

| Status | Meaning | Error codes | Content |
| --- | --- | --- | --- |
| `200` | Export accepted. An empty object means every profile was accepted and persisted; `partialSuccess` reports profiles dropped during validation, or stored with an unrecognized `profileType`, without failing the whole request. |  | `application/json` OtlpProfilesIngestResult |
| `400` | `apm_malformed_profile_json` — the body is not a valid proto3-JSON `ProfilesData`. | `apm_malformed_profile_json` | `application/json` OtlpStatus |
| `401` | Missing or invalid `sk_apm_` bearer credential, or the credential's `effective_at`/`expires_at` window excludes now. A tenant `ApiKeyAuth` `sk_` key is never accepted here. |  | `application/json` OtlpStatus |
| `403` | The credential is active but not authorized for profile ingest. |  | `application/json` OtlpStatus |
| `413` | The wire or decompressed body exceeds its byte cap, or the request carries more than 20 profiles. |  | `application/json` OtlpStatus |
| `415` | Content-Type is not `application/json` (protobuf is never accepted on this route), or Content-Encoding is set to a value other than `identity`, `gzip`, or `deflate`. |  | `application/json` OtlpStatus |
| `429` | Profile ingest rate limit exceeded (600 requests / 60s per `tenantId:projectId`). |  | `application/json` OtlpStatus |
| `503` | The tenant authority scope could not be confirmed, the profile object store is unavailable, or the durable ingest queue is unavailable. | `apm_profile_storage_unavailable` | `application/json` OtlpStatus |

**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. |
| `401` | `WWW-Authenticate` | string | On the collector hosts always `Bearer realm="noxtica-otlp", charset="UTF-8"`; the ingest hosts do not send it. |
| `403` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `413` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `415` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `429` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `429` | `Retry-After` | integer | Seconds until the bucket allows another request. |
| `503` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `503` | `Retry-After` | integer | Always `5` on this status. |

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

| Field | Type | Always returned | Description |
| --- | --- | --- | --- |
| `partialSuccess` | object | No |  |
| `partialSuccess.rejectedProfiles` | string | Yes | Decimal count of rejected profiles |
| `partialSuccess.errorMessage` | string | No |  |

**Response example** (`200`)

```json
{}
```

### Example request

**curl**

```bash
curl -X POST "https://ingest.noxtica.com/v1/profiles" \
  -H "Authorization: Bearer $NOXTICA_INGEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "resourceProfiles": [
    {
      "resource": {
        "attributes": [
          {
            "key": "service.name",
            "value": {
              "stringValue": "checkout-api"
            }
          }
        ]
      },
      "scopeProfiles": [
        {
          "scope": {
            "name": "@noxtica/otel-shim",
            "version": "1.0.0"
          },
          "profiles": [
            {
              "profileId": "AAAAAAAAAAAAAAAAAAAAAA==",
              "startTimeUnixNano": "1700000000000000000",
              "endTimeUnixNano": "1700000001000000000"
            }
          ]
        }
      ]
    }
  ]
}'
```

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

```js
const response = await fetch('https://ingest.noxtica.com/v1/profiles', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.NOXTICA_INGEST_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    resourceProfiles: [
      {
        resource: {
          attributes: [
            {
              key: 'service.name',
              value: {
                stringValue: 'checkout-api',
              },
            },
          ],
        },
        scopeProfiles: [
          {
            scope: {
              name: '@noxtica/otel-shim',
              version: '1.0.0',
            },
            profiles: [
              {
                profileId: 'AAAAAAAAAAAAAAAAAAAAAA==',
                startTimeUnixNano: '1700000000000000000',
                endTimeUnixNano: '1700000001000000000',
              },
            ],
          },
        ],
      },
    ],
  }),
});
console.log(response.status, await response.text());
```

**Python** (standard library)

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

request = urllib.request.Request(
    "https://ingest.noxtica.com/v1/profiles",
    data=json.dumps({
        "resourceProfiles": [
            {
                "resource": {
                    "attributes": [
                        {
                            "key": "service.name",
                            "value": {
                                "stringValue": "checkout-api",
                            },
                        },
                    ],
                },
                "scopeProfiles": [
                    {
                        "scope": {
                            "name": "@noxtica/otel-shim",
                            "version": "1.0.0",
                        },
                        "profiles": [
                            {
                                "profileId": "AAAAAAAAAAAAAAAAAAAAAA==",
                                "startTimeUnixNano": "1700000000000000000",
                                "endTimeUnixNano": "1700000001000000000",
                            },
                        ],
                    },
                ],
            },
        ],
    }).encode(),
    headers={
        "Authorization": f"Bearer {os.environ['NOXTICA_INGEST_KEY']}",
        "Content-Type": "application/json",
    },
    method="POST",
)
with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())
```

## Ingest a deployment marker {#v1ApmIngestDeploymentMarker}

`POST /v1/deployments`

Records one service deployment (a release marker) as a small single-row JSON payload. It is not an OTLP batch, so its request and response shapes are its own rather than the OTLP contract used by traces, metrics, logs and profiles. Only `application/json` is accepted (otherwise `415`), compression is not supported, and the request body is limited to 16 KiB.

`environment` is supplied by the caller but must equal the `environmentId` the ingestion key is bound to: the key's organisation, project and environment decide where a marker lands, and a mismatched value is rejected with `403 apm_environment_mismatch` instead of redirecting the marker. `version` is also the release id.

Authenticate with an ingestion key (`Authorization: Bearer sk_apm_...`) whose allowed signals include `deployments`.

**Hosts.** Send the request to `https://ingest.noxtica.com` (production): this is the host the Backoffice shows next to an ingestion key. The same path and credential are also served by the collector hosts listed under `servers`.

Per-key and per-project rate limits apply; a limited request answers `429` with a `Retry-After` header. The limit values differ between host implementations and are not part of this contract.

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

**Base URL:** `https://ingest.noxtica.com` (Production ingest host), `https://collect.noxtica.com` (Production collector host (same path and credential)).

### Request body

Content type `application/json`, required.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `service` | string | Yes | Pattern `^[A-Za-z0-9._:/-]{1,128}$`. |
| `version` | string | Yes | Also becomes the canonical spine's `release_id` — `release_id` equals the request `version` Pattern `^[A-Za-z0-9_.:/-]{1,128}$`. |
| `environment` | string | Yes | MUST equal the authenticated credential's own `environmentId` scope — a mismatch is rejected with 403 `apm_environment_mismatch` rather than redirecting the marker. Pattern `^[A-Za-z0-9._:-]{1,256}$`. |
| `commit` | string, nullable | No | Git commit SHA Pattern `^[0-9a-f]{7,64}$`. |
| `timestamp` | string (date-time) | No | Defaults to the server's receipt time if omitted. MUST be less than or equal to the receipt time — a deployment marker can never be timestamped in the future. |

### Responses

| Status | Meaning | Error codes | Content |
| --- | --- | --- | --- |
| `202` | Deployment marker accepted and queued to the warehouse. |  | `application/json` ApmDeploymentMarkerAccepted |
| `400` | `apm_malformed_json` — the body is not valid JSON; or `apm_invalid_payload` — a required field is missing, an unknown field is present, or a field fails its own pattern/format check (e.g. `commit` not a 7-64 char lowercase-hex SHA, or `timestamp` after the receipt time). | `apm_invalid_payload`, `apm_malformed_json` | `application/json` ApmDeploymentError |
| `401` | Missing or invalid `sk_apm_` bearer credential, or the credential's `effective_at`/`expires_at` window excludes now. A tenant `ApiKeyAuth` `sk_` key is never accepted here. | `apm_auth_expired` | `application/json` ApmDeploymentError |
| `403` | The credential is active but not authorized for deployment marker ingest, or the request's `environment` does not match the credential's own `environmentId` scope. | `apm_environment_mismatch` | `application/json` ApmDeploymentError |
| `413` | The request body exceeds the 16 KiB cap. | `apm_body_too_large` | `application/json` ApmDeploymentError |
| `415` | Content-Type is not `application/json`. | `apm_unsupported_media_type` | `application/json` ApmDeploymentError |
| `429` | Deployment marker rate limit exceeded (120 requests / 60s per `tenantId:projectId`). | `apm_rate_limited` | `application/json` ApmDeploymentError |
| `503` | The tenant authority scope could not be confirmed, or the durable ingest queue is unavailable. | `apm_authority_unavailable` | `application/json` ApmDeploymentError |

**Response headers**

| Status | Header | Type | Description |
| --- | --- | --- | --- |
| `202` | `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. |
| `401` | `WWW-Authenticate` | string | On the collector hosts always `Bearer realm="noxtica-otlp", charset="UTF-8"`; the ingest hosts do not send it. |
| `403` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `413` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `415` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `429` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `429` | `Retry-After` | integer | Seconds until the bucket allows another request. |
| `503` | `Cache-Control` | const | APM control/query responses are never cacheable. |
| `503` | `Retry-After` | integer | Always `5` on this status. |

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

| Field | Type | Always returned | Description |
| --- | --- | --- | --- |
| `ok` | const | Yes | Always `true`. |
| `deployment_id` | string | Yes | Server-generated `dep_<uuid>` identifier. |
| `service` | string | Yes |  |
| `version` | string | Yes |  |
| `environment` | string | Yes |  |
| `commit` | string, nullable | Yes |  |
| `timestamp` | string (date-time) | Yes |  |

**Response example** (`202`)

```json
{
  "ok": true,
  "deployment_id": "dep-test",
  "service": "checkout-api",
  "version": "2026.09.25-1",
  "environment": "prod",
  "commit": "4f3c2a1",
  "timestamp": "2026-09-25T00:00:00.000Z"
}
```

### Example request

**curl**

```bash
curl -X POST "https://ingest.noxtica.com/v1/deployments" \
  -H "Authorization: Bearer $NOXTICA_INGEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "service": "checkout-api",
  "version": "2026.09.25-1",
  "environment": "prod",
  "commit": "4f3c2a1",
  "timestamp": "2026-09-25T00:00:00.000Z"
}'
```

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

```js
const response = await fetch('https://ingest.noxtica.com/v1/deployments', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.NOXTICA_INGEST_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    service: 'checkout-api',
    version: '2026.09.25-1',
    environment: 'prod',
    commit: '4f3c2a1',
    timestamp: '2026-09-25T00:00:00.000Z',
  }),
});
console.log(response.status, await response.text());
```

**Python** (standard library)

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

request = urllib.request.Request(
    "https://ingest.noxtica.com/v1/deployments",
    data=json.dumps({
        "service": "checkout-api",
        "version": "2026.09.25-1",
        "environment": "prod",
        "commit": "4f3c2a1",
        "timestamp": "2026-09-25T00:00:00.000Z",
    }).encode(),
    headers={
        "Authorization": f"Bearer {os.environ['NOXTICA_INGEST_KEY']}",
        "Content-Type": "application/json",
    },
    method="POST",
)
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._
