Skip to content

Commit c58e4fb

Browse files
authored
docs: document the external API surface (#82)
2 parents 5440c46 + 4872334 commit c58e4fb

2 files changed

Lines changed: 292 additions & 0 deletions

File tree

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
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.

src/i18n/en/nav.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ export default [
122122
slug: 'advanced/api-keys',
123123
key: 'advanced/api-keys',
124124
},
125+
{
126+
text: 'API',
127+
slug: 'advanced/api',
128+
key: 'advanced/api',
129+
},
125130
{
126131
text: 'Audit',
127132
slug: 'advanced/audit',

0 commit comments

Comments
 (0)