API/Sign-in
Get a proof-of-work challenge for sign-in
POST/v1/sign-in-challenges· no key needed
Returns an ALTCHA (v2) proof-of-work challenge. Solve it, then send
the result as altcha with POST /v1/sign-ins. With any ALTCHA
solver (Go, JavaScript, PHP) it takes well under a second, but it
makes sending sign-ins in bulk expensive. It is the headless stand-in
for a CAPTCHA.
data is ALTCHA's own format, unchanged, so a solver can take it as
it is. That is why its fields are camelCase when the rest of this
API is snake_case. If you are writing a solver yourself:
- Try
counter= 0, 1, 2, … For each, derivekeyLengthbytes with PBKDF2/SHA-256, usingcostiterations,saltas the salt, and as the passwordnoncefollowed by the counter as a 4-byte big-endian integer. Stop when the derived key starts withkeyPrefix. - Send
base64(JSON {"challenge": <data>, "solution": {"counter": <n>, "derivedKey": "<hex>"}})asaltcha.
Challenges are signed and never stored, so asking for one creates
nothing. Each expires at parameters.expiresAt, five minutes after
issue, and each can be spent once. Difficulty rises automatically
while sign-in volume is unusually high.
curl -X POST https://api.einvoicing.dev/v1/sign-in-challenges<?php$client = new GuzzleHttp\Client();$response = $client->request('POST', 'https://api.einvoicing.dev/v1/sign-in-challenges', []);$data = json_decode((string) $response->getBody(), true)['data'];req, _ := http.NewRequest(http.MethodPost, "https://api.einvoicing.dev/v1/sign-in-challenges", nil)res, err := http.DefaultClient.Do(req)if err != nil { log.Fatal(err)}defer res.Body.Close()const res = await fetch("https://api.einvoicing.dev/v1/sign-in-challenges", { method: "POST",});const { data } = await res.json();Response
200 A fresh challenge. Never cached, because every challenge is single-use.
| Field | Type | Description |
|---|---|---|
datarequired | SignInChallenge | An ALTCHA v2 proof-of-work challenge, in ALTCHA's own format. Pass it unchanged to an ALTCHA solver. |
{ "data": { "parameters": { "algorithm": "PBKDF2/SHA-256", "cost": 1000, "expiresAt": 1789135991, "keyLength": 32, "keyPrefix": "76daadcb383cfbe788b520e6ef624083", "keySignature": "2e62b0c7f7033399fd56c0d4f073ec11e575168fa198289e6b89134c92c2756d", "nonce": "03acffe437ae1e8f7f33ba01bee2d154", "salt": "fcbaea6dca37599ef2c7e753785b2809" }, "signature": "e14bccd007302003c6a99fc5f45f016f8f4530df32eb587c3b786bc4bdddd602" }}Errors
Every error is application/problem+json (RFC 9457). Branch on type, which is stable, never on title or detail.
| Status | When |
|---|---|
| 429 | Too many requests in a short window. Slow down and retry after the
number of seconds in |
Schemas
SignInChallenge
An ALTCHA v2 proof-of-work challenge, in ALTCHA's own format. Pass it unchanged to an ALTCHA solver.
| Field | Type | Description |
|---|---|---|
parametersrequired | object | What to solve. Signed as a whole, so changing any field invalidates the challenge. |
algorithmrequired | string | The key derivation function to run for each attempt.
|
costrequired | integer | PBKDF2 iterations per attempt. |
keyLengthrequired | integer | Length of the derived key, in bytes. |
keyPrefixrequired | string | The derived key must start with these bytes (hex). |
keySignature | string | Lets the server verify a solution quickly. Opaque to clients; send it back unchanged. |
noncerequired | string | Hex. Followed by the counter as a 4-byte big-endian integer, it is the PBKDF2 password. |
saltrequired | string | Hex. The PBKDF2 salt. |
expiresAtrequired | integer | Unix time, in seconds, after which the challenge is refused. |
signaturerequired | string | The server's HMAC over parameters. Opaque; send it back unchanged. |