Akamai Sensor API Reference
The classic _abck channel: Akamai's obfuscated sensor script, and the telemetry it is supposed to produce. All endpoints are mounted under /akamai on your running container (http://host:3000/akamai/...).
Akamai's other channel is SBSD, which has its own endpoint. A property can run either or both — see the Akamai overview.
Both
/akamaiand/ddare described in one machine-readable spec: download openapi.yml · browse interactively. The WebSocket session below isn't representable in OpenAPI 3.0, so it's documented only here.
There are two ways to drive a solve, depending on your architecture:
| Endpoint | Use when... |
|---|---|
POST /akamai/solve | You just want to hand over a URL + browser profile and let the solver do the fetching, solving, and submission. |
WS /akamai/session | You're driving a real browser (Playwright/Puppeteer) and want each sensor request relayed through that browser's own network stack and cookie jar. |
All error responses include an error: string field. Validation failures return 400; solve-time failures return 500.
Authentication
None. The container is licence-gated at startup, not per-request — there's no API key on any of these endpoints. The boundary is the network you run it on. See Authentication.
Solve admission
Solves share the container's admission queue. When it is full, /solve rejects with outcome: "aborted", outcome_reason: "queue_full" (500); when a request waits past the max, outcome: "timeout", outcome_reason: "queue_wait_timeout" (500).
POST /akamai/solve
End-to-end solve starting from a URL: fetches the page, extracts the sensor script, runs it in the sandbox, and (by default) submits the resulting sensor payload to the origin.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
url | string | yes | Target URL to solve for |
profile | ProfileSnapshot | yes | See below |
js_profile | JsProfilePayload | yes | Browser fingerprint data |
script | { html, js, url } | no | Skip the live fetch; all three sub-fields required together |
cookies | Record<string,string> | no | Seed cookies, only used with script |
proxy | string | no | Outbound proxy URL for this request |
mode | "abck" | "sbsd" | no | Forces mode; otherwise auto-detected from the script |
maxSensors | number | no | Cap on sensor beacons sent |
submit | boolean | no (default true) | false = capture-only, don't submit to origin |
timeout | number, ms | no (default 20000) | Overall deadline |
acceptCookieName | string | no (default _abck) | Cookie checked for acceptance |
request_id, attempt_id, correlation_id | string | no | Provide all three or none — freezes an identity used to validate later submissions |
readiness_state | "created"|"ready"|"solving"|"completed"|"aborted" | no | Only valid state transitions are accepted |
spider | string | no | Free-form label surfaced in metrics |
ProfileSnapshot:
{
"id": "chrome-146-macos",
"chromeFullVersion": "146.0.7680.81",
"os": "macos",
"timezone": "America/New_York",
"timezoneOffsetMinutes": -300,
"tlsClientHello": "chrome_146",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
"httpHeaderTemplates": { "form": [], "iframe": [], "image": [], "xhr": [] }
}profile.id must match the chrome-<version>-<os> format, e.g. chrome-146-macos. If js_profile.chromeVersion / js_profile.os are also present, they must not conflict with profile.id.
js_profile carries fingerprint data used to make the sensor payload match a real browser: audioContext, hardware.{canvas, emptyCanvas, fonts, webgl.{image, params, renderer, vendor}}, hardwareConcurrency, deviceMemory, screen.*, perf, os, chromeVersion, timezone, etc.
Success response 200
{
"success": true,
"accepted": true,
"acceptance_cookie": "abc123",
"cookie_header": "_abck=abc123",
"cookies": { "_abck": "abc123" },
"mode": "abck",
"outcome": "accepted",
"outcome_reason": "cookie_accepted",
"sensors_sent": 2,
"max_sensors_reached": false,
"last_response_status": 200,
"validation_events": [
{ "phase": "terminal", "decision": "accept", "reason": "...", "elapsed_ms": 1234 }
]
}If submit: false, the response is a capture envelope instead — the built sensor submission (method, url, body, headers) is returned without being sent to the origin, so you can submit it yourself.
Error responses
400— validation failure (missing/malformedurl,profile,js_profile, invalidprofile.idformat, malformedscript, partial identity bundle,js_profile/profile.idconflict, invalidreadiness_state, etc.). Body:{ "success": false, "error": "..." }.500— solve failed. Commonoutcome/outcome_reasonpairs:"aborted"/"queue_full"— solve queue at capacity"timeout"/"queue_wait_timeout"— waited too long for a queue slot"timeout"/"deadline_exceeded"— exceededtimeout"aborted"/"signal_aborted"— client disconnected- not accepted after the sensor budget was exhausted (
abckmode), ormaxSensorsnot reached (sbsdmode)
WebSocket session — /akamai/session
ws://host:3000/akamai/sessionFor browser-automation setups where you want the target's own browser context (real TLS stack, real cookie jar) to make each sensor request, rather than having the solver make it server-side.
Protocol
1. Client sends init
{
"type": "init",
"url": "https://target.com/page", // page URL where the challenge was captured
"scriptUrl": "https://target.com/...", // Akamai challenge script URL
"script": "<full script source>", // Akamai JS source code
"html": "<sanitized page HTML>", // page HTML, scripts stripped
"cookies": { "_abck": "...", "...": "..." },
"proxy": "http://user:pass@host:port", // optional, "none" if not used
"profileId": "chrome-146-macos" // required
}Required: type, url, script, html, profileId.
2. Server sends submission (one per sensor round)
{
"type": "submission",
"id": "sub-1", // echo this back
"method": "POST",
"url": "https://target.com/...",
"body": "sensor_data=...",
"headers": { "Content-Type": "..." }
}Relay this as a real XHR/fetch through the browser.
3. Client sends submission_response
{
"type": "submission_response",
"id": "sub-1", // matches the submission ID
"status": 200,
"body": "<response body>",
"cookies": { "_abck": "...", "...": "..." }
}4. Server sends cookie_update and status after each round
{ "type": "cookie_update", "cookies": { "_abck": "..." }, "round": 1, "rval": 2, "accepted": false }
{ "type": "status", "state": "running", "round": 1 }5. Success — when _abck is accepted:
{ "type": "cookie_update", "cookies": { "_abck": "..." }, "accepted": true }
{ "type": "status", "state": "accepted" }Close the socket and continue browsing with the now-valid cookies.
6. Errors
{ "type": "error", "message": "..." }Session limits
- Session TTL: 5 minutes of inactivity. Each
submission_responseresets the timer. - Submission timeout: each
submissionmust get asubmission_responsewithin 30 seconds, or it times out.