Docs menu

Signup — API reference

Creating an account without a credential yet: the address challenge, its verification, account creation, and reading the deposit address before the first API key exists.

MethodPathSummary
POST/v1/accounts/challengeIssue a signing challenge for a TRON address
POST/v1/accounts/challenge/verifyVerify a signed challenge and get a bootstrap token
POST/v1/accountsCreate an account
GET/v1/accounts/deposit-addressDeposit address before the first API key exists

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.

Issue a signing challenge for a TRON address

POST /v1/accounts/challenge · createAccountChallenge

Auth: Public — no credentials.

Step 1 of the address-challenge signup and recovery model — the platform’s one signup model: ownership of an account is proved by signing a nonce with a TRON address, off our infrastructure. We never see a private key, and there is nothing here to phish — the signature proves control of the address and nothing else.

The returned message is human-readable and domain-bound (brand, purpose, nonce, expiry) so that whoever signs it in a wallet can see what they are agreeing to. Sign it with a TIP-191-style personal-sign; the signature must recover address.

Anonymous, rate-limited per IP and per address. Calling it for an address that already has an account is indistinguishable from calling it for one that does not — this endpoint is deliberately not an account-existence oracle.

Request body

JSON (AccountChallengeRequest), required.

FieldTypeRequiredDescription
addressstringyesBase58Check TRON address (starts with T, 34 characters).
purposeenum: signup, login, recoveryWhat the signature will be used for; it appears in the signed message.

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

json
{"address":"TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE","purpose":"signup"}

Responses

StatusMeaning
201Challenge issued.
400Malformed request — bad JSON, unknown field, wrong type.
429Too many requests.
500Something broke on our side.

Response fields (AccountChallenge)

FieldTypeRequiredDescription
noncestringyesSingle-use
addressstringyesBase58Check TRON address (starts with T, 34 characters).
purposeenum: signup, login, recovery
messagestringyesThe exact string to sign, TIP-191 personal-sign style. Domain-bound and human-readable, so that a person signing it in a wallet can see what it says. Sign these bytes verbatim — do not re-wrap, trim or re-encode them.
issued_atstring (date-time)yes
expires_atstring (date-time)yesTen minutes after issue. An expired nonce gives 1010 challenge_invalid.

Example 201 response, from the contract (illustrative values — live numbers come from the API):

json
{
  "nonce": "9f2a7c1e4b6d8a0f3e5c7b9d1f3a5c7e",
  "address": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
  "purpose": "signup",
  "message": "tenergy.me wants you to prove control of this address.\nPurpose: signup\nAddress: TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE\nNonce: 9f2a7c1e4b6d8a0f3e5c7b9d1f3a5c7e\nExpires: 2026-09-11T18:14:05.123Z\nSigning this creates no transaction and moves no funds.\n",
  "issued_at": "2026-09-11T18:04:05.123Z",
  "expires_at": "2026-09-11T18:14:05.123Z"
}

Verify a signed challenge and get a bootstrap token

POST /v1/accounts/challenge/verify · verifyAccountChallenge

Auth: Public — no credentials.

Step 2. Checks that signature recovers address over the challenge message and returns a short-lived bootstrap token — the credential that carries the caller from “no account” to “first API key”, and the answer to the contract’s long-standing gap of having no way to authenticate the calls that precede a key.

The bootstrap token is sent as Authorization: Bearer abt_… and opens exactly four operations, nothing else: POST /v1/accounts, GET /v1/accounts/deposit-address, GET /v1/account and POST /v1/api-keys. It expires in 15 minutes, is single-account, and is never a substitute for an API key.

If the address already has an account on this brand, account_id and account_status are returned — to this caller only, because only this caller proved the signature.

A nonce is single-use. A reused, expired or wrongly-signed nonce is one error, 1010 challenge_invalid, whatever went wrong, so that failures reveal nothing about which addresses exist.

Request body

JSON (AccountChallengeVerifyRequest), required.

FieldTypeRequiredDescription
addressstringyesBase58Check TRON address (starts with T, 34 characters).
noncestringyes
signaturestringyesHex signature over message. Must recover address; 0x prefix optional.

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

json
{
  "address": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
  "nonce": "9f2a7c1e4b6d8a0f3e5c7b9d1f3a5c7e",
  "signature": "1c3e5b7d9f1a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f1a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b"
}

Responses

StatusMeaning
200Signature valid.
400Malformed request — bad JSON, unknown field, wrong type.
4011010 challenge_invalid — unknown, expired or already-used nonce, or a signature that does not recover the address. Take a fresh challenge.
429Too many requests.
500Something broke on our side.

Response fields (BootstrapToken)

FieldTypeRequiredDescription
bootstrap_tokenstringyesSend as Authorization: Bearer abt_…. Fifteen-minute lifetime, four permitted operations, no ordering and no spending. Store it no longer than the signup flow.
expires_atstring (date-time)yes
account_idstring | nullThe account this address already owns, or null when there is none yet.
account_statusenum: unfunded, active, suspended, closed | null

Example 200 response, from the contract (illustrative values — live numbers come from the API):

json
{
  "bootstrap_token": "abt_3f8c2d1e9b7a4c6e8f0a2b4d6e8f0a2b",
  "expires_at": "2026-09-11T18:19:05.123Z",
  "account_id": "acc_01J9Z4K2M7Q8",
  "account_status": "active"
}

Create an account

POST /v1/accounts · createAccount

Auth: Bootstrap token or public.

Step 3. Creates the account owned by the signing address and returns it together with its deposit address. The account is created in status: "unfunded": it exists, it can be read, it can receive money and it can issue API keys; it cannot spend until a deposit confirms.

Authenticate either with the bootstrap token from POST /v1/accounts/challenge/verify, or by passing nonce + signature in the body for a one-shot create. Both prove the same thing.

Idempotent by address. If the address already owns an account on this brand, the existing account is returned with HTTP 200 and nothing is created.

email is optional and is not verified here — it is a recovery and invoicing channel, attached later from the dashboard. Email magic-link and Telegram login are additional human logins on the same account object, not a second signup model.

API keys do not wait for a deposit (since 2026-09-26). The balance, not the credential, holds spending back: POST /v1/orders answers 4001 insufficient_funds until the ledger has a confirmed credit.

Request body

JSON (AccountCreateRequest), required.

FieldTypeRequiredDescription
addressstringyesBase58Check TRON address (starts with T, 34 characters).
noncestring
signaturestring
emailstring (email) | nullOptional recovery and invoicing contact. Not verified here and not required; an account with no recovery channel is a support incident waiting to happen, so the dashboard nudges for one later.
labelstring | null

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

json
{"address":"TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE"}

Responses

StatusMeaning
200The address already owns an account; the existing one is returned.
201Account created, unfunded.
400Malformed request — bad JSON, unknown field, wrong type.
4011010 challenge_invalid, or 1011 bootstrap_token_expired.
422Syntactically valid but semantically impossible.
429Too many requests.
500Something broke on our side.

Response fields (AccountCreated)

FieldTypeRequiredDescription
account_idstringyes
brandstringyes
networkenum: mainnet, nileyes
statusenum: unfunded, active, suspended, closedyes
owner_addressstringyesThe address whose signature owns this account and can recover it.
deposit_addressesarray of object (DepositAddress)yes
deposit_addresses[].currencyenum: TRX, USDTyes
deposit_addresses[].addressstringyesBase58Check TRON address (starts with T, 34 characters).
deposit_addresses[].memostring | nullAlways null since 2026-09-26: every account has an address of its own, so no memo is needed and any memo is ignored. Kept so existing clients do not break.
deposit_addresses[].confirmations_requiredintegerBlocks waited before the deposit is credited.
deposit_addresses[].contractstring | nullThe TRC-20 contract accepted at this address (USDT row only; null for TRX).
deposit_addresses[].rate_nownull | objectUSDT row only. The current SunSwap (or fixed) TRX-per-USDT rate BEFORE the spread, cached 60 s. null when no rate is available right now; the deposit is still accepted and the worker prices it at credit time. Indicative: the credit uses the rate the worker reads when it credits, not this one.
deposit_addresses[].rate_now.trx_per_usdtstringyes
deposit_addresses[].rate_now.sourceenum: sunswap_v3, fixed_envyes
deposit_addresses[].rate_now.atstring (date-time)yes
deposit_addresses[].spread_bpsinteger | nullUSDT row only: basis points taken off the rate (5 = 0.05 %).
deposit_addresses[].minstring | nullUSDT row only: smallest credited USDT deposit; smaller ones are held (held_below_min).
deposit_addresses[].maxstring | nullUSDT row only: largest auto-credited USDT deposit; larger ones are held (held_for_review).
nextobjectWhat the caller should do next, machine-readable, because an agent that cannot infer the next step will invent one.
next.actionstring
next.addressstringBase58Check TRON address (starts with T, 34 characters).
next.reasonstring
created_atstring (date-time)yes

Deposit address before the first API key exists

GET /v1/accounts/deposit-address · getSignupDepositAddress

Auth: Bootstrap token.

The address to fund so that the account becomes active and can issue a key. Readable with the bootstrap token, so an agent that has just created an account can report the address and the minimum to its user without holding any long-lived credential.

Once a key exists, use GET /v1/deposit-addresses, which returns the same addresses.

Parameters

NameInTypeRequiredDescription
currencyqueryenum: TRX, USDT

Responses

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

Response fields

FieldTypeRequiredDescription
account_idstringyes
statusenum: unfunded, active, suspended, closedyes
dataarray of object (DepositAddress)yes
data[].currencyenum: TRX, USDTyes
data[].addressstringyesBase58Check TRON address (starts with T, 34 characters).
data[].memostring | nullAlways null since 2026-09-26: every account has an address of its own, so no memo is needed and any memo is ignored. Kept so existing clients do not break.
data[].confirmations_requiredintegerBlocks waited before the deposit is credited.
data[].contractstring | nullThe TRC-20 contract accepted at this address (USDT row only; null for TRX).
data[].rate_nownull | objectUSDT row only. The current SunSwap (or fixed) TRX-per-USDT rate BEFORE the spread, cached 60 s. null when no rate is available right now; the deposit is still accepted and the worker prices it at credit time. Indicative: the credit uses the rate the worker reads when it credits, not this one.
data[].rate_now.trx_per_usdtstringyes
data[].rate_now.sourceenum: sunswap_v3, fixed_envyes
data[].rate_now.atstring (date-time)yes
data[].spread_bpsinteger | nullUSDT row only: basis points taken off the rate (5 = 0.05 %).
data[].minstring | nullUSDT row only: smallest credited USDT deposit; smaller ones are held (held_below_min).
data[].maxstring | nullUSDT row only: largest auto-credited USDT deposit; larger ones are held (held_for_review).
min_deposit_suninteger (int64)Below this a transfer is held as an unclaimed residual rather than credited. The value is a brand parameter.

Example 200 response, from the contract (illustrative values — live numbers come from the API):

json
{
  "account_id": "acc_01J9Z4K2M7Q8",
  "status": "unfunded",
  "data": [
    {
      "currency": "TRX",
      "address": "TDepositAddressExample1111111111111",
      "memo": null,
      "confirmations_required": 19
    }
  ],
  "min_deposit_sun": 1000000
}

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