The TRACIO Server API lets you query identification events, look up a visitor's history, and manage webhooks. It is workspace-scoped and authenticated with the dashboard session (Clerk) — this is the same API the TRACIO dashboard uses internally.
Note: There is no separate API-only host and no standalone API secret. The API is served on the application host (for example
https://app.tracio.ai/api/v1) and authenticated with your Clerk session JWT. Every request is additionally checked against your workspace role (RBAC), so a token only grants access to data in workspaces you can see.
https://app.tracio.ai/api/v1All event, visitor, and webhook endpoints are scoped to a workspace and live
under /subscriptions/:id, where :id is the workspace ID.
Send your Clerk session JWT in the Authorization header:
curl -H "Authorization: Bearer <clerk-session-jwt>" \ https://app.tracio.ai/api/v1/subscriptions/:id/events/:requestIdThe caller must have the analytics-read permission for the target workspace.
Requests that pass authentication but lack the permission return 403.
| Method | Path | Description |
|---|---|---|
GET | /subscriptions/:id/events/:requestId | Fetch a single event |
GET | /subscriptions/:id/events/search | Search / list events |
GET | /subscriptions/:id/visitors/:visitorId | Fetch a visitor's history |
GET | /subscriptions/:id/webhooks | List webhooks |
POST | /subscriptions/:id/webhooks | Create a webhook |
GET | /subscriptions/:id/webhooks/:webhookId | Fetch a webhook |
PUT | /subscriptions/:id/webhooks/:webhookId | Update a webhook |
DELETE | /subscriptions/:id/webhooks/:webhookId | Delete a webhook |
POST | /subscriptions/:id/webhooks/:webhookId/test | Send a test delivery |
GET | /subscriptions/:id/webhooks/:webhookId/deliveries | List recent delivery attempts |
See Webhooks for the webhook payload and signature details.
Every response is wrapped in a consistent envelope.
Success:
{ "ok": true, "data": {}}Error:
{ "ok": false, "error": { "code": "not_found", "message": "event not found" }}Retrieve a single identification event by its request ID.
Request:
GET /api/v1/subscriptions/:id/events/1710432000_abc123defAuthorization: Bearer <clerk-session-jwt>Response (200 OK):
{ "ok": true, "data": { "request_id": "1710432000_abc123def", "visitor_id": "X7fh2Hg9LkMn3pQr", "workspace_id": "b201f2ba-…", "timestamp": "2024-03-12T16:00:00Z", "url": "https://your-app.com/login", "ip": "94.142.239.124", "user_agent": "Mozilla/5.0 …", "confidence": 0.95, "bot_result": "human", "bot_type": "", "browser_name": "Chrome", "browser_version": "120.0", "os_name": "macOS", "os_version": "14.3", "device_type": "desktop", "incognito": false, "ip_country": "CZ", "ip_city": "Prague", "ip_latitude": 50.05, "ip_longitude": 14.4, "ip_timezone": "Europe/Prague", "linked_id": "user_12345", "tag": "login", "origin": "https://your-app.com", "raw_signals": "{ … }" }}The event object is a flat, snake_case JSON document. Fields that have no
value are returned as their zero value (empty string, 0, or false).
Note: Richer decision, risk, and network signal fields —
decision,risk_score,suspect_score,bot_score, and theis_vpn/is_proxy/is_tor/is_datacenterflags — are present in this read-API JSON, but the read query does not currently populate them, so they always serialize as their zero/empty values. The populated, enriched values are delivered through webhooks, which carry the full payload.
| Field | Type | Description |
|---|---|---|
request_id | string | Unique event identifier |
visitor_id | string | Stable visitor identifier |
workspace_id | string | Workspace the event belongs to |
timestamp | string | Event time (RFC 3339) |
url | string | Page URL where the event was captured |
ip | string | Client IP address |
user_agent | string | Raw client user-agent string |
confidence | number | Model confidence in the identification (0.0–1.0) |
bot_result | string | human, bot, or uncertain |
bot_type | string | Free-form automation label when a bot is detected (e.g. selenium) |
browser_name | string | Detected browser name |
browser_version | string | Detected browser version |
os_name | string | Operating system name |
os_version | string | Operating system version |
device_type | string | Device class (e.g. desktop, mobile) |
incognito | boolean | Whether the visit was in a private/incognito context |
ip_country | string | Country derived from IP |
ip_city | string | City derived from IP |
ip_latitude | number | Latitude derived from IP |
ip_longitude | number | Longitude derived from IP |
ip_timezone | string | IANA timezone derived from IP |
linked_id | string | Linked identifier supplied by the client |
tag | string | Custom tag supplied by the client |
origin | string | Origin header of the originating request |
raw_signals | string | Opaque JSON blob of advanced/raw detection signals (see below) |
raw_signals is an opaque, advanced field: a serialized blob of raw detection
signals used by the dashboard's deeper analysis views. Its shape is not stable
and is intended for internal analysis — integrate against the core fields above,
not against raw_signals.
The decision, risk, and network signal fields (decision, risk_score,
suspect_score, bot_score, is_vpn, is_proxy, is_tor, is_datacenter)
are present in this read-API JSON, but the read query does not currently populate
them — they always serialize as their zero/empty values here. The table above
lists the fields that carry meaningful values from this endpoint. To consume the
populated decision, risk, and network signals, use webhooks.
List events for the workspace, most recent first.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | number | 50 | Page size. Values outside 1–500 fall back to 50. |
visitorId | string | – | Filter by visitor ID |
requestId | string | – | Filter by request ID |
ip | string | – | Filter by IP address |
url | string | – | Substring match on the page URL |
linkedId | string | – | Filter by linked identifier |
since | string | – | Lower time bound (RFC 3339) |
before | string | – | Upper time bound (RFC 3339) |
paginationKey | string | – | Cursor from the previous page (RFC 3339 timestamp) |
Response (200 OK):
{ "ok": true, "data": { "events": [{ "request_id": "…", "visitor_id": "…", "timestamp": "…", "bot_result": "human" }], "paginationKey": "2024-03-12T15:59:58.123456789Z" }}Each object in events has the same full shape as the single-event
endpoint above (all core fields including raw_signals); the example here is
abbreviated for brevity — search is not a lightweight projection.
Results are ordered by timestamp descending. To fetch the next page, pass the
returned paginationKey back as the paginationKey query parameter. When the
last page is reached, paginationKey is omitted from the response.
Retrieve the recent event history for a single visitor. The response uses the
same event object as GET /events/:requestId.
Errors use the standard envelope with a machine-readable code.
{ "ok": false, "error": { "code": "forbidden", "message": "insufficient permissions" }}{ "ok": false, "error": { "code": "not_found", "message": "event not found" }}{ "ok": false, "error": { "code": "internal", "message": "internal server error" }}