API Reference
CaptchaLa provides RESTful API for all server-side integrations.
Base URL
https://apiv1.captcha.laOne base URL only
Every endpoint on this page lives on apiv1.captcha.la. Our standby hosts (bypass.*, fallbackapiv1.*) are failover targets the SDKs reach on their own during an outage — pointing your integration at them returns service_online.
Authentication
All API requests require authentication headers:
X-App-Key: YOUR_APP_KEY
X-App-Secret: YOUR_APP_SECRETWARNING
X-App-Secret is server-side only. Never expose it to browsers, mobile apps, or public repos.
Server-side Validation API
💡 Use a server SDK to skip the boilerplate. Wraps the endpoint, handles retries, surfaces typed errors:
- PHP —
Captcha-La/captchala-php(中文)- Go —
go get github.com/Captcha-La/captchala-go· README
After user passes the frontend CAPTCHA, your server needs to validate the token returned by SDK. Below are the server-side validation endpoints.
Server-side Token Validation
Validate pass_token from frontend SDK (with pt_ prefix). Requires X-App-Key and X-App-Secret headers.
POST /v1/validate
X-App-Key: YOUR_APP_KEY
X-App-Secret: YOUR_APP_SECRET
Content-Type: application/json
{ "pass_token": "pt_xxx", "client_ip": "1.2.3.4" }client_ip is optional but recommended — the end-user's IP from your inbound request. Used for additional risk checks; safe to omit.
{
"code": 0,
"data": {
"valid": true,
"challenge_id": "ch_xxx",
"action": "login",
"uid": null,
"captcha_args": {
"platform": "web",
"user_ip": "1.2.3.4",
"referer": "https://your-site.com/login",
"pkg": null,
"solved_at": 1750000000,
"risk_score": 12
}
}
}TIP
Validate data.valid === true and that data.action matches the scene you expected (reject if a token from a pay flow is presented at /login). Tokens are single-use.
captcha_args
captcha_args echoes the context recorded at solve time — for logging / your own risk scoring:
platform—web/android/ios/flutter/windows/ …user_ip— the end-user IP seen when the challenge was solvedreferer— solve page URL (web);nullon nativepkg— app package / bundle id (native);nullon websolved_at— solve completion time (unix seconds)risk_score— solve-time risk score (0-100, higher = riskier)
Token Types
The SDK may hand your frontend different token types depending on service conditions. The prefix tells you everything:
| Prefix | Issued when | /v1/validate returns | Recommended handling |
|---|---|---|---|
pt_ | Verification passed normally | valid: true (single-use) | Accept |
dg_ | Plan quota exhausted (degraded mode) | valid: false, degraded: true, reason | Your decision — see below |
offline_ | Main API unreachable; issued by the bypass worker | Verified against the backup API automatically when using a server SDK | Accept after backup verification |
co_ | Main and backup unreachable; generated client-side | Cannot be verified server-side | Apply your own risk controls (rate limits, etc.) |
Degraded Mode (quota exhausted)
When an app has exhausted its monthly quota (and overage billing is not available), the widget does not interrupt your end-user flow. Instead of showing an error, the SDK completes immediately with a dg_ token:
- The end user sees no error — your form submits normally.
- Your server's
/v1/validatereturns:
{
"code": 0,
"data": {
"valid": false,
"degraded": true,
"reason": "quota_exhausted",
"expired": false
}
}Key properties:
validis alwaysfalsefor degraded tokens — secure by default. If your code only checksvalid, nothing changes for you.- Whether to let the request through is your decision: accept
valid || degradedto keep conversions flowing while you upgrade your plan, or reject and show your own fallback. dg_tokens are HMAC-signed per app and expire after 5 minutes. Issuing them costs no quota and is never billed.- Server SDKs expose this as
isDegraded()/getDegradedReason()(PHP) andresult.Degraded/result.DegradedReason(Go).
INFO
Expired subscriptions never trigger degraded mode — they automatically fall back to the Free plan and keep serving within the free quota. Degraded mode only covers usage beyond your plan's quota.
Server-issued Challenge Token
Issue a short-lived server_token from your backend. The browser then passes this token when initializing a challenge, proving the request originated from your trusted server.
Issue Server Token
Call this endpoint from your own server only. Requires X-App-Key + X-App-Secret. Response contains a server_token (sct_ prefix) that the frontend forwards to the challenge initialization.
POST /v1/server/challenge/issue
X-App-Key: YOUR_APP_KEY
X-App-Secret: YOUR_APP_SECRET
Content-Type: application/x-www-form-urlencoded
action=login&ttl=300&max_uses=1&bind_ip=1.2.3.4{
"code": 0,
"data": {
"server_token": "sct_xxxxxxxxxxxx",
"expires_in": 300,
"issued_at": 1713600000
}
}Body Parameters (form-urlencoded)
| Field | Description |
|---|---|
action | Business scene, e.g. login, register, pay. Must match the action used in the challenge initialization. |
ttl | Token lifetime in seconds. Default 300, max 900. |
max_uses | Maximum times the token may be consumed. Default 10. |
bind_ip | Bind token to a client IP. Initialization from another IP is rejected. |
bind_device_id | Bind token to a specific device id. |
bind_fingerprint | Bind token to a specific browser fingerprint. |
Initialize Challenge
Called by the SDK to start a CAPTCHA challenge. Accepts an optional server_token issued by /v1/server/challenge/issue.
| Field | Description |
|---|---|
app_key | Your public App Key. |
action | Business scene. Must match the action used when issuing the server_token. |
server_token | Optional; required when the app has server_token_required = true. |
INFO
If server_token_required is enabled for this app in the dashboard, the challenge initialization will reject requests that do not carry a valid server_token.
Offline Fallback Endpoints
CaptchaLa keeps a small standby service on separate hosts so verification still works while the main API is unreachable:
| Host | Called by | Purpose |
|---|---|---|
bypass.captchala.com · bypass.captcha-cdn.net | Web / native SDK (end-user side) | Runs offline verification and issues an offline_ pass token while the main API is down |
fallbackapiv1.captchala.com · fallbackapiv1.captcha-cdn.net | Server SDKs | Validates offline_ tokens (POST /api/validate) |
These are failover targets, not an alternative API. The SDKs switch to them by themselves, only after the main API has actually failed, and switch back as soon as it recovers. Nothing in your own configuration should point at them.
service_online (HTTP 503)
{ "code": -1, "error": "service_online", "message": "Main API is online, please use main API" }You get this while the main API is up: the standby service only serves traffic during an outage. The request reached bypass.* instead of https://apiv1.captcha.la — fix the URL, retrying will not help.
Common causes:
| Cause | Fix |
|---|---|
SDK apiServer option set to a bypass host | Set it to https://apiv1.captcha.la, or remove the option and let the SDK choose a server |
bypassServers overridden by hand | Remove the override unless we gave you specific hosts |
| A custom HTTP client copied a bypass URL (e.g. from devtools during an incident) | Call https://apiv1.captcha.la and let the SDK handle failover — the standby service does not accept normal challenge traffic |
| Load balancer / proxy / DNS rewriting the API host | Point the upstream back at apiv1.captcha.la |
TIP
Server-side validation never needs the fallback host either. POST /v1/validate on apiv1.captcha.la handles pt_ and dg_ tokens, and the PHP / Go server SDKs fall back to the backup API for offline_ tokens automatically.
Error Codes
| Code | Description |
|---|---|
invalid_app_key | Invalid App Key |
invalid_app_secret | Invalid App Secret |
challenge_expired | Challenge expired |
challenge_not_found | Challenge not found |
invalid_answer | Invalid answer |
token_expired | Token expired |
token_already_used | Token already used |
token_not_found | Token not found |
quota_exceeded | Quota exceeded |
rate_limited | Rate limited |
rate_limit_exceeded | Too many issuance requests for this app. Back off and retry. |
service_online | The request hit the offline fallback service while the main API is up. Call https://apiv1.captcha.la instead — see Offline fallback endpoints. |