Overview
The Open API lets exchanges, wallets and payment systems rent TRON energy programmatically: place orders, check their status, estimate prices and receive a callback when an order completes.
| Item | Value |
|---|---|
| Base URL | https://api.tronhub.net |
| Method | POST for every endpoint |
| Content type | application/json |
| Amount unit | sun (1 TRX = 1,000,000 sun) |
| Energy delivery | On-chain delegation to receive_address |
Orders are paid from your account balance. Create an API key on the API Keys page; the API secret is shown only once when the key is created.
The API is designed for server-to-server calls. Never expose your API secret in a browser or mobile app.
Authentication
Every request must carry three headers:
| Header | Description |
|---|---|
API-KEY | Your API key (starts with ak_) |
TIMESTAMP | Current Unix time in seconds. Requests more than 300 seconds away from server time are rejected. |
SIGNATURE | Lowercase hex HMAC-SHA256 signature, see below |
Signature
signature = hex( HMAC-SHA256( key = api_secret, message = TIMESTAMP + "&" + body ) )
bodyis the exact raw request body you send. Serialize your JSON once, sign that string, and send the same string — do not re-serialize it after signing.- If you send an empty body, sign the string
{}. - Use the API secret string as the HMAC key as-is (do not hex-decode it).
- The signature must be lowercase hex.
Key settings
- Permissions —
order(create orders),query(query orders),price(estimate price and market data),balance(account balance), or*for all. - IP whitelist — optional, comma-separated list of exact IP addresses. If set, requests from other IPs are rejected.
- Rate limit — optional requests-per-minute limit per key (0 = unlimited).
Example
import hashlib, hmac, json, time
import requests
API_KEY = "ak_your_api_key"
API_SECRET = "your_api_secret"
BASE_URL = "https://api.tronhub.net"
def call(path, payload=None):
body = json.dumps(payload or {}, separators=(",", ":"))
timestamp = str(int(time.time()))
signature = hmac.new(API_SECRET.encode(), f"{timestamp}&{body}".encode(), hashlib.sha256).hexdigest()
headers = {
"Content-Type": "application/json",
"API-KEY": API_KEY,
"TIMESTAMP": timestamp,
"SIGNATURE": signature,
}
# Send exactly the string that was signed
return requests.post(BASE_URL + path, data=body, headers=headers, timeout=15).json()
print(call("/api/open/price", {"period": "1H", "energy_amount": 65000}))
import crypto from "node:crypto"
const API_KEY = "ak_your_api_key"
const API_SECRET = "your_api_secret"
const BASE_URL = "https://api.tronhub.net"
async function call(path, payload = {}) {
const body = JSON.stringify(payload)
const timestamp = Math.floor(Date.now() / 1000).toString()
const signature = crypto.createHmac("sha256", API_SECRET).update(`${timestamp}&${body}`).digest("hex")
const res = await fetch(BASE_URL + path, {
method: "POST",
headers: { "Content-Type": "application/json", "API-KEY": API_KEY, "TIMESTAMP": timestamp, "SIGNATURE": signature },
body,
})
return res.json()
}
console.log(await call("/api/open/balance"))
func call(path string, payload any) (*http.Response, error) {
body, _ := json.Marshal(payload)
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
mac := hmac.New(sha256.New, []byte(apiSecret))
mac.Write([]byte(timestamp + "&" + string(body)))
signature := hex.EncodeToString(mac.Sum(nil))
req, _ := http.NewRequest(http.MethodPost, baseURL+path, bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("API-KEY", apiKey)
req.Header.Set("TIMESTAMP", timestamp)
req.Header.Set("SIGNATURE", signature)
return http.DefaultClient.Do(req)
}
Responses & errors
All responses share the same envelope. Check the code field, not the HTTP status: API errors are returned with HTTP 200.
{
"code": 200,
"message": "success",
"data": {}
}
data is present only on success. On error, message describes the problem.
Error messages are returned in Chinese by default. Send the optional header X-Locale: en to receive them in English:
X-Locale: en
The X-Locale header is not part of the signature.
| code | Meaning | Typical causes |
|---|---|---|
200 | Success | — |
400 | Invalid parameters | Missing or invalid fields, malformed JSON |
401 | Authentication failed | Missing headers, expired timestamp, unknown or disabled key, invalid signature |
403 | Forbidden | Key lacks the permission, or IP not in the whitelist |
429 | Too many requests | Per-key rate limit exceeded |
500 | Business error | Insufficient balance, order not found, unsupported period, order failed |
A global per-IP limit also applies. When it is exceeded the API responds with HTTP 429 and
code: 429. Retry with backoff.
Create order
POST /api/open/order/create · permission order
Deducts the order amount from your balance and delegates energy to receive_address.
| Field | Type | Required | Description |
|---|---|---|---|
energy_amount | int | Yes | Amount of energy. Must be within the limits returned by market data. |
period | string | Yes | Rental period: 1H, 1D, 3D or 30D (case-sensitive). |
receive_address | string | Yes | Activated TRON address that receives the energy. |
callback_url | string | No | URL notified when the order completes or fails. Must be a publicly reachable http/https URL; private, loopback and internal addresses are rejected. See callbacks. |
out_trade_no | string | No | Your own order reference, unique per account. Recommended: it makes retries safe (see below). |
{
"energy_amount": 65000,
"period": "1H",
"receive_address": "TXk8rQSAvPvBBNtqSoY6nCfsXWCSSpTVQF",
"callback_url": "https://example.com/tron-energy/callback",
"out_trade_no": "ORDER-10001"
}
The amount charged equals total_price from estimate price, including the small-order fee for orders below small_amount.
Retries and out_trade_no. If a create request times out, retry it with the same out_trade_no:
- Same
out_trade_nowith the sameenergy_amount,periodandreceive_address: the existing order is returned and you are not charged again. - Same
out_trade_nowith different parameters: the request is rejected. - While the first request is still being processed, a retry is rejected with a message asking you to query the order later.
Without out_trade_no, a retried request always creates a new order.
Response data:
| Field | Type | Description |
|---|---|---|
order_no | string | Order number |
energy_amount | int | Energy amount |
period | string | Rental period |
receive_address | string | Receiving address |
amount | int | Amount charged (sun) |
status | int | Order status, normally 1 (processing). See status codes. |
out_trade_no | string | Your order reference |
created_at | string | Creation time (RFC 3339) |
{
"code": 200,
"message": "success",
"data": {
"order_no": "E20260928103000a1b2c3d4",
"energy_amount": 65000,
"period": "1H",
"receive_address": "TXk8rQSAvPvBBNtqSoY6nCfsXWCSSpTVQF",
"amount": 1885000,
"status": 1,
"out_trade_no": "ORDER-10001",
"created_at": "2026-09-28T10:30:00+08:00"
}
}
If the order cannot be placed, the response has code: 500, the charge is refunded to your balance, and no callback is sent.
Query order
POST /api/open/order/query · permission query
Provide order_no or out_trade_no. If both are sent, order_no is used.
| Field | Type | Required | Description |
|---|---|---|---|
order_no | string | One of | Order number |
out_trade_no | string | One of | Your order reference |
Response data:
| Field | Type | Description |
|---|---|---|
order_no | string | Order number |
energy_amount | int | Energy amount |
period | string | Rental period |
receive_address | string | Receiving address |
status | int | Order status |
amount | int | Amount charged (sun) |
refund_amount | int | Amount refunded (sun) |
out_trade_no | string | Your order reference |
api_name | string | Name of the API key used for this request |
details | array | null | Delegation records, newest first; null if there are none yet |
created_at | string | Creation time (RFC 3339) |
updated_at | string | Last update time (RFC 3339) |
Each item in details:
| Field | Type | Description |
|---|---|---|
txid | string | Delegation transaction hash (empty until delegated) |
energy_amount | int | Energy amount |
amount | int | Cost (sun) |
status | int | 0 waiting for result, 1 success, 2 failed, 3 refunded |
created_at | string | Submission time (RFC 3339) |
Estimate price
POST /api/open/price · permission price
| Field | Type | Required | Description |
|---|---|---|---|
period | string | Yes | 1H, 1D, 3D or 30D |
energy_amount | int | Yes | Amount of energy |
Response data:
| Field | Type | Description |
|---|---|---|
period | string | Rental period |
energy_amount | int | Energy amount |
price | int | Unit price: sun per energy for 1H, sun per energy per day for day periods |
days | int | Billing multiplier: 1 for 1H, otherwise the number of days |
total_price | int | price × energy_amount × days plus the small-order fee (sun). This is the amount charged when the order is created. |
addition | int | Small-order fee (sun); 0 when energy_amount is at or above small_amount |
Market data
POST /api/open/market · permission price · no request body required
Response data:
| Field | Type | Description |
|---|---|---|
tiered_pricing | array | [{ "period": "1H", "price": 29 }, ...]. 1H is sun per energy; 1D/3D/30D are sun per energy per day (a 3-day order costs price × energy × 3) |
minimum_order_energy | int | Minimum energy per order |
maximum_order_energy | int | Maximum energy per order |
small_amount | int | Orders below this amount include a small-order fee |
small_addition | number | Small-order fee (TRX) |
usdt_energy_need_old | int | Energy for one USDT transfer to an address that holds USDT |
usdt_energy_need_new | int | Energy for one USDT transfer to an address that has never held USDT |
burn_rate | int | Cost of burning TRX instead of using energy (sun per energy) |
Account balance
POST /api/open/balance · permission balance · no request body required
{ "code": 200, "message": "success", "data": { "balance": 150000000 } }
balance is in sun (150,000,000 sun = 150 TRX).
Callbacks
When an order reaches a final state — completed (2) or failed (3) — we send a POST request with a JSON body to its callback_url.
{
"order_no": "E20260928103000a1b2c3d4",
"out_trade_no": "ORDER-10001",
"energy_amount": 65000,
"period": "1H",
"receive_address": "TXk8rQSAvPvBBNtqSoY6nCfsXWCSSpTVQF",
"status": 2,
"amount": 1885000,
"refund_amount": 0
}
Verifying a callback
Callbacks carry API-KEY, TIMESTAMP and SIGNATURE headers, signed the same way as your requests. Verify against the raw request body before parsing it:
expected = hmac.new(API_SECRET.encode(), f"{timestamp}&{raw_body}".encode(), hashlib.sha256).hexdigest()
valid = hmac.compare_digest(expected, signature)
The callback is signed with the secret of the key that placed the order, or your most recently created enabled key if that key has been disabled. If you have no enabled key, the callback is sent without signature headers — treat it as unverified and confirm with query order.
Delivery
- Respond with HTTP 200 to acknowledge. Any other status (including 201 or 204) counts as a failure.
- Each attempt times out after 10 seconds. A callback is attempted up to 3 times in total, a few seconds apart.
- Callbacks may be delivered more than once or not at all. Make your handler idempotent and use query order to reconcile orders that have not been confirmed.
Order status codes
| status | Meaning |
|---|---|
0 | Pending — created, not yet submitted |
1 | Processing — submitted, waiting for delegation |
2 | Completed — energy delegated |
3 | Failed — the charge has been refunded to your balance (refund_amount) |
4 | Refunded (reserved) |