Skip to main content

Best Practices

Follow these best practices to build reliable integrations with the Flow API.

Authentication

Store API Keys Securely

// ❌ Never hardcode API keys
const flow = new Flow('flow_sk_live_abc123...');

// ✅ Use environment variables
const flow = new Flow(process.env.FLOW_API_KEY!);

Use Different Keys for Different Environments

const apiKey = process.env.NODE_ENV === 'production'
? process.env.FLOW_API_KEY_PROD
: process.env.FLOW_API_KEY_DEV;

const flow = new Flow(apiKey);

Rotate Keys Regularly

  • Delete unused keys
  • Create new keys periodically
  • Monitor key usage

Error Handling

Always Handle Errors

// ❌ Silent failures
await flow.posts.create({ channelId: 'channel_123', content: 'Hello' });

// ✅ Proper error handling
try {
await flow.posts.create({ channelId: 'channel_123', content: 'Hello' });
} catch (error) {
if (error instanceof FlowError) {
// Handle specific error types
handleError(error);
}
}

Implement Retry Logic

async function createPostWithRetry(post: CreatePostRequest) {
const maxRetries = 3;
for (let i = 0; i < maxRetries; i++) {
try {
return await flow.posts.create(post);
} catch (error) {
if (error instanceof FlowError && shouldRetry(error)) {
await delay(Math.pow(2, i) * 1000);
continue;
}
throw error;
}
}
}

Rate Limits

Monitor Rate Limit Usage

const response = await flow.posts.list();
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || '0');

if (remaining < 10) {
console.warn('Rate limit low');
// Implement throttling
}

Use Batch Operations

// ❌ 100 requests (rate limit risk)
for (const post of posts) {
await flow.posts.create(post);
}

// ✅ 1 request (rate limit friendly)
await flow.posts.createBatch({ posts });

Idempotency

Always Use Idempotency Keys

// ✅ Safe to retry
const idempotencyKey = `post-${channelId}-${Date.now()}`;
await flow.posts.create(
{ channelId, content: 'Hello' },
{ idempotencyKey }
);

Store Idempotency Keys

const operation = {
idempotencyKey: randomUUID(),
type: 'create-post',
timestamp: Date.now(),
};

await db.saveOperation(operation);
await flow.posts.create(post, {
idempotencyKey: operation.idempotencyKey
});

Performance

Cache Frequently Accessed Data

let channelsCache: Channel[] | null = null;
let cacheExpiry = 0;

async function getChannels(): Promise<Channel[]> {
const now = Date.now();
if (channelsCache && now < cacheExpiry) {
return channelsCache;
}

channelsCache = await flow.channels.list();
cacheExpiry = now + (5 * 60 * 1000); // 5 minutes
return channelsCache;
}

Use Batch Operations

Batch operations are faster and more efficient:

// ✅ Batch create
await flow.posts.createBatch({ posts });

// ✅ Batch update
await flow.posts.updateBatch({ posts });

// ✅ Batch delete
await flow.posts.deleteBatch({ postIds });

Webhooks

Always Verify Signatures

app.post('/webhooks/flow', (req, res) => {
const signature = req.headers['flow-signature'];
const secret = process.env.FLOW_WEBHOOK_SECRET;

if (!verifyWebhookSignature(req.body, signature, secret)) {
return res.status(401).send('Invalid signature');
}

// Process webhook
});

Respond Quickly

Your webhook endpoint should respond within 5 seconds:

app.post('/webhooks/flow', async (req, res) => {
// Acknowledge immediately
res.status(200).send('OK');

// Process asynchronously
processWebhookAsync(req.body);
});

Handle Idempotency

Webhooks may be delivered multiple times:

const processedEvents = new Set<string>();

app.post('/webhooks/flow', (req, res) => {
const eventId = `${req.body.event}-${req.body.timestamp}`;

if (processedEvents.has(eventId)) {
return res.status(200).send('OK'); // Already processed
}

processedEvents.add(eventId);
// Process event
});

Security

Never Commit API Keys

# ❌ Never commit .env files
git add .env

# ✅ Add to .gitignore
echo ".env" >> .gitignore

Use Environment Variables

// ✅ Use environment variables
const apiKey = process.env.FLOW_API_KEY;
const webhookSecret = process.env.FLOW_WEBHOOK_SECRET;

Validate Input

function validatePost(post: CreatePostRequest): void {
if (!post.channelId) {
throw new Error('Channel ID is required');
}
if (!post.content && !post.mediaKeys?.length) {
throw new Error('Content or media is required');
}
if (post.content && post.content.length > 10000) {
throw new Error('Content too long');
}
}

Monitoring

Log All Operations

async function createPostWithLogging(post: CreatePostRequest) {
const startTime = Date.now();
try {
const result = await flow.posts.create(post);
logger.info({
operation: 'create-post',
postId: result.id,
duration: Date.now() - startTime,
});
return result;
} catch (error) {
logger.error({
operation: 'create-post',
error: error.message,
duration: Date.now() - startTime,
});
throw error;
}
}

Monitor Error Rates

const errorCounts = new Map<string, number>();

function trackError(error: FlowError) {
const key = `${error.status}-${error.code}`;
errorCounts.set(key, (errorCounts.get(key) || 0) + 1);

// Alert on high error rates
if (errorCounts.get(key)! > 10) {
alertTeam(`High error rate: ${key}`);
}
}

Testing

Use Test API Keys

const apiKey = process.env.NODE_ENV === 'test'
? process.env.FLOW_API_KEY_TEST
: process.env.FLOW_API_KEY;

const flow = new Flow(apiKey);

Mock API Calls in Tests

// Mock Flow API in tests
jest.mock('@flowdev/sdk', () => ({
Flow: jest.fn().mockImplementation(() => ({
posts: {
create: jest.fn().mockResolvedValue({ id: 'post_123' }),
},
})),
}));

Next Steps