POST /apiv2/bandwidth
Rent TRON bandwidth and delegate it to a recipient address for a fixed period (5 minutes or 1 hour).
⚠️ Access tiers.
- Accredited accounts rent any amount (up to 5000) within the pool size and maximum limits, with multiple concurrent orders. Accreditation is granted by Netts support.
- Without accreditation you can rent 400 units once — the next order is allowed only after the previous rental ends. Requests for amounts other than 400, or a second order while the first is still active, 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 |
5007 | Without accreditation — only one rental at a time; previous order still active (wait until it ends) | 503 |
5008 | Without accreditation — only 400-unit orders allowed; accreditation required for larger amounts | 503 |
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 tiers: accredited accounts rent any amount within pool/maximum limits with concurrent orders; without accreditation — 400 units once (next order only after the previous rental ends). Contact Netts support for accreditation.
- 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.