Protected actions API
Redeem the request id a browser obtains for a protected action (login, signup, checkout) before your server lets the action proceed.
A protected action works in two steps. Your browser integration declares an action (for example login) and receives a requestId; your backend then redeems that id once with POST /v1/protected-actions/verify and decides whether the action may proceed. Use an sk_ server API key with the read:device scope, the default scope of every key, from your own server. Base URL: https://collect.noxtica.com.
| Environment | Base URL |
|---|---|
| Production | https://collect.noxtica.com |
Credential: ApiKeyAuth. See Authentication.
Protected Actions
1 operation
- POST /v1/protected-actions/verify Redeem a protected-action request id
Redeem a protected-action request id
POST /v1/protected-actions/verify
Call this from your backend before letting a protected action (login, sign-up, checkout, …) proceed. Send the requestId your browser integration obtained for the action: the resolved value of noxticaClient.protect(name), the hidden nox_request_id input of a data-nox-protect form, or form.dataset.noxRequestId / the noxtica:protected event of a script-submitted form. Use an sk_ key with the read:device scope, the default scope of every key.
Single use inside a 300-second window. The first valid call consumes the receipt and answers 200 with consumed: true. A replay inside the window is 409 already_consumed. An unknown, expired or other-organisation id is 404 request_not_found, and after 300 seconds every id is 404.
Binding. action is required: an id minted for login never satisfies a checkout check (422 action_mismatch; the receipt is not consumed). expectedDomain (a hostname, origin or URL) additionally binds the page that ran the action (422 domain_mismatch; not consumed). A request that arrives without a requestId (a fail-open timeout, a blocked script) means “no client signal”, never a pass.
Result. verdict is the risk band (minimal, low, medium, high, critical); score (higher is riskier) and confidence (0 to 1) are the values the browser received, and this server-to-server call always returns them. The request body is limited to 2048 bytes. A 500 carries no ok field and never consumes the receipt.
Authentication: Bearer token (ApiKeyAuth), scope read:device.
Request body
Content type application/json, required.
| Field | Type | Required | Description |
|---|---|---|---|
requestId | string (uuid) | Yes | The protected submission’s request id. |
action | string | Yes | The protected action name the tenant expects (login, checkout, …). Pattern ^[a-z][a-z0-9_-]{0,31}$. |
expectedDomain | string | No | Optional hostname/origin/URL the action must have run on. At most 269 characters. |
Responses
| Status | Meaning | Error codes | Content |
|---|---|---|---|
200 | The receipt was valid and has now been consumed. | application/json | |
400 | invalid_body, invalid_request_id, invalid_action or invalid_expected_domain. | invalid_action, invalid_body, invalid_expected_domain, invalid_request_id | application/json ProtectedActionVerifyError |
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 | request_not_found — unknown, expired (older than 300 s) or minted for another tenant. | request_not_found | application/json ProtectedActionVerifyError |
409 | already_consumed — the receipt was already redeemed (replay inside the window). | already_consumed | application/json ProtectedActionVerifyError |
413 | payload_too_large — over the 2048-byte cap. | payload_too_large | application/json ProtectedActionVerifyError |
422 | action_mismatch or domain_mismatch — the receipt is NOT consumed. | action_mismatch, domain_mismatch | application/json ProtectedActionVerifyError |
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. |
requestId | string (uuid) | Yes | |
action | string | Yes | |
fingerprintId | string | Yes | The device id of the protected submission (the same value the dashboard keys on). |
verdict | string | Yes | Allowed values: minimal, low, medium, high, critical. |
score | number | Yes | |
confidence | number | Yes | Minimum 0, maximum 1. |
domain | string | Yes | Hostname of the page that ran the action. |
issuedAt | string (date-time) | Yes | |
expiresAt | string (date-time) | Yes | |
consumed | boolean | Yes | This call consumed the receipt. Allowed values: true. |
consumedAt | string (date-time) | Yes |
Response example (200)
{
"ok": true,
"requestId": "8f1c2a52-6a6e-4b0e-9d52-2a7c1d9e4b10",
"action": "login",
"fingerprintId": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
"verdict": "low",
"score": 12,
"confidence": 0.91,
"domain": "www.example.com",
"issuedAt": "2026-10-08T10:00:00.000Z",
"expiresAt": "2026-10-08T10:05:00.000Z",
"consumed": true,
"consumedAt": "2026-10-08T10:00:02.000Z"
}
Example request
curl
curl -X POST "https://collect.noxtica.com/v1/protected-actions/verify" \
-H "Authorization: Bearer $NOXTICA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"requestId": "8f1c2a52-6a6e-4b0e-9d52-2a7c1d9e4b10",
"action": "login",
"expectedDomain": "www.example.com"
}'
Node.js (global fetch, Node 18 or later)
const response = await fetch('https://collect.noxtica.com/v1/protected-actions/verify', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NOXTICA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
requestId: '8f1c2a52-6a6e-4b0e-9d52-2a7c1d9e4b10',
action: 'login',
expectedDomain: 'www.example.com',
}),
});
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/protected-actions/verify",
data=json.dumps({
"requestId": "8f1c2a52-6a6e-4b0e-9d52-2a7c1d9e4b10",
"action": "login",
"expectedDomain": "www.example.com",
}).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.