Errors & conventions
This page is the reference for how the TRONAgg API reports failures and the cross-cutting rules every endpoint follows: the error envelope, the full error-code catalog, idempotency on purchases, the pagination styles, and the units and formats you will see in every response.
The error envelope
Errors use the standard HTTP status code plus a JSON body with a top-level detail. Most endpoints put a structured object in detail: a machine-readable error code, a human-readable message, and — for a few errors — extra context fields.
{
"detail": {
"error": "insufficient_balance",
"current_balance": 1200000,
"required_amount": 3500000
}
}
Branch on detail.error; show detail.message to a human when present.
Honest exceptions
A few responses do not follow the object-with-error shape. Handle them defensively — read detail, and:
-
Plain-string
detail. Some responses setdetailto a bare string instead of an object. These are:401on any API-key endpoint —"API key required","Invalid API key", or"User not found".403on any API-key endpoint when the account is blocked —"Account is blocked". (The other403,ip_not_allowed, is an object.)429on any API-key endpoint —"Rate limit exceeded", with aRetry-Afterheader. See Rate limits.429onPOST /webhooks/{id}/test—"Too many webhook tests; limit is 5 per hour". The limit is 5 tests per hour per account, shared with the workspace's test button.404onGET /order/{id}—"Order not found"(also returned when the order exists but is not yours).- Every error from the public energy estimators (
/calculator/estimate-energy,/calculator/estimate-energy/usdt/…) — e.g.422"Invalid TRON address format: …",429"Rate limit exceeded". Only a missing required parameter returns the422array described below. 500on unexpected errors —"Internal server error", on any endpoint that has no structuredinternal_errorof its own.
{ "detail": "Order not found" } -
Object keyed by
code, noterror. The order-amount-limit errors onPOST /buyusecode(plusmin/max) — see Order amount limits. -
Request-validation
422. A malformed request body or an out-of-range query parameter (for exampleresource_amount≤ 0, orpage_sizeabove 100) is rejected by the framework with422and adetailarray of field errors. The only422s with an objectdetailare the business errors listed below (AutoEnergyinvalid_addressand the Energy Agent codes).{ "detail": [ { "loc": ["query", "resource_amount"], "msg": "...", "type": "..." } ] }
A robust client therefore checks the type of detail: object → branch on detail.error (falling back to detail.code); string → treat as a human message; array → request-validation error.
Error codes
Programmatic API
error code | HTTP | Endpoint(s) | Extra fields | When |
|---|---|---|---|---|
ip_not_allowed | 403 | All API-key-authenticated endpoints | message | The source IP is outside this key's optional allowlist, or could not be determined. Update the list in Workspace → API keys → IP access, or clear it to remove the restriction. |
invalid_address | 400 | POST /buy | message | receiver_address is not a valid TRON address. Checked before anything is charged. |
unsupported_resource | 400 | GET /estimate, POST /buy | message | A resource_type other than ENERGY or BANDWIDTH was sent, or (POST /buy) a Bandwidth order from an account that can buy Energy only. |
invalid_duration | 400 | GET /estimate | message | A duration other than 1h was sent. (POST /buy rejects it as a 422 body-validation error.) |
invalid_idempotency_key | 400 | POST /buy | message | Idempotency-Key longer than 128 characters. |
insufficient_balance | 400 | POST /buy | current_balance, required_amount | Balance below the order total (both in SUN). No message field on this endpoint. |
validation_error | 400 | POST /webhooks | message | The webhook URL was rejected: not HTTPS; its host is not public (private, loopback or link-local, cannot be resolved, or a tronagg.ai host); the URL is already connected; or the account already has 10 webhooks. |
webhook_deposit_required | 400 | POST /webhooks | message, total_deposited_sun, required_total_deposited_sun | Webhooks need more than 1 TRX of net confirmed deposits: confirmed deposits minus withdrawals (including withdrawal fees). Purchases don't reduce it. total_deposited_sun is your current net figure; required_total_deposited_sun is the threshold (1000001). |
invalid_cursor | 400 | GET /transactions | message | The cursor value could not be parsed. |
reseller_suspended | 403 | POST /buy | — | Purchasing is suspended for your account. Contact support. |
not_found | 404 | DELETE /webhooks/{id}, POST /webhooks/{id}/test | message | No webhook with this id on your account. |
duplicate_request | 409 | POST /buy | message | A request with the same Idempotency-Key is still being processed. See Idempotency. |
delivery_failed | 502 | POST /webhooks/{id}/test | message | The test delivery did not succeed. |
service_unavailable | 503 | GET /estimate, POST /buy | message | Energy purchasing is temporarily unavailable. |
reseller_activation_balance_low | 503 | POST /buy | — | Purchasing is temporarily unavailable for your account. Retry later. |
internal_error | 500 | POST /buy | message | Unexpected server error (captured for investigation). The order was not placed; retry later. |
For the order_amount_below_minimum / order_amount_above_maximum codes on POST /buy, see Order amount limits.
AutoEnergy
The AutoEnergy endpoints (/autoenergy/*) use the object envelope — {"error": <code>, "message": <text>} — with these codes:
error code | HTTP | When |
|---|---|---|
insufficient_balance | 400 | Subscribe or reactivate: your balance is below the entry threshold — the activation fee + one daily fee + one 131k-class transfer. (Carries message here, unlike POST /buy.) |
reseller_suspended | 403 | AutoEnergy is suspended for your account. Contact support. |
not_found | 404 | No such subscription for this account. |
address_busy | 409 | The address already has a live subscription — yours or another account's. If GET /autoenergy/subscriptions/{address} returns it, there is nothing to do; a 404 there means another account covers the address. Reactivate also returns it when another subscription has taken the address since yours was closed. |
address_exists | 409 | You have a closed subscription for this address. Reactivate or delete it first. |
not_reactivatable | 409 | Only a closed subscription can be reactivated. |
not_deletable | 409 | Only a closed subscription can be deleted. Close it first. |
invalid_address | 422 | The address is not a valid TRON address. |
fulfiller_unavailable | 502 | Energy delegation could not be started or resumed; retry. |
autoenergy_temporarily_unavailable | 503 | AutoEnergy is temporarily unavailable for your account; retry later. |
reseller_activation_balance_low | 503 | AutoEnergy is temporarily unavailable for your account; retry later. |
internal_error | 500 | Unexpected server error (captured for investigation). Retry later. |
Other 422s from these endpoints — for example an expire_at in the past, or idle_hours outside 24–72 — are request-validation errors with a detail array, not this object.
Energy Agent
The Energy Agent endpoints (/agent/*, plus the public GET /pay/{public_id}) return their business errors with the code in detail.error, sometimes with extra context fields, and no message — map the code to your own text. Authentication errors and request-validation 422 arrays follow the general rules above.
{ "detail": { "error": "below_min_balance", "min_balance_sun": 10000000, "balance_sun": 2500000 } }
error code | HTTP | Endpoint(s) | Extra fields | When |
|---|---|---|---|---|
program_disabled | 403 | POST /agent/configs, POST /agent/configs/{id}/resume | — | The program is not accepting new or resumed configurations right now. |
not_found | 404 | PATCH / DELETE /agent/configs/{id}, POST /agent/configs/{id}/pause, POST /agent/configs/{id}/resume, GET /pay/{public_id} | — | No such configuration on your account (or no such pay page). |
address_taken | 409 | POST /agent/configs | — | The address already belongs to an active configuration. |
admin_paused | 409 | POST /agent/configs/{id}/resume, DELETE /agent/configs/{id} | — | Paused by TRONAgg support. Contact support. |
generated_not_deletable | 409 | DELETE /agent/configs/{id} | — | A dedicated TRONAgg address can't be deleted; pause it instead. |
address_invalid | 422 | POST /agent/configs | — | Not a valid TRON address, or an address was sent with mode: "generated". |
address_reserved | 422 | POST /agent/configs | — | The address can't be used for an own-wallet configuration. |
config_limit | 422 | POST /agent/configs | max_configs | You already have the maximum number of configurations. |
generated_limit | 422 | POST /agent/configs | max_generated | You already have the maximum number of dedicated TRONAgg addresses. |
units_out_of_bounds | 422 | POST /agent/configs, PATCH /agent/configs/{id} | max_units_cap | max_units is outside 1..max_units_cap. |
energy_out_of_bounds | 422 | POST /agent/configs, PATCH /agent/configs/{id} | energy_min, energy_max | Energy per unit is below energy_min, or Energy × max units is above energy_max (null = no upper bound). |
base_not_mtrx | 422 | POST /agent/configs, PATCH /agent/configs/{id} | — | The base payment is not a positive multiple of 0.001 TRX. |
base_below_cost | 422 | POST /agent/configs, PATCH /agent/configs/{id} | cost_max_sun | The base payment is below TRONAgg's highest hourly price for one unit's Energy. |
below_min_balance | 422 | POST /agent/configs, POST /agent/configs/{id}/resume | min_balance_sun, balance_sun | Your balance is below the activation threshold. |
service_unavailable | 503 | GET /agent/summary, GET /agent/quote, POST /agent/configs, PATCH /agent/configs/{id} (when changing a numeric field) | — | Pricing is temporarily unavailable; retry later. |
Rate limits
Each account may make 1,000 requests per minute, shared by all of its API keys and its
signed-in workspace sessions. Past that the API answers 429 with a plain-string detail and a
Retry-After header holding the number of seconds until the window resets — wait that long and
retry. Order placement is idempotent (see below), so a retried POST /buy after a 429 is safe.
HTTP/1.1 429 Too Many Requests
Retry-After: 23
{ "detail": "Rate limit exceeded" }
Need more? Contact support with your expected request rate.
Idempotency
POST /buy is the only money-moving write, so it accepts an Idempotency-Key header to make retries safe:
- Send any unique string of ≤ 128 characters (a UUID is a good choice). A longer key returns
400 invalid_idempotency_key. - Once a purchase with a given key is placed, its
201response is cached for 24 hours and returned verbatim for any later request that reuses the key — so retrying after a timeout returns the original order instead of placing a second one. - A request that is rejected (for example
400 insufficient_balanceor400 invalid_address) places no order and does not keep the key; fix the cause and retry with the same key. - If a duplicate arrives while the first request is still being processed, it returns
409 duplicate_request. Wait a few seconds and retry with the same key; once the first request finishes you get its cached response. - If processing of the first request was interrupted on our side (rare), the key can stay "in progress" and keep returning
409 duplicate_requestfor up to 24 hours. Before you retry with a new key, checkGET /orders(filter byreceiver_address) to see whether the order was placed — a new key always places a new order. - Keys are scoped per account and are not tied to the request body. Reusing a key for a genuinely different purchase returns the first purchase's response, not a new order.
The header is optional but strongly recommended for any automated buyer.
Pagination
Three list endpoints exist, and — for historical reasons — each paginates differently. Read each on its own terms.
| Endpoint | Request params | Response fields | Style |
|---|---|---|---|
GET /orders | page (≥ 1), page_size (1–100) | orders, total, page, page_size | Page-numbered, with a full total count. |
GET /transactions | limit (1–100), cursor | transactions, has_more, next_cursor | Cursor-based. Pass the previous next_cursor back unchanged as cursor to get the next page; stop when has_more is false. A cursor that doesn't match one of your entries restarts from the newest entry; an unparsable one returns 400 invalid_cursor. |
GET /deposits | limit (1–100), offset (≥ 0) | deposits, has_more | Limit/offset. Advance offset by limit each page; stop when has_more is false. |
All three return newest-first. There is no total on /transactions or /deposits — use has_more to decide whether to fetch another page.
Filters on GET /orders
receiver_addressis an exact match on the order's receiver address. Pass the full 34-character address; a partial address matches nothing.statusis case-insensitive:completedandCOMPLETEDreturn the same orders.
Units & formats
- SUN. Amounts in
*_sunfields are integers denominated in SUN, the smallest TRX unit: 1 TRX = 1,000,000 SUN. Per-Energy-unit prices are the exception: they can be fractional, sounit_price_sunonGET /estimateand the price fields of the public pricing endpoints (price_sun,our_price_sun,market_avg_price_sun) are decimal strings (e.g."27.000000"). - Decimal amounts are strings. Human-facing TRX amounts (
total_price_trx,amount_trx,balance_trx, …) are serialized as JSON strings, not numbers, to preserve exact precision. Parse them with a decimal type, never a float. - Order statuses are upper-case. An order's
statusis one ofPENDING,PROCESSING,AWAITING_PAYMENT,COMPLETED, orFAILED. Orders placed through the API goPENDING→COMPLETEDorFAILED; the other two can appear on orders paid directly from a wallet.COMPLETEDandFAILEDare terminal. - TRON addresses. Addresses are base58 strings that start with
Tand are 34 characters long. - Timestamps. Time fields (
created_at,completed_at, …) are ISO-8601 in UTC.
Order amount limits
Every order has a minimum and maximum resource amount. Read the current bounds from GET /estimate, which returns min_amount and max_amount alongside the price (null means no bound on that side).
POST /buy enforces the same bounds. An out-of-range amount returns 400 with a structured detail that — unlike the other purchase errors — is keyed by code (not error) and carries min / max:
{
"detail": {
"code": "order_amount_below_minimum",
"message": "Order amount 50000 is below the minimum (64000). Increase the amount or contact support.",
"min": 64000,
"max": 1000000
}
}
The values above are illustrative; the live bounds come from GET /estimate. The two codes are order_amount_below_minimum and order_amount_above_maximum. Check GET /estimate first to avoid the round-trip.
Next steps
- Authentication — the
X-API-Keyheader and the401responses above. - Buy energy — the purchase endpoint that most of these errors come from.
- Webhook events — the push alternative to polling for order status.