Pass Castle verification at scale.
A developer API that mints valid Castle tokens for Castle-protected sites and apps — X / Twitter (web) and apps like DailyPay (Android) are our worked examples. Send a request, get a token back in ~50ms. No browsers to babysit, no challenges to solve by hand.
Only pay for what you use
Metered per token, billed against a prepaid balance you control from the dashboard. No surprises.
Metered usage
Only pay for what you use — metered per token against your prepaid balance.
- Web & Android token generation, live now
- Bearer-key auth · per-key usage stats
- Real-time balance + usage dashboard
- No minimums, no expiring credits
Volume tiers
Flat monthly plans with bundled request volume and higher rate limits for high-throughput teams.
- Discounted per-request rate
- Higher, custom rate limits
- Usage alerts + per-key reporting
Closed to everyone right now — start on Pay-As-You-Go today and switch when tiers reopen.
Built for automation
A focused REST API with predictable behavior and clear limits. Here is what you are wiring into.
Android (APP) & Web support
Both endpoints are live. /generate/web mints browser Castle tokens; /generate/android mints native Android application tokens.
Low latency
Typical responses land in around 50 ms — consistent, low-latency token generation built for high-throughput automation.
Pay-as-you-go
$0.00055 per token generated. No minimums, no expiring credits — only pay for what you use.
How it works
Three steps. You send one request, Castle Solver computes a valid token, and you attach it to your request to the Castle-protected site or app (X / Twitter shown as the example). No browsers to run, no challenges to solve by hand.
- Send a requestPOST
/generate/webwith the target site (domain) and a browser User-Agent. - We generate the tokenWe compute a fresh, valid Castle token server-side — no browser farm to babysit.
- Use the tokenAttach the returned token and headers to your request to the Castle-protected site.
Bearer key required. Send Authorization: Bearer YOUR_API_KEY with every request. Get a key from the Pay-As-You-Go plan above.
curl -X POST https://castle.botwitter.com/generate/web \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"cuid": "550e8400e29b41d4a716446655440000",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
"country": "US",
"sec-ch-ua": "\"Chromium\";v=\"131\", \"Google Chrome\";v=\"131\"",
"public_key": "pk_AvRa79bHyJSYSQHnRpcVtzyxetSvFerx",
"domain": "x.com"
}'
// Response:
{
"success": true,
"token": "eyJhbG...",
"cuid": "550e8400e29b41d4a716446655440000",
"cuidGenerated": false,
"domain": "x.com",
"headers": {
"sec-ch-ua": "\"Chromium\";v=\"131\", \"Google Chrome\";v=\"131\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\""
}
}// Generate a random cuid
const cuid = [...Array(32)].map(() =>
Math.floor(Math.random() * 16).toString(16)).join('');
const response = await fetch('https://castle.botwitter.com/generate/web', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY'
},
body: JSON.stringify({
cuid,
user_agent: navigator.userAgent,
country: 'US',
'sec-ch-ua': navigator.userAgentData?.brands?.map(b => `"${b.brand}";v="${b.version}"`).join(', ')
})
});
const data = await response.json();
// Response: { success: true, token: "...", headers: { "sec-ch-ua": "...", ... } }import secrets, requests
cuid = secrets.token_hex(16)
sec_ch_ua = '"Chromium";v="131", "Google Chrome";v="131"'
response = requests.post(
'https://castle.botwitter.com/generate/web',
headers={'Authorization': 'Bearer YOUR_API_KEY'},
json={
'cuid': cuid,
'user_agent': 'Mozilla/5.0...',
'country': 'US',
'sec-ch-ua': sec_ch_ua
}
)
data = response.json()
# Response: {"success": True, "token": "...", "headers": {"sec-ch-ua": "...", ...}}import ("crypto/rand"; "fmt"; "net/http"; "bytes")
b := make([]byte, 16)
rand.Read(b)
cuid := fmt.Sprintf("%x", b)
secChUa := `"Chromium";v="131", "Google Chrome";v="131"`
body := fmt.Sprintf(`{"cuid":"%s","user_agent":"Mozilla/5.0...","country":"US","sec-ch-ua":"%s"}`, cuid, secChUa)
req, _ := http.NewRequest("POST", "https://castle.botwitter.com/generate/web", bytes.NewBuffer([]byte(body)))
req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
req.Header.Set("Content-Type", "application/json")
// Response: {"success": true, "token": "...", "headers": {"sec-ch-ua": "...", ...}}$cuid = bin2hex(random_bytes(16));
$secChUa = '"Chromium";v="131", "Google Chrome";v="131"';
$response = file_get_contents('https://castle.botwitter.com/generate/web', false, stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Authorization: Bearer YOUR_API_KEY\r\nContent-Type: application/json",
'content' => json_encode([
'cuid' => $cuid,
'user_agent' => 'Mozilla/5.0...',
'country' => 'US',
'sec-ch-ua' => $secChUa
])
]
]));
$data = json_decode($response, true);
// Response: ["success" => true, "token" => "...", "headers" => ["sec-ch-ua" => "...", ...]]When you send your request to the target site, set the returned cuid as a __cuid cookie — e.g. Cookie: __cuid=<cuid>.
curl -X POST https://castle.botwitter.com/generate/android \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"public_key": "pk_AvRa79bHyJSYSQHnRpcVtzyxetSvFerx",
"app_label": "DailyPay",
"app_version": "48.0.0",
"app_version_code": 510250073,
"session_id": "sess_9f2c..."
}' # session_id optional — reuse to keep the same device, omit for a fresh one
// Response:
{
"success": true,
"session_id": "sess_9f2c...",
"token": "eyJhbG...",
"client_id": "CL_8a31..."
}const res = await fetch('https://castle.botwitter.com/generate/android', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY'
},
body: JSON.stringify({
public_key: 'pk_AvRa79bHyJSYSQHnRpcVtzyxetSvFerx',
app_label: 'DailyPay',
app_version: '48.0.0',
app_version_code: 510250073,
session_id: 'sess_9f2c...' // optional — reuse to keep the same device
})
});
const { session_id, token, client_id } = await res.json();
// Send token as "X-Castle-Request-Token" and client_id as "X-Castle-Client-Id"import requests
r = requests.post(
'https://castle.botwitter.com/generate/android',
headers={'Authorization': 'Bearer YOUR_API_KEY'},
json={
'public_key': 'pk_AvRa79bHyJSYSQHnRpcVtzyxetSvFerx',
'app_label': 'DailyPay',
'app_version': '48.0.0',
'app_version_code': 510250073,
'session_id': 'sess_9f2c...', # optional — reuse to keep the same device
}
)
data = r.json() # {"success": True, "session_id": "...", "token": "...", "client_id": "..."}Native Android application token. The device identity is tied to the signed session_id — reuse it to stay consistent across calls, or omit it to mint a fresh device.
Send token to the target app as the X-Castle-Request-Token header and client_id as the X-Castle-Client-Id header.
On the app's APKMirror page, the App name → app_label, the Version → app_version, and the numeric version code shown below it → app_version_code.
Frequently asked
Short answers to the questions developers ask most before wiring Castle Solver in.
Authorization: Bearer YOUR_API_KEY. A key is required./generate/web returns a browser Castle token; /generate/android returns a native Android app token as X-Castle-Request-Token + X-Castle-Client-Id. Both endpoints are live.public_key, app_label, app_version, app_version_code) plus a signed session_id. Reuse the returned session_id to keep the same device, or omit it to mint a fresh one. DailyPay is our example app; for your specific app or any other, contact us to add or confirm support.domain field to target a Castle-protected site; it defaults to twitter.com / x.com (our main example). Other Castle-protected sites are supported too — contact us for ones we haven't tuned yet, or for other apps and custom needs.