Skip to main content

Overview

The SO1 API implements rate limiting to ensure fair usage and system stability across all users. Rate limits are enforced per API key and vary by subscription tier.
Rate Limit Headers: All API responses include rate limit information in response headers to help you track your usage.

Rate Limit Tiers

SO1 API rate limits are organized by subscription tier. Higher tiers receive increased limits and priority processing.
TierRequests/MinuteRequests/HourRequests/DayBurst Limit
Free103005,00015
Starter602,00040,000100
Professional30012,000200,000500
Enterprise1,00050,0001,000,0002,000
CustomCustomCustomCustomCustom
Burst Limits: Allow short bursts of traffic above the per-minute rate. Useful for batch operations or webhook processing.

Rate Limit Headers

Every API response includes headers indicating your current rate limit status:
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 287
X-RateLimit-Reset: 1678901234
X-RateLimit-Tier: professional
X-RateLimit-Burst-Remaining: 450

Header Reference

HeaderDescriptionExample
X-RateLimit-LimitMaximum requests allowed per minute300
X-RateLimit-RemainingRequests remaining in current window287
X-RateLimit-ResetUnix timestamp when limit resets1678901234
X-RateLimit-TierCurrent subscription tierprofessional
X-RateLimit-Burst-RemainingBurst capacity remaining450

Rate Limit Exceeded Response

When you exceed your rate limit, the API returns a 429 Too Many Requests response:
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Please retry after 42 seconds.",
    "details": {
      "limit": 300,
      "window": "1m",
      "retryAfter": 42,
      "tier": "professional"
    }
  }
}
Retry-After Header: Always respect the Retry-After header value (in seconds) before making subsequent requests.

Endpoint-Specific Limits

Certain resource-intensive endpoints have additional rate limits:

Control Plane API

Endpoint CategoryAdditional LimitReason
Agent Execution50 concurrent executionsPrevents resource exhaustion
Workflow Triggers100 triggers/minuteProtects workflow infrastructure
Bulk Operations1,000 items/requestLimits payload size
Metrics Queries10 queries/minuteDatabase protection

Veritas API

Endpoint CategoryAdditional LimitReason
Prompt Testing20 tests/minuteLLM quota management
Chain Execution30 chains/minuteComplex operation throttling
Batch Refinement50 prompts/batchProcessing limits

n8n API

Endpoint CategoryAdditional LimitReason
Workflow Execution100 executions/minuten8n instance protection
Webhook Registration10 webhooks/minuteExternal service rate limits
Bulk Workflow Import5 imports/hourValidation overhead

Rate Limiting Best Practices

Always check rate limit headers in your API client and implement proactive throttling:
async function makeAPIRequest(endpoint: string, options: RequestInit) {
  const response = await fetch(endpoint, options);
  
  const remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || '0');
  const limit = parseInt(response.headers.get('X-RateLimit-Limit') || '0');
  
  // Slow down when approaching limit
  if (remaining < limit * 0.1) {
    console.warn(`Approaching rate limit: ${remaining}/${limit} remaining`);
    await sleep(1000); // Add 1s delay
  }
  
  return response;
}
When receiving 429 responses, implement exponential backoff with jitter:
async function retryWithBackoff<T>(
  fn: () => Promise<T>,
  maxRetries: number = 5
): Promise<T> {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (error.status === 429 && attempt < maxRetries - 1) {
        const retryAfter = parseInt(error.headers.get('Retry-After') || '1');
        const jitter = Math.random() * 1000; // 0-1000ms jitter
        const delay = (retryAfter * 1000) + jitter;
        
        console.log(`Rate limited. Retrying after ${delay}ms...`);
        await sleep(delay);
      } else {
        throw error;
      }
    }
  }
  throw new Error('Max retries exceeded');
}
Implement a request queue to stay within rate limits:
class RateLimitedQueue {
  private queue: Array<() => Promise<any>> = [];
  private processing = false;
  private requestsPerMinute: number;
  private lastRequest: number = 0;
  
  constructor(requestsPerMinute: number) {
    this.requestsPerMinute = requestsPerMinute;
  }
  
  async enqueue<T>(fn: () => Promise<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      this.queue.push(async () => {
        try {
          const result = await fn();
          resolve(result);
        } catch (error) {
          reject(error);
        }
      });
      
      if (!this.processing) {
        this.process();
      }
    });
  }
  
  private async process() {
    this.processing = true;
    
    while (this.queue.length > 0) {
      const now = Date.now();
      const timeSinceLastRequest = now - this.lastRequest;
      const minInterval = 60000 / this.requestsPerMinute;
      
      if (timeSinceLastRequest < minInterval) {
        await sleep(minInterval - timeSinceLastRequest);
      }
      
      const task = this.queue.shift();
      if (task) {
        this.lastRequest = Date.now();
        await task();
      }
    }
    
    this.processing = false;
  }
}

// Usage
const queue = new RateLimitedQueue(300); // 300 req/min for Professional tier

await queue.enqueue(() => 
  fetch('https://api.so1.io/v1/agents/execute', { ... })
);
Use batch endpoints to reduce API calls:
// ❌ Bad: Multiple individual requests
for (const agentId of agentIds) {
  await fetch(`https://api.so1.io/v1/agents/${agentId}`);
}

// ✅ Good: Single batch request
const response = await fetch('https://api.so1.io/v1/agents/batch', {
  method: 'POST',
  body: JSON.stringify({ agentIds }),
  headers: { 'Content-Type': 'application/json' }
});
Cache API responses to reduce redundant requests:
const cache = new Map<string, { data: any; expiry: number }>();

async function cachedRequest(url: string, ttl: number = 60000) {
  const cached = cache.get(url);
  
  if (cached && Date.now() < cached.expiry) {
    return cached.data;
  }
  
  const response = await fetch(url);
  const data = await response.json();
  
  cache.set(url, {
    data,
    expiry: Date.now() + ttl
  });
  
  return data;
}

Increasing Rate Limits

Upgrade Your Tier

The most straightforward way to increase rate limits is to upgrade your subscription tier:

Request Custom Limits

For specialized use cases requiring higher limits:
  1. Contact Sales: Email enterprise@so1.io with your use case
  2. Provide Usage Estimates: Include expected traffic patterns
  3. Describe Architecture: Explain how you’ll handle bursts
  4. Review SLA Requirements: Discuss uptime and latency needs
Enterprise Custom Tiers: Available for organizations with >1M requests/day or specialized requirements.

Rate Limit Monitoring

Using the Dashboard

Monitor your rate limit usage in the SO1 Dashboard:
  1. Navigate to Settings → API Keys
  2. View real-time rate limit metrics per key
  3. Set up alerts for approaching limits
  4. Review historical usage patterns

Using the API

Query your current rate limit status programmatically:
curl -X GET https://api.so1.io/v1/rate-limits/status \
  -H "Authorization: Bearer YOUR_API_KEY"
Response:
{
  "tier": "professional",
  "limits": {
    "perMinute": { "limit": 300, "remaining": 287, "resetAt": "2024-03-10T15:45:00Z" },
    "perHour": { "limit": 12000, "remaining": 11456, "resetAt": "2024-03-10T16:00:00Z" },
    "perDay": { "limit": 200000, "remaining": 189234, "resetAt": "2024-03-11T00:00:00Z" }
  },
  "burst": {
    "limit": 500,
    "remaining": 450
  }
}

Troubleshooting

Common Issues

IssueSymptomResolution
Unexpected 429sRate limited despite low usageCheck for multiple API keys using same account; consolidate keys
Burst limit exhausted429s after rapid requestsImplement request pacing with minimum intervals
Reset time confusionUnclear when limits resetUse X-RateLimit-Reset Unix timestamp, not Retry-After
Endpoint-specific limits429 on specific endpoint onlyReview endpoint-specific limits above; use alternative endpoints
Caching not effectiveHigh request volume for static dataIncrease cache TTL; use ETags for conditional requests

Getting Help

If you’re experiencing rate limiting issues: