Skip to content

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 /akamai and /dd are 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:

EndpointUse when...
POST /akamai/solveYou just want to hand over a URL + browser profile and let the solver do the fetching, solving, and submission.
POST /akamai/mitm-solveYou run a MITM proxy in front of your bots and already have the captured challenge response — skips a redundant fetch.
WS /akamai/sessionYou'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:

json
{
  "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

FieldTypeRequiredNotes
urlstringyesTarget URL to solve for
profileProfileSnapshotyesSee below
js_profileJsProfilePayloadyesBrowser fingerprint data
script{ html, js, url }noSkip the live fetch; all three sub-fields required together
cookiesRecord<string,string>noSeed cookies, only used with script
proxystringnoOutbound proxy URL for this request
mode"abck" | "sbsd"noForces mode; otherwise auto-detected from the script
maxSensorsnumbernoCap on sensor beacons sent
submitbooleanno (default true)false = capture-only, don't submit to origin
timeoutnumber, msno (default 20000)Overall deadline
acceptCookieNamestringno (default _abck)Cookie checked for acceptance
request_id, attempt_id, correlation_idstringnoProvide all three or none — freezes an identity used to validate later submissions
readiness_state"created"|"ready"|"solving"|"completed"|"aborted"noOnly valid state transitions are accepted
spiderstringnoFree-form label surfaced in metrics

ProfileSnapshot:

json
{
  "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

json
{
  "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/malformed url, profile, js_profile, invalid profile.id format, malformed script, partial identity bundle, js_profile / profile.id conflict, invalid readiness_state, etc.). Body: { "success": false, "error": "..." }.
  • 500 — solve failed. Common outcome / outcome_reason pairs:
    • "aborted" / "queue_full" — solve queue at capacity
    • "timeout" / "queue_wait_timeout" — waited too long for a queue slot
    • "timeout" / "deadline_exceeded" — exceeded timeout
    • "aborted" / "signal_aborted" — client disconnected
    • not accepted after the sensor budget was exhausted (abck mode), or maxSensors not reached (sbsd mode)

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:

FieldTypeRequired
original_urlstringyes
profile_idstring (chrome-<ver>-<os>)yes
challenge_bodystring (captured HTML)yes
js_profileJsProfilePayloadyes
challenge_headers[string,string][]no (default [])
challenge_statusnumberno
original_cookiesstring (cookie header)no
original_headers[string,string][]no
original_methodstringno
proxy_url / upstream_proxystringno
timezone, timezone_offset_minutesno
sec_ch_ua*, brands, device_memory, vendor, greased_brand*no
http_header_templatesHeaderTemplatesno
script_idstring, {provider}/{script_type}@sha256:{hash}no
user_agentstringno

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/session

For 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

jsonc
{
  "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)

jsonc
{
  "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

jsonc
{
  "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

jsonc
{ "type": "cookie_update", "cookies": { "_abck": "..." }, "round": 1, "rval": 2, "accepted": false }
{ "type": "status", "state": "running", "round": 1 }

5. Success — when _abck is accepted:

jsonc
{ "type": "cookie_update", "cookies": { "_abck": "..." }, "accepted": true }
{ "type": "status", "state": "accepted" }

Close the socket and continue browsing with the now-valid cookies.

6. Errors

jsonc
{ "type": "error", "message": "..." }

Session limits

  • Session TTL: 5 minutes of inactivity. Each submission_response resets the timer.
  • Submission timeout: each submission must get a submission_response within 30 seconds, or it times out.