> 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/api-reference/post-v1-broadcasts.md).

# POST /v1/broadcasts

SaveFee provisions the resource the transaction needs and broadcasts it to TRON.

## Request

```
POST /v1/broadcasts
Authorization: Bearer sf_live_…      (optional — see “Who pays”)
Content-Type: application/json
```

```json
{
  "signedTx": { "… signed transaction object …": null }
}
```

| Field      | Type   | Required | Rules                                                                                                                           |
| ---------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `signedTx` | object | yes      | The complete signed transaction as produced by your TRON library, including `txID`, `raw_data`, `raw_data_hex` and `signature`. |

Send any signed transaction with a single contract call, with an expiration roughly **600 seconds** in the future — the hard floor is 75 seconds. SaveFee reads it to decide what it needs: a contract call is provisioned with ENERGY, and a transaction whose cost is bandwidth is provisioned with BANDWIDTH. The `kind` on the broadcast record tells you how it was read — `usdt` for a USDT transfer, `tron` otherwise.

### Who pays

| `Authorization`    | Charged to                                                                                                                                   |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `Bearer sf_live_…` | **The key's account** — even when the transaction was signed by a different address (a sponsored broadcast). Send the key.                   |
| Not sent           | The SaveFee account belonging to the transaction's `fromAddress`. An address without an account of its own gets `422 wallet.not_registered`. |

{% hint style="warning" %}
Send the key on every broadcast unless you deliberately want the signer to pay. Use the `Bearer` scheme exactly — authentication is optional here, so a header in any other scheme, or an empty token after `Bearer`, is read as no key instead of returning `401`, and the request falls through to the signer's account rather than yours.
{% endhint %}

`Idempotency-Key` is not used on this endpoint; broadcasts are deduplicated by `txId`. Sending the header anyway is harmless.

## Response `202`

```json
{
  "broadcastId": "65f1a2b3c4d5e6f708192a3b",
  "status": "pending_energy"
}
```

{% hint style="info" %}
The `202` body carries `broadcastId` and `status` only. Store `broadcastId` — it is how you read the broadcast back. The on-chain `txId` is the one you signed locally, and it is also returned by `GET /v1/broadcasts/{broadcastId}` and by the `broadcast.accepted` webhook.
{% endhint %}

`202` means *accepted for processing*, not *on-chain*. Poll `GET /v1/broadcasts/{broadcastId}` or wait for the `broadcast.confirmed` webhook.

The fee is debited at this point — see **Behaviour worth knowing**.

## Errors

| Status | `code`                             | Cause                                                                       |
| ------ | ---------------------------------- | --------------------------------------------------------------------------- |
| `400`  | `validation.invalid_field`         | `signedTx` missing or not an object.                                        |
| `400`  | `broadcast.malformed`              | The signed transaction could not be read.                                   |
| `400`  | `broadcast.no_signature`           | The transaction carries no signature.                                       |
| `401`  | `api_key.invalid`                  | A key was sent but is invalid.                                              |
| `401`  | `api_key.revoked`                  | The key was revoked.                                                        |
| `402`  | `wallet.insufficient_balance`      | Balance below the fee.                                                      |
| `409`  | `broadcast.duplicate_tx`           | A broadcast for this `txId` is in flight, or already confirmed on-chain.    |
| `422`  | `broadcast.invalid_tx`             | `txID` does not match the body, or the signature does not match the sender. |
| `422`  | `broadcast.tx_would_revert`        | Simulation failed.                                                          |
| `422`  | `broadcast.insufficient_usdt`      | The sender holds too little USDT for this transfer.                         |
| `422`  | `broadcast.insufficient_bandwidth` | The sender cannot cover the bandwidth this transaction needs.               |
| `422`  | `broadcast.insufficient_time`      | 30 to 75 seconds left on the expiration. Extend to \~600 seconds.           |
| `422`  | `broadcast.tx_expired`             | Under 30 seconds left, or the expiration has already passed.                |
| `422`  | `broadcast.unsupported_contract`   | This transaction type cannot be provisioned.                                |
| `422`  | `wallet.not_registered`            | No key was sent and `fromAddress` has no SaveFee account.                   |
| `503`  | `broadcast.upstream_unavailable`   | A TRON node was unreachable. Retry with backoff.                            |

Every error above is raised **before** the transaction goes to the network, so none of them charge you.

## Behaviour worth knowing

**Deduplication is by `txId`, not by `Idempotency-Key`.** `409 broadcast.duplicate_tx` is returned while a broadcast for that `txId` is being processed, and after it has confirmed. Once a broadcast ends in `failed` or `expired`, the same `txId` is accepted again — that is the supported way to resubmit.

**The debit happens on acceptance.** A `202` takes the fee from the paying balance immediately, not when the transaction confirms. It is a real debit, not a hold; a broadcast that ends in `failed` or `expired` is refunded by a separate entry.

**The fee follows the transaction.** It is based on the resource SaveFee provisions from simulating what you sent, so read `totalCostSun` from the record rather than assuming a figure. For a USDT transfer, a recipient who has never held USDT costs roughly twice one who already has.
