> For the complete documentation index, see [llms.txt](https://tron-energy-doc.crypto-chief.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tron-energy-doc.crypto-chief.com/getting-started/idempotency.md).

# Idempotency and Retries

## Idempotency and Retries

Every order carries an idempotency key that **you** choose. It is required on `POST /v1/orders` and there is no default.

### Why we refuse rather than generate one

A key we invent is a key you cannot repeat. Your retry after a timeout would arrive looking like a fresh request, and you would be charged for the same energy twice. So a request without one is rejected with `IDEMPOTENCY_KEY_REQUIRED` — an error at integration time instead of a double charge in production.

Send it as a header, which is where most HTTP clients expect it:

```
Idempotency-Key: payout-8814
```

or in the body as `idempotency_key`. The header wins if both are present.

### Choosing a key

Use something that identifies the **work**, not the attempt. Your own payout id, withdrawal id or transfer id is ideal: if the same payout is processed twice, you want the second attempt to find the first order rather than open a new one.

| Good                                      | Why                                                     |
| ----------------------------------------- | ------------------------------------------------------- |
| `payout-8814`                             | Stable for that payout however many times it is retried |
| `withdrawal-2026-08-30-1174`              | Unique per unit of work, reproducible                   |
| A random UUID **stored with your record** | Fine, as long as you reuse it on retry                  |

| Bad                      | Why                                                  |
| ------------------------ | ---------------------------------------------------- |
| A fresh UUID per attempt | Defeats the entire mechanism                         |
| A timestamp              | Changes on retry                                     |
| The recipient address    | Two legitimate transfers to the same address collide |

### What a repeat returns

The same key returns the **original order**, whatever became of it, and charges nothing further. That is true whether the first attempt succeeded, was refused, or is still in flight.

{% tabs %}
{% tab title="JavaScript" %}

```javascript
const body = { receive_address: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t" };

const res = await fetch(BASE + "/v1/orders", {
  method: "POST",
  body: canonical(body),          // send exactly what you signed
  headers: {
    "Content-Type": "application/json",
    Merchant: MERCHANT,
    Signature: sign(body, API_KEY),
    "Idempotency-Key": "payout-8814",
  },
});
console.log(await res.json());
```

{% endtab %}

{% tab title="PHP" %}

```php
$body = ['receive_address' => 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t'];

$ch = curl_init(BASE . '/v1/orders');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => canonical($body),  // what you signed
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'Merchant: ' . MERCHANT,
        'Signature: ' . sign($body, API_KEY),
        'Idempotency-Key: payout-8814',
    ],
]);
print_r(json_decode(curl_exec($ch), true));
```

{% endtab %}

{% tab title="GO" %}

```go
body := map[string]any{"receive_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"}
raw, _ := json.Marshal(body)      // Marshal sorts map keys for you

req, _ := http.NewRequest("POST", base+"/v1/orders", bytes.NewReader(raw))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Merchant", merchant)
req.Header.Set("Signature", sign(raw, apiKey))
req.Header.Set("Idempotency-Key", "payout-8814")

res, err := http.DefaultClient.Do(req)
```

{% endtab %}

{% tab title="Python" %}

```python
body = {"receive_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"}

res = requests.post(
    BASE + "/v1/orders",
    data=canonical(body),          # send exactly what you signed
    headers={
        "Content-Type": "application/json",
        "Merchant": MERCHANT,
        "Signature": sign(body, API_KEY),
        "Idempotency-Key": "payout-8814",
    },
)
print(res.json())
```

{% endtab %}

{% tab title="curl" %}

```bash
BODY='{"receive_address":"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"}'

curl "$BASE/v1/orders" \
  -H "Content-Type: application/json" \
  -H "Merchant: $MERCHANT" \
  -H "Signature: $(sign "$BODY")" \
  -H "Idempotency-Key: payout-8814" \
  -d "$BODY"
```

{% endtab %}
{% endtabs %}

`sign()`, `canonical()`, `GET_SIG`, `BASE`, `MERCHANT` and `API_KEY` are from [Authentication](/getting-started/authentication.md).

{% hint style="info" %}
The body of a repeat is ignored. The key identifies the order, and the order keeps the terms it was created with — a second request with the same key and a different address will not redirect the energy.
{% endhint %}

### When your request times out

**Do not send the order again. Ask what happened to it.**

{% tabs %}
{% tab title="JavaScript" %}

```javascript
const res = await fetch(BASE + "/v1/orders/payout-8814", {
  headers: { Merchant: MERCHANT, Signature: GET_SIG },
});
console.log(await res.json());
```

{% endtab %}

{% tab title="PHP" %}

```php
$ch = curl_init(BASE . '/v1/orders/payout-8814');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Merchant: ' . MERCHANT,
        'Signature: ' . md5(API_KEY),
    ],
]);
print_r(json_decode(curl_exec($ch), true));
```

{% endtab %}

{% tab title="GO" %}

```go
req, _ := http.NewRequest("GET", base+"/v1/orders/payout-8814", nil)
req.Header.Set("Merchant", merchant)
req.Header.Set("Signature", getSig)   // md5(apiKey)

res, err := http.DefaultClient.Do(req)
```

{% endtab %}

{% tab title="Python" %}

```python
res = requests.get(
    BASE + "/v1/orders/payout-8814",
    headers={"Merchant": MERCHANT, "Signature": GET_SIG},
)
print(res.json())
```

{% endtab %}

{% tab title="curl" %}

```bash
curl "$BASE/v1/orders/payout-8814" \
  -H "Merchant: $MERCHANT" \
  -H "Signature: $GET_SIG"
```

{% endtab %}
{% endtabs %}

Repeating the `POST` with the same key is also safe — that is the point of the key — but `GET` is cheaper, clearer in your logs, and cannot be got wrong.

There are three answers:

| Response                              | What it means                    | What to do       |
| ------------------------------------- | -------------------------------- | ---------------- |
| `404 NOT_FOUND`                       | The request never reached us     | Send it          |
| An order with `settled: true`         | Finished, one way or the other   | Read `status`    |
| An order with `needs_attention: true` | **We do not know what happened** | Stop. See below. |

### The one state where retrying is wrong

`status: "unresolved"` with `needs_attention: true` means we called a supplier and did not learn the outcome — a timeout, a dropped connection, an answer we never saw. The energy **may have been delivered and may have been paid for**.

Such an order is held open and flagged for a person. It is never retried automatically, and you should not retry it either: doing so is precisely how one transfer gets bought twice.

```json
{
  "idempotency_key": "payout-8814",
  "status": "unresolved",
  "settled": false,
  "needs_attention": true,
  "error": "context deadline exceeded"
}
```

Requesting the same key again returns `409 NEEDS_ATTENTION` rather than a cheerful repeat, so an automated retry loop stops instead of spinning.

**What to do:** treat the transfer as unfunded, let it burn TRX if it cannot wait, and contact support with the key. We reconcile against the supplier and either complete it or refund it.

### The states that are safe to retry

`status: "refused"` means somebody answered and declined — nothing was bought, nothing is owed, and any charge is refunded automatically. Ordering again with a **new** key is fine.

The HTTP status tells the two apart without parsing the body:

| Status                           | Meaning                           |
| -------------------------------- | --------------------------------- |
| `200`                            | Delivered                         |
| `409 NEEDS_ATTENTION`            | Unresolved — do not retry         |
| `502 NO_PRICE` / order `refused` | Nothing bought; safe to try again |
