Device intelligence API
Read devices, events and visits, export data, fetch the sealed-results key, write labels, and manage behavioral baselines.
Server-to-server reads and writes over the devices, events and visits your integrations collected. Authenticate with an sk_ server API key whose scopes cover the operation: read:device, read:fingerprints, read:events, read:visits, read:export, read:sealed_results, write:labels, write:generate, read:behavioral and erase:behavioral. Base URL: https://collect.noxtica.com.
The signed verdict webhooks (submission.scored, risk.high, risk.flagged, risk.cluster_flagged, account.agent_linked) are documented in this area too.
| Environment | Base URL |
|---|---|
| Production | https://collect.noxtica.com |
Credential: ApiKeyAuth. See Authentication.
- Look up a device by ID
- List devices
- List devices linked to an account
- List scoring events
- List visits
- Export fingerprints as CSV or JSON
- Export events as CSV or JSON
- Export visits as CSV or JSON
- Stream scored events as JSON Lines
- Read reason-code firing counts
- Fetch the key that opens Sealed Client Results
- List your organisation’s ground-truth labels
- Write a ground-truth label for a device
- Generate a batch of synthetic devices
- Generate one seeded synthetic device
- Read a subject’s behavioral baseline
- Erase a subject’s behavioral baseline
- Read a subject’s drift-event history
Devices
3 operations
- GET /v1/device/{deviceId} Look up a device by ID
- GET /v1/devices List devices
- GET /v1/accounts/{accountHash}/devices List devices linked to an account
Events & Visits
2 operations
- GET /v1/events List scoring events
- GET /v1/visits List visits
Exports
5 operations
- GET /v1/exports/fingerprints Export fingerprints as CSV or JSON
- GET /v1/exports/events Export events as CSV or JSON
- GET /v1/exports/visits Export visits as CSV or JSON
- GET /v1/exports/scored-events Stream scored events as JSON Lines
- GET /v1/emerging-threats Read reason-code firing counts
Sealed Results
1 operation
- GET /v1/sealed-results/key Fetch the key that opens Sealed Client Results
Labels
2 operations
- GET /v1/labels List your organisation's ground-truth labels
- POST /v1/labels Write a ground-truth label for a device
Generator
2 operations
- POST /v1/generator/fingerprints Generate a batch of synthetic devices
- POST /v1/generator/fingerprint Generate one seeded synthetic device
Behavioral Baseline
3 operations
- GET /v1/behavioral/subjects/{subjectRef} Read a subject's behavioral baseline
- DELETE /v1/behavioral/subjects/{subjectRef} Erase a subject's behavioral baseline
- GET /v1/behavioral/subjects/{subjectRef}/timeline Read a subject's drift-event history
Look up a device by ID
GET /v1/device/{deviceId}
Returns the current score and risk projection of one device — the fingerprintId your browser or mobile integration received — scoped to the calling organisation.
The route answers 404 with {"error":"Device not found","deviceId":"...","requestId":"..."} when the device is unknown, belongs to another organisation, or has not been seen within your organisation’s configured retention window.
reasonCodesis the published, non-personal reason-code projection of the internal flags. It is an empty array unless reason-code output is enabled for your organisation, independently of the rawflagsfield.gpuAttestationTieris masked: a storedhardwareorsoftwaretier is reported asattestedunless publication of the hardware attestation level is enabled for your organisation.gpuVerifiedis not masked and never assertshardware.subscoresanddetailsare the scorer’s per-domain breakdown when present, otherwisenull.softwareSubfp,hardwareSubfpandenvironmentSubfpare server-derived observational sub-fingerprints; they arenullfor devices recorded before those fields existed.
Authentication: Bearer token (ApiKeyAuth), scope read:device.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
deviceId | path | string | Yes | The device_id to look up (URL-decoded from the path segment). |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | The device’s current score/risk projection. | application/json | |
400 | Missing/empty deviceId, or the path segment failed URL-decoding. | invalid_request | application/json ErrorEnvelope |
401 | The Authorization: Bearer sk_... header is missing or malformed, or the key is unknown, revoked, or belongs to a suspended or deleted organisation. The body is deliberately identical for every cause. | invalid_api_key | application/json InvalidApiKeyError |
403 | The API key is valid but does not carry the scope this operation requires; error names the missing scope. | API key does not have read:device scope | application/json ScopeForbiddenError |
404 | { error: 'Device not found', deviceId, requestId } — tenant projection inactive/missing OR the row is absent/outside the retention window. | Device not found | application/json |
429 | Rate limit exceeded. Wait Retry-After seconds (also retryAfter in the body) before retrying; the response carries no other rate-limit headers. | Rate limit exceeded | application/json |
500 | Unexpected server fault. Retry with backoff and quote requestId to support if it persists. | Internal server error | application/json ErrorEnvelope |
Response headers
| Status | Header | Type | Description |
|---|---|---|---|
429 | Retry-After | integer | Seconds until the bucket allows another request. |
Response fields (200, application/json)
| Field | Type | Always returned | Description |
|---|---|---|---|
ok | boolean | Yes | Allowed values: true. |
device | object | Yes | |
device.deviceId | string | No | |
device.domainId | string | No | |
device.score | number | No | |
device.riskLevel | string | No | |
device.confidence | number, nullable | No | |
device.flags | array of string | No | |
device.reasonCodes | array of string | No | Empty unless reason-code output is enabled for your organisation. |
device.subscores | object, nullable | No | |
device.details | object, nullable | No | |
device.country | string, nullable | No | |
device.city | string, nullable | No | |
device.createdAt | string (date-time) | No | |
device.updatedAt | string (date-time) | No | |
device.lastSubmittedAt | string (date-time), nullable | No | |
device.lastSeenAt | string (date-time), nullable | No | |
device.gpuVerified | boolean | No | |
device.gpuAttestationTier | string, nullable | No | Masked unless publication of the hardware attestation level is enabled for your organisation; see description above. Allowed values: attested, suspicious, unavailable, failed, hardware, software. |
device.softwareSubfp | string, nullable | No | |
device.hardwareSubfp | string, nullable | No | |
device.environmentSubfp | string, nullable | No |
Response example (200)
{
"ok": true,
"device": {
"deviceId": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
"domainId": "dom_3f2a8c91d6e5f470",
"score": 1,
"riskLevel": "low",
"confidence": 1,
"flags": [
"example-flags"
],
"reasonCodes": [
"example-reasonCodes"
],
"subscores": {},
"details": {},
"country": "US",
"city": "Istanbul",
"createdAt": "2026-09-13T12:00:00.000Z",
"updatedAt": "2026-09-13T12:00:00.000Z",
"lastSubmittedAt": "2026-09-13T12:00:00.000Z",
"lastSeenAt": "2026-09-13T12:00:00.000Z",
"gpuVerified": true,
"gpuAttestationTier": "attested",
"softwareSubfp": "example-softwareSubfp",
"hardwareSubfp": "example-hardwareSubfp",
"environmentSubfp": "example-environmentSubfp"
}
}
Example request
curl
curl "https://collect.noxtica.com/v1/device/example-deviceId" \
-H "Authorization: Bearer $NOXTICA_API_KEY"
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/device/example-deviceId', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
},
});
console.log(response.status, await response.text());
Python (standard library)
import os
import urllib.request
request = urllib.request.Request(
"https://collect.noxtica.com/v1/device/example-deviceId",
headers={
"Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
},
method="GET",
)
with urllib.request.urlopen(request) as response:
print(response.status, response.read().decode())
List devices
GET /v1/devices
Lists devices, most recently updated first, with filters and keyset pagination. from and to bound the range and may span at most 400 days. source selects real devices, synthetic devices (see the generator operations) or both; all merges both result sets by (updated_at, device_id) descending.
cursor is opaque: pass the nextCursor of the previous page back unchanged and never construct one. limit defaults to 50 and is capped at 200.
Authentication: Bearer token (ApiKeyAuth), scope read:fingerprints.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit | query | integer | No | Minimum 1, maximum 200. Default 50. |
cursor | query | string | No | Opaque keyset cursor from a prior response’s nextCursor. |
from | query | string (date-time) | No | Defaults to 400 days before now. |
to | query | string (date-time) | No | Defaults to now. |
domain_id | query | string | No | |
risk_level | query | string | No | Filter by risk level; the allowed set is deps-injected (riskLevelFilterValues). |
source | query | string | No | Allowed values: real, synthetic, all. Default real. |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | A page of devices. | application/json | |
400 | Invalid source, Invalid risk_level, or Invalid date range (bad/inverted/over-400-day from/to). | invalid_request | application/json ErrorEnvelope |
401 | The Authorization: Bearer sk_... header is missing or malformed, or the key is unknown, revoked, or belongs to a suspended or deleted organisation. The body is deliberately identical for every cause. | invalid_api_key | application/json InvalidApiKeyError |
403 | The API key is valid but does not carry the scope this operation requires; error names the missing scope. | API key does not have read:device scope | application/json ScopeForbiddenError |
429 | Rate limit exceeded. Wait Retry-After seconds (also retryAfter in the body) before retrying; the response carries no other rate-limit headers. | Rate limit exceeded | application/json |
500 | { error: 'Failed to query devices', code: 'devices_list_query_failed' } on a warehouse fault, or a generic internal error. | invalid_request | application/json ErrorEnvelope |
Response headers
| Status | Header | Type | Description |
|---|---|---|---|
429 | Retry-After | integer | Seconds until the bucket allows another request. |
Response fields (200, application/json)
| Field | Type | Always returned | Description |
|---|---|---|---|
ok | boolean | Yes | Allowed values: true. |
devices | array of object | Yes | |
devices[] | object | No | |
devices[].deviceId | string | No | |
devices[].domainId | string | No | |
devices[].score | number, nullable | No | |
devices[].riskLevel | string, nullable | No | |
devices[].flags | array of string | No | |
devices[].country | string, nullable | No | |
devices[].createdAt | string (date-time) | No | |
devices[].updatedAt | string (date-time) | No | |
devices[].isSynthetic | boolean | No | |
hasMore | boolean | Yes | |
nextCursor | string, nullable | Yes |
Response example (200)
{
"ok": true,
"devices": [
{
"deviceId": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
"domainId": "dom_3f2a8c91d6e5f470",
"score": 1,
"riskLevel": "low",
"flags": [
"example-flags"
],
"country": "US",
"createdAt": "2026-09-13T12:00:00.000Z",
"updatedAt": "2026-09-13T12:00:00.000Z",
"isSynthetic": true
}
],
"hasMore": true,
"nextCursor": "cursor_000123"
}
Example request
curl
curl "https://collect.noxtica.com/v1/devices" \
-H "Authorization: Bearer $NOXTICA_API_KEY"
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/devices', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
},
});
console.log(response.status, await response.text());
Python (standard library)
import os
import urllib.request
request = urllib.request.Request(
"https://collect.noxtica.com/v1/devices",
headers={
"Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
},
method="GET",
)
with urllib.request.urlopen(request) as response:
print(response.status, response.read().decode())
List devices linked to an account
GET /v1/accounts/{accountHash}/devices
Lists the devices and agents linked to one of your end-user accounts, with their current score and risk only. accountHash must be the keyed 16-character lowercase-hex pseudonym your organisation already receives elsewhere (for example the account_hash filter of GET /v1/events); a raw account id is never accepted on this path.
Pagination is a keyset cursor over (last_seen_at, id) descending, with the same opaque cursor shape as GET /v1/events.
Authentication: Bearer token (ApiKeyAuth), scope read:fingerprints.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
accountHash | path | string | Yes | The keyed 16-hex-lowercase account pseudonym. Pattern ^[0-9a-f]{16}$. |
limit | query | integer | No | Minimum 1, maximum 200. Default 50. |
cursor | query | string | No |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | The account’s linked device/agent history. | application/json | |
400 | accountHash fails the 16-hex-lowercase shape check. | invalid_request | application/json ErrorEnvelope |
401 | The Authorization: Bearer sk_... header is missing or malformed, or the key is unknown, revoked, or belongs to a suspended or deleted organisation. The body is deliberately identical for every cause. | invalid_api_key | application/json InvalidApiKeyError |
403 | The API key is valid but does not carry the scope this operation requires; error names the missing scope. | API key does not have read:device scope | application/json ScopeForbiddenError |
429 | Rate limit exceeded. Wait Retry-After seconds (also retryAfter in the body) before retrying; the response carries no other rate-limit headers. | Rate limit exceeded | application/json |
500 | { error: 'Failed to query account devices', code: 'account_devices_query_failed' } or a generic internal error. | invalid_request | application/json ErrorEnvelope |
Response headers
| Status | Header | Type | Description |
|---|---|---|---|
429 | Retry-After | integer | Seconds until the bucket allows another request. |
Response fields (200, application/json)
| Field | Type | Always returned | Description |
|---|---|---|---|
ok | boolean | Yes | Allowed values: true. |
accountHash | string | Yes | |
links | array of object | Yes | |
links[] | object | No | |
links[].agentKeyid | string | No | |
links[].agentHost | string, nullable | No | |
links[].deviceId | string | No | |
links[].domainId | string | No | |
links[].firstSeenAt | integer | No | Epoch ms. |
links[].lastSeenAt | integer | No | Epoch ms. |
links[].seenCount | integer | No | |
links[].deviceScore | number, nullable | No | |
links[].deviceRiskLevel | string, nullable | No | |
links[].deviceUpdatedAt | string, nullable | No | |
hasMore | boolean | Yes | |
nextCursor | string, nullable | Yes |
Response example (200)
{
"ok": true,
"accountHash": "a1b2c3d4e5f60718293a4b5c6d7e8f901a2b3c4d5e6f708192a3b4c5d6e7f89",
"links": [
{
"agentKeyid": "example-agentKeyid",
"agentHost": "example-agentHost",
"deviceId": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
"domainId": "dom_3f2a8c91d6e5f470",
"firstSeenAt": 1,
"lastSeenAt": 1,
"seenCount": 1,
"deviceScore": 1,
"deviceRiskLevel": "example-deviceRiskLevel",
"deviceUpdatedAt": "example-deviceUpdatedAt"
}
],
"hasMore": true,
"nextCursor": "cursor_000123"
}
Example request
curl
curl "https://collect.noxtica.com/v1/accounts/example-accountHash/devices" \
-H "Authorization: Bearer $NOXTICA_API_KEY"
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/accounts/example-accountHash/devices', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
},
});
console.log(response.status, await response.text());
Python (standard library)
import os
import urllib.request
request = urllib.request.Request(
"https://collect.noxtica.com/v1/accounts/example-accountHash/devices",
headers={
"Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
},
method="GET",
)
with urllib.request.urlopen(request) as response:
print(response.status, response.read().decode())
List scoring events
GET /v1/events
Lists scored events, newest first, with a keyset cursor over (created_at, id). The optional account_hash filter (a keyed 16-character lowercase-hex pseudonym) applies to source=real only: synthetic events and the merged all view carry no account_hash.
reasonCodes is the published reason-code projection of the event’s internal flags; it is empty unless reason-code output is enabled for your organisation.
Authentication: Bearer token (ApiKeyAuth), scope read:events.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
device_id | query | string | No | |
from | query | string (date-time) | No | |
to | query | string (date-time) | No | |
domain_id | query | string | No | |
account_hash | query | string | No | Applied only when source resolves to real. Pattern ^[0-9a-f]{16}$. |
source | query | string | No | Allowed values: real, synthetic, all. Default real. |
limit | query | integer | No | Minimum 1, maximum 200. Default 50. |
cursor | query | string | No |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | A page of events. | application/json | |
400 | Invalid source, or account_hash fails the 16-hex-lowercase shape check. | invalid_request | application/json ErrorEnvelope |
401 | The Authorization: Bearer sk_... header is missing or malformed, or the key is unknown, revoked, or belongs to a suspended or deleted organisation. The body is deliberately identical for every cause. | invalid_api_key | application/json InvalidApiKeyError |
403 | The API key is valid but does not carry the scope this operation requires; error names the missing scope. | API key does not have read:device scope | application/json ScopeForbiddenError |
429 | Rate limit exceeded. Wait Retry-After seconds (also retryAfter in the body) before retrying; the response carries no other rate-limit headers. | Rate limit exceeded | application/json |
500 | Unexpected server fault. Retry with backoff and quote requestId to support if it persists. | Internal server error | application/json ErrorEnvelope |
Response headers
| Status | Header | Type | Description |
|---|---|---|---|
429 | Retry-After | integer | Seconds until the bucket allows another request. |
Response fields (200, application/json)
| Field | Type | Always returned | Description |
|---|---|---|---|
ok | boolean | Yes | Allowed values: true. |
events | array of object | Yes | |
events[] | object | No | |
events[].id | string | No | |
events[].deviceId | string | No | |
events[].domainId | string | No | |
events[].score | number, nullable | No | |
events[].riskLevel | string, nullable | No | |
events[].confidence | number, nullable | No | |
events[].flags | array of string | No | |
events[].reasonCodes | array of string | No | |
events[].country | string, nullable | No | |
events[].createdAt | string (date-time) | No | |
events[].payloadUnavailable | boolean, nullable | No | |
events[].isSynthetic | boolean | No | |
hasMore | boolean | Yes | |
nextCursor | string, nullable | Yes |
Response example (200)
{
"ok": true,
"events": [
{
"id": "example-id",
"deviceId": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
"domainId": "dom_3f2a8c91d6e5f470",
"score": 1,
"riskLevel": "low",
"confidence": 1,
"flags": [
"example-flags"
],
"reasonCodes": [
"example-reasonCodes"
],
"country": "US",
"createdAt": "2026-09-13T12:00:00.000Z",
"payloadUnavailable": true,
"isSynthetic": true
}
],
"hasMore": true,
"nextCursor": "cursor_000123"
}
Example request
curl
curl "https://collect.noxtica.com/v1/events" \
-H "Authorization: Bearer $NOXTICA_API_KEY"
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/events', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
},
});
console.log(response.status, await response.text());
Python (standard library)
import os
import urllib.request
request = urllib.request.Request(
"https://collect.noxtica.com/v1/events",
headers={
"Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
},
method="GET",
)
with urllib.request.urlopen(request) as response:
print(response.status, response.read().decode())
List visits
GET /v1/visits
Lists visit records, newest first, with a keyset cursor over (created_at, id). Visits have no synthetic partition, so this route has no source filter. meta is the visit’s own parsed JSON metadata and includes external_user_id when the submitting page set it.
Authentication: Bearer token (ApiKeyAuth), scope read:visits.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
device_id | query | string | No | |
from | query | string (date-time) | No | |
to | query | string (date-time) | No | |
domain_id | query | string | No | |
external_user_id | query | string | No | |
limit | query | integer | No | Minimum 1, maximum 200. Default 50. |
cursor | query | string | No |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | A page of visits. | application/json | |
401 | The Authorization: Bearer sk_... header is missing or malformed, or the key is unknown, revoked, or belongs to a suspended or deleted organisation. The body is deliberately identical for every cause. | invalid_api_key | application/json InvalidApiKeyError |
403 | The API key is valid but does not carry the scope this operation requires; error names the missing scope. | API key does not have read:device scope | application/json ScopeForbiddenError |
429 | Rate limit exceeded. Wait Retry-After seconds (also retryAfter in the body) before retrying; the response carries no other rate-limit headers. | Rate limit exceeded | application/json |
500 | Unexpected server fault. Retry with backoff and quote requestId to support if it persists. | Internal server error | application/json ErrorEnvelope |
Response headers
| Status | Header | Type | Description |
|---|---|---|---|
429 | Retry-After | integer | Seconds until the bucket allows another request. |
Response fields (200, application/json)
| Field | Type | Always returned | Description |
|---|---|---|---|
ok | boolean | Yes | Allowed values: true. |
visits | array of object | Yes | |
visits[] | object | No | |
visits[].id | string | No | |
visits[].deviceId | string | No | |
visits[].domainId | string | No | |
visits[].country | string, nullable | No | |
visits[].city | string, nullable | No | |
visits[].meta | object | No | The row’s own parsed JSON meta blob (e.g. external_user_id); shape is submitter-controlled and not locally validated. |
visits[].createdAt | string (date-time) | No | |
hasMore | boolean | Yes | |
nextCursor | string, nullable | Yes |
Response example (200)
{
"ok": true,
"visits": [
{
"id": "example-id",
"deviceId": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
"domainId": "dom_3f2a8c91d6e5f470",
"country": "US",
"city": "Istanbul",
"meta": {},
"createdAt": "2026-09-13T12:00:00.000Z"
}
],
"hasMore": true,
"nextCursor": "cursor_000123"
}
Example request
curl
curl "https://collect.noxtica.com/v1/visits" \
-H "Authorization: Bearer $NOXTICA_API_KEY"
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/visits', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
},
});
console.log(response.status, await response.text());
Python (standard library)
import os
import urllib.request
request = urllib.request.Request(
"https://collect.noxtica.com/v1/visits",
headers={
"Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
},
method="GET",
)
with urllib.request.urlopen(request) as response:
print(response.status, response.read().decode())
Export fingerprints as CSV or JSON
GET /v1/exports/fingerprints
Bulk-exports device fingerprint records for a date range. from and to are required and are validated for both shape and calendar (a malformed string or an impossible date such as 2026-02-30 is rejected) before being echoed into the CSV Content-Disposition filename.
Each request returns at most a fixed maximum number of rows. A capped page sets truncated: true (JSON) or the X-Noxtica-Export-Truncated: true header (CSV) and an opaque keyset next_cursor (JSON) or X-Noxtica-Export-Next-Cursor header (CSV) over (created_at, id) descending; request the next page with that cursor.
reason_codes is the published reason-code projection (empty unless reason-code output is enabled for your organisation) and is the last CSV column, so fixed-position consumers are unaffected.
Authentication: Bearer token (ApiKeyAuth), scope read:export.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
format | query | string | No | Allowed values: json, csv. Default json. |
from | query | string | Yes | REQUIRED. YYYY-MM-DD, YYYY-MM-DD HH:MM:SS, or ISO 8601 (optional ms/offset). |
to | query | string | Yes | |
domain_id | query | string | No | |
source | query | string | No | Allowed values: real, synthetic, all. Default real. |
cursor | query | string | No | Opaque keyset cursor from a prior response’s next_cursor. Omit for page 1. |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | The exported page, as a JSON envelope or a raw CSV body depending on format. | application/json, text/csv | |
400 | Invalid source/Invalid format, missing from/to, or from and to must be YYYY-MM-DD or ISO 8601 datetime values. | invalid_request | application/json ErrorEnvelope |
401 | The Authorization: Bearer sk_... header is missing or malformed, or the key is unknown, revoked, or belongs to a suspended or deleted organisation. The body is deliberately identical for every cause. | invalid_api_key | application/json InvalidApiKeyError |
403 | The API key is valid but does not carry the scope this operation requires; error names the missing scope. | API key does not have read:device scope | application/json ScopeForbiddenError |
429 | Rate limit exceeded. Wait Retry-After seconds (also retryAfter in the body) before retrying; the response carries no other rate-limit headers. | Rate limit exceeded | application/json |
500 | Unexpected server fault. Retry with backoff and quote requestId to support if it persists. | Internal server error | application/json ErrorEnvelope |
Response headers
| Status | Header | Type | Description |
|---|---|---|---|
200 | X-Noxtica-Export-Next-Cursor | string | |
200 | X-Noxtica-Export-Truncated | string | |
429 | Retry-After | integer | Seconds until the bucket allows another request. |
Response fields (200, application/json)
| Field | Type | Always returned | Description |
|---|---|---|---|
ok | boolean | Yes | Allowed values: true. |
count | integer | Yes | |
limit | integer | Yes | |
truncated | boolean | Yes | |
from | string | Yes | |
to | string | Yes | |
next_cursor | string | No | |
fingerprints | array of object | Yes | |
fingerprints[] | object | No | |
fingerprints[].device_id | string | No | |
fingerprints[].domain_id | string | No | |
fingerprints[].score | number, nullable | No | |
fingerprints[].risk_level | string, nullable | No | |
fingerprints[].flags | string | No | Semicolon-joined. |
fingerprints[].reason_codes | string | No | Semicolon-joined; empty unless reason-code output is enabled for your organisation. |
fingerprints[].country | string | No | |
fingerprints[].city | string | No | |
fingerprints[].created_at | string (date-time) | No | |
fingerprints[].updated_at | string (date-time) | No | |
fingerprints[].is_synthetic | string | No | Allowed values: true, false. |
Response example (200)
{
"ok": true,
"count": 1,
"limit": 1,
"truncated": true,
"from": "example-from",
"to": "example-to",
"next_cursor": "cursor_000123",
"fingerprints": [
{
"device_id": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
"domain_id": "dom_3f2a8c91d6e5f470",
"score": 1,
"risk_level": "low",
"flags": "example-flags",
"reason_codes": "example-reason_codes",
"country": "US",
"city": "Istanbul",
"created_at": "2026-09-13T12:00:00.000Z",
"updated_at": "2026-09-13T12:00:00.000Z",
"is_synthetic": "true"
}
]
}
Example request
curl
curl "https://collect.noxtica.com/v1/exports/fingerprints?from=example-from&to=example-to" \
-H "Authorization: Bearer $NOXTICA_API_KEY"
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/exports/fingerprints?from=example-from&to=example-to', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
},
});
console.log(response.status, await response.text());
Python (standard library)
import os
import urllib.request
request = urllib.request.Request(
"https://collect.noxtica.com/v1/exports/fingerprints?from=example-from&to=example-to",
headers={
"Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
},
method="GET",
)
with urllib.request.urlopen(request) as response:
print(response.status, response.read().decode())
Export events as CSV or JSON
GET /v1/exports/events
Bulk-exports scored events for a date range. The contract is identical to GET /v1/exports/fingerprints: the same date validation, per-request row cap, and truncated / next_cursor pagination.
Authentication: Bearer token (ApiKeyAuth), scope read:export.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
format | query | string | No | Allowed values: json, csv. Default json. |
from | query | string | Yes | |
to | query | string | Yes | |
domain_id | query | string | No | |
source | query | string | No | Allowed values: real, synthetic, all. Default real. |
cursor | query | string | No |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | The exported page, as a JSON envelope or a raw CSV body depending on format. | application/json, text/csv | |
400 | Invalid source/Invalid format, missing from/to, or invalid date shape/calendar. | invalid_request | application/json ErrorEnvelope |
401 | The Authorization: Bearer sk_... header is missing or malformed, or the key is unknown, revoked, or belongs to a suspended or deleted organisation. The body is deliberately identical for every cause. | invalid_api_key | application/json InvalidApiKeyError |
403 | The API key is valid but does not carry the scope this operation requires; error names the missing scope. | API key does not have read:device scope | application/json ScopeForbiddenError |
429 | Rate limit exceeded. Wait Retry-After seconds (also retryAfter in the body) before retrying; the response carries no other rate-limit headers. | Rate limit exceeded | application/json |
500 | Unexpected server fault. Retry with backoff and quote requestId to support if it persists. | Internal server error | application/json ErrorEnvelope |
Response headers
| Status | Header | Type | Description |
|---|---|---|---|
200 | X-Noxtica-Export-Next-Cursor | string | |
200 | X-Noxtica-Export-Truncated | string | |
429 | Retry-After | integer | Seconds until the bucket allows another request. |
Response fields (200, application/json)
| Field | Type | Always returned | Description |
|---|---|---|---|
ok | boolean | Yes | Allowed values: true. |
count | integer | Yes | |
limit | integer | Yes | |
truncated | boolean | Yes | |
from | string | Yes | |
to | string | Yes | |
next_cursor | string | No | |
events | array of object | Yes | |
events[] | object | No | |
events[].id | string | No | |
events[].device_id | string | No | |
events[].domain_id | string | No | |
events[].score | number, nullable | No | |
events[].risk_level | string, nullable | No | |
events[].reason_codes | string | No | Semicolon-joined; empty unless reason-code output is enabled for your organisation. |
events[].country | string | No | |
events[].created_at | string (date-time) | No | |
events[].is_synthetic | string | No | Allowed values: true, false. |
Response example (200)
{
"ok": true,
"count": 1,
"limit": 1,
"truncated": true,
"from": "example-from",
"to": "example-to",
"next_cursor": "cursor_000123",
"events": [
{
"id": "example-id",
"device_id": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
"domain_id": "dom_3f2a8c91d6e5f470",
"score": 1,
"risk_level": "low",
"reason_codes": "example-reason_codes",
"country": "US",
"created_at": "2026-09-13T12:00:00.000Z",
"is_synthetic": "true"
}
]
}
Example request
curl
curl "https://collect.noxtica.com/v1/exports/events?from=example-from&to=example-to" \
-H "Authorization: Bearer $NOXTICA_API_KEY"
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/exports/events?from=example-from&to=example-to', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
},
});
console.log(response.status, await response.text());
Python (standard library)
import os
import urllib.request
request = urllib.request.Request(
"https://collect.noxtica.com/v1/exports/events?from=example-from&to=example-to",
headers={
"Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
},
method="GET",
)
with urllib.request.urlopen(request) as response:
print(response.status, response.read().decode())
Export visits as CSV or JSON
GET /v1/exports/visits
Bulk-exports visit records for a date range, with the same date validation, per-request row cap and pagination as the other exports. Visits have no synthetic partition, so there is no source parameter.
Authentication: Bearer token (ApiKeyAuth), scope read:export.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
format | query | string | No | Allowed values: json, csv. Default json. |
from | query | string | Yes | |
to | query | string | Yes | |
domain_id | query | string | No | |
cursor | query | string | No |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | The exported page, as a JSON envelope or a raw CSV body depending on format. | application/json, text/csv | |
400 | Invalid format, missing from/to, or invalid date shape/calendar. | invalid_request | application/json ErrorEnvelope |
401 | The Authorization: Bearer sk_... header is missing or malformed, or the key is unknown, revoked, or belongs to a suspended or deleted organisation. The body is deliberately identical for every cause. | invalid_api_key | application/json InvalidApiKeyError |
403 | The API key is valid but does not carry the scope this operation requires; error names the missing scope. | API key does not have read:device scope | application/json ScopeForbiddenError |
429 | Rate limit exceeded. Wait Retry-After seconds (also retryAfter in the body) before retrying; the response carries no other rate-limit headers. | Rate limit exceeded | application/json |
500 | Unexpected server fault. Retry with backoff and quote requestId to support if it persists. | Internal server error | application/json ErrorEnvelope |
Response headers
| Status | Header | Type | Description |
|---|---|---|---|
200 | X-Noxtica-Export-Next-Cursor | string | |
200 | X-Noxtica-Export-Truncated | string | |
429 | Retry-After | integer | Seconds until the bucket allows another request. |
Response fields (200, application/json)
| Field | Type | Always returned | Description |
|---|---|---|---|
ok | boolean | Yes | Allowed values: true. |
count | integer | Yes | |
limit | integer | Yes | |
truncated | boolean | Yes | |
from | string | Yes | |
to | string | Yes | |
next_cursor | string | No | |
visits | array of object | Yes | |
visits[] | object | No | |
visits[].id | string | No | |
visits[].device_id | string | No | |
visits[].domain_id | string | No | |
visits[].country | string | No | |
visits[].city | string | No | |
visits[].external_user_id | string | No | |
visits[].created_at | string (date-time) | No |
Response example (200)
{
"ok": true,
"count": 1,
"limit": 1,
"truncated": true,
"from": "example-from",
"to": "example-to",
"next_cursor": "cursor_000123",
"visits": [
{
"id": "example-id",
"device_id": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
"domain_id": "dom_3f2a8c91d6e5f470",
"country": "US",
"city": "Istanbul",
"external_user_id": "example-external_user_id",
"created_at": "2026-09-13T12:00:00.000Z"
}
]
}
Example request
curl
curl "https://collect.noxtica.com/v1/exports/visits?from=example-from&to=example-to" \
-H "Authorization: Bearer $NOXTICA_API_KEY"
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/exports/visits?from=example-from&to=example-to', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
},
});
console.log(response.status, await response.text());
Python (standard library)
import os
import urllib.request
request = urllib.request.Request(
"https://collect.noxtica.com/v1/exports/visits?from=example-from&to=example-to",
headers={
"Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
},
method="GET",
)
with urllib.request.urlopen(request) as response:
print(response.status, response.read().decode())
Stream scored events as JSON Lines
GET /v1/exports/scored-events
Streams scored events as newline-delimited JSON, newest first: one scored-event object per line, followed by a trailing _summary line. The stream is capped at a fixed maximum number of rows per request.
tieris coarser thangpuAttestationTieronGET /v1/device/{deviceId}: only a hardware-attested device maps toattested; everything else issuspicious.bandis one ofcritical,high,medium,low,minimal; an unrecognised stored value is reported asminimal.reason_codesholds at most 64 entries and is empty unless reason-code output is enabled for your organisation.
This route validates the shape of from and to only, not calendar validity, unlike the JSON/CSV exports.
Authentication: Bearer token (ApiKeyAuth), scope read:export.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
from | query | string | Yes | REQUIRED. YYYY-MM-DD, YYYY-MM-DD HH:MM:SS, or ISO 8601. |
to | query | string | Yes | |
domain_id | query | string | No | |
cursor | query | string | No | Opaque keyset cursor from a prior response’s next_cursor (in the trailing _summary line). No cursor param on page 1. |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | JSONL body: one scored-event object per line, then a trailing _summary line. | application/x-ndjson | |
400 | Missing from/to, or a value not matching the accepted date shape (this route validates SHAPE only, not calendar validity, unlike the sibling JSON/CSV exports). | invalid_request | application/json ErrorEnvelope |
401 | The Authorization: Bearer sk_... header is missing or malformed, or the key is unknown, revoked, or belongs to a suspended or deleted organisation. The body is deliberately identical for every cause. | invalid_api_key | application/json InvalidApiKeyError |
403 | The API key is valid but does not carry the scope this operation requires; error names the missing scope. | API key does not have read:device scope | application/json ScopeForbiddenError |
429 | Rate limit exceeded. Wait Retry-After seconds (also retryAfter in the body) before retrying; the response carries no other rate-limit headers. | Rate limit exceeded | application/json |
500 | Unexpected server fault. Retry with backoff and quote requestId to support if it persists. | Internal server error | application/json ErrorEnvelope |
Response headers
| Status | Header | Type | Description |
|---|---|---|---|
200 | X-Noxtica-Export-Next-Cursor | string | |
200 | X-Noxtica-Export-Truncated | string | |
429 | Retry-After | integer | Seconds until the bucket allows another request. |
Response fields (200, application/x-ndjson)
Type: string.
Example request
curl
curl "https://collect.noxtica.com/v1/exports/scored-events?from=example-from&to=example-to" \
-H "Authorization: Bearer $NOXTICA_API_KEY"
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/exports/scored-events?from=example-from&to=example-to', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
},
});
console.log(response.status, await response.text());
Python (standard library)
import os
import urllib.request
request = urllib.request.Request(
"https://collect.noxtica.com/v1/exports/scored-events?from=example-from&to=example-to",
headers={
"Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
},
method="GET",
)
with urllib.request.urlopen(request) as response:
print(response.status, response.read().decode())
Read reason-code firing counts
GET /v1/emerging-threats
Returns per-reason-code firing counts and rates over a bounded lookback window, aggregated from the published reason-code catalogue. It reuses the read:export scope.
The report is disabled unless it is enabled for the deployment. When it is, the route does no warehouse work and answers a neutral 200 with {"available":false,"reason":"feature_unavailable","signals":[]}, so you can always probe it safely. A warehouse read fault degrades to an empty signals array and window_sessions: 0; this route never answers 500 for it.
Authentication: Bearer token (ApiKeyAuth), scope read:export.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
window | query | integer | No | Lookback hours; any other value silently falls back to 24. Allowed values: 1, 6, 24, 168, 720. Default 24. |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | The firing-count report, or the feature-disabled neutral empty state. | application/json | |
401 | The Authorization: Bearer sk_... header is missing or malformed, or the key is unknown, revoked, or belongs to a suspended or deleted organisation. The body is deliberately identical for every cause. | invalid_api_key | application/json InvalidApiKeyError |
403 | The API key is valid but does not carry the scope this operation requires; error names the missing scope. | API key does not have read:device scope | application/json ScopeForbiddenError |
429 | Rate limit exceeded. Wait Retry-After seconds (also retryAfter in the body) before retrying; the response carries no other rate-limit headers. | Rate limit exceeded | application/json |
500 | Unexpected server fault. Retry with backoff and quote requestId to support if it persists. | Internal server error | application/json ErrorEnvelope |
Response headers
| Status | Header | Type | Description |
|---|---|---|---|
429 | Retry-After | integer | Seconds until the bucket allows another request. |
Response fields (200, application/json)
| Field | Type | Always returned | Description |
|---|---|---|---|
ok | boolean | Yes | Allowed values: true. |
available | boolean | Yes | |
reason | string | No | Allowed values: feature_unavailable. |
window_hours | integer | Yes | |
window_sessions | integer | No | Present only when available is true. |
signals | array of object | Yes | |
signals[] | object | No | |
signals[].reason_code | string | No | |
signals[].domain | string | No | |
signals[].label | string | No | |
signals[].firing_count | integer | No | |
signals[].firing_rate | number, nullable | No | null when window_sessions is below the 20-session display floor. |
count | integer | Yes |
Response example (200)
{
"ok": true,
"available": true,
"reason": "feature_unavailable",
"window_hours": 1,
"window_sessions": 1,
"signals": [
{
"reason_code": "example-reason_code",
"domain": "app.example.com",
"label": "example-label",
"firing_count": 1,
"firing_rate": 1
}
],
"count": 1
}
Example request
curl
curl "https://collect.noxtica.com/v1/emerging-threats" \
-H "Authorization: Bearer $NOXTICA_API_KEY"
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/emerging-threats', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
},
});
console.log(response.status, await response.text());
Python (standard library)
import os
import urllib.request
request = urllib.request.Request(
"https://collect.noxtica.com/v1/emerging-threats",
headers={
"Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
},
method="GET",
)
with urllib.request.urlopen(request) as response:
print(response.status, response.read().decode())
Fetch the key that opens Sealed Client Results
GET /v1/sealed-results/key
Returns your organisation’s symmetric key for opening Sealed Client Results envelopes. The key is derived on demand and never stored.
keyVersion is optional. Each sealed envelope carries the cleartext kv (key version) it was sealed under, and this route serves any past version that has not been revoked, so rotation never orphans an archived envelope. expiresAt is present only when a key lifetime is configured.
404 sealed_key_version_unavailable—keyVersionis not a positive integer or is newer than the current version. No key material is returned.403— the key lacksread:sealed_results, or the key is revoked for this organisation and version.500 server_misconfigured— sealed results are not provisioned for this deployment; this is distinct from an authentication failure.
Authentication: Bearer token (ApiKeyAuth), scope read:sealed_results.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
keyVersion | query | string | No | Key version to fetch — the kv a Sealed Client Result envelope carries. Omitted = the current version (the response is unchanged). A past version is served from the server’s master ring until it is REVOKED for the tenant (403, as for the current version). A malformed or future version answers 404 sealed_key_version_unavailable. Pattern ^[1-9][0-9]{0,8}$. |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | The tenant’s sealed-results key material. | application/json | |
401 | The Authorization: Bearer sk_... header is missing or malformed, or the key is unknown, revoked, or belongs to a suspended or deleted organisation. The body is deliberately identical for every cause. | invalid_api_key | application/json InvalidApiKeyError |
403 | API key does not have read:sealed_results scope, OR the sealed-results key is revoked for this tenant/version (sealed-results key revoked for this tenant/version). | invalid_request | application/json |
404 | sealed_key_version_unavailable — the requested keyVersion is not a positive integer, or is newer than the current key version. Explicit (never a 500); no key material is returned. | sealed-results key version unavailable, sealed_key_version_unavailable | application/json |
429 | Rate limit exceeded. Wait Retry-After seconds (also retryAfter in the body) before retrying; the response carries no other rate-limit headers. | Rate limit exceeded | application/json |
500 | Unexpected server fault. Retry with backoff and quote requestId to support if it persists. | Internal server error, server_misconfigured | application/json ErrorEnvelope |
Response headers
| Status | Header | Type | Description |
|---|---|---|---|
429 | Retry-After | integer | Seconds until the bucket allows another request. |
Response fields (200, application/json)
| Field | Type | Always returned | Description |
|---|---|---|---|
ok | boolean | Yes | Allowed values: true. |
keyVersion | string | Yes | |
key | string | Yes | base64-encoded raw derived key. |
alg | string | Yes | |
envelope | string | Yes | Allowed values: noxtica-sealed-v1. |
expiresAt | integer | No | Absolute epoch-ms expiry hint; present only when a TTL is configured. |
Response example (200)
{
"ok": true,
"keyVersion": "example-keyVersion",
"key": "example-key",
"alg": "example-alg",
"envelope": "noxtica-sealed-v1",
"expiresAt": 1768435200
}
Example request
curl
curl "https://collect.noxtica.com/v1/sealed-results/key" \
-H "Authorization: Bearer $NOXTICA_API_KEY"
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/sealed-results/key', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
},
});
console.log(response.status, await response.text());
Python (standard library)
import os
import urllib.request
request = urllib.request.Request(
"https://collect.noxtica.com/v1/sealed-results/key",
headers={
"Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
},
method="GET",
)
with urllib.request.urlopen(request) as response:
print(response.status, response.read().decode())
List your organisation’s ground-truth labels
GET /v1/labels
Lists the labels your organisation has written with POST /v1/labels, with optional device_id and label filters and a keyset cursor over (created_at, id) (the same opaque cursor shape as GET /v1/events). It reuses the read:device scope; there is no separate read-labels scope.
Authentication: Bearer token (ApiKeyAuth), scope read:device.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
device_id | query | string | No | |
label | query | string | No | Allowed values: bot, human, fraud, legit, unknown. |
limit | query | integer | No | Minimum 1, maximum 200. Default 50. |
cursor | query | string | No |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | A page of labels. | application/json | |
400 | Invalid label. Expected one of: bot, human, fraud, legit, unknown. | invalid_request | application/json ErrorEnvelope |
401 | The Authorization: Bearer sk_... header is missing or malformed, or the key is unknown, revoked, or belongs to a suspended or deleted organisation. The body is deliberately identical for every cause. | invalid_api_key | application/json InvalidApiKeyError |
403 | The API key is valid but does not carry the scope this operation requires; error names the missing scope. | API key does not have read:device scope | application/json ScopeForbiddenError |
429 | Rate limit exceeded. Wait Retry-After seconds (also retryAfter in the body) before retrying; the response carries no other rate-limit headers. | Rate limit exceeded | application/json |
500 | Unexpected server fault. Retry with backoff and quote requestId to support if it persists. | Internal server error | application/json ErrorEnvelope |
Response headers
| Status | Header | Type | Description |
|---|---|---|---|
429 | Retry-After | integer | Seconds until the bucket allows another request. |
Response fields (200, application/json)
| Field | Type | Always returned | Description |
|---|---|---|---|
ok | boolean | Yes | Allowed values: true. |
labels | array of object | Yes | |
labels[] | object | No | |
labels[].id | string (uuid) | No | |
labels[].deviceId | string | No | |
labels[].eventId | string, nullable | No | |
labels[].label | string | No | Allowed values: bot, human, fraud, legit, unknown. |
labels[].source | string | No | |
labels[].note | string, nullable | No | |
labels[].createdAt | string (date-time) | No | |
labels[].updatedAt | string (date-time) | No | |
hasMore | boolean | Yes | |
nextCursor | string, nullable | Yes |
Response example (200)
{
"ok": true,
"labels": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"deviceId": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
"eventId": "evt_3c9b2f7e4a3d4c8e9f1a2b3c4d5e6f70",
"label": "bot",
"source": "example-source",
"note": "created via API",
"createdAt": "2026-09-13T12:00:00.000Z",
"updatedAt": "2026-09-13T12:00:00.000Z"
}
],
"hasMore": true,
"nextCursor": "cursor_000123"
}
Example request
curl
curl "https://collect.noxtica.com/v1/labels" \
-H "Authorization: Bearer $NOXTICA_API_KEY"
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/labels', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
},
});
console.log(response.status, await response.text());
Python (standard library)
import os
import urllib.request
request = urllib.request.Request(
"https://collect.noxtica.com/v1/labels",
headers={
"Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
},
method="GET",
)
with urllib.request.urlopen(request) as response:
print(response.status, response.read().decode())
Write a ground-truth label for a device
POST /v1/labels
Records a confirmed fraud, legitimate or other label for a device, optionally scoped to one event, to calibrate scoring. The write is an upsert: repeating the same (device_id, event_id) updates label, source, note and updated_at on the existing row instead of creating a duplicate.
The body accepts only device_id, label, event_id, note and source; any other key is rejected with 400 Unknown field. label must be one of bot, human, fraud, legit, unknown.
Authentication: Bearer token (ApiKeyAuth), scope write:labels.
Request body
Content type application/json, required.
| Field | Type | Required | Description |
|---|---|---|---|
device_id | string | Yes | At most 256 characters. |
label | string | Yes | Allowed values: bot, human, fraud, legit, unknown. |
event_id | string, nullable | No | Optional; blank/whitespace-only normalizes to NULL (collapses onto the device-level idempotency slot). At most 256 characters. |
note | string, nullable | No | At most 1024 characters. |
source | string | No | Truncated to 64 chars; defaults to ‘api’ when omitted/blank. At most 64 characters. Default api. |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | The upserted (inserted or updated) label row. | application/json | |
400 | Unknown body field, missing/oversized device_id, invalid label, or an oversized event_id/note. | invalid_request | application/json ErrorEnvelope |
401 | The Authorization: Bearer sk_... header is missing or malformed, or the key is unknown, revoked, or belongs to a suspended or deleted organisation. The body is deliberately identical for every cause. | invalid_api_key | application/json InvalidApiKeyError |
403 | The API key is valid but does not carry the scope this operation requires; error names the missing scope. | API key does not have read:device scope | application/json ScopeForbiddenError |
413 | The request body exceeded this route’s byte cap. | body_too_large | application/json ErrorEnvelope |
415 | Content-Type must be application/json. | invalid_request | application/json ErrorEnvelope |
429 | Rate limit exceeded. Wait Retry-After seconds (also retryAfter in the body) before retrying; the response carries no other rate-limit headers. | Rate limit exceeded | application/json |
500 | Unexpected server fault. Retry with backoff and quote requestId to support if it persists. | Internal server error | application/json ErrorEnvelope |
Response headers
| Status | Header | Type | Description |
|---|---|---|---|
429 | Retry-After | integer | Seconds until the bucket allows another request. |
Response fields (200, application/json)
| Field | Type | Always returned | Description |
|---|---|---|---|
ok | boolean | Yes | Allowed values: true. |
label | object | Yes | |
label.id | string (uuid) | No | |
label.deviceId | string | No | |
label.eventId | string, nullable | No | |
label.label | string | No | Allowed values: bot, human, fraud, legit, unknown. |
label.source | string | No | |
label.note | string, nullable | No | |
label.createdAt | string (date-time) | No | |
label.updatedAt | string (date-time) | No |
Response example (200)
{
"ok": true,
"label": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"deviceId": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
"eventId": "evt_3c9b2f7e4a3d4c8e9f1a2b3c4d5e6f70",
"label": "bot",
"source": "example-source",
"note": "created via API",
"createdAt": "2026-09-13T12:00:00.000Z",
"updatedAt": "2026-09-13T12:00:00.000Z"
}
}
Example request
curl
curl -X POST "https://collect.noxtica.com/v1/labels" \
-H "Authorization: Bearer $NOXTICA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"device_id": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
"label": "bot",
"event_id": "evt_3c9b2f7e4a3d4c8e9f1a2b3c4d5e6f70",
"note": "created via API",
"source": "api"
}'
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/labels', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
device_id: 'dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d',
label: 'bot',
event_id: 'evt_3c9b2f7e4a3d4c8e9f1a2b3c4d5e6f70',
note: 'created via API',
source: 'api',
}),
});
console.log(response.status, await response.text());
Python (standard library)
import json
import os
import urllib.request
request = urllib.request.Request(
"https://collect.noxtica.com/v1/labels",
data=json.dumps({
"device_id": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
"label": "bot",
"event_id": "evt_3c9b2f7e4a3d4c8e9f1a2b3c4d5e6f70",
"note": "created via API",
"source": "api",
}).encode(),
headers={
"Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(request) as response:
print(response.status, response.read().decode())
Generate a batch of synthetic devices
POST /v1/generator/fingerprints
Generates count synthetic devices, each with eventsPerDevice events, derived deterministically from real template fingerprints, for testing and load. Read them back with source=synthetic.
The call is idempotent on (domain_id, seed): a repeated identical request returns the same existing batch with isExisting: true and HTTP 200; a new batch answers 201. domain_id must be an active domain of your organisation, otherwise the route answers 404.
Authentication: Bearer token (ApiKeyAuth), scope write:generate.
Request body
Content type application/json, required.
| Field | Type | Required | Description |
|---|---|---|---|
domain_id | string | Yes | |
seed | string | Yes | Idempotency key for the whole batch. |
count | integer | No | Coerced via Number(); non-positive/non-finite falls back to 10. Minimum 1, maximum 100. Default 10. |
eventsPerDevice | integer | No | Minimum 1, maximum 10. Default 3. |
sourceFrom | string | No | |
sourceTo | string | No | |
sourceLimit | integer | No | Maximum 500. Default 200. |
scrubIPs | boolean | No | Any value other than the literal false is treated as true. Default true. |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | The existing batch, when seed already matches a committed batch for this (tenant, domain). | application/json | |
201 | A newly-committed batch. | application/json | |
400 | domain_id and seed are required, or No template fingerprints found in the specified range. | invalid_request | application/json ErrorEnvelope |
401 | The Authorization: Bearer sk_... header is missing or malformed, or the key is unknown, revoked, or belongs to a suspended or deleted organisation. The body is deliberately identical for every cause. | invalid_api_key | application/json InvalidApiKeyError |
403 | The API key is valid but does not carry the scope this operation requires; error names the missing scope. | API key does not have read:device scope | application/json ScopeForbiddenError |
404 | Domain not found or does not belong to tenant. | invalid_request | application/json ErrorEnvelope |
413 | The request body exceeded this route’s byte cap. | body_too_large | application/json ErrorEnvelope |
415 | Content-Type must be application/json. | invalid_request | application/json ErrorEnvelope |
500 | Unexpected server fault. Retry with backoff and quote requestId to support if it persists. | Internal server error | application/json ErrorEnvelope |
Response fields (200, application/json)
| Field | Type | Always returned | Description |
|---|---|---|---|
ok | boolean | Yes | Allowed values: true. |
isExisting | boolean | Yes | |
batchId | string | Yes | |
seed | string | Yes | |
tenantId | string | Yes | |
domainId | string | Yes | |
deviceIds | array of string | Yes | |
totalDevices | integer | Yes | |
totalEvents | integer | Yes | |
createdAt | string (date-time) | Yes |
Response example (200)
{
"ok": true,
"isExisting": true,
"batchId": "batch_5a1f9c3e7b2d4a6f8091c2d3e4f5a6b7",
"seed": "seed-01",
"tenantId": "tenant_42",
"domainId": "dom_3f2a8c91d6e5f470",
"deviceIds": [
"example-deviceIds"
],
"totalDevices": 1,
"totalEvents": 1,
"createdAt": "2026-09-13T12:00:00.000Z"
}
Response fields (201, application/json)
| Field | Type | Always returned | Description |
|---|---|---|---|
ok | boolean | Yes | Allowed values: true. |
isExisting | boolean | Yes | |
batchId | string | Yes | |
seed | string | Yes | |
tenantId | string | Yes | |
domainId | string | Yes | |
deviceIds | array of string | Yes | |
totalDevices | integer | Yes | |
totalEvents | integer | Yes | |
createdAt | string (date-time) | Yes |
Response example (201)
{
"ok": true,
"isExisting": true,
"batchId": "batch_5a1f9c3e7b2d4a6f8091c2d3e4f5a6b7",
"seed": "seed-01",
"tenantId": "tenant_42",
"domainId": "dom_3f2a8c91d6e5f470",
"deviceIds": [
"example-deviceIds"
],
"totalDevices": 1,
"totalEvents": 1,
"createdAt": "2026-09-13T12:00:00.000Z"
}
Example request
curl
curl -X POST "https://collect.noxtica.com/v1/generator/fingerprints" \
-H "Authorization: Bearer $NOXTICA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain_id": "dom_3f2a8c91d6e5f470",
"seed": "seed-01",
"count": 10,
"eventsPerDevice": 3,
"sourceFrom": "example-sourceFrom",
"sourceTo": "example-sourceTo",
"sourceLimit": 200,
"scrubIPs": true
}'
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/generator/fingerprints', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
domain_id: 'dom_3f2a8c91d6e5f470',
seed: 'seed-01',
count: 10,
eventsPerDevice: 3,
sourceFrom: 'example-sourceFrom',
sourceTo: 'example-sourceTo',
sourceLimit: 200,
scrubIPs: true,
}),
});
console.log(response.status, await response.text());
Python (standard library)
import json
import os
import urllib.request
request = urllib.request.Request(
"https://collect.noxtica.com/v1/generator/fingerprints",
data=json.dumps({
"domain_id": "dom_3f2a8c91d6e5f470",
"seed": "seed-01",
"count": 10,
"eventsPerDevice": 3,
"sourceFrom": "example-sourceFrom",
"sourceTo": "example-sourceTo",
"sourceLimit": 200,
"scrubIPs": True,
}).encode(),
headers={
"Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(request) as response:
print(response.status, response.read().decode())
Generate one seeded synthetic device
POST /v1/generator/fingerprint
Generates one synthetic device from a filtered selection of real template fingerprints. It is idempotent on the derived seed key: a repeated request returns the existing device unchanged with isExisting: true and HTTP 200; a new device answers 201.
json_filters, when present, constrain the candidate templates by JSONPath-style equality against the stored fingerprint, score and collector fields. Domain ownership, scope and body rules are the same as for POST /v1/generator/fingerprints.
Authentication: Bearer token (ApiKeyAuth), scope write:generate.
Request body
Content type application/json, required.
| Field | Type | Required | Description |
|---|---|---|---|
domain_id | string | Yes | |
seed | string | Yes | |
platform | string | No | |
risk_level | string | No | |
country | string | No | |
language | string | No | |
timezone | string | No | |
score_min | number | No | |
score_max | number | No | |
json_filters | array of object | No | Normalized/hashed into the deterministic seed key alongside seed. |
json_filters[] | object | No | |
json_filters[].source | string | Yes | Allowed values: fingerprint, score, cf. |
json_filters[].path | string | Yes | A JSONPath-style equality filter, e.g. $.signals.webgl.vendor. |
json_filters[].value | any | No | |
sourceFrom | string | No | |
sourceTo | string | No | |
sourceLimit | integer | No | Maximum 500. Default 200. |
scrubIPs | boolean | No | Default true. |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | The existing seeded fingerprint (returned unchanged, Cache-Control: no-store). | application/json | |
201 | A newly-committed seeded fingerprint. | application/json | |
400 | domain_id and seed are required, or No template fingerprints found matching the specified filters. | invalid_request | application/json ErrorEnvelope |
401 | The Authorization: Bearer sk_... header is missing or malformed, or the key is unknown, revoked, or belongs to a suspended or deleted organisation. The body is deliberately identical for every cause. | invalid_api_key | application/json InvalidApiKeyError |
403 | The API key is valid but does not carry the scope this operation requires; error names the missing scope. | API key does not have read:device scope | application/json ScopeForbiddenError |
404 | Domain not found or does not belong to tenant. | invalid_request | application/json ErrorEnvelope |
413 | The request body exceeded this route’s byte cap. | body_too_large | application/json ErrorEnvelope |
415 | Content-Type must be application/json. | invalid_request | application/json ErrorEnvelope |
500 | Unexpected server fault. Retry with backoff and quote requestId to support if it persists. | Internal server error | application/json ErrorEnvelope |
Response headers
| Status | Header | Type | Description |
|---|---|---|---|
200 | Cache-Control | string | |
201 | Cache-Control | string |
Response fields (200, application/json)
| Field | Type | Always returned | Description |
|---|---|---|---|
ok | boolean | Yes | Allowed values: true. |
isExisting | boolean | Yes | |
seed | string | Yes | |
filters | object | Yes | The normalized template filters echoed back. |
tenantId | string | Yes | |
domainId | string | Yes | |
batchId | string | Yes | |
deviceId | string | Yes | |
templateEventId | string | Yes | |
fingerprint | object | Yes | The stored synthetic fingerprint blob (template-derived; shape not locally schema-validated). |
score | object | Yes | The stored score blob copied verbatim from the selected template. |
cf | object | Yes | The stored Cloudflare-geo blob, IP-scrubbed when scrubIPs is true. |
createdAt | string (date-time) | Yes | |
updatedAt | string (date-time) | Yes |
Response example (200)
{
"ok": true,
"isExisting": true,
"seed": "seed-01",
"filters": {},
"tenantId": "tenant_42",
"domainId": "dom_3f2a8c91d6e5f470",
"batchId": "batch_5a1f9c3e7b2d4a6f8091c2d3e4f5a6b7",
"deviceId": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
"templateEventId": "example-templateEventId",
"fingerprint": {},
"score": {},
"cf": {},
"createdAt": "2026-09-13T12:00:00.000Z",
"updatedAt": "2026-09-13T12:00:00.000Z"
}
Response fields (201, application/json)
| Field | Type | Always returned | Description |
|---|---|---|---|
ok | boolean | Yes | Allowed values: true. |
isExisting | boolean | Yes | |
seed | string | Yes | |
filters | object | Yes | The normalized template filters echoed back. |
tenantId | string | Yes | |
domainId | string | Yes | |
batchId | string | Yes | |
deviceId | string | Yes | |
templateEventId | string | Yes | |
fingerprint | object | Yes | The stored synthetic fingerprint blob (template-derived; shape not locally schema-validated). |
score | object | Yes | The stored score blob copied verbatim from the selected template. |
cf | object | Yes | The stored Cloudflare-geo blob, IP-scrubbed when scrubIPs is true. |
createdAt | string (date-time) | Yes | |
updatedAt | string (date-time) | Yes |
Response example (201)
{
"ok": true,
"isExisting": true,
"seed": "seed-01",
"filters": {},
"tenantId": "tenant_42",
"domainId": "dom_3f2a8c91d6e5f470",
"batchId": "batch_5a1f9c3e7b2d4a6f8091c2d3e4f5a6b7",
"deviceId": "dev_7a2b9c4d1e6f3a8b5c2d9e4f7a1b6c3d",
"templateEventId": "example-templateEventId",
"fingerprint": {},
"score": {},
"cf": {},
"createdAt": "2026-09-13T12:00:00.000Z",
"updatedAt": "2026-09-13T12:00:00.000Z"
}
Example request
curl
curl -X POST "https://collect.noxtica.com/v1/generator/fingerprint" \
-H "Authorization: Bearer $NOXTICA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain_id": "dom_3f2a8c91d6e5f470",
"seed": "seed-01",
"platform": "example-platform",
"risk_level": "low",
"country": "US",
"language": "example-language",
"timezone": "example-timezone",
"score_min": 1,
"score_max": 1,
"json_filters": [
{
"source": "fingerprint",
"path": "/v1/kyc/verify",
"value": "example-value"
}
],
"sourceFrom": "example-sourceFrom",
"sourceTo": "example-sourceTo",
"sourceLimit": 200,
"scrubIPs": true
}'
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/generator/fingerprint', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
domain_id: 'dom_3f2a8c91d6e5f470',
seed: 'seed-01',
platform: 'example-platform',
risk_level: 'low',
country: 'US',
language: 'example-language',
timezone: 'example-timezone',
score_min: 1,
score_max: 1,
json_filters: [
{
source: 'fingerprint',
path: '/v1/kyc/verify',
value: 'example-value',
},
],
sourceFrom: 'example-sourceFrom',
sourceTo: 'example-sourceTo',
sourceLimit: 200,
scrubIPs: true,
}),
});
console.log(response.status, await response.text());
Python (standard library)
import json
import os
import urllib.request
request = urllib.request.Request(
"https://collect.noxtica.com/v1/generator/fingerprint",
data=json.dumps({
"domain_id": "dom_3f2a8c91d6e5f470",
"seed": "seed-01",
"platform": "example-platform",
"risk_level": "low",
"country": "US",
"language": "example-language",
"timezone": "example-timezone",
"score_min": 1,
"score_max": 1,
"json_filters": [
{
"source": "fingerprint",
"path": "/v1/kyc/verify",
"value": "example-value",
},
],
"sourceFrom": "example-sourceFrom",
"sourceTo": "example-sourceTo",
"sourceLimit": 200,
"scrubIPs": True,
}).encode(),
headers={
"Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(request) as response:
print(response.status, response.read().decode())
Read a subject’s behavioral baseline
GET /v1/behavioral/subjects/{subjectRef}
Reads a subject’s current behavioral-baseline enrollment and its latest drift snapshot. subjectRef is your raw end-user account id: it is never stored, logged or echoed, only keyed-hashed server-side before any lookup.
Not enabled by default: the whole /v1/behavioral/* family answers the GatedNotFound 404 unless behavioral baselines are enabled for the deployment, and only then is the read:behavioral scope checked.
Authentication: Bearer token (ApiKeyAuth), scope read:behavioral.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
subjectRef | path | string | Yes | The tenant’s raw end-user account id (hashed server-side; never stored as given). At most 256 characters. |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | The subject’s baseline enrollment + latest drift. | application/json | |
400 | Invalid subjectRef — empty or over 256 chars. | invalid_request | application/json ErrorEnvelope |
401 | The Authorization: Bearer sk_... header is missing or malformed, or the key is unknown, revoked, or belongs to a suspended or deleted organisation. The body is deliberately identical for every cause. | invalid_api_key | application/json InvalidApiKeyError |
403 | The API key is valid but does not carry the scope this operation requires; error names the missing scope. | API key does not have read:device scope | application/json ScopeForbiddenError |
404 | Subject not found (no current snapshot exists), or V1 endpoint not found when behavioral baselines are not enabled for the deployment. | invalid_request | application/json ErrorEnvelope |
429 | Rate limit exceeded. Wait Retry-After seconds (also retryAfter in the body) before retrying; the response carries no other rate-limit headers. | Rate limit exceeded | application/json |
500 | Unexpected server fault. Retry with backoff and quote requestId to support if it persists. | Internal server error | application/json ErrorEnvelope |
Response headers
| Status | Header | Type | Description |
|---|---|---|---|
429 | Retry-After | integer | Seconds until the bucket allows another request. |
Response fields (200, application/json)
| Field | Type | Always returned | Description |
|---|---|---|---|
ok | boolean | Yes | Allowed values: true. |
subject | object | Yes | |
subject.enrollmentStatus | string | No | A persisted value outside the closed set clamps to learning. Allowed values: learning, active. |
subject.sampleCount | integer | No | |
subject.attestationProvenance | object | No | |
subject.attestationProvenance.gpu_attested | integer | No | |
subject.attestationProvenance.sealed | integer | No | |
subject.attestationProvenance.unattested | integer | No | |
subject.deviation | number, nullable | No | |
subject.families | object, nullable | No | |
subject.families.pointer | number, nullable | No | |
subject.families.keystroke | number, nullable | No | |
subject.families.cadence | number, nullable | No | |
subject.lastDriftAt | string (date-time), nullable | No |
Response example (200)
{
"ok": true,
"subject": {
"enrollmentStatus": "learning",
"sampleCount": 1,
"attestationProvenance": {
"gpu_attested": 1,
"sealed": 1,
"unattested": 1
},
"deviation": 1,
"families": {
"pointer": 1,
"keystroke": 1,
"cadence": 1
},
"lastDriftAt": "2026-09-13T12:00:00.000Z"
}
}
Example request
curl
curl "https://collect.noxtica.com/v1/behavioral/subjects/example-subjectRef" \
-H "Authorization: Bearer $NOXTICA_API_KEY"
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/behavioral/subjects/example-subjectRef', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
},
});
console.log(response.status, await response.text());
Python (standard library)
import os
import urllib.request
request = urllib.request.Request(
"https://collect.noxtica.com/v1/behavioral/subjects/example-subjectRef",
headers={
"Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
},
method="GET",
)
with urllib.request.urlopen(request) as response:
print(response.status, response.read().decode())
Erase a subject’s behavioral baseline
DELETE /v1/behavioral/subjects/{subjectRef}
Erases a subject’s behavioral baseline, for data-subject erasure requests. Live profile state is erased first, then the stored snapshots and drift projections are hard-deleted. If the live-state erase fails the route answers 502 with {"ok":false,"profileErased":false,...} instead of a false 200, because a surviving profile could otherwise re-flush the erased snapshot; the call is safe to retry. Erasing an unknown or already-erased subject is idempotent and answers 200 {"deleted":{"snapshots":0,"driftEvents":0}}.
This erases account-scoped (acct:) subjects only. Device-scoped baselines and the raw behavioral tracks need the separate per-device erasure path in the Backoffice, so a subject with both needs both paths. Availability is the same as for GET /v1/behavioral/subjects/{subjectRef}.
Authentication: Bearer token (ApiKeyAuth), scope erase:behavioral.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
subjectRef | path | string | Yes | At most 256 characters. |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | Erasure completed (or was already a no-op for an unknown subject). | application/json | |
400 | Invalid subjectRef — empty or over 256 chars. | invalid_request | application/json ErrorEnvelope |
401 | The Authorization: Bearer sk_... header is missing or malformed, or the key is unknown, revoked, or belongs to a suspended or deleted organisation. The body is deliberately identical for every cause. | invalid_api_key | application/json InvalidApiKeyError |
403 | The API key is valid but does not carry the scope this operation requires; error names the missing scope. | API key does not have read:device scope | application/json ScopeForbiddenError |
404 | Subject not found, or V1 endpoint not found when behavioral baselines are not enabled for the deployment. | invalid_request | application/json ErrorEnvelope |
429 | Rate limit exceeded. Wait Retry-After seconds (also retryAfter in the body) before retrying; the response carries no other rate-limit headers. | Rate limit exceeded | application/json |
500 | Unexpected server fault. Retry with backoff and quote requestId to support if it persists. | Internal server error | application/json ErrorEnvelope |
502 | { ok:false, profileErased:false, deleted, error: 'behavioral profile durable-object erase failed — retry (idempotent)' } — the erase call is safe to retry. | invalid_request | application/json |
Response headers
| Status | Header | Type | Description |
|---|---|---|---|
429 | Retry-After | integer | Seconds until the bucket allows another request. |
Response fields (200, application/json)
| Field | Type | Always returned | Description |
|---|---|---|---|
ok | boolean | Yes | Allowed values: true. |
profileErased | boolean | Yes | Allowed values: true. |
deleted | object | Yes | |
deleted.snapshots | integer | No | |
deleted.driftEvents | integer | No |
Response example (200)
{
"ok": true,
"profileErased": true,
"deleted": {
"snapshots": 1,
"driftEvents": 1
}
}
Example request
curl
curl -X DELETE "https://collect.noxtica.com/v1/behavioral/subjects/example-subjectRef" \
-H "Authorization: Bearer $NOXTICA_API_KEY"
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/behavioral/subjects/example-subjectRef', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
},
});
console.log(response.status, await response.text());
Python (standard library)
import os
import urllib.request
request = urllib.request.Request(
"https://collect.noxtica.com/v1/behavioral/subjects/example-subjectRef",
headers={
"Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
},
method="DELETE",
)
with urllib.request.urlopen(request) as response:
print(response.status, response.read().decode())
Read a subject’s drift-event history
GET /v1/behavioral/subjects/{subjectRef}/timeline
Reads a subject’s drift events, newest first. The row cap is clamped to 1–100 and there is no pagination cursor on this route. subjectRef handling and availability are the same as for GET /v1/behavioral/subjects/{subjectRef}.
Authentication: Bearer token (ApiKeyAuth), scope read:behavioral.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
subjectRef | path | string | Yes | At most 256 characters. |
limit | query | integer | No | Minimum 1, maximum 100. Default 50. |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | The subject’s drift-event timeline. | application/json | |
400 | Invalid subjectRef — empty or over 256 chars. | invalid_request | application/json ErrorEnvelope |
401 | The Authorization: Bearer sk_... header is missing or malformed, or the key is unknown, revoked, or belongs to a suspended or deleted organisation. The body is deliberately identical for every cause. | invalid_api_key | application/json InvalidApiKeyError |
403 | The API key is valid but does not carry the scope this operation requires; error names the missing scope. | API key does not have read:device scope | application/json ScopeForbiddenError |
404 | Subject not found, or V1 endpoint not found when behavioral baselines are not enabled for the deployment. | invalid_request | application/json ErrorEnvelope |
429 | Rate limit exceeded. Wait Retry-After seconds (also retryAfter in the body) before retrying; the response carries no other rate-limit headers. | Rate limit exceeded | application/json |
500 | Unexpected server fault. Retry with backoff and quote requestId to support if it persists. | Internal server error | application/json ErrorEnvelope |
Response headers
| Status | Header | Type | Description |
|---|---|---|---|
429 | Retry-After | integer | Seconds until the bucket allows another request. |
Response fields (200, application/json)
| Field | Type | Always returned | Description |
|---|---|---|---|
ok | boolean | Yes | Allowed values: true. |
events | array of object | Yes | |
events[] | object | No | |
events[].deviation | number, nullable | No | |
events[].families | object, nullable | No | |
events[].families.pointer | number, nullable | No | |
events[].families.keystroke | number, nullable | No | |
events[].families.cadence | number, nullable | No | |
events[].attestation | string, nullable | No | |
events[].channel | string, nullable | No | |
events[].createdAt | string (date-time) | No | |
count | integer | Yes |
Response example (200)
{
"ok": true,
"events": [
{
"deviation": 1,
"families": {
"pointer": 1,
"keystroke": 1,
"cadence": 1
},
"attestation": "example-attestation",
"channel": "example-channel",
"createdAt": "2026-09-13T12:00:00.000Z"
}
],
"count": 1
}
Example request
curl
curl "https://collect.noxtica.com/v1/behavioral/subjects/example-subjectRef/timeline" \
-H "Authorization: Bearer $NOXTICA_API_KEY"
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/behavioral/subjects/example-subjectRef/timeline', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
},
});
console.log(response.status, await response.text());
Python (standard library)
import os
import urllib.request
request = urllib.request.Request(
"https://collect.noxtica.com/v1/behavioral/subjects/example-subjectRef/timeline",
headers={
"Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
},
method="GET",
)
with urllib.request.urlopen(request) as response:
print(response.status, response.read().decode())
This page is generated from the Noxtica OpenAPI specification release v0.4.0: v1.yaml version 1.3.0-2026-10-27 (SHA-256 3fd15257a022). Do not edit it by hand.