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
- Review Error Handling Guide
- Learn about Rate Limits
- Explore API Reference Overview