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
| Estimate | Quote | Order | |
|---|---|---|---|
| Call | GET /v1/estimate | POST /v1/quotes | POST /v1/orders |
| API key | Not needed | Needed | Needed |
| Binding | No — the period may roll over | Yes, for 120 s (expires_at) | Charges your balance |
| Creates an object | Nothing | A quote (qt_…) | An order (ord_…) |
What each step needs:
| Steps | You need |
|---|---|
| 1 | A terminal with curl, Node.js 18+ or Python 3. Nothing else. |
| 2–5 | An 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")
| Field | Meaning |
|---|---|
price_sun_per_unit | SUN per unit of energy for the whole tier, in the current day-part window. |
energy_amount_sun | amount × price_sun_per_unit. |
activate_amount_sun | Activation fee when receiver is not activated on chain; 0 without a receiver. |
total_amount_sun | What an order would be charged now. 1 TRX = 1,000,000 SUN. |
receiver_activated | true / false, or null when no receiver was sent. |
as_of | When 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"])
status | What it means for you |
|---|---|
allocating, delegated | Still on its way — poll again in a second. |
confirmed, active | The energy is on the receiver. Treat both alike. |
failed | Not delivered; any charge is reversed. |
expired, reclaimed | The 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.