Idempotency
Idempotency keys allow you to safely retry requests without creating duplicate resources.
What is Idempotency?
An idempotent request can be safely retried multiple times with the same result. If you send the same request twice, the second request returns the same result as the first, without creating a duplicate.
Why Use Idempotency?
- Network failures: Retry safely after network errors
- Timeout handling: Retry when requests timeout
- Duplicate prevention: Avoid creating duplicate resources
- Reliability: Ensure operations complete even with transient failures
How It Works
Include an Idempotency-Key header with a unique value:
POST /v1/posts
Idempotency-Key: unique-key-123
Content-Type: application/json
Authorization: Bearer flow_sk_live_...
{
"channelId": "channel_123",
"content": "Hello from Flow!"
}
If you send the same request with the same idempotency key within 24 hours, you'll receive the original response without creating a duplicate resource.
Generating Idempotency Keys
Best Practices
- Use UUIDs - Guaranteed unique
- Include context - Make keys meaningful for debugging
- Store keys - Save keys to verify operations
Examples
import { randomUUID } from 'crypto';
// Simple UUID
const idempotencyKey = randomUUID();
// UUID with context
const idempotencyKey = `${operation}-${resourceId}-${randomUUID()}`;
// Example: "create-post-channel_123-550e8400-e29b-41d4-a716-446655440000"
// Timestamp-based (if you need ordering)
const idempotencyKey = `post-${Date.now()}-${randomUUID()}`;
Supported Endpoints
Idempotency is supported for all POST endpoints:
POST /v1/posts- Create postPOST /v1/posts/batch- Create multiple postsPOST /v1/channels- Create channelPOST /v1/webhooks- Create webhookPOST /v1/api-keys- Create API keyPOST /v1/media/upload- Upload media
Example: Creating a Post
Without Idempotency
// ❌ Risk of duplicates if network fails
try {
const post = await flow.posts.create({
channelId: 'channel_123',
content: 'Hello!',
});
} catch (error) {
// If network fails, retry might create duplicate
const post = await flow.posts.create({
channelId: 'channel_123',
content: 'Hello!',
});
}
With Idempotency
// ✅ Safe to retry
const idempotencyKey = randomUUID();
try {
const post = await flow.posts.create(
{
channelId: 'channel_123',
content: 'Hello!',
},
{
headers: {
'Idempotency-Key': idempotencyKey,
},
}
);
} catch (error) {
// Safe to retry with same key
const post = await flow.posts.create(
{
channelId: 'channel_123',
content: 'Hello!',
},
{
headers: {
'Idempotency-Key': idempotencyKey,
},
}
);
}
Idempotency Key Requirements
- Length: 1-255 characters
- Format: Any valid string (recommend UUIDs)
- Uniqueness: Must be unique per operation
- TTL: Keys are valid for 24 hours
Response Behavior
First Request
POST /v1/posts
Idempotency-Key: unique-key-123
Response: 201 Created with the created resource
Duplicate Request (Same Key)
POST /v1/posts
Idempotency-Key: unique-key-123
Response: 200 OK with the original resource (no duplicate created)
SDK Support
TypeScript SDK
import { Flow } from '@flowdev/sdk';
import { randomUUID } from 'crypto';
const flow = new Flow(apiKey);
// SDK automatically generates idempotency keys
const post = await flow.posts.create({
channelId: 'channel_123',
content: 'Hello!',
}, {
idempotencyKey: randomUUID(), // Optional
});
Python SDK
from flow_sdk import Flow
import uuid
flow = Flow(api_key=api_key)
# SDK automatically generates idempotency keys
post = flow.posts.create(
channel_id='channel_123',
content='Hello!',
idempotency_key=str(uuid.uuid4()) # Optional
)
Go SDK
import (
"github.com/flowdev/go-sdk"
"github.com/google/uuid"
)
client := flow.NewClient(apiKey)
// SDK automatically generates idempotency keys
post, err := client.Posts.Create(&flow.CreatePostRequest{
ChannelID: "channel_123",
Content: "Hello!",
}, flow.WithIdempotencyKey(uuid.New().String()))
Best Practices
1. Always Use Idempotency for Critical Operations
// Critical: Creating a post
const idempotencyKey = `post-${channelId}-${Date.now()}`;
await flow.posts.create(post, { idempotencyKey });
2. Store Idempotency Keys
// Store key with operation for debugging
const operation = {
idempotencyKey: randomUUID(),
type: 'create-post',
channelId: 'channel_123',
timestamp: Date.now(),
};
await db.saveOperation(operation);
await flow.posts.create(post, {
idempotencyKey: operation.idempotencyKey
});
3. Use Meaningful Keys
// Good: Includes context
const key = `create-post-${channelId}-${contentHash}-${timestamp}`;
// Better: UUID with metadata
const key = `${operation}-${resourceId}-${randomUUID()}`;
4. Handle Idempotency Errors
try {
const post = await flow.posts.create(post, { idempotencyKey });
} catch (error) {
if (error.code === 'IDEMPOTENCY_KEY_REUSED') {
// Key was reused with different request body
// Generate new key and retry
const newKey = randomUUID();
const post = await flow.posts.create(post, {
idempotencyKey: newKey
});
}
}
Limitations
- 24-hour TTL: Keys expire after 24 hours
- Request body must match: Same key with different body returns error
- Per-endpoint: Keys are scoped to the endpoint
- Per-API-key: Keys are scoped to your API key
Troubleshooting
"Idempotency key already used with different request"
This means you're using the same key with a different request body. Generate a new key:
// ❌ Same key, different body
await flow.posts.create({ content: 'Hello' }, { idempotencyKey: 'key-1' });
await flow.posts.create({ content: 'World' }, { idempotencyKey: 'key-1' }); // Error!
// ✅ Different keys for different requests
await flow.posts.create({ content: 'Hello' }, { idempotencyKey: 'key-1' });
await flow.posts.create({ content: 'World' }, { idempotencyKey: 'key-2' });
"Idempotency key expired"
Keys expire after 24 hours. Generate a new key for retries after expiration.