Skip to content

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 /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 two 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.
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.

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

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)

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.