> For the complete documentation index, see [llms.txt](https://savefee.gitbook.io/savefee/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://savefee.gitbook.io/savefee/broadcast/track-and-troubleshoot.md).

# Track and troubleshoot

## Reading a broadcast

```bash
curl https://api.savefee.io/v1/broadcasts/65f1a2b3c4d5e6f708192a3b \
  -H "Authorization: Bearer $SAVEFEE_API_KEY"
```

The record comes back inside a `broadcast` wrapper — see **GET /v1/broadcasts/{broadcastId}** for the full schema. Broadcasts are owner-scoped, so a key can only read the broadcasts it submitted.

## Lifecycle

```
pending_energy  →  ready  →  broadcasting  →  confirming  →  confirmed
                                                          ↘  failed / expired
```

| `status`         | Finished | Meaning                                            | Balance         |
| ---------------- | -------- | -------------------------------------------------- | --------------- |
| `pending_energy` | no       | Accepted; the resource is being provisioned.       | already debited |
| `ready`          | no       | The resource is in place; queued to broadcast.     | already debited |
| `broadcasting`   | no       | Being pushed to the TRON network.                  | already debited |
| `confirming`     | no       | On the network, waiting for confirmation.          | already debited |
| `confirmed`      | **yes**  | On-chain and successful.                           | debit stands    |
| `failed`         | **yes**  | Not delivered.                                     | refunded        |
| `expired`        | **yes**  | Not confirmed before the transaction's expiration. | refunded        |

A typical broadcast confirms in about 6 seconds to 2 minutes.

{% hint style="warning" %}
**The fee is taken when the broadcast is accepted, not when it confirms.** A `202` debits the paying account immediately — a real debit, not a hold, so `GET /v1/balance` drops straight away. If the broadcast ends in `failed` or `expired`, the amount comes back as a separate refund entry.
{% endhint %}

{% hint style="warning" %}
**`confirmed` is the only success.** Watch for `failed` **and** `expired` — both end the broadcast and both refund the fee, and both arrive as the `broadcast.failed` webhook, with the payload's `status` telling you which one it was.

`confirming` already carries a `broadcastTxid`, but the transaction is not confirmed yet. Do not release goods or credit a customer before `confirmed`.
{% endhint %}

{% hint style="info" %}
For anything that moves customer money, confirm against the on-chain receipt for `txId` as well as the SaveFee record. The TRON network is the final authority on whether a transaction happened.

Treat any status you do not recognise as **not yet finished** and keep polling.
{% endhint %}

## After a `failed` or `expired` broadcast

The fee is refunded, and the `txId` becomes submittable again — resubmitting the transaction you already signed is the supported way to retry. `409 broadcast.duplicate_tx` is returned only while a broadcast is still in flight or already confirmed, never after one of these two.

Which retry you make depends on how it ended:

* `failed` — the same signed transaction can go straight back in, as long as its expiration has not passed.
* `expired` — the window is gone by definition, so build and sign a fresh transaction with a \~600 second expiration.

## Rejection reasons

Every entry below is returned **before** the transaction is broadcast, so none of them charge you.

### Expiration windows

SaveFee provisions the resource **and then** broadcasts, so a transaction has to arrive with enough time left on it:

| Time left when the request arrives | Result                            |
| ---------------------------------- | --------------------------------- |
| Less than 30 seconds               | `422 broadcast.tx_expired`        |
| 30 to 75 seconds                   | `422 broadcast.insufficient_time` |
| 75 seconds or more                 | Accepted                          |

TronWeb's default expiration is about 60 seconds, which lands in the middle row. Sign with roughly 600 seconds instead. See **Broadcast a transaction**.

### `422 broadcast.insufficient_time`

> insufficient time … to delegate and broadcast

The transaction arrived with between 30 and 75 seconds left. Extend the expiration to roughly 600 seconds and sign again.

### `422 broadcast.tx_expired`

Less than 30 seconds remained, or the expiration had already passed. Build and sign a new transaction.

### `422 broadcast.invalid_tx`

The transaction failed an integrity check. Read `detail` to see which one:

| `detail` contains                      | Cause                                                                                        |
| -------------------------------------- | -------------------------------------------------------------------------------------------- |
| `txID mismatch: declared=… derived=…`  | The `txID` does not match the transaction body. Do not edit a transaction after building it. |
| `Signature does not match fromAddress` | The signature does not recover to the declared sender.                                       |

### `400 broadcast.malformed` and `400 broadcast.no_signature`

The body could not be read as a signed transaction, or it carries no signature. Send the object exactly as your TRON library produced it, after signing.

### `422 broadcast.unsupported_contract`

This transaction type cannot be provisioned. Send a transaction with a single contract call.

### `422 broadcast.tx_would_revert`

SaveFee simulates the transaction before spending anything on it, and the simulation failed. Check that the sender holds what the transaction spends, and that its arguments are what you intended.

### `422 broadcast.insufficient_usdt`

The sender does not hold enough USDT for this transfer. Fund the sender and sign again.

### `422 broadcast.insufficient_bandwidth`

The sender cannot cover the bandwidth the transaction needs. A small amount of TRX at the sender resolves it.

### `422 wallet.not_registered`

The broadcast was sent without an API key, and the signing address has no SaveFee account. Send your API key so your account pays, or complete onboarding for that address — see **Onboarding**.

### `409 broadcast.duplicate_tx`

A broadcast for this `txId` is still being processed, or the transaction is already confirmed on-chain. Read the existing broadcast rather than signing a replacement. Your balance is untouched.

This does not apply after a terminal failure: once a broadcast has ended in `failed` or `expired`, the same `txId` is accepted again.

### `402 wallet.insufficient_balance`

The paying balance does not cover the fee. Top up and retry. The fee follows what the transaction needs — for a USDT transfer, a first-time recipient costs about twice a repeat one.

### `503 broadcast.upstream_unavailable`

A TRON node was unreachable. Nothing was charged — retry with backoff.
