Skip to main content

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

  1. Use UUIDs - Guaranteed unique
  2. Include context - Make keys meaningful for debugging
  3. 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 post
  • POST /v1/posts/batch - Create multiple posts
  • POST /v1/channels - Create channel
  • POST /v1/webhooks - Create webhook
  • POST /v1/api-keys - Create API key
  • POST /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

  1. 24-hour TTL: Keys expire after 24 hours
  2. Request body must match: Same key with different body returns error
  3. Per-endpoint: Keys are scoped to the endpoint
  4. 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.