Send a challenge prompt and its tile images; get back which tiles to click, with the model's confidence for every tile. Unlike the extension, API requests are processed on our servers: images are handled in memory and never stored.
Getting a key
During the beta, keys are issued on request. Email
support@honeybee.wtf with what you plan to
build. Keys look like hs_live_…. Keep yours on your server: never ship
it in browser code or commit it to a repository.
Authentication
Send the key as a bearer token on every request:
Authorization: Bearer hs_live_your_key_here
Solve a grid
POST https://honeybee.wtf/v1/solve
| Field | Type | Description |
|---|---|---|
type | string | "hcaptcha_grid" (the default, and the only type today). |
prompt | string | The challenge text, as shown. Any language; up to 300 characters. |
images | string[] | The tiles in reading order, as base64 or data: URLs. 1 to 16 images, each up to 2 MB and 2048x2048. URLs are not accepted. |
threshold | number | Optional, 0 to 1. Minimum confidence to select a tile; default 0.5. |
Response:
{
"id": "5c1f0e7a9b3d4c21a8e6f0b2d4c6e8a1",
"type": "hcaptcha_grid",
"target": "bus",
"selected": [0, 4, 7],
"scores": [0.97, 0.02, 0.04, 0.01, 0.95, 0.03, 0.02, 0.91, 0.05],
"elapsed_ms": 1840
}
selected holds zero-based tile indexes to click. target is
the object HoneySolver read from an English prompt, or null when it
answered from the full prompt instead. Keep id for support requests.
Check your usage
GET https://honeybee.wtf/v1/usage
{
"key": "hs_live_Ab3dEf…",
"name": "my-project",
"limits": { "per_minute": 60, "daily": 1000 },
"remaining_today": 912,
"resets_in_seconds": 30412,
"history": [{ "day": "2026-10-03", "solved": 88, "errors": 2, "avg_latency_ms": 1910 }]
}
Limits
Each key has a per-minute rate and a daily quota (reset at 00:00 UTC). Only
successful solves count against the quota. Responses carry
X-Quota-Limit, X-Quota-Remaining and
X-RateLimit-Limit; refused requests carry Retry-After in
seconds. There is also a per-address limit in front of the API.
Errors
Errors are JSON: {"error": {"code": "…", "message": "…"}, "id": "…"}
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_request, bad_json, bad_tile, unsupported_type | The request is malformed; the message says what to fix. |
| 401 | unauthorized | Missing, invalid or revoked key. |
| 413 | body_too_large, tile_too_large | The body or an image is over the size limits. |
| 429 | rate_limited, quota_exceeded | Slow down, or wait for the daily reset. See Retry-After. |
| 503 | busy, unavailable | The solver is at capacity; retry after a short delay. |
| 500 | internal_error | Our fault. Retry, and contact us with the id if it persists. |
Examples
curl
curl https://honeybee.wtf/v1/solve \
-H "Authorization: Bearer $HONEYSOLVER_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "Please click each image containing a bus",
"images": ["iVBORw0KGgo...", "iVBORw0KGgo...", "..."]}'
Python
import base64, os, requests
tiles = [base64.b64encode(open(f"tile{i}.png", "rb").read()).decode() for i in range(9)]
r = requests.post(
"https://honeybee.wtf/v1/solve",
headers={"Authorization": f"Bearer {os.environ['HONEYSOLVER_KEY']}"},
json={"prompt": "Please click each image containing a bus", "images": tiles},
timeout=60,
)
r.raise_for_status()
print(r.json()["selected"])
Node.js
import { readFile } from "node:fs/promises";
const images = await Promise.all(
[...Array(9).keys()].map(async (i) => (await readFile(`tile${i}.png`)).toString("base64"))
);
const r = await fetch("https://honeybee.wtf/v1/solve", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.HONEYSOLVER_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ prompt: "Please click each image containing a bus", images }),
});
if (!r.ok) throw new Error((await r.json()).error.message);
console.log((await r.json()).selected);
Use of the API is covered by the Terms of Service and the Privacy Policy.