Prynt API Reference
The Prynt API is organized around REST. All requests and responses use JSON. Authentication is via Bearer token. All API access is over HTTPS.
All endpoints require authentication. Include your secret key as a Bearer token in the Authorization header. The API returns standard HTTP status codes and JSON error bodies.
Authentication
The Prynt API uses Bearer token authentication. Include your secret key in the Authorization header of every request. Secret keys carry full API access — keep them secure.
API Keys
| Key | Prefix | Usage |
|---|---|---|
| Public Key | pk_live_ | Used in client SDKs (browser / mobile). Safe to expose in frontend code. |
| Secret Key | sk_live_ | Used server-side for the REST API. Never expose in client code or version control. |
| Test Keys | pk_test_ / sk_test_ | Sandbox environment. No real data. Use for development and testing. |
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/jsoncurl https://api.prynt.io/v1/events/req_xxx \ -H "Authorization: Bearer sk_live_xxx..."
import { PryntServer } from '@prynt/node'; const prynt = new PryntServer({ secretKey: process.env.PRYNT_SECRET_KEY, });
Errors
Prynt uses standard HTTP status codes. Errors include a JSON body with code, message, and docs link.
Status codes
| Code | Meaning | Description |
|---|---|---|
| 200 | OK | Request succeeded. |
| 400 | Bad Request | Invalid parameters, malformed JSON, or missing required field. |
| 401 | Unauthorized | Missing, invalid, or revoked API key. |
| 403 | Forbidden | Valid key but insufficient permissions for this endpoint. |
| 404 | Not Found | Resource doesn't exist or has expired. |
| 429 | Rate Limited | Too many requests. Check Retry-After header. |
| 500 | Server Error | Internal error. Retry with exponential backoff. |
{
"error": {
"code": "invalid_api_key",
"message": "The API key is invalid or revoked.",
"docs": "https://docs.prynt.io/errors#invalid_api_key"
}
}{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded. Retry in 12s.",
"retryAfter": 12
}
}{
"error": {
"code": "validation_error",
"message": "'conditions' is required.",
"field": "conditions"
}
}📥Events
Events represent individual identification requests. Each call to identify() creates one event linked to a persistent visitor ID.
Retrieve an event
Returns the full identification result for a single event — visitor ID, device data, Smart Signals, ML scores, and rules engine verdict. This is the primary endpoint for server-side verification.
Path parameters
| Parameter | Type | Description |
|---|---|---|
| requestIdRequired | string | The request ID returned by identify(). Format: req_* |
Response fields
allow, challenge, or block. Null if no rules are configured.const event = await prynt.getEvent( 'req_1707832921_a7f2c9' );
curl https://api.prynt.io/v1/events/req_1707832921_a7f2c9 \ -H "Authorization: Bearer sk_live_..."
{
"requestId": "req_1707832921_a7f2c9",
"visitorId": "pv_8kX2mNqR3jT7p",
"confidence": 0.995,
"visitorFound": true,
"firstSeenAt": "2025-09-14T08:12:33Z",
"timestamp": "2026-02-14T11:45:22Z",
"device": {
"platform": "macOS",
"browser": "Chrome 121",
"gpu": "Apple M3 Pro",
"type": "desktop",
"screen": { "w": 2560, "h": 1440 }
},
"signals": {
"bot": { "detected": false },
"vpn": { "detected": false,
"provider": null },
"incognito": false,
"tampered": false,
"emulator": false,
"proxy": false,
"tor": false
},
"scores": {
"abuse": 0.03,
"ato": 0.01,
"bot": 0.02,
"suspect": 4
},
"ip": {
"address": "73.162.xxx.xxx",
"city": "San Francisco",
"country": "US",
"lat": 37.7749,
"lng": -122.4194,
"blocklisted": false
},
"verdict": "allow",
"matchedRule": null
}List events
Returns a paginated list of identification events with optional filters for visitor ID, time range, verdict, and signals.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| visitorId | string | Filter by visitor ID. |
| before | datetime | Return events before this ISO 8601 timestamp. |
| after | datetime | Return events after this timestamp. |
| verdict | string | Filter by verdict. allowchallengeblock |
| bot | boolean | Filter for bot-detected events. |
| limit | integer | Number of results. Max 100.Default: 20 |
| cursor | string | Pagination cursor from previous response. |
curl "https://api.prynt.io/v1/events?verdict=block&limit=10" \ -H "Authorization: Bearer sk_live_..."
{
"data": [
{
"requestId": "req_170783...",
"visitorId": "pv_8kX2mN...",
"confidence": 0.995,
"verdict": "block",
"timestamp": "2026-02-14T11:45Z"
}
],
"hasMore": true,
"nextCursor": "cur_abc123..."
}👤Visitors
A visitor represents a unique device identified by Prynt. Each visitor has a persistent ID and history of all events.
Retrieve a visitor
Returns visitor metadata — first seen date, total visit count, device breakdown, associated IPs, and linked accounts.
Response fields
const visitor = await prynt.getVisitor( 'pv_8kX2mNqR3jT7p' );
{
"visitorId": "pv_8kX2mNqR3jT7p",
"firstSeenAt": "2025-09-14T08:12Z",
"lastSeenAt": "2026-02-14T11:45Z",
"totalVisits": 347,
"linkedAccounts": 1,
"devices": [
{
"platform": "macOS",
"browser": "Chrome 121",
"gpu": "Apple M3 Pro"
}
],
"lists": ["trusted_devices"]
}Delete visitor data
Permanently deletes all data associated with a visitor — events, device profiles, list memberships, and analytics. This action is irreversible and intended for GDPR/CCPA data deletion requests.
curl -X DELETE \ https://api.prynt.io/v1/visitors/pv_8kX2mN... \ -H "Authorization: Bearer sk_live_..."
{
"deleted": true,
"visitorId": "pv_8kX2mNqR3jT7p"
}List visitor events
Returns a paginated list of events for a specific visitor. Use this to view the full history of identifications for a single device.
Path parameters
| Parameter | Type | Description |
|---|---|---|
| visitorIdRequired | string | The visitor ID. Format: pv_* |
Query parameters
| Parameter | Type | Description |
|---|---|---|
| before | datetime | Return events before this ISO 8601 timestamp. |
| after | datetime | Return events after this timestamp. |
| limit | integer | Number of results. Max 100.Default: 20 |
curl "https://api.prynt.io/v1/visitors/pv_8kX2mN.../events?limit=5" \ -H "Authorization: Bearer sk_live_..."
{
"data": [
{
"requestId": "req_1707832921_a7f2c9",
"timestamp": "2026-02-14T11:45:22Z",
"verdict": "allow",
"signals": {
"bot": false,
"vpn": false
}
},
{
"requestId": "req_1707745678_b8c3d2",
"timestamp": "2026-02-14T09:12:15Z",
"verdict": "allow",
"signals": {
"bot": false,
"vpn": false
}
}
],
"hasMore": true
}⚖️Rules
Rules evaluate conditions against identification events and return verdicts (allow, challenge, block) with optional side effects. Rules are evaluated in priority order.
Create a rule
Creates a new rule with conditions, action, optional side effects, and status. Rules propagate globally in under 2 seconds after creation.
Request body
| Field | Type | Description |
|---|---|---|
| nameRequired | string | Human-readable rule name. Max 200 chars. |
| conditionsRequired | object | Condition tree with operator (AND/OR) and rules array. Each rule has field, operator, value. |
| actionRequired | object | Action config with verdict and optional sideEffects array. |
| priority | integer | Evaluation order. Lower = evaluated first.Default: next available |
| status | string | Activation status. livemonitordraft Default: draft |
Condition operators
Side effect types
{
"name": "Block Bot Registrations",
"priority": 1,
"status": "live",
"conditions": {
"operator": "AND",
"rules": [
{
"field": "signals.bot.detected",
"operator": "equals",
"value": true
},
{
"field": "event.type",
"operator": "equals",
"value": "registration"
}
]
},
"action": {
"verdict": "block",
"sideEffects": [
{
"type": "addToList",
"list": "blocked_devices"
}
]
}
}{
"id": "rul_9a7f2c4e8b1d",
"name": "Block Bot Registrations",
"priority": 1,
"status": "live",
"createdAt": "2026-02-14T14:30:00Z",
"propagated": true,
"propagationMs": 1840
}List rules
Returns all rules for the workspace, ordered by priority. Filter by status to view only active, paused, or draft rules.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| status | string | Filter by rule status. livepauseddraft |
| limit | integer | Number of results to return. |
| offset | integer | Pagination offset. |
curl "https://api.prynt.io/v1/rules?status=live" \ -H "Authorization: Bearer sk_live_..."
{
"data": [
{
"id": "rul_9a7f2c4e8b1d",
"name": "Block Bot Registrations",
"status": "live",
"priority": 1,
"conditions": {
"operator": "AND",
"count": 2
},
"action": "block",
"createdAt": "2026-02-14T14:30:00Z"
},
{
"id": "rul_8b6c3d2e9f1a",
"name": "Challenge High-Risk Logins",
"status": "live",
"priority": 2,
"conditions": {
"operator": "OR",
"count": 3
},
"action": "challenge",
"createdAt": "2026-02-13T10:15:00Z"
}
]
}Retrieve a rule
Returns a single rule with full conditions and metadata, including match statistics.
Path parameters
| Parameter | Type | Description |
|---|---|---|
| ruleIdRequired | string | The rule ID. Format: rul_* |
Response fields
curl https://api.prynt.io/v1/rules/rul_9a7f... \ -H "Authorization: Bearer sk_live_..."
{
"id": "rul_9a7f2c4e8b1d",
"name": "Block Bot Registrations",
"status": "live",
"priority": 1,
"conditions": {
"operator": "AND",
"rules": [
{
"field": "signals.bot.detected",
"operator": "equals",
"value": true
},
{
"field": "event.type",
"operator": "equals",
"value": "registration"
}
]
},
"action": {
"verdict": "block",
"sideEffects": [
{
"type": "addToList",
"list": "blocked_devices"
}
]
},
"stats": {
"matchCount": 8429,
"lastMatchAt": "2026-02-14T14:22:10Z"
},
"createdAt": "2026-02-14T14:30:00Z"
}Update a rule
Updates a rule. Partial updates are supported — only include the fields you want to change. Changes propagate globally in approximately 2 seconds.
Path parameters
| Parameter | Type | Description |
|---|---|---|
| ruleIdRequired | string | The rule ID. Format: rul_* |
Request body
| Field | Type | Description |
|---|---|---|
| name | string | Human-readable rule name. Max 200 chars. |
| conditions | object | Condition tree with operator and rules array. |
| action | object | Action config with verdict and optional sideEffects. |
| priority | integer | Evaluation order. Lower = evaluated first. |
| status | string | Activation status. livemonitordraft |
| sideEffects | array | Side effects array. Replaces existing side effects entirely. |
curl -X PUT \ https://api.prynt.io/v1/rules/rul_9a7f... \ -H "Authorization: Bearer sk_live_..." \ -d '{ "status": "paused" }'
{
"id": "rul_9a7f2c4e8b1d",
"name": "Block Bot Registrations",
"status": "paused",
"priority": 1,
"updatedAt": "2026-02-14T15:00:00Z",
"propagated": true,
"propagationMs": 1920
}Delete a rule
Permanently deletes a rule. Active rules are disabled immediately and the rule is removed from the evaluation engine. This action is irreversible.
Path parameters
| Parameter | Type | Description |
|---|---|---|
| ruleIdRequired | string | The rule ID. Format: rul_* |
curl -X DELETE \ https://api.prynt.io/v1/rules/rul_9a7f... \ -H "Authorization: Bearer sk_live_..."
{
"deleted": true,
"ruleId": "rul_9a7f2c4e8b1d"
}Backtest a rule
Runs a rule against up to 30 days of historical events. Returns match count, affected visitors, estimated false positive rate, and a sample of matched events. Use to validate rules before enabling in production.
Request body
| Field | Type | Description |
|---|---|---|
| days | integer | Number of days to look back. Max 30.Default: 7 |
| sampleSize | integer | Number of matched events to include in sample. Max 100.Default: 10 |
Response fields
curl -X POST \ https://api.prynt.io/v1/rules/rul_9a7f.../backtest \ -H "Authorization: Bearer sk_live_..." \ -d '{ "days": 30, "sampleSize": 5 }'
{
"ruleId": "rul_9a7f2c4e8b1d",
"matchCount": 84291,
"totalEvents": 1284102,
"matchRate": 0.0656,
"estimatedFpr": 0.0002,
"days": 30,
"durationMs": 4821,
"sample": [
{
"requestId": "req_170xxx...",
"visitorId": "pv_4xN7q...",
"timestamp": "2026-02-13T09:12Z"
}
]
}📋Lists
Dynamic allow/block/review lists. Reference lists in rule conditions to build persistent policies. Entries are visitor IDs, IP addresses, or emails.
Create a list
Creates a new list for grouping visitors, IPs, or emails. Use in rule conditions with the in and not_in operators.
Request body
| Field | Type | Description |
|---|---|---|
| nameRequired | string | List name. Used in rule conditions. Must be unique, snake_case recommended. |
| typeRequired | string | Entry type. visitoripemail |
| description | string | Optional description for dashboard display. |
| ttl | integer | Auto-expire entries after this many seconds. Null for permanent.Default: null |
{
"name": "blocked_devices",
"type": "visitor",
"description": "Confirmed fraud devices",
"ttl": 2592000
}{
"id": "lst_f3a8c7d2e1b4",
"name": "blocked_devices",
"type": "visitor",
"entryCount": 0,
"ttl": 2592000,
"createdAt": "2026-02-14T15:00Z"
}List all lists
Returns all lists in the workspace. Filter by type to view only visitor, IP, or email lists.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| type | string | Filter by list type. visitoripemail |
| limit | integer | Number of results to return. |
| offset | integer | Pagination offset. |
curl "https://api.prynt.io/v1/lists?type=visitor" \ -H "Authorization: Bearer sk_live_..."
{
"data": [
{
"id": "lst_f3a8c7d2e1b4",
"name": "blocked_devices",
"type": "visitor",
"entryCount": 47,
"createdAt": "2026-02-14T15:00Z",
"updatedAt": "2026-02-14T16:30Z"
},
{
"id": "lst_a2b3c4d5e6f7",
"name": "trusted_devices",
"type": "visitor",
"entryCount": 128,
"createdAt": "2026-02-10T09:15Z",
"updatedAt": "2026-02-14T12:10Z"
}
]
}Update list entries
Add or remove entries from a list in a single atomic operation. Supports bulk operations up to 1,000 entries per call.
Request body
| Field | Type | Description |
|---|---|---|
| add | array | Entries to add. Each entry is a string (visitor ID, IP, or email depending on list type). |
| remove | array | Entries to remove. |
{
"add": [
"pv_8kX2mNqR3jT7p",
"pv_4xN7qKmR9sT2p"
],
"remove": [
"pv_old_visitor_id"
]
}{
"listId": "lst_f3a8c7d2e1b4",
"added": 2,
"removed": 1,
"entryCount": 47
}Delete a list
Permanently deletes a list and all its entries. Rules referencing this list will no longer match on list conditions.
Path parameters
| Parameter | Type | Description |
|---|---|---|
| listIdRequired | string | The list ID to delete. Format: lst_* |
curl -X DELETE \ https://api.prynt.io/v1/lists/lst_f3a8c7... \ -H "Authorization: Bearer sk_live_..."
{
"deleted": true,
"listId": "lst_f3a8c7d2e1b4"
}🔔Webhooks
Receive real-time event notifications via HTTP POST. Configure filters to subscribe only to events matching specific verdicts, signals, or rules.
Create a webhook
Registers an endpoint to receive real-time event data. Prynt signs every payload with HMAC-SHA256 for verification. Failed deliveries are retried with exponential backoff for up to 24 hours.
Request body
| Field | Type | Description |
|---|---|---|
| urlRequired | string | Endpoint URL. Must be HTTPS. |
| events | array | Event types to subscribe to. identificationrule_matchlist_update Default: all events |
| filters | object | Optional filters: verdict, ruleId, botDetected. Only matching events are sent. |
| secret | string | Signing secret for HMAC-SHA256 payload verification. Auto-generated if omitted. |
X-Prynt-Signature header. Verify the HMAC-SHA256 digest of the raw body against this header using your signing secret.{
"url": "https://your-app.com/webhooks/prynt",
"events": ["identification", "rule_match"],
"filters": {
"verdict": ["block", "challenge"]
}
}{
"id": "whk_c4e8b1d2a7f3",
"url": "https://your-app.com/...",
"status": "active",
"secret": "whsec_xxxxxxxxxxxxxx",
"createdAt": "2026-02-14T15:30Z"
}{
"event": "identification",
"timestamp": "2026-02-14T15:32Z",
"data": {
"requestId": "req_170xxx...",
"visitorId": "pv_8kX2mN...",
"verdict": "block",
"signals": { /* ... */ }
}
}List webhooks
Returns all configured webhooks for the workspace.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| status | string | Filter by status. activepaused |
| limit | integer | Number of results. Max 100.Default: 20 |
| offset | integer | Offset for pagination.Default: 0 |
curl "https://api.prynt.io/v1/webhooks?status=active" \ -H "Authorization: Bearer sk_live_..."
{
"data": [
{
"id": "whk_c4e8b1d2a7f3",
"url": "https://your-app.com/...",
"events": ["identification", "rule_match"],
"status": "active",
"createdAt": "2026-02-14T15:30Z",
"lastDeliveryAt": "2026-02-15T07:12Z",
"successRate": 0.998
}
],
"total": 1
}Update a webhook
Updates a webhook configuration. Use to change URL, events filter, or pause/resume delivery.
Path parameters
| Parameter | Type | Description |
|---|---|---|
| idRequired | string | The webhook ID. Format: whk_* |
Request body
| Field | Type | Description |
|---|---|---|
| url | string | New endpoint URL. Must be HTTPS. |
| events | array | Event types to subscribe to. |
| status | string | Set to paused to stop deliveries or active to resume.activepaused |
{
"status": "paused"
}{
"id": "whk_c4e8b1d2a7f3",
"url": "https://your-app.com/...",
"events": ["identification", "rule_match"],
"status": "paused",
"updatedAt": "2026-02-15T07:45Z"
}Delete a webhook
Permanently removes a webhook. Pending deliveries are cancelled immediately.
Path parameters
| Parameter | Type | Description |
|---|---|---|
| idRequired | string | The webhook ID to delete. Format: whk_* |
curl -X DELETE \ https://api.prynt.io/v1/webhooks/whk_c4e8b1... \ -H "Authorization: Bearer sk_live_..."
{
"deleted": true,
"webhookId": "whk_c4e8b1d2a7f3"
}📊Analytics
Query aggregated event and signal data for dashboards, reporting, and trend analysis. Supports time-series, grouping, and filtering.
Query event analytics
Returns aggregated event counts and metrics over a time range. Group by verdict, platform, country, or hour/day/week. Use for building dashboards and monitoring fraud trends.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| fromRequired | datetime | Start of time range (ISO 8601). |
| toRequired | datetime | End of time range. |
| groupBy | string | Aggregation dimension. verdictplatformcountryhourdayweek |
| verdict | string | Filter by verdict. |
curl "https://api.prynt.io/v1/analytics/events?from=2026-02-07T00:00:00Z&to=2026-02-14T00:00:00Z&groupBy=verdict" \ -H "Authorization: Bearer sk_live_..."
{
"from": "2026-02-07T00:00Z",
"to": "2026-02-14T00:00Z",
"total": 1284102,
"groups": [
{
"verdict": "allow",
"count": 1187421,
"pct": 0.9247
},
{
"verdict": "challenge",
"count": 48290,
"pct": 0.0376
},
{
"verdict": "block",
"count": 48391,
"pct": 0.0377
}
]
}Query Smart Signal analytics
Returns aggregated Smart Signal detection counts and trends. Use for monitoring signal distribution and detection rates.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| fromRequired | datetime | Start of time range (ISO 8601). |
| toRequired | datetime | End of time range. |
| groupBy | string | Aggregation dimension. signaldayweek |
| signal | string | Filter by specific signal name (e.g., bot, vpn, incognito). |
curl "https://api.prynt.io/v1/analytics/signals?from=2026-02-01T00:00:00Z&to=2026-02-15T00:00:00Z&groupBy=signal" \ -H "Authorization: Bearer sk_live_..."
{
"from": "2026-02-01T00:00Z",
"to": "2026-02-15T00:00Z",
"total": 2847291,
"signals": [
{
"signal": "bot",
"count": 84291,
"rate": 0.0296,
"trend": "up"
},
{
"signal": "vpn",
"count": 12847,
"rate": 0.0045,
"trend": "stable"
},
{
"signal": "incognito",
"count": 47821,
"rate": 0.0168,
"trend": "down"
}
]
}Rate Limits
The API enforces per-key rate limits to ensure fair usage. Limits vary by plan and endpoint. Rate limit status is included in response headers.
Response headers
| Header | Description |
|---|---|
| X-RateLimit-Limit | Maximum requests allowed per window. |
| X-RateLimit-Remaining | Requests remaining in current window. |
| X-RateLimit-Reset | Unix timestamp when the window resets. |
| Retry-After | Seconds to wait before retrying (only on 429 responses). |
Limits by plan
| Plan | Server API | Mgmt API | Burst |
|---|---|---|---|
| Starter | 100/min | 30/min | 10/sec |
| Pro | 1,000/min | 100/min | 50/sec |
| Enterprise | Custom | Custom | Custom |
HTTP/1.1 200 OK X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 847 X-RateLimit-Reset: 1707836400 Content-Type: application/json
async function withRetry(fn, retries = 3) { for (let i = 0; i < retries; i++) { try { return await fn(); } catch (e) { if (e.status !== 429) throw e; const wait = e.retryAfter ?? 2 ** i; await new Promise( r => setTimeout(r, wait * 1000) ); } } }