Risk scoring and agent trust API
Score a request, look up IP and device reputation, verify AI agents, and mint signed crawler vouchers from your server.
Call these operations from your own servers with an sk_ server API key (Authorization: Bearer sk_...). Every call is scoped to your organisation. Base URL: https://collect.noxtica.com (production, sk_prod_ keys).
Most operations need only the read:device scope, which every key carries by default. A Score API key created under Integrations & API is a server API key limited to read:device: it can call POST /v1/score and every other operation that needs only that scope. IP and device reputation (POST /v1/ip/reputation, POST /v1/verify/reputation) and agent verification are metered against your plan; the reputation module and Agent Passports are not enabled by default.
| Environment | Base URL |
|---|---|
| Production | https://collect.noxtica.com |
Credential: ApiKeyAuth. See Authentication.
Reputation
1 operation
- POST /v1/verify/reputation Check email, website and phone reputation
Crawl Vouchers
1 operation
- POST /v1/crawl/vouchers Mint a crawler-access voucher
Agent Trust
3 operations
- GET /v1/agent/passport Issue a signed Agent Passport (GET)
- POST /v1/agent/passport Issue a signed Agent Passport (POST)
- POST /v1/agent/verify Verify a Web Bot Auth agent signature
IP & Risk Scoring
2 operations
- POST /v1/ip/reputation Look up IP reputation
- POST /v1/score Score a signals payload
Check email, website and phone reputation
POST /v1/verify/reputation
Evaluates the reputation of an email address, a website and/or a phone number supplied by your backend and returns a fused verdict. At least one of email, website or phone is required. The checks are bounded and fail open: a check that cannot complete contributes no signal instead of failing the call.
This operation is not enabled by default. When the reputation module is not enabled for your organisation the route answers the GatedNotFound 404, and it does so before the API key is checked. A served call counts toward your organisation’s reputation usage. Raw email and website values are not logged.
Authentication: Bearer token (ApiKeyAuth), scope read:reputation.
Request body
Content type application/json, required.
| Field | Type | Required | Description |
|---|---|---|---|
email | string | No | |
website | string | No | |
phone | string | No |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | The fused reputation verdict. | application/json | |
400 | The request failed validation. error is a route-specific string such as Invalid body or invalid_request; read the operation description for the exact values it can return. | Invalid body | 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 | The byte-identical “V1 endpoint not found” response returned when the route’s module is not enabled for your organisation, and also for a path that does not exist — indistinguishable by design (anti-enumeration), so do not treat it as proof the path is wrong. | V1 endpoint 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. |
verdict | string | Yes | |
signals | object | Yes | Per-signal sub-scores from evaluateReputation; the engine’s internal signal-shape union is not locally re-validated by this handler. |
confidence | number | Yes |
Response example (200)
{
"ok": true,
"verdict": "example-verdict",
"signals": {},
"confidence": 1
}
Example request
curl
curl -X POST "https://collect.noxtica.com/v1/verify/reputation" \
-H "Authorization: Bearer $NOXTICA_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/verify/reputation', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
});
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/verify/reputation",
data=json.dumps({}).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())
Mint a crawler-access voucher
POST /v1/crawl/vouchers
Mints a signed, stateless voucher that a verified crawler (identified by agentKey) presents to your Noxtica edge to satisfy a charge pricing rule. The edge verifies only the signature and binding. The response’s header field names the request header that carries the voucher.
The voucher is always scoped to the organisation that owns the API key; a tenant id in the body is never used. The route is fail-closed: it answers 503 when no signing key is available and never emits an unsigned voucher.
Payment capture is not part of this operation. It does not verify that a payment was taken; it only checks that a payment reference (paymentRef in the body, or the payment-reference request header) is present, and only requires one when your deployment is configured to. Outstanding vouchers are capped per month, and the cap is claimed only after the voucher has been signed, so a signing failure never consumes it.
Authentication: Bearer token (ApiKeyAuth), scope write:crawl_voucher.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Nox-Payment-Ref | header | string | No | Alternative to the body’s paymentRef; operator-attested, not independently verified this iteration. |
Request body
Content type application/json, required.
| Field | Type | Required | Description |
|---|---|---|---|
agentKey | string | Yes | The crawler identity this voucher pays for (’*’ = the catch-all pricing rule). At most 256 characters. |
ttlSeconds | integer | No | Minimum 60, maximum 86400. Default 3600. |
paymentRef | string | No | Operator-attested payment reference; presence-only checked. |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | The minted voucher. | application/json | |
400 | agentKey (non-empty string) is required, or Invalid JSON body. | 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 |
402 | Payment context required — returned only when the deployment requires a payment reference and neither paymentRef nor the payment-reference header was supplied. | invalid_request | application/json ErrorEnvelope |
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 |
503 | Crawl voucher minting is not configured (no signing key is available) or Voucher quota authority unavailable. | 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. |
voucher | string | Yes | <base64url(payload)>.<base64url(hmac)>, or the HMAC-signing-trust envelope form when that feature is armed. |
expiresAt | integer | Yes | Epoch seconds. |
header | string | Yes | Allowed values: X-Nox-Crawl-Voucher. |
Response example (200)
{
"ok": true,
"voucher": "example-voucher",
"expiresAt": 1768435200,
"header": "X-Nox-Crawl-Voucher"
}
Example request
curl
curl -X POST "https://collect.noxtica.com/v1/crawl/vouchers" \
-H "Authorization: Bearer $NOXTICA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agentKey": "example-agentKey",
"ttlSeconds": 3600,
"paymentRef": "example-paymentRef"
}'
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/crawl/vouchers', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
agentKey: 'example-agentKey',
ttlSeconds: 3600,
paymentRef: 'example-paymentRef',
}),
});
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/crawl/vouchers",
data=json.dumps({
"agentKey": "example-agentKey",
"ttlSeconds": 3600,
"paymentRef": "example-paymentRef",
}).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())
Issue a signed Agent Passport (GET)
GET /v1/agent/passport
Runs the same Web Bot Auth verdict as POST /v1/agent/verify (RFC 9421 signature verification plus your organisation’s agent policy registry) and returns one ES256-signed, offline-verifiable envelope. Verify the signature against the published JWKS (GET /.well-known/noxtica/agent-passport-jwks.json, falling back to /.well-known/noxtica/jwks.json) by kid; no round trip to Noxtica is needed.
The GET form reads only the three Web Bot Auth headers of the incoming request (signature-input, signature, signature-agent) and an optional forwarded attestation from the query parameters tier, anchored, riskBand and jti. sub is set only for a cryptographically verified verdict; every other verdict carries sub: null. An unverified allow is withheld or downgraded before signing.
Availability. Agent Passports are not enabled by default. When they are not enabled for the deployment the route answers 404 {"error":"Not found"}, a different body from the usual V1 endpoint not found; when your organisation has not opted into agent verification it answers 403 {"error":"feature disabled"}. The route shares its rate-limit bucket with POST /v1/agent/verify, and a verified issuance counts toward your agent-verification usage.
Authentication: Bearer token (ApiKeyAuth), scope read:device.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
url | query | string (uri) | No | The signed target URI @authority is reconstructed from; degrades to the request’s own URL when absent/malformed. |
tier | query | string | No | Optional caller-forwarded attestation tier; a non-member value is silently dropped (fail-open). Allowed values: hardware, software, suspicious, unavailable, failed. |
anchored | query | string | No | Optional caller-forwarded “server-anchored single-use claim succeeded” flag; only the literal string true is honoured. Allowed values: true, false. |
riskBand | query | string | No | Allowed values: critical, high, medium, low, minimal. |
jti | query | string | No | OPTIONAL caller-forwarded device-correlation id, echoed back as the passport’s untrusted cid (never the server-minted replay jti). |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | The ES256-signed Agent Passport envelope (SignedEnvelope<AgentPassportPayload>). A tenant backend verifies this OFFLINE against the published JWKS by kid — no round-trip to Noxtica is required. | 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 | Your organisation has not opted into agent verification (feature disabled). | feature disabled | application/json |
404 | Agent Passports are not enabled for this deployment. The body is {"error":"Not found"}, returned before the API key is checked. | 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 fault; the handler otherwise never throws. | invalid_request | application/json ErrorEnvelope |
503 | The signing key is unavailable or invalid (fail-closed: there is no silent fallback to another key). | 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 |
|---|---|---|---|
alg | string | Yes | Allowed values: ES256. |
kid | string | Yes | |
iat | integer | Yes | Epoch seconds. |
exp | integer | No | Epoch seconds; the signed expiry window (== the payload ttl). |
kv | integer | No | OPTIONAL signing key-version; present only when the trust-lifecycle signer is in use. |
spv | integer | No | OPTIONAL signing-policy-version; same presence contract as kv. |
sig | string | Yes | base64url ES256 signature over the payload. |
payload | object | Yes | |
payload.typ | string | Yes | Allowed values: agent-passport. |
payload.iss | string | Yes | Allowed values: noxtica. |
payload.aud | string | Yes | The authenticated tenant id (audience binding). |
payload.pv | integer | Yes | Passport schema version: 1 = legacy claims, 2 = explicit callerAsserted trust marker (when enabled for the deployment). Allowed values: 1, 2. |
payload.verdict | string | Yes | Allowed values: verified, unsigned, invalid_signature, expired, replay. |
payload.attestation | object | Yes | |
payload.attestation.tier | string | No | Allowed values: hardware, software, suspicious, unavailable, failed. |
payload.attestation.anchored | boolean | No | |
payload.attestation.provenance | string | No | Allowed values: caller_attested. |
payload.callerAsserted | boolean | No | Present only when pv:2. Allowed values: true. |
payload.riskBand | string | Yes | Allowed values: critical, high, medium, low, minimal. |
payload.policyAction | string, nullable | Yes | Allowed values: allow, deny. |
payload.graduatedAction | string | No | Present only when graduated agent policy is enabled for your organisation. Allowed values: allow, observe, challenge, deny. |
payload.jti | string (uuid) | Yes | SERVER-MINTED per-issuance id — the consumer MUST reject a repeated jti (replay). |
payload.sub | string, nullable | Yes | The verified subject (keyid, else the signature-agent host); null unless verdict is ‘verified’. |
payload.tenantId | string | Yes | |
payload.cid | string, nullable | Yes | OPTIONAL untrusted caller-forwarded correlation id — never the replay id. |
payload.disclosure | object, nullable | Yes | |
payload.disclosure.required | boolean | No | Allowed values: true. |
payload.disclosure.regulation | string | No | Allowed values: eu_ai_act_art_50. |
payload.disclosure.obligation | string | No | |
payload.disclosure.agentVerified | boolean | No | |
payload.issuedAt | string (date-time) | Yes | |
payload.ttl | integer | Yes | Seconds; the signed envelope exp window. |
Response example (200)
{
"alg": "ES256",
"kid": "kid-2026-09",
"iat": 1,
"exp": 1,
"kv": 1,
"spv": 1,
"sig": "example-sig",
"payload": {
"typ": "agent-passport",
"iss": "noxtica",
"aud": "example-aud",
"pv": 1,
"verdict": "verified",
"attestation": {
"tier": "hardware",
"anchored": true,
"provenance": "caller_attested"
},
"callerAsserted": true,
"riskBand": "critical",
"policyAction": "allow",
"graduatedAction": "allow",
"jti": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sub": "example-sub",
"tenantId": "tenant_42",
"cid": "example-cid",
"disclosure": {
"required": true,
"regulation": "eu_ai_act_art_50",
"obligation": "example-obligation",
"agentVerified": true
},
"issuedAt": "2026-09-13T12:00:00.000Z",
"ttl": 1
}
}
Example request
curl
curl "https://collect.noxtica.com/v1/agent/passport" \
-H "Authorization: Bearer $NOXTICA_API_KEY"
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/agent/passport', {
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/agent/passport",
headers={
"Authorization": f"Bearer {os.environ['NOXTICA_API_KEY']}",
},
method="GET",
)
with urllib.request.urlopen(request) as response:
print(response.status, response.read().decode())
Issue a signed Agent Passport (POST)
POST /v1/agent/passport
Same verdict and signing pipeline as GET /v1/agent/passport; the POST form lets your backend forward the signature material in the JSON body instead of request headers, like POST /v1/agent/verify. The same availability rules, error responses, rate-limit bucket and usage counting apply.
Authentication: Bearer token (ApiKeyAuth), scope read:device.
Request body
Content type application/json, optional.
| Field | Type | Required | Description |
|---|---|---|---|
headers | object | No | The forwarded Web Bot Auth signature headers (signature-input, signature, signature-agent, lower-cased keys); non-string values are dropped. |
headers.* | string | No | |
url | string (uri) | No | The signed target URI; a malformed value degrades to the request’s own URL. |
method | string | No | The signed HTTP method the signature covers; defaults to GET when omitted/blank. |
attestation | object | No | |
attestation.tier | string | No | Allowed values: hardware, software, suspicious, unavailable, failed. |
attestation.anchored | boolean | No | |
riskBand | string | No | Allowed values: critical, high, medium, low, minimal. |
jti | string | No | OPTIONAL caller-forwarded device-correlation id, echoed back as the passport’s untrusted cid. |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | The ES256-signed Agent Passport envelope (SignedEnvelope<AgentPassportPayload>). A tenant backend verifies this OFFLINE against the published JWKS by kid — no round-trip to Noxtica is required. | 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 | Your organisation has not opted into agent verification (feature disabled). | feature disabled | application/json |
404 | Agent Passports are not enabled for this deployment. The body is {"error":"Not found"}, returned before the API key is checked. | Not found | application/json |
413 | Payload too large — the request body exceeded the size limit. | 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 fault. | invalid_request | application/json ErrorEnvelope |
503 | The signing key is unavailable or invalid (fail-closed: there is no silent fallback to another key). | 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 |
|---|---|---|---|
alg | string | Yes | Allowed values: ES256. |
kid | string | Yes | |
iat | integer | Yes | Epoch seconds. |
exp | integer | No | Epoch seconds; the signed expiry window (== the payload ttl). |
kv | integer | No | OPTIONAL signing key-version; present only when the trust-lifecycle signer is in use. |
spv | integer | No | OPTIONAL signing-policy-version; same presence contract as kv. |
sig | string | Yes | base64url ES256 signature over the payload. |
payload | object | Yes | |
payload.typ | string | Yes | Allowed values: agent-passport. |
payload.iss | string | Yes | Allowed values: noxtica. |
payload.aud | string | Yes | The authenticated tenant id (audience binding). |
payload.pv | integer | Yes | Passport schema version: 1 = legacy claims, 2 = explicit callerAsserted trust marker (when enabled for the deployment). Allowed values: 1, 2. |
payload.verdict | string | Yes | Allowed values: verified, unsigned, invalid_signature, expired, replay. |
payload.attestation | object | Yes | |
payload.attestation.tier | string | No | Allowed values: hardware, software, suspicious, unavailable, failed. |
payload.attestation.anchored | boolean | No | |
payload.attestation.provenance | string | No | Allowed values: caller_attested. |
payload.callerAsserted | boolean | No | Present only when pv:2. Allowed values: true. |
payload.riskBand | string | Yes | Allowed values: critical, high, medium, low, minimal. |
payload.policyAction | string, nullable | Yes | Allowed values: allow, deny. |
payload.graduatedAction | string | No | Present only when graduated agent policy is enabled for your organisation. Allowed values: allow, observe, challenge, deny. |
payload.jti | string (uuid) | Yes | SERVER-MINTED per-issuance id — the consumer MUST reject a repeated jti (replay). |
payload.sub | string, nullable | Yes | The verified subject (keyid, else the signature-agent host); null unless verdict is ‘verified’. |
payload.tenantId | string | Yes | |
payload.cid | string, nullable | Yes | OPTIONAL untrusted caller-forwarded correlation id — never the replay id. |
payload.disclosure | object, nullable | Yes | |
payload.disclosure.required | boolean | No | Allowed values: true. |
payload.disclosure.regulation | string | No | Allowed values: eu_ai_act_art_50. |
payload.disclosure.obligation | string | No | |
payload.disclosure.agentVerified | boolean | No | |
payload.issuedAt | string (date-time) | Yes | |
payload.ttl | integer | Yes | Seconds; the signed envelope exp window. |
Response example (200)
{
"alg": "ES256",
"kid": "kid-2026-09",
"iat": 1,
"exp": 1,
"kv": 1,
"spv": 1,
"sig": "example-sig",
"payload": {
"typ": "agent-passport",
"iss": "noxtica",
"aud": "example-aud",
"pv": 1,
"verdict": "verified",
"attestation": {
"tier": "hardware",
"anchored": true,
"provenance": "caller_attested"
},
"callerAsserted": true,
"riskBand": "critical",
"policyAction": "allow",
"graduatedAction": "allow",
"jti": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sub": "example-sub",
"tenantId": "tenant_42",
"cid": "example-cid",
"disclosure": {
"required": true,
"regulation": "eu_ai_act_art_50",
"obligation": "example-obligation",
"agentVerified": true
},
"issuedAt": "2026-09-13T12:00:00.000Z",
"ttl": 1
}
}
Example request
curl
curl -X POST "https://collect.noxtica.com/v1/agent/passport" \
-H "Authorization: Bearer $NOXTICA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"headers": {
"key": "example-headers"
},
"url": "https://verify.noxtica.com/s/kycs.tenant_42.9f3a8b7c6d5e4f3a2b1c0d9e8f7a6b5c",
"method": "example-method",
"attestation": {
"tier": "hardware",
"anchored": true
},
"riskBand": "critical",
"jti": "example-jti"
}'
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/agent/passport', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
headers: {
key: 'example-headers',
},
url: 'https://verify.noxtica.com/s/kycs.tenant_42.9f3a8b7c6d5e4f3a2b1c0d9e8f7a6b5c',
method: 'example-method',
attestation: {
tier: 'hardware',
anchored: true,
},
riskBand: 'critical',
jti: 'example-jti',
}),
});
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/agent/passport",
data=json.dumps({
"headers": {
"key": "example-headers",
},
"url": "https://verify.noxtica.com/s/kycs.tenant_42.9f3a8b7c6d5e4f3a2b1c0d9e8f7a6b5c",
"method": "example-method",
"attestation": {
"tier": "hardware",
"anchored": True,
},
"riskBand": "critical",
"jti": "example-jti",
}).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())
Verify a Web Bot Auth agent signature
POST /v1/agent/verify
Your backend forwards an incoming request’s signature headers and target URL. Noxtica verifies the Web Bot Auth (RFC 9421) signature and joins the verdict to your organisation’s agent policy registry for an allow or deny decision.
Verification fails open end to end: any fault yields verdict unsigned and matchedAction: null. The endpoint is advisory and never blocks an agent itself. A structurally matched but unverified identity can never receive a binary allow (it is withheld to null), and a graduated allow is downgraded to challenge.
Some response fields are additive and appear only when the matching capability is enabled for your organisation: kya and policyAction (the graduated ladder) and disclosure (EU AI Act Article 50 disclosure, only for a detected or verified agent).
Authentication: Bearer token (ApiKeyAuth), scope read:device.
Request body
Content type application/json, required.
| Field | Type | Required | Description |
|---|---|---|---|
headers | object | Yes | The forwarded Web Bot Auth signature headers (signature-input, signature, signature-agent). |
headers.* | string | No | |
url | string (uri) | No | The signed target URI the signature covers. |
attestation | object | No | |
attestation.tier | string | No | Allowed values: hardware, software, suspicious, unavailable, failed. |
attestation.anchored | boolean | No | |
riskBand | string | No | Allowed values: critical, high, medium, low, minimal. |
jti | string | No | Device-correlated token id; when graduated agent policy is enabled for your organisation and it has registry rows, it completes an agent_attestation_chain link. |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | A resolved Web Bot Auth verdict plus the KYA registry join. | application/json | |
400 | The request failed validation. error is a route-specific string such as Invalid body or invalid_request; read the operation description for the exact values it can return. | Invalid body | 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. |
verdict | string | Yes | Allowed values: verified, unsigned, invalid_signature, expired, replay. |
keyid | string, nullable | Yes | |
signatureAgent | string, nullable | Yes | |
matchedAction | string, nullable | Yes | Allowed values: allow, deny. |
attestation | object | No | Present ONLY when the caller forwarded a valid tier. |
attestation.tier | string | No | Allowed values: hardware, software, suspicious, unavailable, failed. |
attestation.anchored | boolean | No | |
attestation.provenance | string | No | Always caller_attested — this handler never independently corroborates the forwarded tier. Allowed values: caller_attested. |
riskBand | string | No | Allowed values: critical, high, medium, low, minimal. |
kya | object | No | Present only when graduated agent policy is enabled for your organisation. |
kya.verified | boolean | No | |
kya.agentId | string, nullable | No | |
kya.source | string | No | Allowed values: web-bot-auth, skyfire. |
kya.attestationTier | string | No | Allowed values: hardware, software, suspicious, unavailable, failed. |
kya.attestationTierProvenance | string | No | Allowed values: caller_attested. |
kya.cfVerified | boolean | No | Present ONLY on a Cloudflare Verified AI Agents match. |
kya.cfVerifiedName | string | No | |
kya.trustCaveat | string | No | Present ONLY when the identity came from the Skyfire interop adapter. Allowed values: structural_only_signature_unverified. |
disclosure | object | No | Present only when EU AI Act disclosure is enabled for your organisation and a detected or verified agent is present. |
disclosure.required | boolean | No | Allowed values: true. |
disclosure.regulation | string | No | Allowed values: eu_ai_act_art_50. |
disclosure.obligation | string | No | |
disclosure.agentVerified | boolean | No | |
policyAction | string | No | Present only when graduated agent policy is enabled for your organisation and an attributable identity exists — the advisory graduated enforcement tier (Noxtica never acts on it). Allowed values: allow, observe, challenge, deny. |
Response example (200)
{
"ok": true,
"verdict": "verified",
"keyid": "example-keyid",
"signatureAgent": "example-signatureAgent",
"matchedAction": "allow",
"attestation": {
"tier": "hardware",
"anchored": true,
"provenance": "caller_attested"
},
"riskBand": "critical",
"kya": {
"verified": true,
"agentId": "example-agentId",
"source": "web-bot-auth",
"attestationTier": "hardware",
"attestationTierProvenance": "caller_attested",
"cfVerified": true,
"cfVerifiedName": "example-cfVerifiedName",
"trustCaveat": "structural_only_signature_unverified"
},
"disclosure": {
"required": true,
"regulation": "eu_ai_act_art_50",
"obligation": "example-obligation",
"agentVerified": true
},
"policyAction": "allow"
}
Example request
curl
curl -X POST "https://collect.noxtica.com/v1/agent/verify" \
-H "Authorization: Bearer $NOXTICA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"headers": {
"key": "example-headers"
},
"url": "https://verify.noxtica.com/s/kycs.tenant_42.9f3a8b7c6d5e4f3a2b1c0d9e8f7a6b5c",
"attestation": {
"tier": "hardware",
"anchored": true
},
"riskBand": "critical",
"jti": "example-jti"
}'
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/agent/verify', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
headers: {
key: 'example-headers',
},
url: 'https://verify.noxtica.com/s/kycs.tenant_42.9f3a8b7c6d5e4f3a2b1c0d9e8f7a6b5c',
attestation: {
tier: 'hardware',
anchored: true,
},
riskBand: 'critical',
jti: 'example-jti',
}),
});
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/agent/verify",
data=json.dumps({
"headers": {
"key": "example-headers",
},
"url": "https://verify.noxtica.com/s/kycs.tenant_42.9f3a8b7c6d5e4f3a2b1c0d9e8f7a6b5c",
"attestation": {
"tier": "hardware",
"anchored": True,
},
"riskBand": "critical",
"jti": "example-jti",
}).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())
Look up IP reputation
POST /v1/ip/reputation
Returns the reputation verdict {category, source, confidence} for one IP address supplied by your backend, from the same feed Noxtica uses to score its own submissions. When the address is not listed the response is listed: false; an unlisted address, an empty feed or a lookup fault never produces an error.
The queried IP is echoed back in the response, which is your own input. It is not stored or logged unless your organisation has opted into raw-IP storage; only the derived verdict is retained. A served call counts toward your organisation’s IP-reputation usage.
Authentication: Bearer token (ApiKeyAuth), scope read:device.
Request body
Content type application/json, required.
| Field | Type | Required | Description |
|---|---|---|---|
ip | string | Yes | An IPv4 dotted-quad or IPv6 address. |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | The derived reputation verdict, or listed:false when the engine has no signal. | application/json | |
400 | The request failed validation. error is a route-specific string such as Invalid body or invalid_request; read the operation description for the exact values it can return. | Invalid body | 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. |
ip | string | Yes | Echoed back verbatim (the caller’s own query input). |
listed | boolean | Yes | |
verdict | object | No | Present ONLY when listed is true. |
verdict.category | string | No | |
verdict.source | string | No | |
verdict.confidence | number | No |
Response example (200)
{
"ok": true,
"ip": "example-ip",
"listed": true,
"verdict": {
"category": "example-category",
"source": "example-source",
"confidence": 1
}
}
Example request
curl
curl -X POST "https://collect.noxtica.com/v1/ip/reputation" \
-H "Authorization: Bearer $NOXTICA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ip": "example-ip"
}'
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/ip/reputation', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
ip: 'example-ip',
}),
});
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/ip/reputation",
data=json.dumps({
"ip": "example-ip",
}).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())
Score a signals payload
POST /v1/score
Scores an already-collected signals payload from your backend and returns the headline score, confidence, tier and flags that the Noxtica collector computes for its own submissions. Call it from a server with an sk_ key that carries the read:device scope (the default scope of every key); never from a browser.
Request. Send either the complete enriched payload {"signals": {...}, "serverSideSignals": {...}, ...} or, for older callers, the inner signals object on its own, which is wrapped once. The body may be up to 256 KiB. The raw payload is not logged.
Response. score is the engine’s score, tier its risk band and flags the flags that fired. confidence is an integer from 0 to 100 (50 is neutral). attestation is always null on this route: the payload is supplied by the caller and cannot be trusted as hardware evidence, so no hardware-verification claim is ever made.
Allowance. Calls within your plan’s monthly Score API allowance are included. Beyond it the score is still served and the response carries a nudge object with code: free_tier_exceeded; the score is never withheld.
Errors. 400 Invalid body when the body is not a JSON object or is larger than 256 KiB, and Invalid signals when signals is present but is not an object; 401, 403, 429 and 500 as on every key-authenticated route. No 413 is returned: an oversized body is a 400.
Authentication: Bearer token (ApiKeyAuth), scope read:device.
Request body
Content type application/json, required.
Type: object.
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | The re-scored verdict, mapped onto the public 0-100 confidence scale. | application/json | |
400 | The request failed validation. error is a route-specific string such as Invalid body or invalid_request; read the operation description for the exact values it can return. | Invalid body | 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. |
score | number | Yes | |
confidence | integer | Yes | Normalised to 0-100 (the engine’s native value is 0-1), matching /v1/attest/verify’s scale. Minimum 0, maximum 100. |
tier | string | Yes | The engine’s riskLevel band. |
flags | array of string | Yes | |
attestation | any, nullable | Yes | ALWAYS null on this surface — see the honesty-gate note above. |
nudge | object | No | Present ONLY once the free monthly allotment is exceeded — informational only, never withholds the score. |
nudge.code | string | No | Allowed values: free_tier_exceeded. |
nudge.message | string | No | |
nudge.free_serving_allotment | integer | No |
Response example (200)
{
"ok": true,
"score": 1,
"confidence": 0,
"tier": "example-tier",
"flags": [
"example-flags"
],
"attestation": null,
"nudge": {
"code": "free_tier_exceeded",
"message": "example-message",
"free_serving_allotment": 1
}
}
Example request
curl
curl -X POST "https://collect.noxtica.com/v1/score" \
-H "Authorization: Bearer $NOXTICA_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/score', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
});
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/score",
data=json.dumps({}).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())
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.