POST /apiv2/order1h
Create a 1-hour energy rental order through multiple energy providers with automatic failover.
Endpoint URL
POST https://netts.io/apiv2/order1hRequest Headers
| Header | Required | Description |
|---|---|---|
| Content-Type | Yes | application/json |
| X-API-KEY | Yes | Your API key from Netts dashboard |
| X-Real-IP | Yes | IP address from your whitelist |
Request Body
{
"amount": 131000,
"receiveAddress": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE"
}Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| amount | integer | Yes | Energy amount to rent (minimum: 61000, maximum: 3000000) |
| receiveAddress | string | Yes | TRON address that will receive the energy (TRC-20 format) |
Provider Selection
The API automatically selects the optimal energy provider based on:
- Cost efficiency - Always finds the lowest available price
- Availability - Ensures sufficient energy reserves
- Reliability - Uses providers with high success rates
- Speed - Prioritizes fastest delivery times
Example Requests
cURL
curl -X POST https://netts.io/apiv2/order1h \
-H "Content-Type: application/json" \
-H "X-API-KEY: your_api_key" \
-H "X-Real-IP: your_whitelisted_ip" \
-d '{
"amount": 131000,
"receiveAddress": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE"
}'Python
import requests
url = "https://netts.io/apiv2/order1h"
headers = {
"Content-Type": "application/json",
"X-API-KEY": "your_api_key",
"X-Real-IP": "your_whitelisted_ip"
}
payload = {
"amount": 131000,
"receiveAddress": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE"
}
response = requests.post(url, headers=headers, json=payload)
data = response.json()
if response.status_code == 200:
detail = data.get('detail', {})
order_data = detail.get('data', {})
print(f"Order ID: {order_data.get('orderId')}")
print(f"Transaction Hash: {order_data.get('hash')}")
print(f"Energy Delivered: {order_data.get('energy')}")
print(f"Cost: {order_data.get('paidTRX')} TRX")
print(f"Delegate Address: {order_data.get('delegateAddress')}")
else:
error_detail = data.get('detail', data)
print(f"Error Code: {error_detail.get('code', 'N/A')}")
print(f"Error Message: {error_detail.get('msg', error_detail)}")Response
Success Response (200 OK)
{
"detail": {
"code": 10000,
"msg": "Successful, 2.23 TRX deducted",
"data": {
"orderId": "1H123456",
"paidTRX": 2.23,
"hash": "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456",
"delegateAddress": "TNp5gsJhBmZFXgCdgjMgr8pEZ8fHgXUHDq",
"energy": 131050
}
}
}Response Fields
| Field | Type | Description |
|---|---|---|
| detail.code | integer | Always 10000 for successful orders |
| detail.msg | string | Success message with deducted amount |
| detail.data.orderId | string | Unified order ID (format: 1H{request_id}) |
| detail.data.paidTRX | number | Total cost in TRX (includes activation fee if address was not activated) |
| detail.data.hash | string | null | Transaction hash. Field is always present but may be empty - some providers don't return hash immediately. Use /apiv2/order_check after 1 minute to get the hash |
| detail.data.delegateAddress | string | Pool address that delegated energy |
| detail.data.energy | integer | Energy amount + buffer (typically +50) |
Error Responses
Authentication Error (401)
{
"detail": "Invalid API key or IP not in whitelist"
}Insufficient Balance (403)
{
"code": 1004,
"msg": "Insufficient funds. Required: 2.23 TRX, Available: 1.50 TRX"
}Service Unavailable (503)
{
"code": 5003,
"msg": "Service temporarily unavailable. All energy providers are currently unavailable."
}Provider Errors (503)
{
"code": 5001,
"msg": "Energy provider temporarily unavailable"
}{
"code": 5002,
"msg": "Energy provider temporarily unavailable"
}{
"code": 5004,
"msg": "Energy provider requires higher minimum amount"
}Internal Server Error (500)
{
"code": 5000,
"msg": "Internal server error occurred"
}Error Code Reference
| Code | Description | HTTP Status |
|---|---|---|
10000 | Success | 200 |
10000 | Success (cached response) | 208 |
- | Duplicate request still processing | 409 |
1004 | Insufficient balance | 403 |
5000 | Internal server error | 500 |
5001 | Energy provider unavailable | 503 |
5002 | Energy provider unavailable | 503 |
5003 | Energy service unavailable | 503 |
5004 | Energy provider minimum not met | 503 |
Rate Limits
The following rate limits apply to this endpoint (per IP address):
| Period | Limit | Description |
|---|---|---|
| 1 second | 50 requests | Maximum 50 requests per second |
Rate Limit Headers
RateLimit-Limit: 50
RateLimit-Remaining: 49
RateLimit-Reset: 1
X-RateLimit-Limit-Second: 50
X-RateLimit-Remaining-Second: 49Rate Limit Exceeded (429)
{
"message": "API rate limit exceeded"
}Idempotency
The API supports idempotency to prevent duplicate order processing. When you send multiple identical requests, the system ensures the order is processed only once.
How Idempotency Works
Request uniqueness is determined by a combination of:
- Request timestamp (1-second window)
- Energy amount
- Receiver address
- API key
Each request is given a 1-second uniqueness window. To protect the system from abuse and ensure proper processing, requests with identical parameters cannot be sent more frequently than once per second.
Current behavior: The system automatically protects clients from erroneous retries on already ordered energy. If you accidentally send the same request twice, you won't be charged twice.
Supplying Your Own Key
You can take idempotency into your own hands by sending the X-Idempotency-Key header. When it is present, that value alone decides whether a request is a repeat, and the automatic combination above is not used. When it is absent, nothing changes — the server derives the key for you.
| Header | X-Idempotency-Key |
| Format | Exactly 64 lowercase hexadecimal characters — a SHA-256 digest |
| Lifetime | 24 hours from the first request carrying that key |
| Scope | Your account. The same value sent by a different account never returns your result |
A key of any other shape — a UUID with dashes, base64, uppercase hex — is rejected with 400 before the order is placed and before anything is charged:
{
"detail": "Invalid idempotency key format. Must be 64-character hexadecimal string."
}The format differs from other endpoints.
/apiv2/withdraw,/apiv2/bandwidthand the orchestrator accept a 16–64 character base64 key. This endpoint accepts only a 64-character hex digest, so key-building code copied from those endpoints returns 400 here.
How to form the key
Derive it from your API key. That makes the value unique to your account, reproducible on a retry, and impossible for anyone else to arrive at:
import hashlib
import hmac
def make_idempotency_key(api_key: str, address: str, amount: int, nonce: str) -> str:
message = f"{address}:{amount}:{nonce}"
return hmac.new(api_key.encode(), message.encode(), hashlib.sha256).hexdigest()The
noncebelongs to the order, not to the request. Pick it once, when the order is created on your side, and pass that same value on every send of that order — the first attempt and every retry alike. Generating a fresh value inside the sending function (str(uuid.uuid4())on each call) gives every attempt a different key, so a retry after a timeout is accepted as a second order and charged again. The simplest correct choice is the order id you already have: it exists before the first attempt and survives a restart of your process.
# once, when the order appears in your system
order = create_order(address="TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE", amount=131000)
# on the first attempt and on every retry — the same three inputs, the same key
key = make_idempotency_key(API_KEY, order.address, order.amount, order.id)
headers = {
"Content-Type": "application/json",
"X-API-KEY": API_KEY,
"X-Idempotency-Key": key,
}A key lives 24 hours. After that the same nonce is free again and starts a new order.
Do not use a value anyone else could arrive at — 64 zeros, the digest of a fixed word. Keys share one space across accounts. Such a collision never exposes another account's order, but your request is refused with 409 until their key expires, which is not the answer you want in the middle of a retry.
Placing Two Identical Orders
Sometimes you genuinely want the same order twice — the same amount of energy to the same address, one after the other. The automatic key cannot tell that from a retry: the two requests are byte-for-byte identical, and the only thing separating them is the moment they arrive.
Without a key of your own, the outcome depends on the gap between them:
| Gap between the two requests | What happens |
|---|---|
| Inside the same 1-second window | The second request is taken for a repeat. It is not executed: you get 208 and the first order's response, orderId included. Nothing is charged for it |
| More than a second apart | Two different keys — both orders are placed and both are charged |
So if you rely on the automatic key, leave more than one second between two identical orders, and read the status code: 208 means the order you just sent was not placed.
A pause is a workaround, not a fix. It separates every request, including the ones you never meant to repeat — a retry after a timeout, a double click, a message redelivered by your queue. Those also arrive later than the window, so they are placed as separate orders and charged separately. The response timeout of this endpoint is 10 seconds, which is already far outside the window: the automatic key does not protect a retry that follows a timeout.
Your own key removes the guesswork, because the decision moves to the only side that knows the answer:
| What you are doing | What you send | Result |
|---|---|---|
| A second, genuinely new order | A new nonce | A new key — the order is placed |
| A retry of an order whose outcome you do not know | The nonce of the first attempt | The same key — 208, the original response, no second charge |
The second row is the reason the header exists, and it is where implementations usually slip: see the note under How to form the key.
HTTP Status Codes for Duplicate Requests
| Status Code | Name | Description |
|---|---|---|
| 200 | OK | Order processed successfully (first request) |
| 208 | Already Reported | Order was already processed, returning cached response |
| 409 | Conflict | Request is currently being processed, do not retry |
Duplicate Request - Already Processed (208)
When a duplicate request is received for an already completed order:
{
"detail": {
"code": 10000,
"msg": "Successful, 2.54 TRX deducted",
"data": {
"hash": "9e4c20e21e01e4c39b21b670d1ea1fc1e4b0de94d8fbd4c190d5378ba911dfae",
"energy": 65050,
"orderId": "1H70bcc7962a",
"paidTRX": 2.535,
"delegateAddress": "TNp5gsJhBmZFXgCdgjMgr8pEZ8fHgXUHDq"
}
},
"idempotency": {
"status": "completed",
"cached": true,
"original_created_at": "2025-12-03T10:34:49.104896"
}
}The response body is identical to the original successful response, with an additional idempotency object indicating this is a cached response.
Duplicate Request - Still Processing (409)
When a duplicate request arrives while the original is still being processed:
{
"success": false,
"error": "duplicate_request_processing",
"message": "This request is currently being processed. Please wait and do not retry.",
"idempotency_key": "b9e67b2412d33c92a4f0e1c3d5b7a9f2e4c6d8b0a2f4e6c8d0b2a4f6e8c0d2b4",
"retry_after_seconds": 3
}Recommendation: Wait for the specified retry_after_seconds before checking the order status.
Best Practices
- Do not send parallel requests with the same parameters - wait for each response
- Use a new
noncefor every new order, and the first attempt'snoncefor every retry of it - Never rebuild the
nonceat send time — a retry must reproduce the key of the first attempt, not a new one - Handle 409 responses by waiting, not by immediately retrying
- Check
idempotency.cachedfield to identify cached responses — a208means the order you just sent was not placed
Postpaid Accounts
Accounts on postpaid billing use the same endpoint, URL, headers and request body as everyone else. The difference is how the order is paid:
- Nothing is deducted from your deposit. Each successful order is added to your account balance due in the settlement currency agreed in your contract. You pay it by invoice, according to your agreement.
- Your account has a credit limit. While the amount due is within the limit, orders are accepted even with a zero deposit.
- Only delivered services are billed. An order that is rejected or not executed costs nothing.
- If the receiving address has to be activated, the activation is part of the same order and is billed as a separate charge.
Postpaid billing is enabled by Netts for an account on request. Every response tells you how the order is paid: paymentMode is postpaid or prepaid.
Additional request fields
| Parameter | Type | Required | Description |
|---|---|---|---|
| test | boolean | No | true — calculate the price only. Nothing is ordered, delivered or billed |
| freezePrice | boolean | No | true — fix the price under your contract for 60 seconds. Nothing is ordered or billed |
| quoteId | string | No | Place the order at the price of a quote issued by freezePrice |
| priceCurrency | string | No | TRX (default) or the currency of your contract, as in settlement.asset. For postpaid accounts the amounts in your contract currency are always included |
quoteId cannot be combined with test or freezePrice. Any other field in the request body is rejected.
Price check (test: true)
{
"amount": 65000,
"receiveAddress": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
"test": true
}{
"detail": {
"code": 10010,
"msg": "Test calculation",
"data": {
"amount": 65000,
"unitPriceSun": "20",
"discount": null,
"energyTRX": "1.300000",
"activationTRX": "1.100000",
"wouldCostTRX": "2.400000",
"paidTRX": 0,
"paymentMode": "postpaid",
"activationCheck": "inactive",
"alt": {"USDC": {"energy": "0.430541", "activation": "0.364304", "total": "0.794845"}},
"fxAt": "2026-10-19T07:15:18.469870+00:00"
}
}
}| Field | Description |
|---|---|
| unitPriceSun | Price per energy unit in SUN, including your personal discount if you have one |
| discount | Your personal discount (type, value) or null |
| energyTRX / activationTRX / wouldCostTRX | Energy, activation (if the address needs it) and total, in TRX |
| activationCheck | inactive — the address will be activated and billed; active — no activation; unknown — could not be checked right now |
alt.<currency> | The same amounts in the currency of your contract |
| fxAt | Time of the exchange rate used for the amounts in your contract currency |
Fixed-price quote (freezePrice: true)
Use a quote to fix the price under your contract before you order.
- Send the order with
"freezePrice": true. The response has code10011(Quote issued), the same fields as a price check, plusquoteIdandexpiresAt. - Within 60 seconds send the order with
"quoteId": "<quoteId>"and the sameamountandreceiveAddress. - The order is billed at the quoted price — the price under your contract at the moment of the quote — even if the price changes in between.
A quote can be used once and only for the same product: a 1-hour quote cannot pay for a 5-minute order. An expired or already used quote is rejected and nothing is ordered.
{
"amount": 65000,
"receiveAddress": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
"quoteId": "q_4f540f16a2c94b7f8e3d1a6b5c0e9d72"
}Successful order
{
"detail": {
"code": 10000,
"msg": "Successful, 1.30 TRX for energy + 1.10 TRX for address activation, postpaid",
"data": {
"orderId": "1Hf9544a1508",
"paidTRX": 2.4,
"hash": "032bda8fa6911b42c15a6d8d30520dc22bcc4077972a1503b6a3fae491967eb4",
"delegateAddress": "TVnkj2jHKgmea8DVJFo2teD7rRSpH51bne",
"energy": 65000,
"activationHash": "259e1ab276114ae717a6f62d7d25eb1f89a2dccb637cbe41be626df3c2a78519",
"paymentMode": "postpaid",
"energyTRX": "1.300000",
"activationTRX": "1.100000",
"totalTRX": "2.400000"
}
}
}| Field | Description |
|---|---|
| paidTRX | Cost of the service in TRX. For postpaid accounts it is not deducted from the deposit |
| paymentMode | postpaid |
| energyTRX / activationTRX / totalTRX | Exact amounts, 6 decimal places |
| quoteId, alt | Present when the order was placed with a quote: the quote ID and the quoted amounts |
| activationFinal | Present and false only when the activation amount is preliminary; the final amount appears on your invoice |
All other fields are the same as for prepaid orders.
Credit limit reached (402)
{
"detail": {
"code": 1004,
"msg": "Postpaid credit limit reached.",
"paymentMode": "postpaid",
"reason": "limit_reached"
}
}The order is not executed and nothing is billed. New orders are accepted again once your payment is registered or your limit is increased.
Postpaid error codes
| Code | HTTP | Description |
|---|---|---|
10010 | 200 | Price check result (test) |
10011 | 200 | Quote issued (freezePrice) |
1004 | 402 | Postpaid credit limit reached |
4100 | 400 | Invalid request |
4101 | 400 | Unsupported priceCurrency |
4102 | 400 | quoteId combined with test or freezePrice |
4103 | 409 | Quote already used |
4104 | 409 | Quote expired |
4105 | 409 | Idempotency key already used for a different request |
4106 | 404 | Quote not found |
4107 | 409 | Order parameters differ from the quote |
4108 | 400 | Invalid quoteId format |
4109 | 422 | Not available for sub-accounts |
5032 | 503 | Price temporarily unavailable |
5033 | 503 | Exchange rate temporarily unavailable |
5034 | 503 | Billing temporarily unavailable — the order was not accepted |
5036 | 503 | Credit check temporarily unavailable — retry later |
For all 4xx and 503 responses above nothing is ordered and nothing is billed.
Notes
- Energy is delivered instantly upon successful order (typically within 0.5-10 seconds)
- API response timeout: Maximum 10 seconds, typically responds in up to 2 seconds
- Address activation: If receiver address is not activated, Netts activates it at cost price
- Activation delay: For non-activated addresses, API response may take up to 6 seconds due to activation process
- Orders are processed 24/7 with automatic provider failover
- Minimum energy amount: 61,000 units
- Maximum energy amount: 3,000,000 units per order
- Energy buffer: +50 units added automatically for provider compensation (free of charge)
- Transaction hash: Field is always present but may be empty if provider doesn't return it immediately. To retrieve the hash, call /apiv2/order_check no earlier than 1 minute after placing the order
- Provider selection: Automatic based on cost and availability
- Order ID format:
1H{request_id}for unified tracking - Pricing: Dynamic based on time of day and energy amount
- Duration: Fixed 1 hour (3600 seconds)
- Rate limiting: 50 requests per second per IP address