HTTP API contract

Keep verification credentials on the backend and verify against the site contract

Keep verification credentials on the backend and verify against the site contract

Keep verification credentials on the backend and verify against the site contract

Keep verification credentials on the backend and verify against the site contract

Keep verification credentials on the backend and verify against the site contract

Keep verification credentials on the backend and verify against the site contract

Captchas.irDeveloper center

A bound request, a pinned session, a one-use receipt Know each boundary and its errors before integrating

On this pageHTTP API

API reference

All bodies are JSON, at most 64 KiB. Unknown fields are rejected (400 INVALID_BODY). Every response carries an X-Request-ID header, repeated in error bodies

Headers and credentials

HeaderWhereNotes
Authorization: Bearer dck_…BackendBackend credential with the required scope; never in a browser
X-DigiCaptcha-Site-KeyBrowserPublic site key; not a secret
X-DigiCaptcha-Session-TokenBrowserThe session's completion_token, for challenge, redeem and complete
Idempotency-KeyEverywhereRequired for intent/session creation, complete and siteverify; 16 to 128 characters from A-Z a-z 0-9 . _ : -
OriginBrowserSent by the browser; must be one of the site's allowed origins
X-CSRF-TokenPanelFor admin API changes, together with the digicaptcha_session cookie

Public endpoints

POST/v1/intentsBearer · intents:create
site_id
Site ID; must be the credential's site
action
Operation name, pattern ^[a-zA-Z0-9_.:-]{1,64}$
request_hash
Pattern ^sha256:[a-f0-9]{64}$
binding_version
Only "jcs-v1"
expires_in_seconds
30 to 600; default 300

Response 201: {intent_id, action, expires_at}

POST/v1/sessionsSite key · Origin
intent_id
Required; an intent of the same site
locale
Optional
client_capabilities
Optional; any JSON

Response 201: {session_id, intent_id, completion_token, expires_at, policy_version, challenge} The challenge payload and renderer depend on the selected engine; Cap configuration is not a universal contract for other engines Keep the session token private; an eligible replay using the same idempotency key can return the original issuance response

POST/v1/sessions/{id}/cap/challengeSite key · Session token

Body {}. Returns Cap's response unchanged, with Cap's status code. Not a success

POST/v1/sessions/{id}/cap/redeemSite key · Session token

Body {token, solutions} from the Cap solver. Returns Cap's response with a token. No receipt has been issued yet

The example below is for Cap Each engine has its own provider_evidence contract A timeout after upstream verify is indeterminate; never infer success or switch the issued session to another engine

POST/v1/sessions/{id}/completeSite key · Session token

Body {"provider_evidence": {"token": "…"}} with the redeem token. Captchas.ir verifies it with Cap using the site secret. Response 200: {receipt, receipt_expires_at, intent_id, session_id}. Repeating with the same Idempotency-Key returns the same receipt without calling Cap again

The same request and Idempotency-Key can replay the original response within the same recovery epoch A fresh key cannot consume the receipt again Database restore invalidates artifacts from the earlier epoch

POST/v1/siteverifyBearer · receipts:verify

Body {receipt, intent_id, action, request_hash}. Response 200: {success: true, verification_id, intent_id, site_id, action, request_hash, verified_at}. A consumed receipt gets 409 ALREADY_CONSUMED, another site's receipt 403 BINDING_MISMATCH, an expired one 410 SESSION_EXPIRED

GET/v1/stats?days=30Bearer · dcs_…

Read-only stats for one site, for an outside dashboard or script. Issue the token on the site page under “Protection and stats access”; it is shown once and each site has one active token. Response 200: {site_id, name, days, totals, series, countries}. A revoked or wrong token gets 401

Other endpoints

PathNotes
OPTIONS /v1/sessions…Preflight for registered origins; Access-Control-Max-Age: 60
GET /livezProcess is alive
GET /readyzDatabase reachable; stage is m1-sqlite or m1-postgres
GET /v1/capabilitiesCatalog of eight engines with implemented and readiness
GET /widget/cap.jsCaptchas.ir widget script
GET /widget/vendor/{name}cap.min.js, cap_wasm_bg.wasm and hashwx.wasm
GET /playground/Full in-browser flow test; its calls need an admin session

Admin API

The panel uses these routes. Authentication is the digicaptcha_session cookie, and every change needs an X-CSRF-Token header equal to the digicaptcha_csrf cookie

PathPurpose
POST /admin/v1/session/loginSign in with {username, password}
POST /admin/v1/session/mfaComplete the second factor with {method, code}
POST /admin/v1/session/mfa/emailSend an email code during sign-in
POST /admin/v1/session/bootstrapFirst sign-in with {token}
GET /admin/v1/sessionCurrent session
POST /admin/v1/session/logoutSign out
GET /admin/v1/monitorMonitor page data
GET /admin/v1/security/mfaSecond-factor state; …/mfa/totp and …/mfa/email routes enable and change it
GET /admin/v1/sitesList sites with cursor and page_size
POST /admin/v1/sitesNew site with {name, environment, allowed_origins} and an Idempotency-Key
GET /admin/v1/sites/{id}Site detail with keys, credentials and instances
POST /admin/v1/sites/{id}/public-keysNew site key
POST /admin/v1/sites/{id}/credentialsBackend credential with {scopes}
POST /admin/v1/credentials/{id}/rotateRotate a credential
POST /admin/v1/sites/{id}/cap-instancesConnect Cap with {name, endpoint, admin_key, hashwx_difficulty}
POST /admin/v1/cap-instances/{id}/rotate-secretRotate the site secret in Cap
GET /admin/v1/providersEngine catalog
PUT /admin/v1/public-keys/{id}Label a public key with {label}
POST /admin/v1/public-keys/{id}/revokeRevoke one public key
PUT /admin/v1/credentials/{id}Label and scopes of a server key with {label, scopes}
POST /admin/v1/credentials/{id}/revokeRevoke one server key
DELETE /admin/v1/sites/{id}Delete a site with {confirm_name}, which must equal the site name
POST /admin/v1/security/mfa/recoveryTen new recovery codes with {password}, each usable once
PUT /admin/v1/sites/{id}Edit {name, environment, allowed_origins, status, revision}; status is active or disabled and a stale revision answers 409
GET /admin/v1/stats/overview?days=30Daily series, today and totals for every site, 1 to 366 days
GET /admin/v1/stats/sitesToday per site
GET /admin/v1/sites/{id}/stats?days=30Daily series for one site
POST /admin/v1/session/passwordChange the password with {current_password, new_password}; other sessions are signed out
GET /admin/v1/versionBuild revision, Go version, schema and Cap version
GET /healthzSame answer as /readyz
GET /admin/v1/events/trafficTraffic events filtered by site, kind, country, ip, with cursor and limit. Kinds: session.issued, cap.solved, cap.failed, cap.indeterminate, verify.ok, verify.rejected, blocked.rate
GET /admin/v1/events/auditAudit trail with a prefix such as cap. and cursor
GET /admin/v1/stats/countries?days=30Counts by country, optional site
GET /admin/v1/stats/hourly?hours=24Hourly series, optional site
GET /admin/v1/stats/addressesBusiest addresses, as stored by the IP mode
GET PUT /admin/v1/settings{ip_mode, retention_days, login_captcha_site}; ip_mode is masked, full or off, retention 1 to 90 days. The sign-in captcha site needs an active public key and Cap instance, otherwise 409
GET POST /admin/v1/mcp-tokensList and create MCP tokens with {name}, at most twenty active; DELETE /admin/v1/mcp-tokens/{id} revokes one
PUT /admin/v1/sites/{id}/settings{rate_limit_per_min} from 0 to 100,000; zero means no cap
POST DELETE /admin/v1/sites/{id}/stats-tokenIssue or revoke the site stats token
PUT /admin/v1/cap-instances/{id}/config{hashwx_difficulty, obfuscation_level}; difficulty 50,000 to 5,000,000 and obfuscation 1 to 10. Applied on Cap first, then recorded
GET /admin/v1/session/login-captchaWhether the sign-in captcha is on. The /login-captcha frame opens the sign-in intent with POST on the same path, and the receipt goes in the captcha field of the sign-in body

Errors

Every error has the same shape:

json
{
  "error": {
    "code": "ALREADY_CONSUMED",
    "message": "This receipt was already consumed.",
    "request_id": "5f0c2a9e8b7d4c3e1a2b3c4d5e6f7a8b",
    "retryable": false
  }
}
HTTPcodeMeaning
400INVALID_BODY · INVALID_REQUESTBad JSON, unknown field or failed validation, including a missing Idempotency-Key
401UNAUTHENTICATEDCredential, key or session missing or invalid
403FORBIDDENMissing scope, disallowed origin or wrong CSRF token
403BINDING_MISMATCHReceipt or intent belongs to another site, action or installation
404NOT_FOUNDNot found in this tenant
409ALREADY_CONSUMEDThe receipt was already consumed
409IDEMPOTENCY_CONFLICTSame key with a different body
409OPERATION_IN_PROGRESSThe same operation is still running; retryable: true
409CONFLICTThe resource is not in a state that accepts this operation
410SESSION_EXPIREDIntent, session or receipt has expired
413BODY_TOO_LARGEBody over 64 KiB
400CAPTCHA_REQUIREDThe panel sign-in captcha is on and its receipt is missing or was not accepted
422INVALID_PROOFCap rejected the token, or it belongs to another Cap site
502UPSTREAM_REJECTEDCap rejected an admin request
503UPSTREAM_UNAVAILABLECap unreachable for challenge or redeem; retryable: true
503UPSTREAM_RESULT_UNKNOWNVerification outcome unknown; no receipt issued, do not retry with the same proof
503NO_ELIGIBLE_PROVIDERNo eligible active engine is available for the site
503STORAGE_UNAVAILABLEDatabase unavailable; retryable: true
429RATE_LIMITEDToo many sign-in or browser session calls from one address, or the site's per-IP session cap is full; wait for Retry-After seconds
503MAIL_NOT_CONFIGUREDSMTP is not configured
503ADAPTERS_NOT_IMPLEMENTEDFoundation mode, no database
500INTERNAL_ERRORInternal error; the operation failed closed

Idempotency

  • Generate one random key per logical operation and send the same key when retrying that operation
  • The same key with the same body returns the stored response. That is not a second consumption and creates no new success
  • The same key with a different body gets 409 IDEMPOTENCY_CONFLICT
  • If the earlier operation is still running you get 409 OPERATION_IN_PROGRESS
  • After UPSTREAM_RESULT_UNKNOWN do not retry blindly. Start a new intent and session

Engine protocol routes

This inventory follows the registered routes in this version A route does not prove configuration, entitlement or a successful engine test in your environment The public key, Origin and issued session token must match the same site and pinned engine

MethodRoute
POST/v1/sessions/{id}/cap/challenge
POST/v1/sessions/{id}/cap/redeem
POST/v1/sessions/{id}/wicketkeeper/challenge
GET/v1/sessions/{id}/fcaptcha/api/pow/challenge
POST/v1/sessions/{id}/fcaptcha/api/verify
POST/v1/sessions/{id}/gocaptcha/data
POST/v1/sessions/{id}/gocaptcha/check
POST/v1/sessions/{id}/digitalcaptcha/challenge
POST/v1/sessions/{id}/digitalcaptcha/check
POST/v1/sessions/{id}/captchapro/data
POST/v1/sessions/{id}/captchapro/verify
POST/v1/sessions/{id}/iconcaptcha
POST/v1/sessions/{id}/mosparo/api/v1/frontend/{action}
POST/v1/sessions/{id}/mcaptcha/config
POST/v1/sessions/{id}/mcaptcha/verify
POST/v1/sessions/{id}/mcaptcha/frame
POST/v1/sessions/{id}/altcha/challenge
POST/v1/sessions/{id}/altcha/verify

Download the API contract

Registered JSON API routes are extracted from this source version and fixture requests and responses are checked against the contract Provider passthrough payloads can remain open schemas HTML, demos and static assets are outside this contract

OpenAPI 3.1 JSON · OpenAPI 3.1 YAML