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

# Idempotency

> How to safely retry requests with the INDX Customer API

The INDX Customer API supports **idempotency** for POST requests to prevent duplicate operations when retrying failed requests. This allows you to safely retransmit the same request with confidence that the server will execute the operation only once.

<Note>
  Idempotency is supported for transfers and wire withdrawals (any POST operations via the INDX API).
</Note>

## How Idempotency Works

Idempotency is implemented using the optional **X-IDEMPOTENCY-KEY** header:

1. Include a unique UUID in the `X-IDEMPOTENCY-KEY` header when making a POST request
2. If the request fails or times out, retry with the same idempotency key
3. The server will execute the operation only once, even if you send multiple requests
4. Duplicate requests within 24 hours return a 409 conflict response

This ensures:

* Safe retry logic for network failures or timeouts
* Prevention of duplicate transfers or withdrawals
* Idempotent behavior for critical financial operations

## Using the Idempotency Key

The `X-IDEMPOTENCY-KEY` header is **optional**. When provided, it must be a valid UUID (RFC 9562 format).

### Generating an Idempotency Key

```python theme={null}
import uuid

# Generate a unique idempotency key for this request
idempotency_key = str(uuid.uuid4())
# Example: "69de51e7-c587-44ce-a4e2-2f6ec330bfdf"
```

### Making an Idempotent Request

```python theme={null}
import requests
import uuid

url = "https://api.indx.com/customer/transfer"

headers = {
    "X-API-KEY": api_key,
    "X-API-SIGNATURE": signature_b64,
    "X-API-TIMESTAMP": timestamp,
    "X-API-NONCE": nonce,
    "X-IDEMPOTENCY-KEY": str(uuid.uuid4()),  # Add idempotency key
    "Content-Type": "application/json"
}

data = {
    "from_account": "acc_123456",
    "to_account": "acc_789012",
    "amount": 100.00,
    "currency": "USD"
}

response = requests.post(url, headers=headers, json=data)
```

## Retry Behavior

### Successful First Request

When a request with a new idempotency key succeeds:

```json theme={null}
// HTTP 201 Created
{
  "status": "success",
  "data": {
    "transfer_id": "txn_abc123",
    "amount": 100.00,
    "status": "completed"
  }
}
```

### Duplicate Request Detection

If you retry the same request with the same idempotency key within 24 hours:

```json theme={null}
// HTTP 409 Conflict
{
  "message": "request conflict"
}
```

This indicates the original operation already succeeded, and no duplicate was created.

## Complete Retry Example

```python theme={null}
import requests
import uuid
import time

def make_transfer_with_retry(from_account, to_account, amount, max_retries=3):
    """
    Make a transfer with automatic retry logic using idempotency.
    """
    # Generate idempotency key once for all retry attempts
    idempotency_key = str(uuid.uuid4())

    for attempt in range(max_retries):
        try:
            url = "https://api.indx.com/customer/transfer"

            # Generate fresh authentication headers for each attempt
            timestamp = str(int(time.time() * 1000))
            nonce = str(uuid.uuid4())
            # ... generate signature ...

            headers = {
                "X-API-KEY": API_KEY,
                "X-API-SIGNATURE": signature_b64,
                "X-API-TIMESTAMP": timestamp,
                "X-API-NONCE": nonce,
                "X-IDEMPOTENCY-KEY": idempotency_key,  # Same key for all retries
                "Content-Type": "application/json"
            }

            data = {
                "from_account": from_account,
                "to_account": to_account,
                "amount": amount,
                "currency": "USD"
            }

            response = requests.post(url, headers=headers, json=data, timeout=30)

            if response.status_code == 201:
                # Success - operation completed
                return response.json()
            elif response.status_code == 409:
                # Conflict - operation already completed in previous attempt
                print("Transfer already processed")
                return {"status": "duplicate", "message": "request conflict"}
            elif response.status_code == 400:
                # Bad request - don't retry
                raise ValueError(f"Invalid request: {response.json()}")
            else:
                # Other error - retry
                print(f"Attempt {attempt + 1} failed: {response.status_code}")
                if attempt < max_retries - 1:
                    time.sleep(2 ** attempt)  # Exponential backoff
                    continue
                else:
                    raise Exception(f"Transfer failed after {max_retries} attempts")

        except requests.exceptions.Timeout:
            print(f"Attempt {attempt + 1} timed out")
            if attempt < max_retries - 1:
                time.sleep(2 ** attempt)
                continue
            else:
                raise
        except requests.exceptions.RequestException as e:
            print(f"Network error on attempt {attempt + 1}: {e}")
            if attempt < max_retries - 1:
                time.sleep(2 ** attempt)
                continue
            else:
                raise

# Usage
transfer_result = make_transfer_with_retry("acc_123456", "acc_789012", 100.00)
print(transfer_result)
```

## Error Responses

### Invalid UUID Format

If the `X-IDEMPOTENCY-KEY` header contains an invalid UUID:

```json theme={null}
// HTTP 400 Bad Request
{
  "message": "invalid UUID passed as x-idempotency-key"
}
```

Valid UUID formats include:

* `69de51e7-c587-44ce-a4e2-2f6ec330bfdf` (UUID v4 with hyphens)
* `69de51e7c58744cea4e22f6ec330bfdf` (UUID without hyphens)

## Best Practices

### Generate Idempotency Keys Client-Side

Always generate UUIDs on the client before making requests:

```python theme={null}
import uuid

# Generate once per logical operation
idempotency_key = str(uuid.uuid4())

# Use the same key for all retry attempts of this operation
```

### Use Idempotency for All POST Requests

Include idempotency keys for all transfers and wire withdrawals to ensure safe retry behavior:

```python theme={null}
# Always include for financial operations
headers["X-IDEMPOTENCY-KEY"] = str(uuid.uuid4())
```

### Store Keys for Audit Trail

Log idempotency keys with your transactions for debugging and reconciliation:

```python theme={null}
logger.info(f"Initiating transfer with idempotency key: {idempotency_key}")
```

### Handle 409 Responses Appropriately

A 409 response means the operation already succeeded:

```python theme={null}
if response.status_code == 409:
    # Don't treat this as an error - the operation completed
    print("Transfer already processed successfully")
```

### Idempotency Key Expiration

Idempotency keys are valid for **24 hours**. After 24 hours:

* The same key can be reused (though this is not recommended)
* A new request with the old key will be processed as a new operation

## Without Idempotency Keys

Requests without the `X-IDEMPOTENCY-KEY` header will process normally:

* No duplicate detection occurs
* Each request creates a new operation
* Retrying may result in duplicate transfers

<Warning>
  Without idempotency keys, network failures or timeouts may result in duplicate operations if you retry requests. Always use idempotency keys for transfers and withdrawals.
</Warning>
