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/vote is the core write endpoint for Mundial de Clicks. Rather than sending one HTTP request per click, the client accumulates clicks for up to 400 ms and dispatches them as a single batch — up to 10 votes across up to 3 countries. All rate-limit enforcement, captcha-session validation, and DragonFly counter increments happen atomically inside a single Lua script, so there are no partial writes or race conditions.

Authentication / Prerequisites

In production (NODE_ENV=production), every request must carry a valid cap_session cookie issued by POST /api/captcha/redeem. If the cookie is absent, expired, or tied to a different IP, the endpoint returns 403 with reason: "captcha_required". In development (captcha disabled via CAP_API_URL being empty), no session cookie is required — votes are processed directly.
If the server is running in production mode but CAP_API_URL is not configured, the endpoint returns 503 captcha_required for every request. Configure the captcha service before deploying.

Request

Method: POST
Content-Type: application/json
Body size limit: 2048 bytes (hard limit, enforced by stream-reading — not trusted Content-Length)

Body Parameters

votes
object
required
Map of country code → vote count. Keys are ISO 3166-1 alpha-2 lowercase codes. Maximum 3 distinct country codes per request. Total votes across all countries must be ≤ 10.Invalid codes, non-integer values, and zero or negative counts are silently ignored during normalization. If the normalized total is 0 after filtering, the request is rejected with invalid_payload.

Valid Country Codes

The following codes correspond to the 16 nations competing in the Mundial de Clicks:
CodeCountryCodeCountry
arArgentinamaMarruecos
beBélgicamxMéxico
brBrasilnoNoruega
caCanadápyParaguay
coColombiaptPortugal
egEgiptochSuiza
esEspañausUSA
frFranciaenInglaterra

Example Request

{
  "votes": {
    "es": 5,
    "ar": 3
  }
}
const res = await fetch('/api/vote', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ votes: { es: 5, ar: 3 } }),
});

Responses

200 — Success

All or part of the batch was accepted. Even when some votes were blocked by the rate limiter, the endpoint returns 200 (not 429) as long as at least one vote was accepted. Check accepted and blocked to reconcile optimistic UI state.
{
  "ok": true,
  "counts": { "es": 1043, "ar": 892 },
  "accepted": 8,
  "blocked": 0,
  "remaining": 2
}
ok
boolean
Always true on a 200 response.
counts
object
Updated cumulative vote totals for each country included in the batch, keyed by country code. Only countries present in the request are returned.
accepted
number
Number of votes from the batch that were written to DragonFly. Always ≤ the total requested.
blocked
number
Number of votes from the batch that were rejected by the rate limiter. accepted + blocked equals the total votes sent.
remaining
number
Remaining token-bucket capacity in the current rate-limit window after this request was processed.

429 — Rate Limited

The entire batch was blocked — no votes were accepted. The client should wait at least retryAfter milliseconds before retrying.
{
  "ok": false,
  "reason": "rate_limited",
  "accepted": 0,
  "blocked": 5,
  "remaining": 0,
  "retryAfter": 750
}
retryAfter
number
Milliseconds until the rate-limit window resets and the client may retry. Apply this as a cooldown in the UI to avoid hammering the endpoint.

Error Responses

StatusreasonDescription
400invalid_payloadMalformed JSON, no valid votes after normalization, more than 3 distinct country codes, or total votes exceed 10.
403captcha_requiredNo cap_session cookie present, or the session has expired or is bound to a different IP.
413payload_too_largeRequest body exceeds the 2048-byte hard limit.
429rate_limitedIP exceeded the rate-limit window. No votes were accepted.
500errorInternal server error — typically a DragonFly connection failure or Lua script error.
503captcha_requiredServer is running in production mode but CAP_API_URL is not configured.

Client Usage

The following is the actual implementation from src/scripts/api.ts. It wraps the fetch call and returns a typed VoteResponse, falling back to { ok: false, reason: "error" } on network failure so the caller always receives a consistent shape.
async function sendVotes(votes: Map<string, number>): Promise<VoteResponse> {
  const res = await fetch('/api/vote', {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ votes: Object.fromEntries(votes) }),
    keepalive: true,
  });
  return (await res.json()) as VoteResponse;
}
The client flushes its pending-vote accumulator every 400 ms (FLUSH_MS). Clicks that arrive during an in-flight request are held in the pending map and sent in the next flush. This means 20 rapid clicks produce at most 1–2 requests per second, not 20 — substantially reducing both server load and DragonFly write pressure.
When accepted < total requested, the difference was silently blocked by the rate limiter. The client reconciles its optimistic UI by calling revert(code, blocked) for each country in the batch, in the same order the Lua script processed them, so the displayed vote counts never drift permanently above the server’s authoritative totals.

Build docs developers (and LLMs) love