Akamai API Reference
Solves Akamai Bot Manager sensor challenges — _abck / bm-sz cookie challenges and SBSD. All endpoints are mounted under /akamai on your running container (http://host:3000/akamai/...).
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 three 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. |
POST /akamai/mitm-solve | You run a MITM proxy in front of your bots and already have the captured challenge response — skips a redundant fetch. |
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. |
GET /akamai/queue-metrics reports on the shared solve-admission queue used by both HTTP endpoints.
All error responses include an error: string field. Validation failures return 400; solve-time failures return 500.
GET /akamai/queue-metrics
Inspect the internal solve queue — useful for autoscaling or backpressure decisions upstream of this container.
Response 200:
{
"active": 3,
"queued": 1,
"totalAdmitted": 128,
"totalCompleted": 125,
"totalRejected": 2,
"totalTimedOut": 0
}By default the queue allows 8 concurrent solves and a queue depth of 32, with a 10s max queue wait. When full, /solve and /mitm-solve reject with outcome: "aborted", outcome_reason: "queue_full" (500). When a request waits past the max, they reject with 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)
POST /akamai/mitm-solve
Same solve pipeline as /solve, but for MITM-proxy architectures — you pass an already-captured challenge response (challenge_body / challenge_headers) instead of the solver re-fetching the page, avoiding an extra round trip to the origin.
Request body
Superset of /solve's optional fields, plus:
| Field | Type | Required |
|---|---|---|
original_url | string | yes |
profile_id | string (chrome-<ver>-<os>) | yes |
challenge_body | string (captured HTML) | yes |
js_profile | JsProfilePayload | yes |
challenge_headers | [string,string][] | no (default []) |
challenge_status | number | no |
original_cookies | string (cookie header) | no |
original_headers | [string,string][] | no |
original_method | string | no |
proxy_url / upstream_proxy | string | no |
timezone, timezone_offset_minutes | no | |
sec_ch_ua*, brands, device_memory, vendor, greased_brand* | no | |
http_header_templates | HeaderTemplates | no |
script_id | string, {provider}/{script_type}@sha256:{hash} | no |
user_agent | string | no |
Plus everything shared with /solve: accept_cookie_name, attempt_id, correlation_id, maxSensors, mode, readiness_state, request_id, submit, timeout.
Response
Same shape as /solve. The final cookies / cookie_header are the result of merging original_cookies with the challenge's Set-Cookie headers and the newly-solved cookies. acceptance_cookie defaults to _abck unless you set accept_cookie_name.
Error responses
Same conventions as /solve. 400 validation additionally checks original_url, profile_id format, challenge_body, and identity consistency between the request fields.
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.