REST API for the EnergyTRX TRON energy resale platform. Buy energy, manage Smart Energy subscriptions, list transactions, and integrate with your own scripts and bots.
Public endpoints (e.g. /prices) don't need auth. Read endpoints need the Bearer token. Write endpoints additionally need an HMAC signature — see Authentication.
Every request carries Authorization: Bearer ek_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx. Keys can be revoked at any time from the dashboard — revocation is immediate.
path+query — the request path exactly as sent, starting with /api/v1, without scheme or host, plus ?query if there is one — e.g. /api/v1/smart-energy/clsmart123/pause.
sha256_hex(body) — lowercase hex SHA-256 of the exact body bytes you send. For an empty body that is e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.
Because the method and path are signed, a captured signature can't be re-pointed at another endpoint or resource. Serialize the JSON body once and send that same string — re-serializing after signing changes the bytes.
X-Timestamp must be within 5 minutes of the server clock, otherwise 401 TIMESTAMP_INVALID.
Each signature is accepted once per API key, method and path. Sending the same signed request again inside the 5-minute window returns 401 SIGNATURE_REPLAYED — sign every request, including retries, with the current time.
Two byte-identical requests to the same endpoint signed in the same second have the same signature, so the second is rejected. If you get SIGNATURE_REPLAYED for a request you really want to send twice, re-sign it a second later — and use an Idempotency-Key to make retries of money-moving calls safe.
Exception: on endpoints that accept Idempotency-Key, re-sending a byte-identical signed request with the same key (what HTTP retry middleware does) returns the stored result — or 409 while the first attempt is still running. It is never executed twice.
Requests without X-Signature-Version (or with 1) are verified with the original scheme: hex HMAC-SHA256 of ${timestamp}.${body}. It keeps working, but it does not cover the method or path — a v1 signature of an empty-body request is the same for every empty-body endpoint. Responses to v1-signed requests carry Deprecation: true, a Link header with rel="deprecation" pointing here, and a Warning: 299 header. Replay protection applies to v1 as well. Please migrate to version 2.
The money-moving endpoints accept an optional Idempotency-Key header so a request can be retried after a timeout or network error without running twice:
POST /orders
POST /smart-energy
POST /smart-energy/{id}/topup
Use a new unique value (a UUID is recommended; 1–255 visible ASCII characters) for each operation and send the same value when retrying it. Keys are kept for 24 hours and are scoped to your account (all of its API keys).
Situation
Response
First request with a key
Runs normally; its status and body are stored for 24 hours.
Same key, same request, after it finished
The stored response, byte for byte, with Idempotent-Replayed: true. Nothing runs again.
Same key while the first request is still running
409 IDEMPOTENCY_KEY_IN_PROGRESS with Retry-After: 1.
Same key, different method, path or body
422 IDEMPOTENCY_KEY_REUSED.
No Idempotency-Key header
Every request runs (previous behaviour).
"The same request" means the same method, path (incl. query) and body bytes. The stored result is replayed whether it was a success or an error (for example 402 INSUFFICIENT_BALANCE): to try again after fixing the cause, use a new key. Requests rejected before they run — authentication, signature, rate limit or body validation errors — are not stored, so you can fix them and retry with the same key. If a first attempt never finishes (e.g. a server restart mid-request) its key stays "in progress" until it expires; check the resource (e.g. GET /orders) before retrying with a new key.
Node.js — retry with the same key
// signV2() as in the Authentication section.constsleep = (ms) => newPromise((r) =>setTimeout(r, ms));
asyncfunctioncreateOrder(targetAddress, energyAmount) {
const idempotencyKey = crypto.randomUUID(); // one key per operation, reused on every retryconst path = "/api/v1/orders";
const body = JSON.stringify({ target_address: targetAddress, energy_amount: energyAmount });
for (let attempt = 1; attempt <= 5; attempt++) {
let res;
try {
res = awaitfetch(`https://energy-trx.com${path}`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ENERGYTRX_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
...signV2(process.env.ENERGYTRX_API_SECRET, "POST", path, body) // fresh signature per attempt
},
body
});
} catch {
awaitsleep(1000 * attempt); // network error or timeout: retry with the same keycontinue;
}
const json = await res.json();
if (json.error?.code === "IDEMPOTENCY_KEY_IN_PROGRESS") {
awaitsleep(1000); // the first attempt is still runningcontinue;
}
// Idempotent-Replayed: true → this is the stored result of an earlier attempt.return json;
}
thrownewError("Order request did not complete — check GET /orders before trying again.");
}
Subscribe an HTTPS endpoint to events on the webhooks page of your dashboard (creating, editing and deleting subscriptions is dashboard-only; GET /webhooks lists them). The signing secret (whsec_…) is shown once at creation. Events you can subscribe to:
Each delivery is a POST with a JSON body shaped { id, event, user_id, created, data }.
Delivery
Only a 2xx response within 10 seconds counts as delivered.
Redirects are not followed: a 3xx response is a failed attempt. Point the webhook at the final URL.
Failed attempts are retried after 1 min, 5 min, 30 min, 2 h and 12 h. Webhooks whose deliveries keep failing are disabled automatically — re-enable them from the dashboard once fixed.
Only the first 2 KB of your response body is read (for delivery diagnostics); keep responses short.
Delivery is at-least-once: use X-Energytrx-Delivery to ignore duplicates. It stays the same across retries of one delivery.
Headers
Header
Value
X-Energytrx-Event
Event type, e.g. order.completed.
X-Energytrx-Delivery
Delivery id — stable across retries; use it to drop duplicates.
X-Energytrx-Timestamp
Unix seconds when this attempt was signed.
X-Energytrx-Signature-V2
t=<timestamp>,v1=<hex HMAC-SHA256(secret, "<timestamp>.<raw body>")> — verify this one.
X-Energytrx-Signature
Legacy: hex HMAC-SHA256(secret, raw body). Unchanged; no replay protection.
Read the raw request body before parsing JSON, and the t and v1 values from X-Energytrx-Signature-V2.
Compute hex HMAC-SHA256 of ${t}.${rawBody} with your webhook secret and compare it with v1 in constant time.
Reject the delivery if t is more than 5 minutes from your clock — this stops replays.
Ignore deliveries whose X-Energytrx-Delivery you have already processed.
Every retry is signed again with a fresh t. The legacy X-Energytrx-Signature header (HMAC of the body only) is still sent unchanged for existing integrations, but it can't detect replays — prefer the v2 header.
Node.js
import crypto from"node:crypto";
// rawBody: the request body exactly as received (string or Buffer), not re-serialized JSON.exportfunctionverifyEnergyTrxWebhook(rawBody, headers, secret, toleranceSec = 300) {
const header = headers["x-energytrx-signature-v2"] ?? "";
const parts = Object.fromEntries(
header.split(",").map((kv) => {
const i = kv.indexOf("=");
return [kv.slice(0, i).trim(), kv.slice(i + 1).trim()];
})
);
const t = Number(parts.t);
if (!Number.isInteger(t) || !parts.v1) returnfalse;
if (Math.abs(Math.floor(Date.now() / 1000) - t) > toleranceSec) returnfalse; // stale or replayedconst expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Python
import hashlib, hmac, time
defverify_energytrx_webhook(raw_body: bytes, headers, secret: str, tolerance: int = 300) -> bool:
header = headers.get("X-Energytrx-Signature-V2", "")
parts = dict(p.strip().split("=", 1) for p in header.split(",") if"="in p)
try:
t = int(parts["t"])
except (KeyError, ValueError):
returnFalseifabs(time.time() - t) > tolerance:
returnFalse# stale or replayed
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))
bash — manual check
# t and v1 from X-Energytrx-Signature-V2, body saved byte-for-byte to body.json
t=1760000000
expected=$( { printf"%s.""$t"; cat body.json; } | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | awk '{print $NF}')
echo"$expected"# must equal v1
Optional. A unique value per operation (a UUID is recommended, 1-255 visible ASCII chars). Retrying with the same key within 24 h returns the stored result (header `Idempotent-Replayed: true`) instead of executing again.
Refunds the order cost to your TRX balance. Only an order that was never dispatched for delivery can be cancelled (see the order's `cancellable` field); dispatched orders return 409.
{"data":[{"id":"clsmart1234567890","address":"TXyZrecipient1234567890ABCDEF","product_slug":"smart-delegation-65k","product_name":"Smart Energy 65k","status":"active","balance_trx":"100.000000","total_delegations":42,"deleted_at":null,"refund_amount_trx":null,"created_at":"2026-05-20T08:00:00.000Z"}],"meta":{"request_id":"ab12cd34ef56gh78","count":1}}
Optional. A unique value per operation (a UUID is recommended, 1-255 visible ASCII chars). Retrying with the same key within 24 h returns the stored result (header `Idempotent-Replayed: true`) instead of executing again.
{"data":{"id":"clsmart1234567890","address":"TXyZrecipient1234567890ABCDEF","product_slug":"smart-delegation-65k","product_name":"Smart Energy 65k","status":"active","balance_trx":"100.000000","total_delegations":42,"deleted_at":null,"refund_amount_trx":null,"created_at":"2026-05-20T08:00:00.000Z"},"meta":{"request_id":"ab12cd34ef56gh78"}}
`1` re-reads the live delivery state before responding (at most once per 30 s per subscription; otherwise the stored state is returned).
Response
200 Subscription
json
{"data":{"id":"clsmart1234567890","address":"TXyZrecipient1234567890ABCDEF","product_slug":"smart-delegation-65k","product_name":"Smart Energy 65k","status":"active","balance_trx":"100.000000","total_delegations":42,"deleted_at":null,"refund_amount_trx":null,"created_at":"2026-05-20T08:00:00.000Z"},"meta":{"request_id":"ab12cd34ef56gh78"}}
{"data":{"id":"clsmart1234567890","address":"TXyZrecipient1234567890ABCDEF","product_slug":"smart-delegation-65k","product_name":"Smart Energy 65k","status":"active","balance_trx":"100.000000","total_delegations":42,"deleted_at":null,"refund_amount_trx":null,"created_at":"2026-05-20T08:00:00.000Z"},"meta":{"request_id":"ab12cd34ef56gh78"}}
{"data":{"id":"clsmart1234567890","address":"TXyZrecipient1234567890ABCDEF","product_slug":"smart-delegation-65k","product_name":"Smart Energy 65k","status":"active","balance_trx":"100.000000","total_delegations":42,"deleted_at":null,"refund_amount_trx":null,"created_at":"2026-05-20T08:00:00.000Z"},"meta":{"request_id":"ab12cd34ef56gh78"}}
Optional. A unique value per operation (a UUID is recommended, 1-255 visible ASCII chars). Retrying with the same key within 24 h returns the stored result (header `Idempotent-Replayed: true`) instead of executing again.
e.g.0f8fad5b-d9cb-469f-a165-70867728950e
Request body
Content-Type: application/json
json
{"top_up_trx":50}
Response
200 Topped up
json
{"data":{"id":"clsmart1234567890","address":"TXyZrecipient1234567890ABCDEF","product_slug":"smart-delegation-65k","product_name":"Smart Energy 65k","status":"active","balance_trx":"100.000000","total_delegations":42,"deleted_at":null,"refund_amount_trx":null,"created_at":"2026-05-20T08:00:00.000Z"},"meta":{"request_id":"ab12cd34ef56gh78"}}
Your transaction ledger, newest first. Some non-customer system entries are excluded, as are below-minimum deposits unless `include_uncredited=1`. `meta.summary` carries totals over the whole filtered set.
Returns the public projection — never the signing secret. Read-only on this surface; create / edit / delete / test live behind the dashboard at /dashboard/webhooks (session-authenticated). Outbound deliveries are signed: `X-Energytrx-Timestamp` + `X-Energytrx-Signature-V2` (timestamped, recommended) and the legacy `X-Energytrx-Signature` — see `x-events.signatures`.
Returns the public projection — never the full key or secret. Read-only on this surface; key creation + revocation live behind the dashboard at /dashboard/api-keys (session-authenticated).