Skip to content
einvoicing.dev

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.

Request
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" } }'

Parameters

NameInTypeDescription
sign_in_idrequiredpathUlid

The id returned by POST /v1/sign-ins.

Request body

application/json· required

FieldTypeDescription
coderequiredstringThe six-digit code from the email.
keyKeyRequestHow to set up a new key.
Example bodyapplication/json
{  "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.

FieldTypeDescription
datarequiredSignInResultThe account signed in to, and the key the sign-in created.
Example responseapplication/json
{  "data": {    "account": {      "id": "01J9Z3M8W2C4K6P8R0T2V4X6Z8",      "email": "steve@example.com",      "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.

StatusWhen
400

The body could not be parsed as the XML or JSON its Content-Type declares. Distinct from 422, which means the body parsed and its contents were rejected. Problem type is malformed-body.

/problems/malformed-body

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 POST /v1/sign-ins. Problem type is sign-in-expired.

/problems/sign-in-expired

422

The code was wrong (invalid-code, with attempts_remaining), or the body did not match this schema (invalid-request).

429

Too many requests in a short window. Slow down and retry after the number of seconds in Retry-After. Problem type is rate-limited.

/problems/rate-limited

Schemas

ConfirmSignInRequest

The emailed code, and optionally how to set up the key it produces.

FieldTypeDescription
coderequiredstringThe six-digit code from the email.
keyKeyRequestHow to set up a new key.

KeyRequest

How to set up a new key.

FieldTypeDescription
namerequiredstringWhat the key is for, so it can be recognised later. It is not secret.
modestringlive keys are metered and can manage the account; test keys are free and cannot.

livetest

expires_atstring or null · date-timeWhen 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.

FieldTypeDescription
accountrequiredAccountAn account. Identified by its email address; it has no password.
account_createdrequiredbooleanTrue when this sign-in created the account.
keyrequiredNewApiKeyA 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.