Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/midudev/mundial-de-clicks/llms.txt

Use this file to discover all available pages before exploring further.

POST /api/captcha/challenge sits between the Cap widget (running in the browser) and the Cap standalone server. Rather than letting the widget contact the Cap server directly — which would require CORS headers and expose the server URL — every challenge request passes through this endpoint. The server intercepts the call to enforce per-IP rate limiting and to compute a difficulty level based on how many votes the caller has already cast today, so high-volume clients face harder puzzles without any client-side influence over that value.

When captcha is disabled

If CAP_API_URL is not set (or DragonFly is unavailable), the endpoint returns immediately:
{ "error": "captcha_disabled" }
Status: 404 Not Found

Request

POST /api/captcha/challenge HTTP/1.1
No request body is required. Any body sent by the widget is ignored — difficulty is set server-side to prevent clients from requesting easier puzzles. The CF-Connecting-IP header must be present. In production this is injected by Cloudflare. In local development (non-production NODE_ENV) the fallback reads X-Real-IP instead. X-Forwarded-For is never trusted.

Rate limiting

Before proxying to Cap, the endpoint calls consumeAbuseLimit('captcha:challenge', ip, max, 60) backed by DragonFly (or an in-memory fallback when DragonFly is absent). The window is 60 seconds.
Environment variableDefaultMeaning
CAP_CHALLENGE_MAX_PER_MINUTE6Max challenge requests per IP per 60-second window
When the limit is exceeded, the response includes a retry-after header containing the number of seconds until the current window resets:
{ "error": "rate_limited" }
Status: 429 Too Many Requests

Difficulty calculation

The difficulty sent to the Cap server is computed entirely by the application:
function challengeDifficulty(dailyVotes: number): number {
  const base = Math.max(1, config.captcha.challengeDifficultyBase);   // default 4
  const max  = Math.max(base, config.captcha.challengeDifficultyMax); // default 8
  const step = Math.max(1, config.captcha.challengeDifficultyStepVotes); // default 250
  return Math.min(max, base + Math.floor(dailyVotes / step));
}
The dailyVotes value is the higher of the IP-based daily count and the voter_id-based daily count (readDailyVotesMax). This prevents a client from obtaining easy puzzles by switching IPs while retaining the same voter_id cookie. Examples with default settings (base 4, max 8, step 250):
Daily votesDifficulty
0 – 2494
250 – 4995
500 – 7496
750 – 9997
1 000 +8 (capped)
Adjust CAP_CHALLENGE_DIFFICULTY_BASE, CAP_CHALLENGE_DIFFICULTY_MAX, and CAP_CHALLENGE_DIFFICULTY_STEP_VOTES to tune the difficulty curve for your expected traffic. A lower step value penalises repeat voters more aggressively.

Response

On success, the endpoint passes through the Cap server’s response verbatim with its original HTTP status code:
{ "challenge": "<challenge-token>" }
The challenge token is an opaque string issued by the Cap server. The widget uses it to present the proof-of-work puzzle to the user and will include it in the subsequent POST /api/captcha/redeem call.

Error responses

StatusBody error fieldCause
403trusted_ip_requiredCF-Connecting-IP is absent (production) or no usable IP can be determined
404captcha_disabledCAP_API_URL is not set or DragonFly is unavailable
429rate_limitedExceeded CAP_CHALLENGE_MAX_PER_MINUTE in the current window
502captcha_unreachableThe Cap server did not respond within CAP_HTTP_TIMEOUT_MS (default 5 000 ms)
503rate_limit_unavailableDragonFly threw while incrementing the abuse counter
503daily_limit_unavailableDragonFly threw while reading the daily vote count for difficulty calculation

Environment variables

VariableDefaultEffect
CAP_API_URL(unset)Base URL of the Cap standalone server (including site key). Required.
CAP_CHALLENGE_MAX_PER_MINUTE6Per-IP challenge rate limit.
CAP_CHALLENGE_DIFFICULTY_BASE4Minimum PoW difficulty.
CAP_CHALLENGE_DIFFICULTY_MAX8Maximum PoW difficulty.
CAP_CHALLENGE_DIFFICULTY_STEP_VOTES250Daily votes needed to increase difficulty by one step.
CAP_HTTP_TIMEOUT_MS5000Hard timeout (ms) for the server-to-Cap fetch.

Build docs developers (and LLMs) love