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