API/Sign-in
Exchange a one-time code for an API key
POST/v1/sign-ins/{sign_in_id}/confirmation· no key needed
Confirms a sign-in with the code from the email and returns a new key. The secret appears in this response and nowhere else, ever.
- The first confirmation for an address creates the account on the Free plan.
- Every later confirmation adds a key to that account, and the address is sent a notice that a key was created.
A wrong code is 422, with the attempts left on the problem. Once a
sign-in has been used, has expired or has run out of attempts, it
answers 410 and a new one has to be requested. An unknown
sign_in_id is also 410, never 404, so the endpoint cannot be
used to probe which ids exist.
curl -X POST https://api.einvoicing.dev/v1/sign-ins/<sign_in_id>/confirmation \ -H "Content-Type: application/json" \ -d '{ "code": "482913", "key": { "name": "CLI on steve-laptop", "mode": "live" } }'<?php$client = new GuzzleHttp\Client();$response = $client->request('POST', 'https://api.einvoicing.dev/v1/sign-ins/<sign_in_id>/confirmation', [ 'headers' => [ 'Accept' => 'application/json', ], 'json' => [ 'code' => '482913', 'key' => [ 'name' => 'CLI on steve-laptop', 'mode' => 'live', ], ],]);$data = json_decode((string) $response->getBody(), true)['data'];payload := `{ "code": "482913", "key": { "name": "CLI on steve-laptop", "mode": "live" }}`req, _ := http.NewRequest(http.MethodPost, "https://api.einvoicing.dev/v1/sign-ins/<sign_in_id>/confirmation", strings.NewReader(payload))req.Header.Set("Content-Type", "application/json")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-ins/<sign_in_id>/confirmation", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ "code": "482913", "key": { "name": "CLI on steve-laptop", "mode": "live" } }),});const { data } = await res.json();Parameters
| Name | In | Type | Description |
|---|---|---|---|
sign_in_idrequired | path | Ulid | The |
Request body
application/json· required
| Field | Type | Description |
|---|---|---|
coderequired | string | The six-digit code from the email. |
key | KeyRequest | How to set up a new key. |
{ "code": "482913", "key": { "name": "CLI on steve-laptop", "mode": "live" }}Response
201 The code was accepted and a key was created. Store key.secret now.
| Field | Type | Description |
|---|---|---|
datarequired | SignInResult | The account signed in to, and the key the sign-in created. |
{ "data": { "account": { "id": "01J9Z3M8W2C4K6P8R0T2V4X6Z8", "email": "[email protected]", "plan": "free", "created_at": "2026-09-11T14:05:02.117Z" }, "account_created": true, "key": { "id": "01J9Z3M8X5D7F9H1K3M5P7R9T1", "name": "CLI on steve-laptop", "mode": "live", "prefix": "einv_live_k3m9x2qa", "created_at": "2026-09-11T14:05:02.117Z", "last_used_at": null, "expires_at": null, "revoked_at": null, "secret": "einv_live_k3m9x2qa_Zx81QmPq4sVt7LwN2cBy6RfJ0hKd9GuTe3Ao5W" } }}Errors
Every error is application/problem+json (RFC 9457). Branch on type, which is stable, never on title or detail.
| Status | When |
|---|---|
| 400 | The body could not be parsed as the XML or JSON its |
| 410 | This sign-in can no longer be used. It was already used, expired,
ran out of attempts, or never existed. Request a new one with
|
| 422 | The code was wrong ( |
| 429 | Too many requests in a short window. Slow down and retry after the
number of seconds in |
Schemas
ConfirmSignInRequest
The emailed code, and optionally how to set up the key it produces.
| Field | Type | Description |
|---|---|---|
coderequired | string | The six-digit code from the email. |
key | KeyRequest | How to set up a new key. |
KeyRequest
How to set up a new key.
| Field | Type | Description |
|---|---|---|
namerequired | string | What the key is for, so it can be recognised later. It is not secret. |
mode | string | live keys are metered and can manage the account; test keys are free and cannot.
|
expires_at | string or null · date-time | When the key stops working. Null means it never expires. Short expiries suit CI. |
SignInResult
The account signed in to, and the key the sign-in created.
| Field | Type | Description |
|---|---|---|
accountrequired | Account | An account. Identified by its email address; it has no password. |
account_createdrequired | boolean | True when this sign-in created the account. |
keyrequired | NewApiKey | A key as created, the only time its secret is ever shown. The fields
of ApiKey plus secret. It is spelled out in full rather than
composed with allOf, because ApiKey refuses unknown fields and
would reject secret.
|