## Overview

The Figment API supports **idempotent requests** on select endpoints. Idempotency guarantees that submitting the same request more than once produces the same outcome as submitting it once - protecting clients against duplicate operations caused by network errors, timeouts, or retries.

This is particularly important for **provisioning operations** where inadvertently creating duplicate resources (e.g. extra validators) would be costly or difficult to reverse.

---

## How It Works

When you include the `X-Figment-Idempotency-Key` header on a supported request:

1. **First request** - The API processes your request normally and **caches the response** tied to your key.
2. **Retry with same key + same request body** - The API returns the **cached response immediately**, without re-executing the operation.
3. **Retry with same key + different body** - The API returns a **409 error**. You must generate a new key for a different operation.
4. **Retry while in-flight** - If the original request is still processing, the API returns a **409 error**. Wait briefly and retry.

Only successful responses (HTTP 2xx) are cached. If the operation fails (4xx, 5xx, or an exception), the key is released so you can safely retry with the **same key and request body**.

---

## Making an Idempotent Request

### Required Header

| Header | Type | Description |
| --- | --- | --- |
| `X-Figment-Idempotency-Key` | String (UUID recommended) | A unique key you generate per logical operation. Must be stable across retries of the **same** operation. |

### Generating a Key

Generate a UUID (v4) per logical operation. Store it client-side so you can reuse it on retries.

Bash

```shell
# Example: generate a key before sending
IDEMPOTENCY_KEY=$(uuidgen)
```

### Example Request

Bash

```shell
curl -X POST https://api.figment.io/external/ethereum/validators \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -H "X-Figment-Idempotency-Key: f47ac10b-58cc-4372-a567-0e02b2c3d479" \
  -d '{
    "withdrawal_address": "0xYourWithdrawalAddress",
    "validators_count": 2,
    "network": "mainnet"
  }'
```

Retrying this exact call (same key, same body) is safe — the API will return the original response without provisioning additional validators.

---

## Supported Endpoints

| Method | Path | Description |
| --- | --- | --- |
| `POST` | `/external/ethereum/validators` | Provision Ethereum validators (standard) |
| `POST` | `/external/ethereum/validators/0x02` | Provision Ethereum validators (Pectra / 0x02 withdrawal credential type) |

Idempotency is **opt-in** — if you omit the header, the request is processed normally with no idempotency guarantees.

---

## Response Scenarios

| Scenario | HTTP Status | Behaviour |
| --- | --- | --- |
| First request - succeeds | 201 Created | Request processed; response cached against the key. |
| Retry - same key, same body | 201 Created | Cached response returned. Operation **not** re-executed. |
| Retry - same key, **different** body | 409 Conflict | Fingerprint mismatch. Generate a new key for this operation. |
| Retry - first request still processing | 409 Conflict | In-flight. Wait a moment and retry with the same key + body. |
| First request - action returns 4xx/5xx | (original error status) | Key released. You may retry with the **same key and body**. |
| No header provided | (action's status) | Idempotency skipped entirely. Request processed normally. |

---

## Error Response Format

When idempotency enforcement triggers a conflict, the API responds with HTTP **409 Conflict**:

JSON

```json
{
  "error": {
    "message": "a request with this idempotency key is currently being processed",
    "details": [
      {
        "param": "idempotency_key",
        "message": "a request with the X-Figment-Idempotency-Key header value is currently being processed",
        "code": 1000,
        "context": {}
      }
    ]
  }
}
```

JSX

```jsx
{
  "error": {
    "message": "same idempotency key used with different request parameters",
    "details": [
      {
        "param": "idempotency_key",
        "message": "the X-Figment-Idempotency-Key header value is currently being used with different request parameters than a previous request",
        "code": 1000,
        "context": {}
      }
    ]
  }
}
```

Both **in-flight** and **fingerprint mismatch** errors use error code `1000`. Use the `message` field to distinguish between the two cases.

---

## Retry Guidance

| Error | Action |
| --- | --- |
| 409 — in-flight (`"currently being processed"`) | Wait (e.g. exponential backoff), then **retry with the same key and body**. |
| 409 — fingerprint mismatch (`"different request parameters"`) | **Do not retry with this key.** Generate a fresh UUID for the new operation. |
| 4xx / 5xx from the action itself | The key has been released. **Retry with the same key and body** after addressing the underlying error. |
| Network timeout / no response | **Retry with the same key and body.** If the server processed the request before the timeout, you'll receive the cached response. |

---

## Key Scoping & Lifetime

- Keys are **scoped to your organization** — the same string used by two different organizations is treated as independent.
- Keys have **no expiry** and are stored indefinitely. Once a 2xx response is cached, all future requests with the same key will return that cached response.
- Use a **new key** for each distinct logical operation.

---

## Appendix

### Request Fingerprinting

The API computes a **SHA-256 fingerprint** of the request body using canonical JSON (deterministic key ordering). This means:

- Key order in the JSON body **does not matter** — `{"a":1,"b":2}` and `{"b":2,"a":1}` are considered identical.
- Any change to a **value** (e.g. changing `validators_count` from `2` to `3`) produces a different fingerprint and will trigger a 409 conflict if the key was already used.

⚠️ Content-Type: application/json is required. Requests without a JSON body cannot be fingerprinted.

---

## Summary Flowchart

```

No

2xx

4xx/5xx or exception

Yes

Yes

No

Yes

No

POST request with header X-Figment-Idempotency-Key

Key seen before?

Insert sentinel record (in-flight)

Execute action

Cache response against key

Return 201 to client

Release key (sentinel deleted)

Return error to client (client may retry same key)

Still in-flight?

Return 409 (in-flight)

Body fingerprint matches?

Return cached response

Return 409 (fingerprint mismatch)
```

---
