API Reference
CaptchaLa provides RESTful API for all server-side integrations.
Base URL
https://apiv1.captcha.laAuthentication
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.
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. |