API keys — API reference
Managing the credentials of this account.
| Method | Path | Summary |
|---|---|---|
GET | /v1/api-keys | List the keys of this account |
POST | /v1/api-keys | Create an API key |
PATCH | /v1/api-keys/{keyId} | Edit a key |
DELETE | /v1/api-keys/{keyId} | Revoke a key |
Generated from openapi.yaml at build time. Base URL https://api.tenergy.me/v1, or https://api-nile.tenergy.me/v1 on Nile (Environments). Every request below is signed as in Authentication unless its Auth line says otherwise.
List the keys of this account
GET /v1/api-keys · listApiKeys
Auth: API key (HMAC).
Secrets are never returned by this endpoint — a secret exists in a response exactly once,
at creation. The key shown here is the public identifier sent in X-API-KEY.
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | Missing, malformed or rejected credentials. |
500 | Something broke on our side. |
Response fields
| Field | Type | Required | Description |
|---|---|---|---|
data | array of object (ApiKey) | yes | |
data[].id | string | yes | |
data[].key | string | yes | The public identifier sent in X-API-KEY. Not secret. |
data[].label | string | yes | |
data[].scopes | array of enum (13 values, ApiKeyScope) | yes | |
data[].ip_allowlist | array of string | ||
data[].is_active | boolean | yes | |
data[].last_used_at | string (date-time) | null | When this key last signed a request. Written at most once a minute per key, so it answers “is this key still in use”, not “to the second, when”. | |
data[].last_used_ip | string | null | The source address of that last request. null until the key is used. | |
data[].expires_at | string (date-time) | null | ||
data[].created_at | string (date-time) | yes | |
limit | integer | yes | How many active keys this account may hold at once. A product limit, not a permission: it caps how many credentials exist, never what any of them may do. Past it, POST /api-keys answers 3017 api_key_limit_reached; revoking a key frees a slot immediately. |
used | integer | yes | Active keys right now — revoked and expired ones do not count. |
Create an API key
POST /v1/api-keys · createApiKey
Auth: API key (HMAC) or bootstrap token.
Creates a key and returns its secret once. The secret is not stored in a recoverable form; if it is lost, delete the key and create another.
The first key of an account is created with the bootstrap token from the signup
flow; every later one with an existing key that itself holds keys.create.
Also on an unfunded account (since 2026-09-26), so an integration can read its deposit address with the new key and top up without a human in the loop.
scopes is required and has no default. The API deliberately does not invent a
starter set of permissions: a key’s reach is a decision for the account owner, taken
with the list in front of them. A request without scopes is rejected with
2001 validation_failed, and no client library, agent flow or dashboard form may
supply one on the owner’s behalf.
One permission vocabulary
Permissions are named area.action and there is one list of names for the whole
platform. The names below are the ones an API key may carry in v1. The remaining
names in that vocabulary — balance.withdraw, billing.write, invoices.read,
referral.read, referral.withdraw, team.read, team.invite, team.remove,
team.grant, settings.write, audit.read — exist for use elsewhere in the platform
(team management in the dashboard) but are not key-eligible in v1.
| Scope | What it opens |
|---|---|
prices.read | POST /estimate/transfer (GET /prices, GET /estimate and GET /resources/{address} are public and need no scope) |
balance.read | GET /account, GET /balance — balances and lifetime counters |
balance.topup_address | GET /deposit-addresses — the account’s deposit address and its retired ones |
orders.read | GET /orders (also as CSV), GET /orders/{id}, GET /batches…, GET /quotes/{id}, GET /account/stats (sums over the same orders) |
orders.create | POST /orders, POST /quotes, POST /batches, POST /batches/{id}/cancel — spends money |
orders.reclaim | POST /orders/{id}/reclaim — ends a rental early, no refund |
subscriptions.read | GET /subscriptions… |
subscriptions.write | create, patch, cancel subscriptions — commits to recurring spend |
webhooks.read | GET /webhooks…, including GET /webhooks/{id}/deliveries |
webhooks.write | create, patch, delete, rotate, test webhook endpoints |
keys.read | GET /api-keys |
keys.create | POST /api-keys, PATCH /api-keys/{id} — a key with this can widen its own account’s reach |
keys.revoke | DELETE /api-keys/{id} — can stop a production integration instantly |
GET /ledger, GET /account/addresses and GET /session/history are dashboard-session
operations and no scope opens them to a key.
A key can only be created with scopes that the creating key itself holds, so
keys.create cannot be used to escalate beyond what the caller already has. A key
created with the bootstrap token may carry any key-eligible scope, because the signing
address is the account’s owner.
Request body
JSON (ApiKeyRequest), required.
| Field | Type | Required | Description |
|---|---|---|---|
label | string | Optional. Omitted, the key is named Key N at the next free index, so several keys can be created without inventing names for them. A label is a name, not a permission — which is why it may have a default and scopes never will. | |
scopes | array of enum (13 values, ApiKeyScope) | yes | Required, no default, no server-side suggestion. The names are the key-eligible subset of the one platform permission vocabulary; see the endpoint description for what each opens. |
ip_allowlist | array of string | Source addresses allowed to use this key, IPv4/IPv6 addresses or CIDR blocks. An empty or absent list means any IP — acceptable for a read-only key, a bad idea for one that can spend. | |
expires_at | string (date-time) | null | Automatic revocation time. null for a key that does not expire. |
Example request body, from the contract (illustrative values):
{
"label": "grafana exporter",
"scopes": ["balance.read","orders.read"],
"ip_allowlist": ["203.0.113.10"]
}
Responses
| Status | Meaning |
|---|---|
201 | Created. secret appears only here. |
400 | Malformed request — bad JSON, unknown field, wrong type. |
401 | Missing, malformed or rejected credentials. |
403 | Authenticated, but this key is not allowed to do it. |
409 | 3017 api_key_limit_reached — the account already holds the maximum number of active keys. details.limit and details.used; revoke one to free a slot. |
422 | Syntactically valid but semantically impossible. |
500 | Something broke on our side. |
Response fields
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | |
key | string | yes | The public identifier sent in X-API-KEY. Not secret. |
label | string | yes | |
scopes | array of enum (13 values, ApiKeyScope) | yes | |
ip_allowlist | array of string | ||
is_active | boolean | yes | |
last_used_at | string (date-time) | null | When this key last signed a request. Written at most once a minute per key, so it answers “is this key still in use”, not “to the second, when”. | |
last_used_ip | string | null | The source address of that last request. null until the key is used. | |
expires_at | string (date-time) | null | ||
created_at | string (date-time) | yes | |
secret | string | yes | HMAC secret, 256 bits, shown once. sk_live_… on the production host and sk_test_… on the Nile host, matching the key id. It is not stored in a recoverable form: if it is lost, revoke the key and create another. |
Edit a key
PATCH /v1/api-keys/{keyId} · updateApiKey
Auth: API key (HMAC).
Change label, ip_allowlist, is_active, or scopes. Narrowing scopes takes effect
immediately. Widening them is subject to the same rule as creation: the calling key must
already hold every scope being granted.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
keyId | path | string | yes |
Request body
JSON (ApiKeyPatch), required.
| Field | Type | Required | Description |
|---|---|---|---|
label | string | ||
scopes | array of enum (13 values, ApiKeyScope) | ||
ip_allowlist | array of string | ||
is_active | boolean | ||
expires_at | string (date-time) | null |
Responses
| Status | Meaning |
|---|---|
200 | Updated |
400 | Malformed request — bad JSON, unknown field, wrong type. |
401 | Missing, malformed or rejected credentials. |
403 | Authenticated, but this key is not allowed to do it. |
404 | No such object, or it belongs to another account. The two are not distinguished. |
422 | Syntactically valid but semantically impossible. |
500 | Something broke on our side. |
Response fields (ApiKey)
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | |
key | string | yes | The public identifier sent in X-API-KEY. Not secret. |
label | string | yes | |
scopes | array of enum (13 values, ApiKeyScope) | yes | |
ip_allowlist | array of string | ||
is_active | boolean | yes | |
last_used_at | string (date-time) | null | When this key last signed a request. Written at most once a minute per key, so it answers “is this key still in use”, not “to the second, when”. | |
last_used_ip | string | null | The source address of that last request. null until the key is used. | |
expires_at | string (date-time) | null | ||
created_at | string (date-time) | yes |
Revoke a key
DELETE /v1/api-keys/{keyId} · deleteApiKey
Auth: API key (HMAC).
Immediate and irreversible. In-flight requests signed with this key start failing at once; orders it already created are unaffected and keep running.
A key cannot delete itself — that would leave an account with no way back in if it were the last one. Revoking your own key is done from the dashboard.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
keyId | path | string | yes |
Responses
| Status | Meaning |
|---|---|
204 | Revoked. No body. |
401 | Missing, malformed or rejected credentials. |
403 | Authenticated, but this key is not allowed to do it. |
404 | No such object, or it belongs to another account. The two are not distinguished. |
500 | Something broke on our side. |