Skip to Content
Getting StartedRate Limits & Errors

Rate Limits & Errors

The VelaFlows API enforces rate limits to ensure fair usage and platform stability. This page explains how rate limiting works, how to handle errors, and best practices for building resilient integrations.

Rate Limits

Default Limits

Rate limits are applied per API token, per endpoint:

Endpoint CategoryLimitWindow
Read endpoints (GET)300 requests15 minutes
Write endpoints (POST, PATCH, PUT)100 requests15 minutes
Delete endpoints (DELETE)50 requests15 minutes
Authentication endpoints20 requests15 minutes
Bulk operations10 requests15 minutes

Rate Limit Headers

Every API response includes rate limit headers so you can track your usage:

X-RateLimit-Limit: 300 X-RateLimit-Remaining: 297 X-RateLimit-Reset: 1711296000
HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the current window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the current window resets

429 Too Many Requests

When you exceed the rate limit, the API returns a 429 status code:

{ "success": false, "error": { "code": "TOO_MANY_REQUESTS", "message": "Rate limit exceeded. Try again in 45 seconds." } }

The response also includes a Retry-After header with the number of seconds to wait before retrying:

HTTP/1.1 429 Too Many Requests Retry-After: 45

Error Reference

Error Response Format

All errors follow a consistent structure:

{ "success": false, "error": { "code": "ERROR_CODE", "message": "Human-readable description", "details": { "errors": [ { "path": "email", "message": "Invalid email address" } ] } } }

HTTP Status Codes

StatusCodeDescription
400BAD_REQUESTThe request body is malformed or missing required fields
401UNAUTHORIZEDAuthentication failed — token is missing, invalid, or expired
403FORBIDDENThe token doesn’t have permission for this action
404NOT_FOUNDThe requested resource doesn’t exist
409CONFLICTThe action conflicts with the current state (e.g., duplicate resource)
422VALIDATION_ERRORThe request body failed validation — check details.errors
429TOO_MANY_REQUESTSRate limit exceeded — wait and retry
500INTERNAL_ERRORSomething went wrong on our end — contact support if persistent

Validation Errors (422)

When a request fails validation, the details.errors array tells you exactly what’s wrong:

{ "success": false, "error": { "code": "VALIDATION_ERROR", "message": "Validation failed", "details": { "errors": [ { "path": "email", "message": "Invalid email address" }, { "path": "firstName", "message": "First name is required" } ] } } }

Each entry in errors tells you which field (path) failed and why (message).

Best Practices

Implement Exponential Backoff

When you receive a 429 or 5xx error, don’t retry immediately. Use exponential backoff with jitter:

async function fetchWithRetry(url, options, maxRetries = 3) { for (let attempt = 0; attempt <= maxRetries; attempt++) { const response = await fetch(url, options) if (response.ok) { return response.json() } if (response.status === 429 || response.status >= 500) { if (attempt === maxRetries) { throw new Error(`Failed after ${maxRetries} retries`) } const retryAfter = response.headers.get('Retry-After') const baseDelay = retryAfter ? parseInt(retryAfter, 10) * 1000 : Math.pow(2, attempt) * 1000 // Add jitter to avoid thundering herd const jitter = Math.random() * 1000 await new Promise(resolve => setTimeout(resolve, baseDelay + jitter)) continue } // Non-retryable error const error = await response.json() throw new Error(error.error?.message || 'Request failed') } }

Cache Responses

For data that doesn’t change frequently, cache API responses to reduce the number of requests:

const cache = new Map() const CACHE_TTL = 5 * 60 * 1000 // 5 minutes async function getCachedConversation(id) { const cacheKey = `conversation:${id}` const cached = cache.get(cacheKey) if (cached && Date.now() - cached.timestamp < CACHE_TTL) { return cached.data } const data = await velaflows.getConversation(id) cache.set(cacheKey, { data, timestamp: Date.now() }) return data }

Use Webhooks Instead of Polling

Instead of polling the API every few seconds to check for new conversations or messages, subscribe to webhooks. Webhooks push events to your server in real-time, eliminating the need for repeated API calls.

Polling: 300 requests/15min to check for updates (wasteful) Webhooks: 0 requests — VelaFlows notifies you when something happens

Paginate Efficiently

When listing large datasets, use pagination parameters to fetch only what you need:

# Fetch page 1 with 20 items GET /api/v1/inbox/conversations?page=1&limit=20 # Fetch next page GET /api/v1/inbox/conversations?page=2&limit=20

Don’t set limit higher than you need. Fetching 100 items when you only display 10 wastes bandwidth and counts against your rate limit.

Batch Operations

Where available, use batch endpoints instead of making individual requests:

# Instead of 50 individual tag operations: POST /api/v1/tags/bulk { "entityType": "conversation", "entityIds": ["id1", "id2", ...], "tagIds": ["tag1", "tag2"] }