On this page

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.

EnvironmentBase URL
Production ingest hosthttps://ingest.noxtica.com
Production collector host (same path and credential)https://collect.noxtica.com

Credential: ApmIngestKeyAuth. See Authentication.

How this page is organisedBar length shows each section’s share of the text. Select a section to jump to it.

APM Ingest

5 operations

Operations in Telemetry ingestion API

Ingest an OTLP/HTTP trace export

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

NameInTypeRequiredDescription
Content-EncodingheaderstringNoDefaults to identity (uncompressed) when absent. Any other value is rejected with 415. Allowed values: identity, gzip, deflate.

Request body

Content type application/json, required.

FieldTypeRequiredDescription
resourceSpansarray of OtlpResourceSpansNo
resourceSpans[]object (OtlpResourceSpans)No
resourceSpans[].resourceobjectNo
resourceSpans[].resource.attributesarray of OtlpKeyValueNo
resourceSpans[].resource.attributes[]object (OtlpKeyValue)No
resourceSpans[].resource.attributes[].keystringNo
resourceSpans[].resource.attributes[].valueobject (OtlpAnyValue)NoProto3-JSON mapping of opentelemetry.proto.common.v1.AnyValue — exactly one property is present per the proto3 JSON oneof convention.
resourceSpans[].resource.droppedAttributesCountintegerNoMinimum 0.
resourceSpans[].scopeSpansarray of OtlpScopeSpansNo
resourceSpans[].scopeSpans[]object (OtlpScopeSpans)No
resourceSpans[].scopeSpans[].scopeobjectNo
resourceSpans[].scopeSpans[].scope.namestringNo
resourceSpans[].scopeSpans[].scope.versionstringNo
resourceSpans[].scopeSpans[].scope.attributesarray of OtlpKeyValueNo
resourceSpans[].scopeSpans[].scope.droppedAttributesCountintegerNoMinimum 0.
resourceSpans[].scopeSpans[].spansarray of OtlpSpanNo
resourceSpans[].scopeSpans[].spans[]object (OtlpSpan)NoA 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[].schemaUrlstringNo
resourceSpans[].schemaUrlstringNo

Content type application/x-protobuf, required.

Type: string (binary).

Responses

StatusMeaningError codesContent
200Export accepted. An empty object means every span was accepted; partialSuccess reports spans dropped during validation without failing the whole request.application/json OtlpTraceIngestResult
400apm_malformed_otlp_json/apm_malformed_otlp_protobuf — the body does not decode as a valid ExportTraceServiceRequest in its declared Content-Type.apm_malformed_otlp_jsonapplication/json OtlpStatus
401Missing 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
403The credential is active but not authorized for trace ingest.application/json OtlpStatus
413The wire or decompressed body exceeds its byte cap, or the request carries more than 500 spans.application/json OtlpStatus
415Content-Type is not application/json, or Content-Encoding is set to a value other than identity, gzip, or deflate.application/json OtlpStatus
429Trace ingest rate limit exceeded (600 requests / 60s per tenantId:projectId).application/json OtlpStatus
503The tenant authority scope could not be confirmed, or the durable ingest queue is unavailable.application/json OtlpStatus

Response headers

StatusHeaderTypeDescription
200Cache-ControlconstAPM control/query responses are never cacheable.
400Cache-ControlconstAPM control/query responses are never cacheable.
401Cache-ControlconstAPM control/query responses are never cacheable.
401WWW-AuthenticatestringOn the collector hosts always Bearer realm="noxtica-otlp", charset="UTF-8"; the ingest hosts do not send it.
403Cache-ControlconstAPM control/query responses are never cacheable.
413Cache-ControlconstAPM control/query responses are never cacheable.
415Cache-ControlconstAPM control/query responses are never cacheable.
429Cache-ControlconstAPM control/query responses are never cacheable.
429Retry-AfterintegerSeconds until the bucket allows another request.
503Cache-ControlconstAPM control/query responses are never cacheable.
503Retry-AfterintegerAlways 5 on this status.

Response fields (200, application/json)

FieldTypeAlways returnedDescription
partialSuccessobjectNo
partialSuccess.rejectedSpansstringYesDecimal count of rejected spans
partialSuccess.errorMessagestringNo

Response example (200)

{}

Example request

curl

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)

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)

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

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

NameInTypeRequiredDescription
Content-EncodingheaderstringNoDefaults to identity (uncompressed) when absent. Any other value is rejected with 415. Allowed values: identity, gzip, deflate.

Request body

Content type application/json, required.

FieldTypeRequiredDescription
resourceMetricsarray of objectNo
resourceMetrics[]objectNoOne 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

StatusMeaningError codesContent
200Export 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
400apm_malformed_metrics_payload — the body does not decode as a valid ExportMetricsServiceRequest in its declared Content-Type.apm_malformed_metrics_payloadapplication/json OtlpStatus
401Missing 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
403The credential is active but not authorized for metrics ingest.application/json OtlpStatus
413The wire or decompressed body exceeds its byte cap, or the request carries more than 500 data points.application/json OtlpStatus
415Content-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
429Metrics ingest rate limit exceeded (600 requests / 60s per tenantId:projectId).application/json OtlpStatus
503The tenant authority scope could not be confirmed, or the durable ingest queue is unavailable.application/json OtlpStatus

Response headers

StatusHeaderTypeDescription
200Cache-ControlconstAPM control/query responses are never cacheable.
400Cache-ControlconstAPM control/query responses are never cacheable.
401Cache-ControlconstAPM control/query responses are never cacheable.
401WWW-AuthenticatestringOn the collector hosts always Bearer realm="noxtica-otlp", charset="UTF-8"; the ingest hosts do not send it.
403Cache-ControlconstAPM control/query responses are never cacheable.
413Cache-ControlconstAPM control/query responses are never cacheable.
415Cache-ControlconstAPM control/query responses are never cacheable.
429Cache-ControlconstAPM control/query responses are never cacheable.
429Retry-AfterintegerSeconds until the bucket allows another request.
503Cache-ControlconstAPM control/query responses are never cacheable.
503Retry-AfterintegerAlways 5 on this status.

Response fields (200, application/json)

FieldTypeAlways returnedDescription
partialSuccessobjectNo
partialSuccess.rejectedDataPointsstringYesDecimal count of rejected data points
partialSuccess.errorMessagestringNo

Response example (200)

{}

Example request

curl

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)

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)

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

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

NameInTypeRequiredDescription
Content-EncodingheaderstringNoDefaults to identity (uncompressed) when absent. Any other value is rejected with 415. Allowed values: identity, gzip, deflate.

Request body

Content type application/json, required.

FieldTypeRequiredDescription
resourceLogsarray of objectNo
resourceLogs[]objectNoOne 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

StatusMeaningError codesContent
200Export 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
400apm_malformed_logs_payload — the body does not decode as a valid ExportLogsServiceRequest in its declared Content-Type.apm_malformed_logs_payloadapplication/json OtlpStatus
401Missing 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
403The credential is active but not authorized for logs ingest.application/json OtlpStatus
413The wire or decompressed body exceeds its byte cap, or the request carries more than 500 log records.application/json OtlpStatus
415Content-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
429Logs ingest rate limit exceeded (600 requests / 60s per tenantId:projectId).application/json OtlpStatus
503The tenant authority scope could not be confirmed, or the durable ingest queue is unavailable.application/json OtlpStatus

Response headers

StatusHeaderTypeDescription
200Cache-ControlconstAPM control/query responses are never cacheable.
400Cache-ControlconstAPM control/query responses are never cacheable.
401Cache-ControlconstAPM control/query responses are never cacheable.
401WWW-AuthenticatestringOn the collector hosts always Bearer realm="noxtica-otlp", charset="UTF-8"; the ingest hosts do not send it.
403Cache-ControlconstAPM control/query responses are never cacheable.
413Cache-ControlconstAPM control/query responses are never cacheable.
415Cache-ControlconstAPM control/query responses are never cacheable.
429Cache-ControlconstAPM control/query responses are never cacheable.
429Retry-AfterintegerSeconds until the bucket allows another request.
503Cache-ControlconstAPM control/query responses are never cacheable.
503Retry-AfterintegerAlways 5 on this status.

Response fields (200, application/json)

FieldTypeAlways returnedDescription
partialSuccessobjectNo
partialSuccess.rejectedLogRecordsstringYesDecimal count of rejected log records
partialSuccess.errorMessagestringNo

Response example (200)

{}

Example request

curl

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)

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)

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

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

NameInTypeRequiredDescription
Content-EncodingheaderstringNoDefaults to identity (uncompressed) when absent. Any other value is rejected with 415. Allowed values: identity, gzip, deflate.

Request body

Content type application/json, required.

FieldTypeRequiredDescription
resourceProfilesarray of objectNo
resourceProfiles[]objectNoOne proto3-JSON ResourceProfiles entry (resource, scopeProfiles[]) — see the OTLP profiles proto3-JSON schema for the full nested shape.

Responses

StatusMeaningError codesContent
200Export 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
400apm_malformed_profile_json — the body is not a valid proto3-JSON ProfilesData.apm_malformed_profile_jsonapplication/json OtlpStatus
401Missing 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
403The credential is active but not authorized for profile ingest.application/json OtlpStatus
413The wire or decompressed body exceeds its byte cap, or the request carries more than 20 profiles.application/json OtlpStatus
415Content-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
429Profile ingest rate limit exceeded (600 requests / 60s per tenantId:projectId).application/json OtlpStatus
503The tenant authority scope could not be confirmed, the profile object store is unavailable, or the durable ingest queue is unavailable.apm_profile_storage_unavailableapplication/json OtlpStatus

Response headers

StatusHeaderTypeDescription
200Cache-ControlconstAPM control/query responses are never cacheable.
400Cache-ControlconstAPM control/query responses are never cacheable.
401Cache-ControlconstAPM control/query responses are never cacheable.
401WWW-AuthenticatestringOn the collector hosts always Bearer realm="noxtica-otlp", charset="UTF-8"; the ingest hosts do not send it.
403Cache-ControlconstAPM control/query responses are never cacheable.
413Cache-ControlconstAPM control/query responses are never cacheable.
415Cache-ControlconstAPM control/query responses are never cacheable.
429Cache-ControlconstAPM control/query responses are never cacheable.
429Retry-AfterintegerSeconds until the bucket allows another request.
503Cache-ControlconstAPM control/query responses are never cacheable.
503Retry-AfterintegerAlways 5 on this status.

Response fields (200, application/json)

FieldTypeAlways returnedDescription
partialSuccessobjectNo
partialSuccess.rejectedProfilesstringYesDecimal count of rejected profiles
partialSuccess.errorMessagestringNo

Response example (200)

{}

Example request

curl

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)

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)

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

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.

FieldTypeRequiredDescription
servicestringYesPattern ^[A-Za-z0-9._:/-]{1,128}$.
versionstringYesAlso becomes the canonical spine’s release_id — release_id equals the request version Pattern ^[A-Za-z0-9_.:/-]{1,128}$.
environmentstringYesMUST 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}$.
commitstring, nullableNoGit commit SHA Pattern ^[0-9a-f]{7,64}$.
timestampstring (date-time)NoDefaults 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

StatusMeaningError codesContent
202Deployment marker accepted and queued to the warehouse.application/json ApmDeploymentMarkerAccepted
400apm_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_jsonapplication/json ApmDeploymentError
401Missing 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_expiredapplication/json ApmDeploymentError
403The 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_mismatchapplication/json ApmDeploymentError
413The request body exceeds the 16 KiB cap.apm_body_too_largeapplication/json ApmDeploymentError
415Content-Type is not application/json.apm_unsupported_media_typeapplication/json ApmDeploymentError
429Deployment marker rate limit exceeded (120 requests / 60s per tenantId:projectId).apm_rate_limitedapplication/json ApmDeploymentError
503The tenant authority scope could not be confirmed, or the durable ingest queue is unavailable.apm_authority_unavailableapplication/json ApmDeploymentError

Response headers

StatusHeaderTypeDescription
202Cache-ControlconstAPM control/query responses are never cacheable.
400Cache-ControlconstAPM control/query responses are never cacheable.
401Cache-ControlconstAPM control/query responses are never cacheable.
401WWW-AuthenticatestringOn the collector hosts always Bearer realm="noxtica-otlp", charset="UTF-8"; the ingest hosts do not send it.
403Cache-ControlconstAPM control/query responses are never cacheable.
413Cache-ControlconstAPM control/query responses are never cacheable.
415Cache-ControlconstAPM control/query responses are never cacheable.
429Cache-ControlconstAPM control/query responses are never cacheable.
429Retry-AfterintegerSeconds until the bucket allows another request.
503Cache-ControlconstAPM control/query responses are never cacheable.
503Retry-AfterintegerAlways 5 on this status.

Response fields (202, application/json)

FieldTypeAlways returnedDescription
okconstYesAlways true.
deployment_idstringYesServer-generated dep_<uuid> identifier.
servicestringYes
versionstringYes
environmentstringYes
commitstring, nullableYes
timestampstring (date-time)Yes

Response example (202)

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

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)

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)

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.