> ## Documentation Index
> Fetch the complete documentation index at: https://docs.portalhq.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Idempotency Keys

> Prevent duplicate transactions by including an idempotency key with your requests.

When broadcasting transactions through Portal's Enclave MPC API, network failures, timeouts, or client-side retries can result in duplicate operations. Idempotency keys provide a mechanism to safely retry requests without the risk of processing the same operation twice.

### Supported endpoints

Idempotency keys are supported on the following Enclave MPC API endpoints:

| Method | Endpoint                                        | Applicable Conditions      |
| ------ | ----------------------------------------------- | -------------------------- |
| `POST` | `https://mpc-client.portalhq.io/v1/assets/send` | All requests.              |
| `POST` | `https://mpc-client.portalhq.io/v1/sign`        | See Broadcast RPC methods. |

<Note>
  Idempotency keys are optional. If the `Idempotency-Key` header is omitted, the
  request is processed normally without idempotency enforcement.
</Note>

### Broadcast RPC methods

Idempotency keys are only compatible with RPC methods that broadcast transactions.

| Chain(s)       | RPC method(s)                                                 |
| -------------- | ------------------------------------------------------------- |
| Ethereum / EVM | `eth_sendTransaction`                                         |
| Solana         | `sol_signAndSendTransaction`, `sol_signAndConfirmTransaction` |
| Tron           | `tron_sendTransaction`                                        |
| Stellar        | `stellar_sendTransaction`                                     |

### How it works

1. Include the `Idempotency-Key` header with a unique value in your request.
2. Portal creates a record with status `PENDING` and processes the request normally.
3. On a successful broadcast via an RPC provider, the record is updated to `BROADCASTED`. On failure, it is updated to `FAILED`.
4. Any subsequent request with the same idempotency key from the same client is rejected with a `409` — it will not be re-processed, regardless of the original request's outcome.

<Note>
  If a request stays in `PENDING` status for more than 10 minutes (for example,
  due to an unexpected process interruption), it is automatically marked as
  `UNKNOWN`.
</Note>

### Expected response handling flow

Follow the expected flow in order to safely retry, handle response codes, and to protect yourself against double spending.

1. Submit a transaction with a unique idempotency key.
2. Add retry logic to handle failures and uncertain outcomes:
   a. if the status code is `409` and the `id` is `IDEMPOTENT_REQUEST_IN_PROGRESS` then retry with backoff.
   b. if the status code is `409` and the `id` is `IDEMPOTENT_REQUEST_ALREADY_COMPLETED` then stop retrying. The transaction has been broadcast.
   c. if the status code is `409` and the `id` is `IDEMPOTENT_REQUEST_PREVIOUSLY_FAILED` then stop retrying. The transaction failed cleanly. Follow error handling logic to re-submit with a different idempotency key. It is safe to submit again, without risk of double spending.
   d. if the status code is `409` and the `id` is `IDEMPOTENT_REQUEST_UNEXPECTED_STATE` then stop retrying. The transaction has failed in an unknown state. Follow error handling logic. You may not want to automatically resend without verifying balances first. It is **not** safe to spend again. You are at risk of double spending.
   e. if the status code is `422` then stop retrying. You are submitting different transactions with the same idempotency key. Follow error handling logic.

### Key format

| Rule               | Detail                                                                   |
| ------------------ | ------------------------------------------------------------------------ |
| Max length         | 255 characters                                                           |
| Allowed characters | `A-Z`, `a-z`, `0-9`, `-`, `.`, `_`, `~` (RFC 3986 unreserved characters) |

<Note>
  V4 UUIDs are a good default choice for idempotency keys (e.g.
  `550e8400-e29b-41d4-a716-446655440000`).
</Note>

### Key expiration

Idempotency keys expire after **24 hours**. After expiration, the same key value can be reused for a new request.

### Response behavior

| Scenario                                        | Status | Error ID                               |
| ----------------------------------------------- | ------ | -------------------------------------- |
| First request with a new key                    | `200`  | —                                      |
| Same key, same body — original still processing | `409`  | `IDEMPOTENT_REQUEST_IN_PROGRESS`       |
| Same key, same body — original succeeded        | `409`  | `IDEMPOTENT_REQUEST_ALREADY_COMPLETED` |
| Same key, same body — original failed           | `409`  | `IDEMPOTENT_REQUEST_PREVIOUSLY_FAILED` |
| Same key, same body — original in unknown state | `409`  | `IDEMPOTENT_REQUEST_UNEXPECTED_STATE`  |
| Same key, **different** body                    | `422`  | `IDEMPOTENCY_KEY_REUSED`               |

<Warning>
  Once a request is made with an idempotency key, that key cannot be reused for
  any new request within the 24-hour expiration window — even if the original
  request failed. Generate a new key for each new operation.
</Warning>

### Error format

Idempotency errors follow the same structure as other MPC errors documented on the [Error Codes](/resources/error-codes) page:

```json theme={null}
{
  "id": "IDEMPOTENT_REQUEST_ALREADY_COMPLETED",
  "message": "Request already completed"
}
```

### Example

```bash theme={null}
curl --request POST \
  --url https://mpc-client.portalhq.io/v1/assets/send \
  --header 'Authorization: Bearer [token]' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000' \
  --data '{
  "share": "share",
  "chain": "ethereum",
  "to": "0xdFd8302f44727A6348F702fF7B594f127dE3A902",
  "token": "NATIVE",
  "amount": "0.0001"
}'
```

### Best practices

* **Generate a unique key per request**: Use a V4 UUID or another random identifier with enough entropy to avoid collisions.
* **Store the key before sending**: Persist the key to your database or logs before making the request, so you can correlate retries.
* **Reuse the same key only for identical retries**: If you need to retry the exact same operation with the same request body, reuse the key. If any parameters change, generate a new key.
* **Handle rejection responses gracefully**: When you receive a `409` or `422` response, check the `id` field to determine whether the original request is still processing, already succeeded, or failed.
* **Use distinct keys per logical operation**: Each independent transaction or signing operation should have its own unique idempotency key.
