Docs menu

Quickstart

Five requests take you from a price to a delegated order: estimate, sign, quote, order, confirm. The first one needs no key, so you can run it before you have an account.

Before you begin

EstimateQuoteOrder
CallGET /v1/estimatePOST /v1/quotesPOST /v1/orders
API keyNot neededNeededNeeded
BindingNo — the period may roll overYes, for 120 s (expires_at)Charges your balance
Creates an objectNothingA quote (qt_…)An order (ord_…)

What each step needs:

StepsYou need
1A terminal with curl, Node.js 18+ or Python 3. Nothing else.
2–5An account and an API key with the scopes those calls need. Keys are available right after signup; only the order in step 4 needs a balance, so create the key before the deposit. The agent quickstart walks through creating both; the key’s permissions are the ones you choose — there is no default set.

Rehearse on Nile first: https://api-nile.tenergy.me/v1, with its own account, keys (ak_test_…) and ledger, funded from the public Nile faucet. Try it on Nile is the five-step run; Environments lists what Nile cannot prove.

Five lines to your first order

Step 1 — Price the order

GET /v1/estimate is public: what 65,000 energy for one hour costs right now, with activation added when the receiver has never been used on chain.

curl -sG https://api-nile.tenergy.me/v1/estimate \
  -d resource=energy -d amount=65000 -d tier=1h \
  -d receiver=TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE
const url = new URL("https://api-nile.tenergy.me/v1/estimate");
url.search = new URLSearchParams({
  resource: "energy",
  amount: "65000",
  tier: "1h",
  receiver: "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
}).toString();

const estimate = await (await fetch(url)).json();
console.log(estimate.total_amount_sun, "SUN");
import json, urllib.parse, urllib.request

query = urllib.parse.urlencode({
    "resource": "energy",
    "amount": 65000,
    "tier": "1h",
    "receiver": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
})
with urllib.request.urlopen(f"https://api-nile.tenergy.me/v1/estimate?{query}") as res:
    estimate = json.load(res)
print(estimate["total_amount_sun"], "SUN")
FieldMeaning
price_sun_per_unitSUN per unit of energy for the whole tier, in the current day-part window.
energy_amount_sunamount × price_sun_per_unit.
activate_amount_sunActivation fee when receiver is not activated on chain; 0 without a receiver.
total_amount_sunWhat an order would be charged now. 1 TRX = 1,000,000 SUN.
receiver_activatedtrue / false, or null when no receiver was sent.
as_ofWhen the estimate was computed. It is not binding.

The live price table behind it is GET /v1/prices, also public. The API sells 5m and 1h; 1d is switched off and answers 2003 tier_unavailable. Check available on each row rather than hardcoding tiers.

Step 2 — Sign requests with your key

Every other call carries three headers: X-API-KEY, X-API-TIMESTAMP and X-API-SIGN = base64(HMAC-SHA256(secret, timestamp + METHOD + path + query + body)). The helper below is the whole scheme; Authentication explains each part.

export TENERGY_KEY=ak_test_…      # the key id from the dashboard
export TENERGY_SECRET=sk_test_…   # shown once, when the key was created
BASE=https://api-nile.tenergy.me/v1

# tenergy METHOD PATH [BODY] — PATH is relative to /v1 and may carry a query string.
tenergy() {
  local method=$1 path=$2 body=${3:-}
  local ts sign
  ts=$(date -u +%Y-%m-%dT%H:%M:%S.000Z)
  sign=$(printf '%s' "$ts$method/v1$path$body" \
    | openssl dgst -sha256 -hmac "$TENERGY_SECRET" -binary | base64)
  curl -sS -X "$method" "$BASE$path" \
    -H "X-API-KEY: $TENERGY_KEY" -H "X-API-TIMESTAMP: $ts" -H "X-API-SIGN: $sign" \
    ${body:+-H "Content-Type: application/json" --data-raw "$body"}
}
import { createHmac } from "node:crypto";

const BASE = "https://api-nile.tenergy.me/v1";
const KEY = process.env.TENERGY_KEY!; // ak_test_… on Nile
const SECRET = process.env.TENERGY_SECRET!; // shown once, when the key was created

export async function tenergy(method: string, pathAndQuery: string, body?: unknown) {
  const raw = body === undefined ? "" : JSON.stringify(body); // sign the bytes you send
  const url = new URL(BASE + pathAndQuery);
  const ts = new Date().toISOString();
  const sign = createHmac("sha256", SECRET)
    .update(ts + method + url.pathname + url.search + raw)
    .digest("base64");
  const res = await fetch(url, {
    method,
    headers: {
      "X-API-KEY": KEY,
      "X-API-TIMESTAMP": ts,
      "X-API-SIGN": sign,
      ...(raw ? { "Content-Type": "application/json" } : {}),
    },
    body: raw || undefined,
  });
  return res.json();
}
import base64, hashlib, hmac, json, os, urllib.error, urllib.request
from datetime import datetime, timezone
from urllib.parse import urlsplit

BASE = "https://api-nile.tenergy.me/v1"
KEY, SECRET = os.environ["TENERGY_KEY"], os.environ["TENERGY_SECRET"]

def tenergy(method, path_and_query, body=None):
    raw = "" if body is None else json.dumps(body, separators=(",", ":"))  # sign the bytes you send
    url = BASE + path_and_query
    parts = urlsplit(url)
    ts = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
    signed = ts + method + parts.path + (f"?{parts.query}" if parts.query else "") + raw
    sign = base64.b64encode(hmac.new(SECRET.encode(), signed.encode(), hashlib.sha256).digest()).decode()
    headers = {"X-API-KEY": KEY, "X-API-TIMESTAMP": ts, "X-API-SIGN": sign}
    if raw:
        headers["Content-Type"] = "application/json"
    req = urllib.request.Request(url, data=raw.encode() or None, method=method, headers=headers)
    try:
        with urllib.request.urlopen(req) as res:
            return json.load(res)
    except urllib.error.HTTPError as err:
        return json.load(err)  # the error envelope: branch on error.slug

Check it with GET /v1/balance. A wrong secret answers 401 with 1002 invalid_signature; an unknown or revoked key answers 1006 key_revoked.

Step 3 — Pin the price with a quote

Optional. A quote holds total_amount_sun for 120 seconds; skip it and pass max_price_sun on the order if a ceiling is all you need.

tenergy POST /quotes '{"resource":"energy","amount":65000,"tier":"1h","receiver":"TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE"}'
const quote = await tenergy("POST", "/quotes", {
  resource: "energy",
  amount: 65000,
  tier: "1h",
  receiver: "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
});
quote = tenergy("POST", "/quotes", {
    "resource": "energy",
    "amount": 65000,
    "tier": "1h",
    "receiver": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
})

Read id, total_amount_sun and expires_at. An expired quote is refused with 3005 quote_expired — take a fresh one.

Step 4 — Place the order

Always send your own client_order_id. Repeating the identical request returns the original order with 200 and charges nothing, so a timeout is never a reason to buy twice.

tenergy POST /orders '{"quote_id":"qt_01J9Z5NB2K4R","client_order_id":"payout-8821"}'   # id from step 3
const order = await tenergy("POST", "/orders", {
  quote_id: quote.id,
  client_order_id: "payout-8821",
});
order = tenergy("POST", "/orders", {"quote_id": quote["id"], "client_order_id": "payout-8821"})

201 means accepted and paid for, not delivered. status is usually active already; allocating means delivery is still running.

Step 5 — Wait for confirmed

Poll the order by your own id, or register a webhook and receive order.confirmed.

tenergy GET /orders/cid:payout-8821
const current = await tenergy("GET", "/orders/cid:payout-8821");
if (current.partial) console.log("delivered", current.delivered_amount, "of", current.amount);
current = tenergy("GET", "/orders/cid:payout-8821")
if current.get("partial"):
    print("delivered", current["delivered_amount"], "of", current["amount"])
statusWhat it means for you
allocating, delegatedStill on its way — poll again in a second.
confirmed, activeThe energy is on the receiver. Treat both alike.
failedNot delivered; any charge is reversed.
expired, reclaimedThe rental window is over.

Check partial. A partly delivered order is still confirmed/active, with delivered_amount below amount and the difference already refunded — a field, not a separate state.

Next steps

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