Docs menu

API keys — API reference

Managing the credentials of this account.

MethodPathSummary
GET/v1/api-keysList the keys of this account
POST/v1/api-keysCreate 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

StatusMeaning
200OK
401Missing, malformed or rejected credentials.
500Something broke on our side.

Response fields

FieldTypeRequiredDescription
dataarray of object (ApiKey)yes
data[].idstringyes
data[].keystringyesThe public identifier sent in X-API-KEY. Not secret.
data[].labelstringyes
data[].scopesarray of enum (13 values, ApiKeyScope)yes
data[].ip_allowlistarray of string
data[].is_activebooleanyes
data[].last_used_atstring (date-time) | nullWhen 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_ipstring | nullThe source address of that last request. null until the key is used.
data[].expires_atstring (date-time) | null
data[].created_atstring (date-time)yes
limitintegeryesHow 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.
usedintegeryesActive 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.

ScopeWhat it opens
prices.readPOST /estimate/transfer (GET /prices, GET /estimate and GET /resources/{address} are public and need no scope)
balance.readGET /account, GET /balance — balances and lifetime counters
balance.topup_addressGET /deposit-addresses — the account’s deposit address and its retired ones
orders.readGET /orders (also as CSV), GET /orders/{id}, GET /batches…, GET /quotes/{id}, GET /account/stats (sums over the same orders)
orders.createPOST /orders, POST /quotes, POST /batches, POST /batches/{id}/cancel — spends money
orders.reclaimPOST /orders/{id}/reclaim — ends a rental early, no refund
subscriptions.readGET /subscriptions…
subscriptions.writecreate, patch, cancel subscriptions — commits to recurring spend
webhooks.readGET /webhooks…, including GET /webhooks/{id}/deliveries
webhooks.writecreate, patch, delete, rotate, test webhook endpoints
keys.readGET /api-keys
keys.createPOST /api-keys, PATCH /api-keys/{id} — a key with this can widen its own account’s reach
keys.revokeDELETE /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.

FieldTypeRequiredDescription
labelstringOptional. 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.
scopesarray of enum (13 values, ApiKeyScope)yesRequired, 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_allowlistarray of stringSource 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_atstring (date-time) | nullAutomatic revocation time. null for a key that does not expire.

Example request body, from the contract (illustrative values):

json
{
  "label": "grafana exporter",
  "scopes": ["balance.read","orders.read"],
  "ip_allowlist": ["203.0.113.10"]
}

Responses

StatusMeaning
201Created. secret appears only here.
400Malformed request — bad JSON, unknown field, wrong type.
401Missing, malformed or rejected credentials.
403Authenticated, but this key is not allowed to do it.
4093017 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.
422Syntactically valid but semantically impossible.
500Something broke on our side.

Response fields

FieldTypeRequiredDescription
idstringyes
keystringyesThe public identifier sent in X-API-KEY. Not secret.
labelstringyes
scopesarray of enum (13 values, ApiKeyScope)yes
ip_allowlistarray of string
is_activebooleanyes
last_used_atstring (date-time) | nullWhen 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_ipstring | nullThe source address of that last request. null until the key is used.
expires_atstring (date-time) | null
created_atstring (date-time)yes
secretstringyesHMAC 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

NameInTypeRequiredDescription
keyIdpathstringyes

Request body

JSON (ApiKeyPatch), required.

FieldTypeRequiredDescription
labelstring
scopesarray of enum (13 values, ApiKeyScope)
ip_allowlistarray of string
is_activeboolean
expires_atstring (date-time) | null

Responses

StatusMeaning
200Updated
400Malformed request — bad JSON, unknown field, wrong type.
401Missing, malformed or rejected credentials.
403Authenticated, but this key is not allowed to do it.
404No such object, or it belongs to another account. The two are not distinguished.
422Syntactically valid but semantically impossible.
500Something broke on our side.

Response fields (ApiKey)

FieldTypeRequiredDescription
idstringyes
keystringyesThe public identifier sent in X-API-KEY. Not secret.
labelstringyes
scopesarray of enum (13 values, ApiKeyScope)yes
ip_allowlistarray of string
is_activebooleanyes
last_used_atstring (date-time) | nullWhen 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_ipstring | nullThe source address of that last request. null until the key is used.
expires_atstring (date-time) | null
created_atstring (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

NameInTypeRequiredDescription
keyIdpathstringyes

Responses

StatusMeaning
204Revoked. No body.
401Missing, malformed or rejected credentials.
403Authenticated, but this key is not allowed to do it.
404No such object, or it belongs to another account. The two are not distinguished.
500Something broke on our side.

    ↑ ↓ to move · Enter to open · Esc to close