API/Keys
Create an API key
POST/v1/keys· API key
Creates a key on the calling key's account. The secret appears in this response and nowhere else, ever, so the request cannot be replayed: see "Retries". An account can hold several active keys at once, which is how a key is rotated without downtime. Create the new one, deploy it, then revoke the old one.
curl -X POST https://api.einvoicing.dev/v1/keys \ -H "Authorization: Bearer $EINVOICING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "GitHub Actions", "mode": "test", "expires_at": "2027-09-11T00:00:00.000Z" }'<?php$client = new GuzzleHttp\Client();$response = $client->request('POST', 'https://api.einvoicing.dev/v1/keys', [ 'headers' => [ 'Authorization' => 'Bearer ' . getenv('EINVOICING_API_KEY'), 'Accept' => 'application/json', ], 'json' => [ 'name' => 'GitHub Actions', 'mode' => 'test', 'expires_at' => '2027-09-11T00:00:00.000Z', ],]);$data = json_decode((string) $response->getBody(), true)['data'];payload := `{ "name": "GitHub Actions", "mode": "test", "expires_at": "2027-09-11T00:00:00.000Z"}`req, _ := http.NewRequest(http.MethodPost, "https://api.einvoicing.dev/v1/keys", strings.NewReader(payload))req.Header.Set("Authorization", "Bearer "+os.Getenv("EINVOICING_API_KEY"))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/keys", { method: "POST", headers: { Authorization: `Bearer ${process.env.EINVOICING_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "name": "GitHub Actions", "mode": "test", "expires_at": "2027-09-11T00:00:00.000Z" }),});const { data } = await res.json();Request body
application/json· required
| 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. |
{ "name": "GitHub Actions", "mode": "test", "expires_at": "2027-09-11T00:00:00.000Z"}Response
201 The key was created. Store secret now.
| Field | Type | Description |
|---|---|---|
datarequired | 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.
|
{ "data": { "id": "01J9Z4C2E4G6J8M0N2Q4S6V8W0", "name": "GitHub Actions", "mode": "test", "prefix": "einv_test_p7r2c8vn", "created_at": "2026-09-11T16:00:00.000Z", "last_used_at": null, "expires_at": "2027-09-11T00:00:00.000Z", "revoked_at": null, "secret": "einv_test_p7r2c8vn_Hq3Lm9Xw2Ks7Pd4Vz8Nb1Tc6Rf0Jy5Ga3Ue7Wo" }}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 |
| 401 | No key, or a key that is unknown or revoked. Problem |
| 403 | A test key cannot manage keys or billing. Use a live key. Problem
|
| 422 | The body parsed and was rejected. Includes unknown fields, which are
refused rather than silently dropped. Problem |
| 429 | Too many requests in a short window. Slow down and retry after the
number of seconds in |
Schemas
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. |
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`.
| Field | Type | Description |
|---|---|---|
idrequired | Ulid | A ULID. Opaque and time-ordered. |
namerequired | string | What the key is for. |
moderequired | string | Whether the key is metered (live) or free and limited (test).
|
prefixrequired | string | The public start of the key, enough to recognise it and never enough to use it. |
created_atrequired | Timestamp · date-time | RFC 3339, UTC, millisecond precision. |
last_used_atrequired | string or null · date-time | Always null on a key that was just created. |
expires_atrequired | string or null · date-time | When the key stops working. Null if never. |
revoked_atrequired | string or null · date-time | Always null on a key that was just created. |
secretrequired | string | The full key. Store it now. It cannot be retrieved again. |