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.
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 delivered 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. Rentals for 5 minutes and for 1 hour count against the same bandwidth limit.
- Only delivered services are billed: bandwidth delegated to the address, or TRX sent instead (
fulfilledBy = trx). The amount is the same as for prepaid orders. - An order that is rejected, not executed, a test run (
test: true) or answered withenough(check: true) costs nothing. - A voluntary early reclaim does not reduce the amount due, the same as for prepaid orders.
- Access tiers, amounts, periods and all other rules of this endpoint are the same as for prepaid accounts.
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.
Price check (test: true)
{
"amount": 1000,
"receiveAddress": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
"period": "5m",
"test": true
}{
"detail": {
"code": 10003,
"status": "test",
"msg": "Test run — no on-chain action, no charge",
"data": {
"orderId": "B5M1a2b3c4d5e6f70",
"testAction": "would_delegate",
"wouldCostTRX": 0.7,
"paidTRX": 0,
"bandwidth": 1000,
"period": "5m",
"receiverFreeBandwidth": 600,
"paymentMode": "postpaid"
}
}
}wouldCostTRX is the amount that would be added to your balance due, in TRX. Nothing is delegated or billed.
Successful order
{
"detail": {
"code": 10000,
"status": "completed",
"msg": "Successful, 0.700000 TRX, postpaid",
"data": {
"orderId": "B5M0f9e8d7c6b5a41",
"paidTRX": 0.7,
"fulfilledBy": "bandwidth",
"hash": ["<txid>"],
"bandwidth": 1000,
"period": "5m",
"paymentMode": "postpaid",
"totalTRX": "0.700000"
}
}
}| Field | Description |
|---|---|
| paidTRX | Cost of the service in TRX. For postpaid accounts it is not deducted from the deposit |
| paymentMode | postpaid |
| totalTRX | Exact amount, 6 decimal places |
All other fields, including the TRX branch (fulfilledBy = trx, trxSendHash), 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 |
|---|---|---|
1004 | 402 | Postpaid credit limit reached |
5032 | 503 | Price temporarily unavailable |
5034 | 503 | Billing temporarily unavailable — the order was not accepted |
5036 | 503 | Credit check temporarily unavailable — retry later |
For all responses above nothing is ordered and nothing is billed. All other codes are the same as for prepaid orders.
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.