CAPTCHA handling¶
TEBHOS requires a CAPTCHA solution on a small set of abuse-sensitive operations. The production instance uses hCaptcha. A client reads the provider and its site key from instance discovery, renders the provider's widget, and sends the solution in a request header.
Discovering the provider¶
GET /.well-known/tebhos includes a captcha object:
| Field | Type | Description |
|---|---|---|
provider |
string | hcaptcha, turnstile or none |
hcaptcha_site_key |
?string | The hCaptcha site key |
turnstile_site_key |
?string | The Turnstile site key, when one is configured |
{
"captcha": {
"provider": "hcaptcha",
"hcaptcha_site_key": "00000000-0000-0000-0000-000000000000",
"turnstile_site_key": null
}
}
hcaptcha is the provider TEBHOS uses. turnstile is supported by the API for instances configured with it. none means the widget is not configured; clients should still handle CAPTCHA_REQUIRED as described below instead of assuming that no challenge will ever be sent.
Gated operations¶
| Method | Route | Operation |
|---|---|---|
| POST | /auth/login |
Log in with a password |
| POST | /auth/send-verification |
Send the registration verification code |
| POST | /oauth2/applications |
Create an application |
Registration itself is not gated. POST /auth/register requires the verification code that only a CAPTCHA-checked POST /auth/send-verification can deliver, so the CAPTCHA on the verification step protects registration as well.
On login and on sending the verification code, the CAPTCHA check runs before the operation's rate limits and before any account lookup. Application creation checks its rate limit first and the CAPTCHA second.
Request headers¶
| Header | Description |
|---|---|
X-Captcha-Token |
The solution returned by the provider widget |
X-Captcha-Type? |
The provider that produced the solution: hcaptcha or turnstile. Omitted or any other value means hcaptcha |
An absent or empty X-Captcha-Token on a gated operation returns 400 CAPTCHA_REQUIRED.
The retry handshake¶
- Send the request without CAPTCHA headers.
- On 400
CAPTCHA_REQUIRED, show the provider's widget with the site key from discovery and obtain a solution. - Retry the same request with
X-Captcha-Tokenset to the solution and, optionally,X-Captcha-Type.
An accepted solution lets the operation proceed. A rejected solution returns 400 INVALID_CAPTCHA.
curl -X POST https://tebhos.com/api/v1/auth/login \
-H "Content-Type: application/json" \
-H "X-Captcha-Token: $HCAPTCHA_RESPONSE" \
-H "X-Captcha-Type: hcaptcha" \
-d '{"login": "[email protected]", "password": "correct horse battery staple"}'
A solution is single-use
The provider treats an already redeemed solution as invalid. After INVALID_CAPTCHA, obtain a new solution before retrying; never replay the previous X-Captcha-Token.
Provider verification¶
TEBHOS verifies the solution with the provider, passing the client IP address. A rejected solution, a provider error and a verification timeout (10 seconds) all return 400 INVALID_CAPTCHA; the response does not distinguish them.
Naming a provider in X-Captcha-Type that the instance has no secret for is a server configuration error and returns 500 INTERNAL_SERVER_ERROR. Omit the header, or send hcaptcha, unless discovery reports turnstile.
Error codes¶
| Code | Status | Description |
|---|---|---|
CAPTCHA_REQUIRED |
400 | The operation is gated and the request has no solution |
INVALID_CAPTCHA |
400 | The provider rejected the solution, or verification could not be completed |
Both use the ordinary error response.