Skip to main content

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 set detail to a bare string instead of an object. These are:

    • 401 on any API-key endpoint — "API key required", "Invalid API key", or "User not found".
    • 403 on any API-key endpoint when the account is blocked — "Account is blocked". (The other 403, ip_not_allowed, is an object.)
    • 429 on any API-key endpoint — "Rate limit exceeded", with a Retry-After header. See Rate limits.
    • 429 on POST /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.
    • 404 on GET /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 the 422 array described below.
    • 500 on unexpected errors — "Internal server error", on any endpoint that has no structured internal_error of its own.
    { "detail": "Order not found" }
  • Object keyed by code, not error. The order-amount-limit errors on POST /buy use code (plus min / max) — see Order amount limits.

  • Request-validation 422. A malformed request body or an out-of-range query parameter (for example resource_amount ≤ 0, or page_size above 100) is rejected by the framework with 422 and a detail array of field errors. The only 422s with an object detail are the business errors listed below (AutoEnergy invalid_address and 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 codeHTTPEndpoint(s)Extra fieldsWhen
ip_not_allowed403All API-key-authenticated endpointsmessageThe 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_address400POST /buymessagereceiver_address is not a valid TRON address. Checked before anything is charged.
unsupported_resource400GET /estimate, POST /buymessageA resource_type other than ENERGY or BANDWIDTH was sent, or (POST /buy) a Bandwidth order from an account that can buy Energy only.
invalid_duration400GET /estimatemessageA duration other than 1h was sent. (POST /buy rejects it as a 422 body-validation error.)
invalid_idempotency_key400POST /buymessageIdempotency-Key longer than 128 characters.
insufficient_balance400POST /buycurrent_balance, required_amountBalance below the order total (both in SUN). No message field on this endpoint.
validation_error400POST /webhooksmessageThe 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_required400POST /webhooksmessage, total_deposited_sun, required_total_deposited_sunWebhooks 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_cursor400GET /transactionsmessageThe cursor value could not be parsed.
reseller_suspended403POST /buy—Purchasing is suspended for your account. Contact support.
not_found404DELETE /webhooks/{id}, POST /webhooks/{id}/testmessageNo webhook with this id on your account.
duplicate_request409POST /buymessageA request with the same Idempotency-Key is still being processed. See Idempotency.
delivery_failed502POST /webhooks/{id}/testmessageThe test delivery did not succeed.
service_unavailable503GET /estimate, POST /buymessageEnergy purchasing is temporarily unavailable.
reseller_activation_balance_low503POST /buy—Purchasing is temporarily unavailable for your account. Retry later.
internal_error500POST /buymessageUnexpected 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 codeHTTPWhen
insufficient_balance400Subscribe 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_suspended403AutoEnergy is suspended for your account. Contact support.
not_found404No such subscription for this account.
address_busy409The 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_exists409You have a closed subscription for this address. Reactivate or delete it first.
not_reactivatable409Only a closed subscription can be reactivated.
not_deletable409Only a closed subscription can be deleted. Close it first.
invalid_address422The address is not a valid TRON address.
fulfiller_unavailable502Energy delegation could not be started or resumed; retry.
autoenergy_temporarily_unavailable503AutoEnergy is temporarily unavailable for your account; retry later.
reseller_activation_balance_low503AutoEnergy is temporarily unavailable for your account; retry later.
internal_error500Unexpected 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 codeHTTPEndpoint(s)Extra fieldsWhen
program_disabled403POST /agent/configs, POST /agent/configs/{id}/resume—The program is not accepting new or resumed configurations right now.
not_found404PATCH / 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_taken409POST /agent/configs—The address already belongs to an active configuration.
admin_paused409POST /agent/configs/{id}/resume, DELETE /agent/configs/{id}—Paused by TRONAgg support. Contact support.
generated_not_deletable409DELETE /agent/configs/{id}—A dedicated TRONAgg address can't be deleted; pause it instead.
address_invalid422POST /agent/configs—Not a valid TRON address, or an address was sent with mode: "generated".
address_reserved422POST /agent/configs—The address can't be used for an own-wallet configuration.
config_limit422POST /agent/configsmax_configsYou already have the maximum number of configurations.
generated_limit422POST /agent/configsmax_generatedYou already have the maximum number of dedicated TRONAgg addresses.
units_out_of_bounds422POST /agent/configs, PATCH /agent/configs/{id}max_units_capmax_units is outside 1..max_units_cap.
energy_out_of_bounds422POST /agent/configs, PATCH /agent/configs/{id}energy_min, energy_maxEnergy per unit is below energy_min, or Energy × max units is above energy_max (null = no upper bound).
base_not_mtrx422POST /agent/configs, PATCH /agent/configs/{id}—The base payment is not a positive multiple of 0.001 TRX.
base_below_cost422POST /agent/configs, PATCH /agent/configs/{id}cost_max_sunThe base payment is below TRONAgg's highest hourly price for one unit's Energy.
below_min_balance422POST /agent/configs, POST /agent/configs/{id}/resumemin_balance_sun, balance_sunYour balance is below the activation threshold.
service_unavailable503GET /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 201 response 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_balance or 400 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_request for up to 24 hours. Before you retry with a new key, check GET /orders (filter by receiver_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.

EndpointRequest paramsResponse fieldsStyle
GET /orderspage (≥ 1), page_size (1–100)orders, total, page, page_sizePage-numbered, with a full total count.
GET /transactionslimit (1–100), cursortransactions, has_more, next_cursorCursor-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 /depositslimit (1–100), offset (≥ 0)deposits, has_moreLimit/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_address is an exact match on the order's receiver address. Pass the full 34-character address; a partial address matches nothing.
  • status is case-insensitive: completed and COMPLETED return the same orders.

Units & formats​

  • SUN. Amounts in *_sun fields 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, so unit_price_sun on GET /estimate and 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 status is one of PENDING, PROCESSING, AWAITING_PAYMENT, COMPLETED, or FAILED. Orders placed through the API go PENDING → COMPLETED or FAILED; the other two can appear on orders paid directly from a wallet. COMPLETED and FAILED are terminal.
  • TRON addresses. Addresses are base58 strings that start with T and 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-Key header and the 401 responses above.
  • Buy energy — the purchase endpoint that most of these errors come from.
  • Webhook events — the push alternative to polling for order status.