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.
APM Ingest
5 operations
- POST /v1/traces Ingest an OTLP/HTTP trace export
- POST /v1/metrics Ingest an OTLP/HTTP metrics export
- POST /v1/logs Ingest an OTLP/HTTP logs export
- POST /v1/profiles Ingest an OTLP/HTTP JSON profiles export
- POST /v1/deployments Ingest a deployment marker
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
| 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)
{}
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
| 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)
{}
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
| 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)
{}
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
| 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)
{}
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.
| 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)
{
"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.