Docs
One endpoint. Two prompts.
CaptchaKraken speaks the OpenAI chat-completions API. If your tooling can talk to OpenAI, it can talk to this — change the base URL and the key, and send a screenshot.
Setup
Base URL and key.
# same client, same request shape — only the endpoint moves
export CAPTCHA_KRAKEN_API_KEY=ck_live_…
export VLLM_BASE_URL=https://api.captchakraken.com/v1Keys are minted from the dashboard, or by the MCP server below. They look like ck_live_ followed by a public prefix and a secret half, and the secret is shown exactly once at issue. We store only a SHA-256 of it, so a lost key is replaced rather than recovered.
Authenticate with a standard bearer header: Authorization: Bearer ck_live_…. The endpoint is api.captchakraken.com.
Important
This is a captcha model, not a general vision model.
The endpoint accepts two prompts — the grid-selection prompt and the click/drag prompt — and rejects anything else with a 400 unrecognized_prompt. It is not a general-purpose image endpoint and will not behave as one.
Send the prompts as written. They are pinned to the model's training distribution, and paraphrasing them measurably costs accuracy.
Request
Solving a grid challenge.
Send the captcha screenshot as a data URL and the prompt as text, in that order, inside one user turn.
curl https://api.captchakraken.com/v1/chat/completions \
-H "Authorization: Bearer $CAPTCHA_KRAKEN_API_KEY" \
-H "Content-Type: application/json" \
-H "x-ck-session: $(uuidgen)" \
-d '{
"model": "captcha",
"messages": [
{
"role": "system",
"content": "You are an expert captcha solver. Respond ONLY with the JSON action."
},
{
"role": "user",
"content": [
{ "type": "image_url",
"image_url": { "url": "data:image/png;base64,<SCREENSHOT>" } },
{ "type": "text", "text": "<PROMPT — see below>" }
]
}
],
"temperature": 0,
"max_tokens": 128,
"chat_template_kwargs": { "enable_thinking": false }
}'The grid prompt, verbatim
Substitute the real grid size and the matching hint line — Separate images. for a 3×3 of distinct photos, Single large image split into tiles. for a 4×4 subdivided one.
Solve the captcha grid by choosing the cell numbers that match the description from the captcha image prompt.
Grid: 3x3 (9 cells)
Hint: Separate images. Select only clear matches.
If no tiles match the description (e.g., they have all been cleared or none were present), return an empty list for target_ids: [].
Return JSON Array: [list of cell numbers (1-9)]From an OpenAI SDK
from openai import OpenAI
client = OpenAI(
api_key=os.environ["CAPTCHA_KRAKEN_API_KEY"],
base_url="https://api.captchakraken.com/v1",
)
response = client.chat.completions.create(
model="captcha",
messages=[...], # shape as above
temperature=0,
max_tokens=128,
extra_headers={"x-ck-session": session_uuid},
)The response is a normal chat completion
The answer arrives as JSON in the message content. Grid challenges return the cell numbers to click; click/drag challenges return labelled coordinates on a normalised 0–1000 scale.
{
"choices": [
{ "message": { "role": "assistant", "content": "[6, 8]" } }
],
"usage": { "prompt_tokens": 1102, "completion_tokens": 7 }
}Headers
Two optional headers that affect your bill.
| Header | Why it matters |
|---|---|
x-ck-session | A UUID identifying one captcha attempt. Responses sharing a session are grouped, and that grouping is what both per-attempt limits are counted over — the 5-response billing ceiling and the 10-response abandon threshold. Without it, every response bills independently and neither engages — send a fresh UUID per captcha, and reuse it across every response for it. |
x-ck-client | Identifies the integration, e.g. camoufox/0.4.11. Used for attribution and support; it has no effect on price. |
There is no header that sets the puzzle class. Pricing is derived from the prompt in the body, so a header cannot move a request into a cheaper bracket.
Errors
Branch on the code, never on the message.
Errors use OpenAI's envelope, so your SDK will parse them. The prose gets reworded; error.code does not.
| Status | Code | Meaning | What to do |
|---|---|---|---|
| 401 | missing_api_key | No Authorization header was sent. | Send Authorization: Bearer ck_live_… |
| 401 | invalid_api_key | The key is unknown or has been revoked. | Check the key, or mint a new one. |
| 402 | insufficient_credits | The account balance cannot cover the round. | Top up. The same key resumes working immediately. |
| 403 | account_suspended | The account is suspended. | Contact support. |
| 429 | rate_limited | Too many requests from this key. | Back off; honour the Retry-After header. |
| 400 | unrecognized_prompt | The prompt is not one this service solves. | Use one of the two supported prompts, unmodified. |
| 400 | invalid_request | The body is malformed. | Check the JSON against the shape above. |
| 413 | request_too_large | The screenshot exceeds the body limit. | Downscale or crop before encoding. |
| 409 | solve_abandoned | One attempt was served 10 times without settling. | Usually IP reputation, not the answers. Start a new x-ck-session. |
| 502 | upstream_unavailable | The solver fleet is unreachable. Not your fault. | Retry shortly with backoff. Nothing is billed. |
A revoked key and an unknown key both return invalid_api_key. Telling the difference apart would confirm to an attacker which of their guesses was once real.
MCP
Managing the account from an agent.
The same account, driven from an MCP client. It signs you in through GitHub, mints and revokes keys, and reads back what you have spent.
# Claude Code, Claude Desktop, or any MCP client
claude mcp add captchakraken -- npx -y captchakraken-mcpSign in
sign_in prints a short code and a link. You approve it in the browser with GitHub; the account is created on the spot if it does not exist, free credits and all.
Mint keys
create_api_key returns a live ck_live_ key, once. list_api_keys and revoke_api_key do the rest.
Watch the money
get_balance and get_usage report the balance, the burn rate and the per-day spend. get_topup_link opens Stripe.
The token the MCP server holds manages the account — it cannot solve captchas, and an API key cannot manage the account. Two credentials, two blast radii. Revoke either from the dashboard.
Billing
$0.30 per 1,000 image responses.
You are billed per inference response — no subscription, no monthly minimum, no per-seat fee, and no charge at all for challenges that never reach the model.
| What you send | Credits each | Per 1,000 responses |
|---|---|---|
| Image response reCAPTCHA and hCaptcha grids, click and drag puzzles | 3 | $0.30 |
| Video response Video challenges, which only the hosted model handles | 10 | $1.00 |
| reCAPTCHA checkbox, Cloudflare Turnstile Detected locally; no model call is made. | 0 | Free |
Most image captchas are solved in 1–2 image inference requests, and most videos are solved in 1.
$1.00 = 10,000 credits. The per-response rate is the contractual figure and is what your account is actually charged; the response counts above are what we typically see, not a guarantee — a captcha that fights back takes more, and you are billed for what was actually served.
Credits
Buy what you need. It does not expire.
33,333 image responses
83,333 image responses
333,333 image responses
Those counts are division, not an estimate — 3 credits per image response into the pack. Credits carry over indefinitely and are not tied to a billing period. Need volume pricing? Talk to us.
How billing works
Four rules, and no others.
One response, one charge
Every inference response costs the credits listed for its class. The class is derived from what you actually sent — an image or a video — not from a header, so a request cannot be relabelled into the cheaper bracket by anyone, including us.
Our misses are free
When the model under-selects and the challenge must be asked again for that reason, the extra response costs nothing and is labelled missed-tiles-retry on your usage so you can see it happened.
One captcha, one ceiling
At most 5 responses are billable per solve attempt — $1.50 per 1,000 even in the worst case. We keep answering free up to 10, then abandon the attempt. A pathological captcha cannot drain an account.
Failures we caused are not billed
If a response fails on our side — capacity, an error, anything that is not an answer — it is recorded at zero credits and appears on your usage with the reason.
Two things move your effective cost, and you should hear them here rather than infer them from an invoice. A reCAPTCHA grid re-draws after each click, so a session on a flagged IP draws harder challenges and lands above the typical response count. And if a vendor rejects a submission the model got right — a function of IP reputation and browser fingerprint more than of the answer — that response was still served and still billed. Tell us your setup and we will estimate it with you before you spend anything.
Common questions
Before you ask.
Is there a subscription?
No. Credits are prepaid and drawn down as you use them. There is no monthly fee, no seat count, and no minimum spend.
Do credits expire?
No. They stay on the account until they are used.
What if I run out mid-run?
Requests are refused with a clear insufficient_credits error rather than being served into a negative balance. Top up and the same key resumes working immediately.
Can I get a refund?
Unused credits, yes — see the refund policy. Credits already spent on solves we delivered are not refundable.
Do you offer volume pricing?
Yes, above the published packs. Get in touch with your expected monthly volume and challenge mix.