POST /apiv2/bandwidth
Rent TRON bandwidth and delegate it to a recipient address for a fixed period (5 minutes or 1 hour).
⚠️ Access by request only. This endpoint is not enabled for all accounts. To obtain access (an API key and inclusion in the bandwidth rental whitelist), please contact Netts support. Requests from non-whitelisted users are rejected.
Endpoint URL
POST https://netts.io/apiv2/bandwidthRequest 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 |
| X-Idempotency-Key | No | Optional client-generated key (base64) to safely retry without double-ordering. If omitted, the server generates one automatically |
Request Body
{
"amount": 1500,
"receiveAddress": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"period": "5m"
}Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| amount | integer | Yes | Bandwidth units to rent (minimum: 400, maximum: 5000) |
| receiveAddress | string | Yes | TRON address that will receive the bandwidth (T…, 34 chars, base58) |
| period | string | Yes | Rental duration: "5m" (5 minutes) or "1h" (1 hour) |
| trx_send | boolean | No | Guaranteed transaction: if no bandwidth is available, send TRX to the address instead so the transaction still goes through. Works only when amount = 400 (ignored otherwise). Default false |
| check | boolean | No | If true and the recipient already has more than 400 bandwidth, the order is not delegated and no funds are charged (status enough). Default false |
| test | boolean | No | Dry run. If true, the full order flow is simulated — the response tells you the outcome that would occur and the price that would be charged — without any on-chain action and without charging. Default false |
Example Requests
The examples below also build and send the
X-Idempotency-Keyso an accidental repeat does not create a second order. See Idempotency for the full rules.
cURL
API_KEY="your_api_key"
ADDR="TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
AMOUNT=1500
PERIOD="5m"
NONCE=$(( $(date +%s) / 2 )) # stable for retries within a 2s window; or your own order UUID
# X-Idempotency-Key = base64( HMAC-SHA256( API_KEY, "addr:amount:period:nonce" ) )
IDEMP=$(printf '%s' "${ADDR}:${AMOUNT}:${PERIOD}:${NONCE}" \
| openssl dgst -sha256 -hmac "$API_KEY" -binary | base64)
curl -X POST https://netts.io/apiv2/bandwidth \
-H "Content-Type: application/json" \
-H "X-API-KEY: $API_KEY" \
-H "X-Real-IP: your_whitelisted_ip" \
-H "X-Idempotency-Key: $IDEMP" \
-d "{\"amount\": $AMOUNT, \"receiveAddress\": \"$ADDR\", \"period\": \"$PERIOD\"}"Python
import time, hmac, hashlib, base64, requests
API_KEY = "your_api_key"
url = "https://netts.io/apiv2/bandwidth"
payload = {
"amount": 1500,
"receiveAddress": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"period": "5m",
}
# X-Idempotency-Key = base64( HMAC-SHA256( API_KEY, "addr:amount:period:nonce" ) )
# Generate ONCE per order and resend the same value on every retry.
nonce = str(int(time.time() // 2)) # 2s bucket; or your own order UUID
message = f"{payload['receiveAddress']}:{payload['amount']}:{payload['period']}:{nonce}"
idem_key = base64.b64encode(
hmac.new(API_KEY.encode(), message.encode(), hashlib.sha256).digest()
).decode()
headers = {
"Content-Type": "application/json",
"X-API-KEY": API_KEY,
"X-Real-IP": "your_whitelisted_ip",
"X-Idempotency-Key": idem_key,
}
response = requests.post(url, headers=headers, json=payload)
data = response.json()
detail = data.get("detail", {})
if response.status_code == 200 and detail.get("status") == "completed":
d = detail["data"]
print(f"Order ID: {d['orderId']}")
print(f"Hashes: {d['hash']}") # array of delegation tx hashes
print(f"Bandwidth: {d['bandwidth']} for {d['period']}")
print(f"Cost: {d['paidTRX']} TRX")
else:
print(f"Code: {detail.get('code')} | {detail.get('msg', detail)}")A full client example (Python + cURL) is provided with the service package (handler_bandwidth/doc/client_example/).
Response
Success — bandwidth delegated (200 OK)
{
"detail": {
"code": 10000,
"status": "completed",
"msg": "Successful",
"data": {
"orderId": "B5M<key14>",
"paidTRX": "<amount charged in TRX>",
"fulfilledBy": "bandwidth",
"hash": ["a1b2c3...", "d4e5f6..."],
"bandwidth": 1500,
"period": "5m"
}
}
}Success — TRX sent instead of bandwidth (200 OK, only amount=400 + trx_send=true)
When the pool has no bandwidth and trx_send is enabled, TRX is sent to the address so the transaction still passes. A fixed charge applies in this case, regardless of the requested period.
{
"detail": {
"code": 10000,
"status": "completed",
"msg": "Successful (sent TRX, bandwidth unavailable)",
"data": {
"orderId": "B5M<key14>",
"paidTRX": "<amount charged in TRX>",
"fulfilledBy": "trx",
"trxSendHash": ["<txid>"],
"hash": [],
"bandwidth": 400,
"period": "5m"
}
}
}Already enough — not charged (200 OK, only with check=true)
{
"detail": {
"code": 10002,
"status": "enough",
"msg": "enough band for 1 transfer",
"data": { "orderId": "B5M<key14>", "paidTRX": 0, "bandwidth": 400, "period": "5m" }
}
}Processing — external provider (202 Accepted)
Returned when the order is handed to an external provider asynchronously. Poll the status endpoint (below) using orderId until it completes.
{
"detail": {
"code": 10001,
"status": "processing",
"msg": "Order accepted, processed by an external provider. Poll the status endpoint.",
"data": { "orderId": "B5M<key14>", "bandwidth": 1500, "period": "5m" }
}
}Test run (200 OK, only with test=true)
The whole order flow is simulated. testAction tells you what would happen and wouldCostTRX what would be charged. Nothing is delegated, no TRX is sent, nothing is charged (paidTRX: 0).
{
"detail": {
"code": 10003,
"status": "test",
"msg": "Test run — no on-chain action, no charge",
"data": {
"orderId": "B5M<...>",
"testAction": "would_delegate",
"wouldCostTRX": "<amount that would be charged in TRX>",
"paidTRX": 0,
"bandwidth": 400,
"period": "5m",
"receiverFreeBandwidth": 600
}
}
}testAction values: would_delegate (bandwidth would be delegated), would_trx_send (no bandwidth, amount=400 + trx_send → TRX would be sent), enough (recipient already has enough, with check=true), or would_error:<reason> (e.g. no_bandwidth, not_whitelisted).
Response Fields
| Field | Type | Description |
|---|---|---|
| detail.code | integer | 10000 delegated/TRX, 10002 enough, 10001 processing |
| detail.status | string | completed / enough / processing / failed |
| detail.data.orderId | string | Order ID, format B5M… (5m) / B1H… (1h) — use it for the status endpoint |
| detail.data.paidTRX | number | Amount charged in TRX (0 when enough) |
| detail.data.fulfilledBy | string | bandwidth (delegated) / trx (TRX sent) |
| detail.data.hash | array | Delegation transaction hashes (up to 10). Always an array (empty for the TRX branch) |
| detail.data.trxSendHash | array | TRX transfer hash(es), present only when fulfilledBy = trx |
| detail.data.bandwidth | integer | Bandwidth units delegated |
| detail.data.period | string | Rental period (5m / 1h) |
Status Endpoint
GET https://netts.io/apiv2/bandwidth/status/{orderId}Headers: X-API-KEY + X-Real-IP (the order must belong to the authenticated user).
| Order state | HTTP | code | status |
|---|---|---|---|
| Completed | 200 | 10000 | completed (with hash / trxSendHash) |
| In progress | 200 | 10001 | processing |
| Already enough | 200 | 10002 | enough |
| Failed | 200 | 5003 | failed |
| Not found / not yours | 404 | -1 | — |
Reclaim Endpoint
Voluntarily reclaim (undelegate) the bandwidth of one of your delegated orders before its period expires. The bandwidth is undelegated automatically and the transaction hash is returned.
POST https://netts.io/apiv2/bandwidth/reclaim/{orderId}Headers: X-API-KEY + X-Real-IP (the order must belong to the authenticated user).
| Order state | HTTP | code | status | Result |
|---|---|---|---|---|
| Delegated → reclaimed now | 200 | 10004 | reclaimed | reclaimHash (undelegate tx hashes) |
| Already reclaimed | 200 | 10004 | reclaimed | reclaimHash + msg "already reclaimed" |
| Not in a delegated state (nothing to reclaim) | 400 | 5005 | failed | — |
| Reclaim did not complete yet | 503 | 5003 | failed | retry shortly |
| Not found / not yours | 404 | -1 | — | — |
curl -X POST https://netts.io/apiv2/bandwidth/reclaim/B5M<...> \
-H "X-API-KEY: your_api_key" -H "X-Real-IP: your_whitelisted_ip"{
"detail": {
"code": 10004,
"status": "reclaimed",
"msg": "Bandwidth reclaimed",
"data": { "orderId": "B5M<...>", "reclaimHash": ["<txid>"] }
}
}import requests
order_id = "B5M..." # the orderId from your rental response
url = f"https://netts.io/apiv2/bandwidth/reclaim/{order_id}"
headers = {"X-API-KEY": "your_api_key", "X-Real-IP": "your_whitelisted_ip"}
resp = requests.post(url, headers=headers)
detail = resp.json()["detail"]
if resp.status_code == 200 and detail["status"] == "reclaimed":
print(f"Reclaimed: {detail['data']['reclaimHash']} ({detail['msg']})")
else:
print(f"Code {detail.get('code')}: {detail.get('msg', detail)}")The rental charge is not refunded on a voluntary early reclaim — reclaiming only returns the delegated bandwidth to the pool ahead of the period.
Error Responses
Authentication Error (401)
{ "detail": { "code": -1, "msg": "Invalid API key or IP not in whitelist" } }Insufficient Balance (403)
{ "detail": { "code": 1004, "status": "failed", "msg": "Insufficient funds" } }Validation Error (400)
{ "detail": { "code": 5004, "status": "failed", "msg": "Bandwidth amount out of range (400..5000)" } }Delegation Failed / Service Unavailable (503)
{ "detail": { "code": 5003, "status": "failed", "msg": "Bandwidth delegation failed" } }Error Code Reference
| Code | Description | HTTP Status |
|---|---|---|
10000 | Success (delegated, or TRX sent) | 200 |
10000 | Success (cached response) | 208 |
10001 | Accepted, processing by external provider | 202 |
10002 | Recipient already has enough bandwidth (not charged) | 200 |
10003 | Test run — outcome + price preview, nothing charged (test=true) | 200 |
10004 | Bandwidth reclaimed (voluntary undelegate) — reclaimHash returned | 200 |
- | Duplicate request still processing | 409 |
-1 | Invalid API key / IP not in whitelist | 401 |
1004 | Insufficient balance | 403 |
1005 | No payer address for user | 400 |
5004 | Invalid amount/period (validation) | 400 |
5005 | Nothing to reclaim (order is not in a delegated state) | 400 |
5003 | Bandwidth delegation failed / unavailable | 503 |
5000 | Internal server error | 500 |
Rate Limits
| Period | Limit | Description |
|---|---|---|
| 1 second | 50 requests | Maximum 50 requests per second per IP |
Rate Limit Exceeded (429)
{ "message": "API rate limit exceeded" }Idempotency
Send the optional X-Idempotency-Key header so an accidental repeat does not create a second order — the original response is returned with HTTP 208. If you do not send the header, the server derives a key automatically from your request parameters within a short time window.
How to form the key
The key is base64( HMAC-SHA256( secret, message ) ) — a 44-character base64 string, where:
- secret = your API key (
X-API-KEY); - message = the fields joined with
:—receiveAddress:amount:period:nonce.
nonce is any value that is stable across retries of the same logical order but different between distinct orders — e.g. a UUID you keep for that order, or a coarse timestamp bucket. Generate the key once per order and resend the exact same value on every retry.
import hmac, hashlib, base64, time
def make_idempotency_key(api_key, receive_address, amount, period, nonce=None):
if nonce is None:
nonce = str(int(time.time() // 2)) # 2-second bucket; or your own order UUID
message = f"{receive_address}:{amount}:{period}:{nonce}"
digest = hmac.new(api_key.encode(), message.encode(), hashlib.sha256).digest()
return base64.b64encode(digest).decode() # 44-char base64# then send it as a header:
-H "X-Idempotency-Key: <base64_key>"Including period in the message is important: renting the same address for 5m and for 1h are different orders and must produce different keys.
Validation. A supplied
X-Idempotency-Keymust be a base64 string of 16–64 characters (charsetA–Z a–z 0–9 + / = _ -). A malformed or over-long key is rejected with HTTP 400 (code 5004).
| Status Code | Meaning |
|---|---|
| 200 | Processed successfully (first request) |
| 208 | Already processed successfully — cached response returned (no second charge) |
| 409 | The same request is currently being processed — wait, do not retry yet |
Retrying after a failure. Only successful results (
completed/enough) are cached. If the previous attempt failed or timed out (no funds were charged), you may safely retry with the sameX-Idempotency-Key— the order is attempted again rather than returning the old error. While an attempt is still in progress you get409; wait and retry.
Notes
- Access is granted by request only — contact Netts support to be whitelisted.
- Minimum: 400 units. Maximum: 5000 units per order (current configuration).
- Periods:
5m(300 s) and1h(3600 s). The bandwidth is automatically reclaimed when the period expires. - No buffer: exactly the requested amount is delegated.
- hash is an array: a single order may produce up to 10 delegation hashes — all are returned.
- Pricing: charged in TRX, based on the requested amount and period; rates may vary by time of day. Contact support for current pricing.
- Small-order compensation (delegation): for orders under 1000 units, a fixed 0.372 TRX is added to the price as compensation for the on-chain delegation and reclaim. Orders of 1000 units or more have no such addition.
- TRX-send compensation: when the order is fulfilled by sending TRX (
fulfilledBy = trx), a fixed 0.268 TRX is added instead (compensation for the on-chain TRX transfer). - trx_send: only for
amount = 400; if no bandwidth is available, TRX is sent to the address so the transaction still passes. - check: skips delegation (and charging) when the recipient already has more than 400 bandwidth.
- Order ID format:
B5M…(5 minutes) /B1H…(1 hour). - Response timeout: up to ~12 seconds while waiting for delegation; typically 1–2 seconds.