|
| 1 | +--- |
| 2 | +title: API |
| 3 | +description: The Prosopo HTTP API — every endpoint you can call from outside the portal, what it is for, and what it expects. |
| 4 | +i18nReady: false |
| 5 | +--- |
| 6 | + |
| 7 | +Anything you can do on the portal you can also do over HTTP: provision site keys, write access control |
| 8 | +rules, pull traffic and audit data, manage team members and configure Prosopo Protect. This page lists |
| 9 | +every endpoint that is reachable from outside Prosopo and what each one is for. |
| 10 | + |
| 11 | +**Base URL:** `https://api.prosopo.io` |
| 12 | + |
| 13 | +## Two kinds of credential |
| 14 | + |
| 15 | +| | Credential | Endpoints | |
| 16 | +|---|---|---| |
| 17 | +| **Verification** | Your site's **secret key**, in the request body | [`/siteverify`](/en/basics/server-side-verification/) | |
| 18 | +| **Management API** | An **API key**, in the `Authorization` header | Everything on this page | |
| 19 | + |
| 20 | +Verification is the hot path your backend calls once per form submission, so it is deliberately separate: |
| 21 | +it takes the secret key of the site being verified and needs no API key. See |
| 22 | +[Server-side verification](/en/basics/server-side-verification/). |
| 23 | + |
| 24 | +Everything else is the management API, described below. |
| 25 | + |
| 26 | +## Authentication |
| 27 | + |
| 28 | +Create an API key in the portal under **API Keys**, or with [`/api-keys/create`](#api-keys). Send it as a |
| 29 | +bearer token: |
| 30 | + |
| 31 | +```bash |
| 32 | +curl -X POST https://api.prosopo.io/sites/get \ |
| 33 | + -H "Authorization: Bearer YOUR_API_KEY" \ |
| 34 | + -H "Content-Type: application/json" \ |
| 35 | + -d '{}' |
| 36 | +``` |
| 37 | + |
| 38 | +A call succeeds only if all of the following hold: |
| 39 | + |
| 40 | +- The **API** feature is enabled on your account. It is off by default — contact support to have it turned on. |
| 41 | +- The feature the endpoint belongs to (Sites, Access Rules, Traffic, …) is enabled on your account. |
| 42 | +- The key carries the permission listed against that endpoint below. |
| 43 | +- The key has not expired. |
| 44 | + |
| 45 | +Two things follow from how the token is built: |
| 46 | + |
| 47 | +- **The token carries only your account id and the key id.** Permissions live on your account and are read on |
| 48 | + every request, so a key never has to be reissued to stay valid — but there is no endpoint for editing them |
| 49 | + either. Changing what a key can do means deleting it and creating a replacement. |
| 50 | +- **The account comes from the key.** Several request bodies still accept an `accountId` or `token` field; |
| 51 | + they are ignored for API-key callers, and the account is always the one that owns the key. |
| 52 | + |
| 53 | +### Key expiry |
| 54 | + |
| 55 | +Every key expires. `expiresIn` (seconds) is set at creation and defaults to **30 days** if you omit it — |
| 56 | +there is no non-expiring key. An expired key returns `401`. Rotate by creating a new key and deleting the old one. |
| 57 | + |
| 58 | +## Request and response format |
| 59 | + |
| 60 | +Send `POST` with a JSON body unless the table says otherwise (`application/x-www-form-urlencoded` and |
| 61 | +`multipart/form-data` are also accepted). Endpoints that take no parameters accept an empty body or `{}`. |
| 62 | + |
| 63 | +Errors come back as: |
| 64 | + |
| 65 | +```json |
| 66 | +{ "error": { "code": 403, "message": "Insufficient permissions", "key": "API.INSUFFICIENT_PERMISSIONS" } } |
| 67 | +``` |
| 68 | + |
| 69 | +| Status | When | |
| 70 | +|---|---| |
| 71 | +| `400` | Body missing, unparsable, or failing schema validation | |
| 72 | +| `401` | `Authorization` header missing, malformed, or the key has expired | |
| 73 | +| `403` | Key lacks the permission, the feature is not enabled on the account, the account is disabled, or a write was attempted on an account that still owes a payment method | |
| 74 | +| `404` | Account, site key or Protect instance not found | |
| 75 | +| `500` | Unhandled server error | |
| 76 | + |
| 77 | +The `key` is stable and never translated, so log that rather than the message. Errors the widget itself |
| 78 | +shows visitors are listed in the [Error Reference](/en/basics/error-reference/). |
| 79 | + |
| 80 | +## Sites |
| 81 | + |
| 82 | +Provision and configure site keys from your own tooling — useful if you spin up tenants, staging |
| 83 | +environments or customer sites programmatically rather than clicking through the portal. |
| 84 | + |
| 85 | +Requires the **Sites** feature. |
| 86 | + |
| 87 | +| Endpoint | Permission | Body | Returns | |
| 88 | +|---|---|---|---| |
| 89 | +| `POST /sites/get` | `getSites` | `{}` | Array of every site on the account | |
| 90 | +| `POST /site/get` | `getSite` | `{ siteKey }` | One site | |
| 91 | +| `POST /sites/create` | `createSite` | `{ name, settings }` | The created site | |
| 92 | +| `POST /sites/update` | `updateSite` | `{ siteKey, name?, settings?, isDefault? }` | The updated site | |
| 93 | +| `POST /sites/delete` | `deleteSite` | `{ siteKey }` | `{ success, deactivatedSiteKey }` | |
| 94 | + |
| 95 | +A site object carries `name`, `siteKey`, `secretKey`, `settings`, `active`, `createdAt` and `updatedAt`. |
| 96 | +Treat responses as secret: they contain the site's secret key. |
| 97 | + |
| 98 | +`settings` is the same configuration the portal edits — `domains` (required, at least one), `captchaType`, |
| 99 | +`frictionlessThreshold`, `imageThreshold`, `powDifficulty`, `verifiedTimeout`, `solutionTimeout`, |
| 100 | +`ipValidationRules`, `spamFilter`, `trafficFilter`, `honeypot` and the rest. See |
| 101 | +[CAPTCHA Types](/en/basics/captcha-types/), [Safety Threshold](/en/basics/safety-threshold/), |
| 102 | +[Image Accuracy Threshold](/en/advanced/image-threshold/), [IP Validation Rules](/en/advanced/ip-validation-rules/), |
| 103 | +[Traffic Filter](/en/advanced/traffic-filter/) and [Email Filter](/en/advanced/spam-filter/). |
| 104 | + |
| 105 | +On create: |
| 106 | + |
| 107 | +- `name` must be alphanumeric with hyphens and underscores, and unique within your account. |
| 108 | +- The site key and secret key are generated for you; you cannot choose them. |
| 109 | +- Domains are normalised before they are stored: lowercased, with `http(s)://`, a leading `www.` and any |
| 110 | + trailing slash stripped. Subdomain wildcards such as `*.example.com` are accepted; a bare `*` is not. |
| 111 | +- The number of non-localhost domains a site may carry is capped by your plan — one on the free tier. |
| 112 | +- Delete deactivates the site rather than erasing it, and the last remaining site cannot be deactivated. |
| 113 | + |
| 114 | +New and changed sites are pushed to the CAPTCHA providers asynchronously, so allow a short delay before |
| 115 | +the widget picks up a change. |
| 116 | + |
| 117 | +## Access control rules |
| 118 | + |
| 119 | +Write and revoke rules from your own detection stack — feed a SIEM verdict, a fraud signal or an abuse |
| 120 | +report straight into a block without a human in the portal. |
| 121 | + |
| 122 | +Requires the **Access Rules** feature. The concepts (fields, operators, policies, precedence) are covered in |
| 123 | +[Access Control Rules](/en/advanced/access-control-rules/). |
| 124 | + |
| 125 | +| Endpoint | Permission | Body | Returns | |
| 126 | +|---|---|---|---| |
| 127 | +| `POST /access-control/get` | `getRules` | `{ page?, limit?, ruleGroupId?, sortBy?, sortOrder? }` | `{ rules, ruleCount, ruleGroupsAndCounts, page, pages }` | |
| 128 | +| `POST /access-control/create` | `createRule` | `{ rule }` | `{ status: "Added new rule" }` | |
| 129 | +| `POST /access-control/delete` | `deleteRule` | `{ userScopeHash }` | `{ status: "Deleted rule" }` | |
| 130 | +| `POST /access-control/group/delete` | `deleteRuleGroup` | `{ ruleGroupId }` | `{ status: "Group removal pending. Job ID: …" }` | |
| 131 | + |
| 132 | +`sortBy` is one of `createdAt`, `description`, `expiry`, `userScopeHash`, `ruleGroupId`; `sortOrder` is `1` |
| 133 | +or `-1`; `limit` caps at 10000. |
| 134 | + |
| 135 | +A `rule` looks like this: |
| 136 | + |
| 137 | +```bash |
| 138 | +curl -X POST https://api.prosopo.io/access-control/create \ |
| 139 | + -H "Authorization: Bearer YOUR_API_KEY" \ |
| 140 | + -H "Content-Type: application/json" \ |
| 141 | + -d '{ |
| 142 | + "rule": { |
| 143 | + "type": "block", |
| 144 | + "description": "Scraper reported by fraud pipeline", |
| 145 | + "conditions": [ |
| 146 | + { "field": "ip", "operator": "equals", "value": "1.1.1.1" }, |
| 147 | + { "field": "countryCode", "operator": "equals", "value": "US" } |
| 148 | + ], |
| 149 | + "expiry": "2026-01-01T00:00:00.000Z" |
| 150 | + } |
| 151 | + }' |
| 152 | +``` |
| 153 | + |
| 154 | +- `type` is `block` (fail the request outright) or `restrict` (serve a harder or easier challenge). A |
| 155 | + `restrict` rule may also carry `captchaType`, `solvedImagesCount`, `imageThreshold`, `powDifficulty`, |
| 156 | + `unsolvedImagesCount`, `frictionlessScore` or `deferToVerify`. A `block` rule must not set `captchaType` |
| 157 | + or `solvedImagesCount` — those are rejected, because a block applies to every CAPTCHA type. |
| 158 | +- `conditions` are ANDed. `field` is one of `ip`, `ipMask`, `userId`, `ja4Hash`, `userAgent`, `countryCode`, |
| 159 | + `asn`; `operator` must be `equals` — any other operator is dropped silently. |
| 160 | +- **`expiry` defaults to one hour from creation if you omit it.** Set it explicitly for anything long-lived. |
| 161 | +- `ruleGroupId` is a free-form string; group deletion removes every rule sharing it, asynchronously via a job. |
| 162 | +- Rules apply account-wide, across every site key, and are pushed to the providers asynchronously. |
| 163 | + |
| 164 | +To delete a single rule you need its `userScopeHash`, which is returned by `/access-control/get`. Each rule |
| 165 | +comes back with its `conditions` rebuilt, so a read round-trips into the shape `create` accepts. |
| 166 | + |
| 167 | +## Traffic |
| 168 | + |
| 169 | +Pull the numbers behind the portal's charts into your own dashboards or billing reconciliation. |
| 170 | + |
| 171 | +Requires the **Traffic** feature and the `getTraffic` permission. |
| 172 | + |
| 173 | +| Endpoint | Body | Returns | |
| 174 | +|---|---|---| |
| 175 | +| `POST /gettrafficdata` | `{ token, accountId, siteKeys?, startDate?, endDate?, month?, year? }` | Array of per-period count rows | |
| 176 | +| `POST /getlivesessions` | `{ token, accountId, siteKey, windowMinutes?, bucketSeconds? }` | `{ points, windowMinutes, bucketSeconds, uniqueIps, topIps }` | |
| 177 | + |
| 178 | +- `token` and `accountId` are required by the schema but ignored — the account comes from your API key. |
| 179 | + Send any non-empty string. |
| 180 | +- `/gettrafficdata` takes either an ISO `startDate`/`endDate` pair or a `month` (1–12) and `year`, defaulting |
| 181 | + to the current month. Omit `siteKeys` for every site on the account; naming a site key you do not own is a |
| 182 | + `404`. Ranges longer than seven days are aggregated per day rather than per hour. |
| 183 | +- `/getlivesessions` reads raw sessions, so it is capped: `windowMinutes` 5–360 (default 60) and |
| 184 | + `bucketSeconds` 30–3600 (default 60). `points` only contains buckets that saw traffic — gap-fill client-side. |
| 185 | + `topIps` is annotated with country, ASN and VPN/proxy/datacenter flags on Professional and Enterprise plans. |
| 186 | + |
| 187 | +## Audit records |
| 188 | + |
| 189 | +Search individual CAPTCHA attempts — the same data as the portal's [Audit](/en/advanced/audit/) page — to |
| 190 | +investigate an incident or export evidence. |
| 191 | + |
| 192 | +Requires the **Search Captcha Records** feature and the `searchCaptchaRecords` permission. |
| 193 | + |
| 194 | +| Endpoint | Body | Returns | |
| 195 | +|---|---|---| |
| 196 | +| `POST /audit/searchcaptcharecords` | `{ siteKey?, captchaType?, searchCriteria?, startDate?, endDate?, pagination? }` | `{ records, total, totalIsExact?, limit, hasMore, lastId?, lastTimestamp? }` | |
| 197 | + |
| 198 | +- `captchaType` is `pow`, `image`, `puzzle`, `all` (default) or `blocked` — the last covers requests stopped |
| 199 | + before a CAPTCHA was chosen, which exist only as sessions. |
| 200 | +- `startDate` / `endDate` are epoch milliseconds. They default to the last seven days, and are clamped to a |
| 201 | + 30-day lookback — an older `startDate` is silently pulled forward rather than rejected. |
| 202 | +- Omit `siteKey` to search every site on the account; naming one you do not own is a `404`. |
| 203 | +- `pagination` is `{ limit, lastId?, lastTimestamp? }`, `limit` 1–100 (default 20). Page forward by feeding |
| 204 | + the `lastId` and `lastTimestamp` from the previous response back in. |
| 205 | +- `searchCriteria` narrows on `ip`, `ja4`, `userAgent`, `userAccount`, `countryCode`, `deviceType`, `vpn`, |
| 206 | + `webView`, `iFrame`, `status`, `selectionReason`, `resultReason`, `accessRule`, `policyType`, |
| 207 | + `triggeredDetectors` or a `freeText` substring across the displayable fields. |
| 208 | +- `total` is capped server-side; when `totalIsExact` is `false` the real total is higher. |
| 209 | + |
| 210 | +## Team members |
| 211 | + |
| 212 | +Mirror your own identity system — add a starter, revoke a leaver — without anyone logging into the portal. |
| 213 | + |
| 214 | +Requires the **Users** feature. |
| 215 | + |
| 216 | +| Endpoint | Permission | Body | Returns | |
| 217 | +|---|---|---|---| |
| 218 | +| `GET /users/get` | `getUsers` | — | Array of users | |
| 219 | +| `POST /users/create` | `createUser` | `{ email, name, role }` | The created user | |
| 220 | +| `PUT /users/update` | `updateUser` | `{ email, name?, userType?, marketingPreferences? }` | `{ success }` | |
| 221 | +| `POST /users/delete` | `deleteUser` | `{ email }` | `{ success, deletedUserEmail }` | |
| 222 | + |
| 223 | +`role` and `userType` are `admin` or `viewer`. The account owner cannot be created, changed or removed |
| 224 | +through the API, and the last remaining user cannot be deleted. |
| 225 | + |
| 226 | +## API keys |
| 227 | + |
| 228 | +Rotate credentials on a schedule from CI, and grant permissions the portal's key editor does not offer. |
| 229 | + |
| 230 | +Requires the **API** feature. |
| 231 | + |
| 232 | +| Endpoint | Permission | Body | Returns | |
| 233 | +|---|---|---|---| |
| 234 | +| `POST /api-keys/get` | `getApiKeys` | `{}` | Array of keys, each including its token | |
| 235 | +| `POST /api-keys/create` | `createApiKey` | `{ name, permissions, expiresIn? }` | The created key, including its token | |
| 236 | +| `POST /api-keys/delete` | `deleteApiKey` | `{ apiKeyId }` | `{ success: true }` | |
| 237 | + |
| 238 | +`permissions` is keyed by feature: |
| 239 | + |
| 240 | +```json |
| 241 | +{ |
| 242 | + "name": "CI rule writer", |
| 243 | + "expiresIn": 604800, |
| 244 | + "permissions": { |
| 245 | + "AccessRules": ["getRules", "createRule", "deleteRule"], |
| 246 | + "Traffic": ["getTraffic"] |
| 247 | + } |
| 248 | +} |
| 249 | +``` |
| 250 | + |
| 251 | +Every feature named must be enabled on the account, and at least one permission must be granted, or the |
| 252 | +call is rejected. Deleting a key invalidates it immediately. See [API Keys](/en/advanced/api-keys/) for the |
| 253 | +full permission list. |
| 254 | + |
| 255 | +## Prosopo Protect |
| 256 | + |
| 257 | +Configure and observe edge protection: manage instances, read the verdict log and maintain the edge access |
| 258 | +rules. See [Prosopo Protect](/en/protect-edge/) for what the product does. |
| 259 | + |
| 260 | +Requires the **Protect** feature, and every endpoint takes the single `updateProtectSettings` permission. |
| 261 | +The portal's key editor does not offer Protect permissions, so a key that can reach these must be created |
| 262 | +through `/api-keys/create`. |
| 263 | + |
| 264 | +| Endpoint | Body | Purpose | |
| 265 | +|---|---|---| |
| 266 | +| `POST /protect/instances` | `{}` | List instances | |
| 267 | +| `POST /protect/instances/create` | `{ name, cname, globalSiteKey, ipCategoryRules, … }` | Create an instance | |
| 268 | +| `POST /protect/instances/update` | `{ id, … }` | Update an instance | |
| 269 | +| `POST /protect/instances/delete` | `{ id }` | Delete an instance | |
| 270 | +| `POST /protect/client-jwt/generate` | `{ protectInstanceId, expiresInWeeks }` | Issue the JWT the edge worker authenticates with (`0` weeks = effectively unlimited) | |
| 271 | +| `POST /protect/verdicts` | `{ siteKey?, since?, until?, limit?, offset?, decision?, sources?, firedRules?, asn?, deviceType?, requestPath?, minRulesFired? }` | Page the verdict log | |
| 272 | +| `POST /protect/verdicts/search` | `{ ip?, jti?, asn?, siteKey? }` | Find verdicts for one client | |
| 273 | +| `POST /protect/verdicts/distinct-sources` | `{ since, until?, siteKey? }` | Distinct verdict sources in a window | |
| 274 | +| `POST /protect/verdicts/distinct-fired-rules` | `{ since, until?, siteKey? }` | Distinct fired rule ids in a window | |
| 275 | +| `POST /protect/risk-history` | `{ jti, siteKey? }` | Risk score history for one session | |
| 276 | +| `POST /protect/session-telemetry` | `{ jti, siteKey? }` | Telemetry for one session | |
| 277 | +| `POST /protect/traffic-summary` | `{ siteKey?, since?, bucket?, source? }` | Bucketed traffic totals | |
| 278 | +| `POST /protect/traffic-by-dimension` | `{ groupBy, siteKey?, since?, until?, bucket?, topN?, … }` | Time series split by `decision`, `source`, `country`, `asn`, `ip_category`, `ja4`, `user_agent`, `request_path`, `fired_rule`, `device_type`, `enforcement_policy` or `none` | |
| 279 | +| `POST /protect/access-rules` | `{ siteKey? }` | List edge access rules | |
| 280 | +| `POST /protect/access-rules/create` | `{ rules: [{ type, value, verdict }] }` | Add edge access rules | |
| 281 | +| `POST /protect/access-rules/delete` | `{ conditions: [{ type, value }] }` | Remove edge access rules | |
| 282 | + |
| 283 | +- Omit `siteKey` and the endpoint uses your account's active instance; with no matching instance the response |
| 284 | + is `404` with `{ "error": "No active protect instance found" }`. |
| 285 | +- Edge rule `verdict` is `allow`, `challenge` or `block`, and `type` is one of the fields Protect matches on: |
| 286 | + `ip`, `ipMask`, `ja4Hash`, `userAgent`, `countryCode` or `asn`. |
| 287 | +- The read endpoints proxy the verdict-log service and return its payload unchanged. |
0 commit comments