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:
- First request - The API processes your request normally and caches the response tied to your key.
- Retry with same key + same request body - The API returns the cached response immediately, without re-executing the operation.
- Retry with same key + different body - The API returns a 409 error. You must generate a new key for a different operation.
- 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.
# Example: generate a key before sending
IDEMPOTENCY_KEY=$(uuidgen)Example Request
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:
{
"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": {}
}
]
}
}{
"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_countfrom2to3) 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
flowchart TD
A["POST request with header X-Figment-Idempotency-Key"] --> B{"Key seen before?"}
B -- No --> C["Insert sentinel record (in-flight)"] --> D["Execute action"]
D -- "2xx" --> E["Cache response against key"] --> F["Return 201 to client"]
D -- "4xx/5xx or exception" --> G["Release key (sentinel deleted)"] --> H["Return error to client (client may retry same key)"]
B -- Yes --> I{"Still in-flight?"}
I -- Yes --> J["Return 409 (in-flight)"]
I -- No --> K{"Body fingerprint matches?"}
K -- Yes --> L["Return cached response"]
K -- No --> M["Return 409 (fingerprint mismatch)"]
