--- title: API Reference description: "CaptchaLa REST API reference: endpoints, request and response schemas, error codes, rate limits, and authentication." --- # API Reference CaptchaLa provides RESTful API for all server-side integrations. ## Base URL ``` https://apiv1.captcha.la ``` ::: warning One 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`](#offline-fallback). ::: ## Authentication All API requests require authentication headers: ``` X-App-Key: YOUR_APP_KEY X-App-Secret: YOUR_APP_SECRET ``` ::: warning `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`](https://github.com/Captcha-La/captchala-php) ([δΈ­ζ–‡](https://github.com/Captcha-La/captchala-php/blob/main/README_zh.md)) > - **Go** β€” `go get github.com/Captcha-La/captchala-go` Β· [README](https://github.com/Captcha-La/captchala-go) 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. ```bash 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. ```json { "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. ::: ::: info 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 solved - `referer` β€” solve page URL (web); `null` on native - `pkg` β€” app package / bundle id (native); `null` on web - `solved_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) {#degraded-mode} 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/validate` returns: ```json { "code": 0, "data": { "valid": false, "degraded": true, "reason": "quota_exhausted", "expired": false } } ``` Key properties: - `valid` is **always `false`** for degraded tokens β€” secure by default. If your code only checks `valid`, nothing changes for you. - Whether to let the request through is **your decision**: accept `valid || degraded` to 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) and `result.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 {#server-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. ```bash 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 ``` ```json { "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 {#offline-fallback} 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) ```json { "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](#offline-fallback). |