Mobile Akamai Sensor Data API
Generate the x-acf-sensor-data header required by Akamai-protected mobile APIs.
Note: Currently only Android devices are supported for sensor data generation. iOS is not supported at this time.
Authentication
All requests require an X-API-Key header.
X-API-Key: your-api-key
Generate Sensor Data
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
app |
string | Yes | Lowercase app name |
proxy |
string | No | Your proxy URL (protocol://[user:pass@]host:port). Required only for apps that have SDK or SBSD challenge solving enabled — the proxy is used to fetch challenge scripts from the target domain and submit solutions. If the app doesn't use these challenges, sensor data is generated locally and no proxy is needed. |
Response Body
| Field | Description |
|---|---|
status | "succeeded" or "failed" |
sensor_data | Encrypted string to use as the x-acf-sensor-data header |
app_version | App version string matching the target's Akamai SDK config |
android_id | 16-char hex device ID (Settings.Secure.ANDROID_ID) |
ro_build_version_release | Android OS version |
ro_build_id | Android build ID |
ro_product_manufacturer | Device manufacturer |
ro_product_model | Device model |
Success Response (200)
json{
"status": "succeeded",
"sensor_data": "6,a,...<encrypted sensor data>",
"app_version": "9.3.3",
"android_id": "a3f8c91b2e4d7f06",
"ro_build_version_release": "16",
"ro_product_manufacturer": "SAMSUNG",
"ro_product_model": "SM-S936B",
"ro_build_id": "AP3A.250405.038"
}
Error Responses
| Status | Meaning |
|---|---|
| 400 | Invalid request body or unsupported app name |
| 401 | Missing or invalid API key |
| 402 | Insufficient token balance |
| 500 | Sensor data generation failed (retry) |
Examples
Note: We will share the per-app TLS config with you if you have your own TLS proxy. If not, you can use our Mobile TLS Relay. Please note that not using TLS proxy will result in blocked requests even if the sensor data header is valid. Using our built-in TLS proxy for SDK / SBDS requests is mandatory and seamless for the user and cannot be done on user side. SDK / SBSD challenges usage depends on the per-app configuration and cannot be enabled or disabled by the user as it would result in blocked requests.
Node.js
javascriptconst response = await fetch("https://mobile.botpulse.io/akamai-sensor-data", {
method: "POST",
headers: {
"X-API-Key": "your-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
app: "footlocker",
proxy: "http://user:pass@proxy.example.com:8080",
}),
});
const data = await response.json();
// data.sensor_data — use as x-acf-sensor-data header
// data.ro_product_model — e.g. "SM-S936B"
// data.ro_build_version_release — e.g. "16"
Go
gopackage main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
body, _ := json.Marshal(map[string]string{
"app": "footlocker",
"proxy": "http://user:pass@proxy.example.com:8080",
})
req, _ := http.NewRequest("POST", "https://mobile.botpulse.io/akamai-sensor-data", bytes.NewReader(body))
req.Header.Set("X-API-Key", "your-api-key")
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result struct {
Status string `json:"status"`
SensorData string `json:"sensor_data"`
AppVersion string `json:"app_version"`
AndroidId string `json:"android_id"`
RoBuildVersionRelease string `json:"ro_build_version_release"`
RoProductManufacturer string `json:"ro_product_manufacturer"`
RoProductModel string `json:"ro_product_model"`
RoBuildId string `json:"ro_build_id"`
}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Println("Sensor data:", result.SensorData[:50]+"...")
// Use result.SensorData as the x-acf-sensor-data header
}
Notes
- Proxy is only required for apps with SDK/SBSD challenges enabled. When needed, the proxy fetches challenge scripts from the target domain and submits solutions. To get best results please don't use datacenter proxies.
- Proxy ports. Our infrastructure has firewall rules for standard proxy provider ports (Bright Data, etc.). If you use a proxy with a non-standard port, please let us know in advance so we can whitelist it.
- TLS service fees. SDK and SBSD challenge solving routes requests through our TLS proxy service to match real mobile TLS fingerprints. This incurs additional per-request fees for the TLS service on top of the sensor data generation cost.
- Single-use. Each call generates a fresh sensor data string with a new random device profile. Generate a new one for each request to the target API.
- Device consistency. Use the
ro_*fields from the response to build all necessary headers (User-Agent, device model, etc.) so they match the generated sensor data. If you need additional device properties included in the response, please contact us.
Mobile TLS Relay API
Mobile TLS Relay forwards your HTTP requests through your proxy while applying a real mobile app TLS fingerprint. This makes requests appear as if they come from a real mobile device.
Authentication
All requests require an X-API-Key header.
X-API-Key: your-api-key
Relay Request
Sends an HTTP request through a proxy with the correct mobile TLS fingerprint. The TLS fingerprint is automatically selected based on the target URL's hostname. Sessions are managed automatically — a persistent TLS connection is created on the first request and reused for subsequent requests with the same hostname + proxy combination. Sessions are automatically destroyed after 1 minute of inactivity.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
proxy |
string | Yes | Your proxy URL (protocol://[user:pass@]host:port) |
method |
string | Yes | HTTP method (GET, POST, PUT, DELETE, etc.) |
url |
string | Yes | Full target URL |
headers |
object | Yes | Request headers as key-value pairs |
body |
string | No | Request body string (for POST/PUT). Stringify JSON before passing. |
unique_id |
string | No | Optional identifier to isolate parallel sessions. Use different values when you need multiple independent sessions simultaneously. |
Response Body
| Field | Description |
|---|---|
status | HTTP status code from the target server |
headers | Response headers from the target server |
body | Response body as a string |
Success Response (200)
json{
"status": 200,
"headers": {
"content-type": "application/json",
"server": "nginx"
},
"body": "{\"data\": \"response from target\"}"
}
Error Responses
| Status | Meaning |
|---|---|
| 400 | Invalid request body or unsupported hostname |
| 401 | Missing or invalid API key |
| 402 | Insufficient token balance |
| 500 | Relay failed (proxy error, connection timeout, etc.) |
Examples
Node.js
javascriptconst TLS_URL = "https://ctls.botpulse.io";
const API_KEY = "your-api-key";
const PROXY = "protocol://[user:pass@]host:port";
// GET request
const response = await fetch(`${TLS_URL}/relay`, {
method: "POST",
headers: {
"X-API-Key": API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
proxy: PROXY,
method: "GET",
url: "https://api.footlocker.com/v4/search",
headers: {
"User-Agent": "Footlocker/9.3.3 okhttp/4.12.0 Android/16",
"Accept": "application/json",
},
}),
});
const { status, headers, body } = await response.json();
console.log(`Target responded with ${status}`);
// POST request (same session is reused automatically)
const response2 = await fetch(`${TLS_URL}/relay`, {
method: "POST",
headers: {
"X-API-Key": API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
proxy: PROXY,
method: "POST",
url: "https://api.footlocker.com/v4/cart",
headers: {
"User-Agent": "Footlocker/9.3.3 okhttp/4.12.0 Android/16",
"Content-Type": "application/json",
"Accept": "application/json",
},
body: JSON.stringify({ sku: "12345", size: "10" }),
}),
});
Go
gopackage main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
tlsURL := "https://ctls.botpulse.io"
apiKey := "your-api-key"
proxy := "protocol://[user:pass@]host:port"
payload, _ := json.Marshal(map[string]interface{}{
"proxy": proxy,
"method": "GET",
"url": "https://api.footlocker.com/v4/search",
"headers": map[string]string{
"User-Agent": "Footlocker/9.3.3 okhttp/4.12.0 Android/16",
"Accept": "application/json",
},
})
req, _ := http.NewRequest("POST", tlsURL+"/relay", bytes.NewReader(payload))
req.Header.Set("X-API-Key", apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result struct {
Status int `json:"status"`
Headers map[string]string `json:"headers"`
Body string `json:"body"`
}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("Target responded with %d\n", result.Status)
}
Notes
- Automatic sessions. You don't need to manage sessions. The service automatically creates a persistent TLS connection on the first request for a given hostname + proxy combination and reuses it for subsequent requests. Sessions are destroyed after 1 minute of inactivity.
- TLS fingerprinting. The correct mobile app TLS fingerprint is automatically selected based on the target URL's hostname. You don't need to configure anything.
- Header ordering. Headers you pass in the
headersfield will be sent in the wire order configured for the target domain, not alphabetically. Just pass the headers you need. - Proxy ports. Our infrastructure has firewall rules for standard proxy provider ports (Bright Data, etc.). If you use a proxy with a non-standard port, please let us know in advance so we can whitelist it.
Fastly Challenge Solver API
Solve Fastly _fs-ch challenges and receive bypass cookies ready to use in your requests.
Authentication
All requests (except GET /ping) require an API key via the X-API-Key header:
X-API-Key: your-api-key
Requests without a valid key receive a 401 Unauthorized response.
Solve Challenge
Request
Send a JSON body with the target URL and proxy:
bashcurl -X POST https://api.botpulse.io/f \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"input": {"url": "https://example.com/search?q=test", "proxy": "http://user:pass@proxy.example.com:8080"}}'
json{
"input": {
"url": "https://example.com/search?q=test",
"proxy": "http://user:pass@proxy.example.com:8080"
}
}
Fields
| Field | Type | Required | Description |
|---|---|---|---|
input.url |
string | Yes | The full URL of the page protected by Fastly's challenge. Must be a valid URL. |
input.proxy |
string | Yes | Your proxy URL (protocol://[user:pass@]host:port). The proxy is used to fetch the challenge page and scripts from the target domain, perform the PAT handshake, and submit solutions. All outbound requests to the target go through this proxy. |
Response
Success (HTTP 200)
json{
"status": "succeeded",
"result": {
"cookies": [
"_fs_ch_session=abc123",
"_fs_ch_exp=1760394626"
]
}
}
| Field | Type | Description |
|---|---|---|
status | string | Always "succeeded" on success. |
result.cookies | string[] | List of Fastly bypass cookies in name=value format. |
Failure (HTTP 400)
json{
"status": "failed",
"error": "Could not find Fastly script ID in initial page"
}
| Field | Type | Description |
|---|---|---|
status | string | Always "failed" on failure. |
error | string | Specific error describing what went wrong. |
Unauthorized (HTTP 401)
json{
"status": "failed",
"error": "Unauthorized"
}
Returned when the X-API-Key header is missing or invalid.
Using the cookies
Attach the returned cookies to subsequent requests to the target site using the Cookie header:
Cookie: _fs_ch_session=abc123; _fs_ch_exp=1760394626
cURL
bash# Step 1: Solve the challenge
RESPONSE=$(curl -s -X POST https://api.botpulse.io/f \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"input": {"url": "https://example.com/search?q=test", "proxy": "http://user:pass@proxy.example.com:8080"}}')
# Step 2: Extract cookies (requires jq)
COOKIES=$(echo "$RESPONSE" | jq -r '.result.cookies | join("; ")')
# Step 3: Make a request with the cookies
curl -s "https://example.com/search?q=test" \
-H "Cookie: $COOKIES"
Python
pythonimport requests
# Step 1: Solve the challenge
response = requests.post(
"https://api.botpulse.io/f",
headers={"X-API-Key": "your-api-key"},
json={
"input": {
"url": "https://example.com/search?q=test",
"proxy": "http://user:pass@proxy.example.com:8080",
}
},
)
data = response.json()
# Step 2: Use the cookies
cookies = {}
for cookie in data["result"]["cookies"]:
name, value = cookie.split("=", 1)
cookies[name] = value
# Step 3: Make a request with the cookies
page = requests.get("https://example.com/search?q=test", cookies=cookies)
print(page.text)
Node.js
javascript// Step 1: Solve the challenge
const response = await fetch("https://api.botpulse.io/f", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "your-api-key",
},
body: JSON.stringify({
input: {
url: "https://example.com/search?q=test",
proxy: "http://user:pass@proxy.example.com:8080",
},
}),
});
const data = await response.json();
// Step 2: Use the cookies
const cookieHeader = data.result.cookies.join("; ");
// Step 3: Make a request with the cookies
const page = await fetch("https://example.com/search?q=test", {
headers: { Cookie: cookieHeader },
});
Error cases
The error field contains a specific message describing what failed:
| Scenario | HTTP Status | Example error value |
|---|---|---|
| Missing or invalid API key | 401 | "Unauthorized" |
| Invalid or missing fields | 400 | Validation error details |
| No Fastly challenge found on page | 400 | "Could not find Fastly script ID in initial page" |
| Token extraction failed | 400 | "Could not extract token from challenge script" |
| No challenges returned | 400 | "No challenges received from post-back" |
| Unknown challenge type | 400 | "Unsupported challenge type: xyz" |
| Server rejected solutions | 400 | "Solution post-back rejected by server (status: error)" |
| No cookies after solving | 400 | "No _fs_ch cookies received after solving challenges" |
Web Akamai Solver API
Generate sensor data for Akamai-protected web applications.
Note: Documentation under construction. Will be available shortly.
注意:文档正在建设中,即将发布。