Rate Limits
API requests are rate limited based on authentication type:| Authentication Type | Rate Limit | Window |
|---|---|---|
API Key (x-api-key) | 60 requests | per minute |
| JWT (Dashboard users) | 600 requests | per minute |
| Unauthenticated | 10 requests | per minute |
| Application Token | 100 requests | per hour |
Rate Limit Headers
Every API response includes rate limit information in the headers:| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in window |
X-RateLimit-Remain | Requests remaining in current window |
X-RateLimit-Reset | Seconds until rate limit resets |
Example Response Headers
Copy
Ask AI
HTTP/1.1 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remain: 47
X-RateLimit-Reset: 45
Content-Type: application/json
429 Rate Limit Exceeded
When you exceed the rate limit, you’ll receive a429 Too Many Requests response:
Copy
Ask AI
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 60
X-RateLimit-Remain: 0
X-RateLimit-Reset: 30
Content-Type: application/json
{
"code": "rate_limit_exceeded",
"message": "Too many requests. Please retry after the rate limit window resets."
}
X-RateLimit-Reset header indicates the number of seconds to wait before retrying.
Handling Rate Limits
1. Monitor Rate Limit Headers
Always check the rate limit headers in your responses to avoid hitting limits:JavaScript
Copy
Ask AI
const response = await fetch('https://api.platform.dakota.xyz/customers', {
headers: {
'X-API-Key': 'AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc='
}
});
const remaining = parseInt(response.headers.get('X-RateLimit-Remain'));
const resetTime = parseInt(response.headers.get('X-RateLimit-Reset'));
const limit = parseInt(response.headers.get('X-RateLimit-Limit'));
// Calculate percentage of rate limit used
const usedPercentage = ((limit - remaining) / limit) * 100;
if (remaining < 50) {
console.warn(`Rate limit critical: ${remaining}/${limit} requests remaining (${usedPercentage.toFixed(1)}% used)`);
} else if (remaining < 100) {
console.info(`Rate limit warning: ${remaining}/${limit} requests remaining (${usedPercentage.toFixed(1)}% used)`);
}
// Calculate time until reset
const secondsUntilReset = resetTime - Math.floor(Date.now() / 1000);
if (secondsUntilReset > 0) {
console.log(`Rate limit resets in ${secondsUntilReset} seconds`);
}
Python
Copy
Ask AI
import requests
import time
from datetime import datetime
response = requests.get(
'https://api.platform.dakota.xyz/customers',
headers={'X-API-Key': 'AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc='}
)
remaining = int(response.headers.get('X-RateLimit-Remain', 0))
reset_time = int(response.headers.get('X-RateLimit-Reset', 0))
limit = int(response.headers.get('X-RateLimit-Limit', 1000))
# Calculate percentage of rate limit used
used_percentage = ((limit - remaining) / limit) * 100
if remaining < 50:
print(f'Rate limit critical: {remaining}/{limit} requests remaining ({used_percentage:.1f}% used)')
elif remaining < 100:
print(f'Rate limit warning: {remaining}/{limit} requests remaining ({used_percentage:.1f}% used)')
# Calculate time until reset
seconds_until_reset = reset_time - int(time.time())
if seconds_until_reset > 0:
reset_datetime = datetime.fromtimestamp(reset_time)
print(f'Rate limit resets in {seconds_until_reset} seconds at {reset_datetime}')
Go
Copy
Ask AI
package main
import (
"fmt"
"net/http"
"strconv"
"time"
)
func checkRateLimit(response *http.Response) {
remainingStr := response.Header.Get("X-RateLimit-Remain")
resetStr := response.Header.Get("X-RateLimit-Reset")
limitStr := response.Header.Get("X-RateLimit-Limit")
remaining, _ := strconv.Atoi(remainingStr)
resetTime, _ := strconv.ParseInt(resetStr, 10, 64)
limit, _ := strconv.Atoi(limitStr)
// Calculate percentage of rate limit used
usedPercentage := float64(limit-remaining) / float64(limit) * 100
if remaining < 50 {
fmt.Printf("Rate limit critical: %d/%d requests remaining (%.1f%% used)\n",
remaining, limit, usedPercentage)
} else if remaining < 100 {
fmt.Printf("Rate limit warning: %d/%d requests remaining (%.1f%% used)\n",
remaining, limit, usedPercentage)
}
// Calculate time until reset
now := time.Now().Unix()
secondsUntilReset := resetTime - now
if secondsUntilReset > 0 {
fmt.Printf("Rate limit resets in %d seconds\n", secondsUntilReset)
}
}
func main() {
client := &http.Client{}
req, _ := http.NewRequest("GET", "https://api.platform.dakota.xyz/customers", nil)
req.Header.Add("X-API-Key", "AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=")
resp, err := client.Do(req)
if err != nil {
fmt.Printf("Error making request: %v\n", err)
return
}
defer resp.Body.Close()
checkRateLimit(resp)
}
Rust
Copy
Ask AI
use reqwest::header::{HeaderMap, HeaderValue};
use std::time::{SystemTime, UNIX_EPOCH};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
let mut headers = HeaderMap::new();
headers.insert("X-API-Key", HeaderValue::from_static("AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc="));
let response = client
.get("https://api.platform.dakota.xyz/customers")
.headers(headers)
.send()
.await?;
// Extract rate limit headers
let remaining: i32 = response
.headers()
.get("X-RateLimit-Remain")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.parse().ok())
.unwrap_or(0);
let reset_time: u64 = response
.headers()
.get("X-RateLimit-Reset")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.parse().ok())
.unwrap_or(0);
let limit: i32 = response
.headers()
.get("X-RateLimit-Limit")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.parse().ok())
.unwrap_or(1000);
// Calculate percentage of rate limit used
let used_percentage = ((limit - remaining) as f64 / limit as f64) * 100.0;
if remaining < 50 {
println!("Rate limit critical: {}/{} requests remaining ({:.1}% used)",
remaining, limit, used_percentage);
} else if remaining < 100 {
println!("Rate limit warning: {}/{} requests remaining ({:.1}% used)",
remaining, limit, used_percentage);
}
// Calculate time until reset
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)?
.as_secs();
if reset_time > now {
let seconds_until_reset = reset_time - now;
println!("Rate limit resets in {} seconds", seconds_until_reset);
}
Ok(())
}
Java
Copy
Ask AI
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
import java.time.Instant;
public class DakotaRateLimitMonitor {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.platform.dakota.xyz/customers"))
.header("X-API-Key", "AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=")
.GET()
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
checkRateLimit(response);
}
public static void checkRateLimit(HttpResponse<String> response) {
String remainingStr = response.headers().firstValue("X-RateLimit-Remain").orElse("0");
String resetStr = response.headers().firstValue("X-RateLimit-Reset").orElse("0");
String limitStr = response.headers().firstValue("X-RateLimit-Limit").orElse("1000");
int remaining = Integer.parseInt(remainingStr);
long resetTime = Long.parseLong(resetStr);
int limit = Integer.parseInt(limitStr);
// Calculate percentage of rate limit used
double usedPercentage = ((double)(limit - remaining) / limit) * 100;
if (remaining < 50) {
System.out.printf("Rate limit critical: %d/%d requests remaining (%.1f%% used)%n",
remaining, limit, usedPercentage);
} else if (remaining < 100) {
System.out.printf("Rate limit warning: %d/%d requests remaining (%.1f%% used)%n",
remaining, limit, usedPercentage);
}
// Calculate time until reset
long now = Instant.now().getEpochSecond();
long secondsUntilReset = resetTime - now;
if (secondsUntilReset > 0) {
System.out.printf("Rate limit resets in %d seconds%n", secondsUntilReset);
}
}
}
2. Implement Exponential Backoff
When you receive a 429 response, implement exponential backoff with jitter:JavaScript
Copy
Ask AI
class DakotaApiClient {
constructor(apiKey, baseUrl = 'https://api.platform.dakota.xyz') {
this.apiKey = apiKey;
this.baseUrl = baseUrl;
this.maxRetries = 5;
this.baseDelay = 1000; // 1 second
}
async makeRequest(endpoint, options = {}) {
const url = `${this.baseUrl}${endpoint}`;
const requestOptions = {
...options,
headers: {
'X-API-Key': this.apiKey,
'Content-Type': 'application/json',
...options.headers
}
};
// Add idempotency key for POST requests
if (options.method === 'POST') {
requestOptions.headers['X-Idempotency-Key'] = crypto.randomUUID();
}
return this.makeRequestWithBackoff(url, requestOptions);
}
async makeRequestWithBackoff(url, options, attempt = 0) {
try {
const response = await fetch(url, options);
// Handle rate limiting
if (response.status === 429) {
if (attempt >= this.maxRetries) {
throw new Error(`Rate limit exceeded after ${this.maxRetries} retries`);
}
return this.retryWithBackoff(url, options, attempt);
}
// Handle other 5xx errors with retry
if (response.status >= 500 && attempt < this.maxRetries) {
return this.retryWithBackoff(url, options, attempt);
}
return response;
} catch (error) {
if (error.name === 'TypeError' && attempt < this.maxRetries) {
// Network error, retry
return this.retryWithBackoff(url, options, attempt);
}
throw error;
}
}
async retryWithBackoff(url, options, attempt) {
const retryAfter = 1; // Default to 1 second if no Retry-After header
const exponentialDelay = this.baseDelay * Math.pow(2, attempt);
const jitter = Math.random() * 1000;
const totalDelay = Math.min(exponentialDelay + jitter, 60000); // Cap at 60s
console.log(`Rate limited. Retrying in ${totalDelay}ms (attempt ${attempt + 1}/${this.maxRetries})`);
await new Promise(resolve => setTimeout(resolve, totalDelay));
return this.makeRequestWithBackoff(url, options, attempt + 1);
}
}
// Usage example
const dakota = new DakotaApiClient('AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=');
try {
const customers = await dakota.makeRequest('/customers');
console.log('Customers retrieved:', customers.data);
} catch (error) {
console.error('Failed after retries:', error.message);
}
Python
Copy
Ask AI
import time
import random
import requests
import uuid
from typing import Optional, Dict, Any
class DakotaApiClient:
def __init__(self, api_key: str, base_url: str = 'https://api.platform.dakota.xyz'):
self.api_key = api_key
self.base_url = base_url
self.max_retries = 5
self.base_delay = 1 # 1 second
self.session = requests.Session()
self.session.headers.update({
'X-API-Key': api_key,
'Content-Type': 'application/json'
})
def make_request(self, endpoint: str, method: str = 'GET',
data: Optional[Dict[Any, Any]] = None,
headers: Optional[Dict[str, str]] = None) -> requests.Response:
url = f'{self.base_url}{endpoint}'
request_headers = headers or {}
# Add idempotency key for POST requests
if method.upper() == 'POST':
request_headers['X-Idempotency-Key'] = str(uuid.uuid4())
return self._make_request_with_backoff(url, method, data, request_headers)
def _make_request_with_backoff(self, url: str, method: str,
data: Optional[Dict[Any, Any]],
headers: Dict[str, str],
attempt: int = 0) -> requests.Response:
try:
response = self.session.request(
method, url, json=data, headers=headers, timeout=30
)
# Handle rate limiting
if response.status_code == 429:
if attempt >= self.max_retries:
raise Exception(f'Rate limit exceeded after {self.max_retries} retries')
return self._retry_with_backoff(url, method, data, headers, attempt, response)
# Handle server errors with retry
if response.status_code >= 500 and attempt < self.max_retries:
return self._retry_with_backoff(url, method, data, headers, attempt, response)
response.raise_for_status()
return response
except requests.exceptions.RequestException as e:
if attempt < self.max_retries:
return self._retry_with_backoff(url, method, data, headers, attempt)
raise e
def _retry_with_backoff(self, url: str, method: str,
data: Optional[Dict[Any, Any]],
headers: Dict[str, str], attempt: int,
response: Optional[requests.Response] = None) -> requests.Response:
retry_after = 1 # Default to 1 second
if response and 'Retry-After' in response.headers:
retry_after = int(response.headers['Retry-After'])
exponential_delay = self.base_delay * (2 ** attempt)
jitter = random.uniform(0, 1)
total_delay = min(exponential_delay + jitter, 60) # Cap at 60 seconds
print(f'Rate limited. Retrying in {total_delay:.2f}s (attempt {attempt + 1}/{self.max_retries})')
time.sleep(total_delay)
return self._make_request_with_backoff(url, method, data, headers, attempt + 1)
# Usage example
dakota = DakotaApiClient('AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=')
try:
response = dakota.make_request('/customers')
customers = response.json()
print(f'Customers retrieved: {customers["data"]}')
except Exception as e:
print(f'Failed after retries: {e}')
Go
Copy
Ask AI
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"math"
"math/rand"
"net/http"
"strconv"
"time"
"github.com/google/uuid"
)
type DakotaApiClient struct {
ApiKey string
BaseURL string
MaxRetries int
BaseDelay time.Duration
Client *http.Client
}
func NewDakotaApiClient(apiKey string) *DakotaApiClient {
return &DakotaApiClient{
ApiKey: apiKey,
BaseURL: "https://api.platform.dakota.xyz",
MaxRetries: 5,
BaseDelay: time.Second,
Client: &http.Client{Timeout: 30 * time.Second},
}
}
func (c *DakotaApiClient) MakeRequest(endpoint, method string, data interface{}) (*http.Response, error) {
url := c.BaseURL + endpoint
var body io.Reader
if data != nil {
jsonData, err := json.Marshal(data)
if err != nil {
return nil, err
}
body = bytes.NewBuffer(jsonData)
}
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
req.Header.Set("X-API-Key", c.ApiKey)
req.Header.Set("Content-Type", "application/json")
if method == "POST" {
req.Header.Set("X-Idempotency-Key", uuid.New().String())
}
return c.makeRequestWithBackoff(req, 0)
}
func (c *DakotaApiClient) makeRequestWithBackoff(req *http.Request, attempt int) (*http.Response, error) {
resp, err := c.Client.Do(req)
if err != nil {
if attempt < c.MaxRetries {
return c.retryWithBackoff(req, attempt, nil)
}
return nil, err
}
// Handle rate limiting
if resp.StatusCode == 429 {
if attempt >= c.MaxRetries {
return nil, fmt.Errorf("rate limit exceeded after %d retries", c.MaxRetries)
}
return c.retryWithBackoff(req, attempt, resp)
}
// Handle server errors with retry
if resp.StatusCode >= 500 && attempt < c.MaxRetries {
return c.retryWithBackoff(req, attempt, resp)
}
return resp, nil
}
func (c *DakotaApiClient) retryWithBackoff(req *http.Request, attempt int, resp *http.Response) (*http.Response, error) {
retryAfter := 1 * time.Second
if resp != nil {
if retryAfterHeader := resp.Header.Get("Retry-After"); retryAfterHeader != "" {
if seconds, err := strconv.Atoi(retryAfterHeader); err == nil {
retryAfter = time.Duration(seconds) * time.Second
}
}
}
exponentialDelay := c.BaseDelay * time.Duration(math.Pow(2, float64(attempt)))
jitter := time.Duration(rand.Float64() * float64(time.Second))
totalDelay := exponentialDelay + jitter
if totalDelay > 60*time.Second {
totalDelay = 60 * time.Second
}
fmt.Printf("Rate limited. Retrying in %v (attempt %d/%d)\n", totalDelay, attempt+1, c.MaxRetries)
time.Sleep(totalDelay)
return c.makeRequestWithBackoff(req, attempt+1)
}
func main() {
dakota := NewDakotaApiClient("AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=")
resp, err := dakota.MakeRequest("/customers", "GET", nil)
if err != nil {
fmt.Printf("Failed after retries: %v\n", err)
return
}
defer resp.Body.Close()
fmt.Println("Request succeeded!")
}
Rust
Copy
Ask AI
use reqwest::header::{HeaderMap, HeaderValue};
use serde_json::Value;
use std::time::Duration;
use tokio::time::sleep;
use uuid::Uuid;
use rand::Rng;
pub struct DakotaApiClient {
api_key: String,
base_url: String,
max_retries: u32,
base_delay: Duration,
client: reqwest::Client,
}
impl DakotaApiClient {
pub fn new(api_key: String) -> Self {
Self {
api_key,
base_url: "https://api.platform.dakota.xyz".to_string(),
max_retries: 5,
base_delay: Duration::from_secs(1),
client: reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.build()
.unwrap(),
}
}
pub async fn make_request(
&self,
endpoint: &str,
method: reqwest::Method,
data: Option<&Value>,
) -> Result<reqwest::Response, Box<dyn std::error::Error>> {
let url = format!("{}{}", self.base_url, endpoint);
let mut headers = HeaderMap::new();
headers.insert("X-API-Key", HeaderValue::from_str(&self.api_key)?);
headers.insert("Content-Type", HeaderValue::from_static("application/json"));
if method == reqwest::Method::POST {
headers.insert("X-Idempotency-Key", HeaderValue::from_str(&Uuid::new_v4().to_string())?);
}
let mut request_builder = self.client.request(method, &url).headers(headers);
if let Some(json_data) = data {
request_builder = request_builder.json(json_data);
}
self.make_request_with_backoff(request_builder, 0).await
}
async fn make_request_with_backoff(
&self,
request_builder: reqwest::RequestBuilder,
attempt: u32,
) -> Result<reqwest::Response, Box<dyn std::error::Error>> {
let request = request_builder.try_clone()
.ok_or("Failed to clone request")?;
let response = self.client.execute(request.build()?).await;
match response {
Ok(resp) => {
if resp.status() == 429 {
if attempt >= self.max_retries {
return Err(format!("Rate limit exceeded after {} retries", self.max_retries).into());
}
return self.retry_with_backoff(request_builder, attempt, Some(&resp)).await;
}
if resp.status().as_u16() >= 500 && attempt < self.max_retries {
return self.retry_with_backoff(request_builder, attempt, Some(&resp)).await;
}
Ok(resp)
}
Err(e) => {
if attempt < self.max_retries {
return self.retry_with_backoff(request_builder, attempt, None).await;
}
Err(e.into())
}
}
}
async fn retry_with_backoff(
&self,
request_builder: reqwest::RequestBuilder,
attempt: u32,
response: Option<&reqwest::Response>,
) -> Result<reqwest::Response, Box<dyn std::error::Error>> {
let mut retry_after = Duration::from_secs(1);
if let Some(resp) = response {
if let Some(retry_header) = resp.headers().get("Retry-After") {
if let Ok(seconds_str) = retry_header.to_str() {
if let Ok(seconds) = seconds_str.parse::<u64>() {
retry_after = Duration::from_secs(seconds);
}
}
}
}
let exponential_delay = self.base_delay * 2_u32.pow(attempt);
let jitter = Duration::from_millis(rand::thread_rng().gen_range(0..1000));
let total_delay = std::cmp::min(exponential_delay + jitter, Duration::from_secs(60));
println!("Rate limited. Retrying in {:?} (attempt {}/{})", total_delay, attempt + 1, self.max_retries);
sleep(total_delay).await;
self.make_request_with_backoff(request_builder, attempt + 1).await
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let dakota = DakotaApiClient::new("AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=".to_string());
match dakota.make_request("/customers", reqwest::Method::GET, None).await {
Ok(_) => println!("Request succeeded!"),
Err(e) => println!("Failed after retries: {}", e),
}
Ok(())
}
Java
Copy
Ask AI
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Optional;
import java.util.Random;
import java.util.UUID;
public class DakotaApiClient {
private final String apiKey;
private final String baseUrl;
private final int maxRetries;
private final Duration baseDelay;
private final HttpClient client;
private final Random random;
public DakotaApiClient(String apiKey) {
this.apiKey = apiKey;
this.baseUrl = "https://api.platform.dakota.xyz";
this.maxRetries = 5;
this.baseDelay = Duration.ofSeconds(1);
this.client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(30))
.build();
this.random = new Random();
}
public HttpResponse<String> makeRequest(String endpoint, String method, String jsonData)
throws IOException, InterruptedException {
String url = baseUrl + endpoint;
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("X-API-Key", apiKey)
.header("Content-Type", "application/json");
if ("POST".equalsIgnoreCase(method)) {
requestBuilder.header("X-Idempotency-Key", UUID.randomUUID().toString());
if (jsonData != null) {
requestBuilder.POST(HttpRequest.BodyPublishers.ofString(jsonData));
} else {
requestBuilder.POST(HttpRequest.BodyPublishers.noBody());
}
} else {
requestBuilder.GET();
}
return makeRequestWithBackoff(requestBuilder.build(), 0);
}
private HttpResponse<String> makeRequestWithBackoff(HttpRequest request, int attempt)
throws IOException, InterruptedException {
try {
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
// Handle rate limiting
if (response.statusCode() == 429) {
if (attempt >= maxRetries) {
throw new RuntimeException("Rate limit exceeded after " + maxRetries + " retries");
}
return retryWithBackoff(request, attempt, response);
}
// Handle server errors with retry
if (response.statusCode() >= 500 && attempt < maxRetries) {
return retryWithBackoff(request, attempt, response);
}
return response;
} catch (IOException e) {
if (attempt < maxRetries) {
return retryWithBackoff(request, attempt, null);
}
throw e;
}
}
private HttpResponse<String> retryWithBackoff(HttpRequest request, int attempt,
HttpResponse<String> response) throws IOException, InterruptedException {
Duration retryAfter = Duration.ofSeconds(1);
if (response != null) {
Optional<String> retryAfterHeader = response.headers().firstValue("Retry-After");
if (retryAfterHeader.isPresent()) {
try {
int seconds = Integer.parseInt(retryAfterHeader.get());
retryAfter = Duration.ofSeconds(seconds);
} catch (NumberFormatException ignored) {
// Use default retry after
}
}
}
long exponentialDelayMs = baseDelay.toMillis() * (long) Math.pow(2, attempt);
long jitterMs = random.nextInt(1000);
long totalDelayMs = Math.min(exponentialDelayMs + jitterMs, 60000); // Cap at 60s
System.out.printf("Rate limited. Retrying in %dms (attempt %d/%d)%n",
totalDelayMs, attempt + 1, maxRetries);
Thread.sleep(totalDelayMs);
return makeRequestWithBackoff(request, attempt + 1);
}
public static void main(String[] args) {
DakotaApiClient dakota = new DakotaApiClient("AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=");
try {
HttpResponse<String> response = dakota.makeRequest("/customers", "GET", null);
System.out.println("Request succeeded!");
} catch (Exception e) {
System.out.println("Failed after retries: " + e.getMessage());
}
}
}
3. Distribute Requests Over Time
Instead of making all requests at once, distribute them evenly:JavaScript
Copy
Ask AI
class RateLimitedClient {
constructor(apiKey, requestsPerMinute = 900) {
this.apiKey = apiKey;
this.interval = 60000 / requestsPerMinute; // ms between requests
this.lastRequest = 0;
}
async makeRequest(url, options = {}) {
const now = Date.now();
const timeToWait = this.interval - (now - this.lastRequest);
if (timeToWait > 0) {
await new Promise(resolve => setTimeout(resolve, timeToWait));
}
this.lastRequest = Date.now();
return fetch(url, {
...options,
headers: {
'X-API-Key': this.apiKey,
...options.headers
}
});
}
}
Best Practices
1. Stay Under the Limit
- Target 90% of your rate limit (900 requests/minute) to leave buffer room
- Monitor your usage patterns and adjust accordingly
2. Batch Operations
- Use bulk endpoints when available
- Group related operations together
3. Cache Results
- Cache API responses when appropriate to reduce API calls
- Use ETags or last-modified headers for efficient caching
4. Implement Circuit Breaker
- Stop making requests temporarily after multiple rate limit errors
- Gradually resume requests after cooling down
JavaScript
Copy
Ask AI
class DakotaCircuitBreaker {
constructor(options = {}) {
this.failures = 0;
this.successCount = 0;
this.threshold = options.threshold || 5; // Failures before opening
this.timeout = options.timeout || 60000; // 1 minute
this.resetTimeout = options.resetTimeout || 30000; // 30 seconds
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.nextAttempt = Date.now();
this.lastFailureTime = null;
}
async call(fn) {
if (this.state === 'OPEN') {
if (Date.now() < this.nextAttempt) {
throw new Error(`Circuit breaker is OPEN. Next attempt in ${Math.ceil((this.nextAttempt - Date.now()) / 1000)}s`);
}
this.state = 'HALF_OPEN';
console.log('Circuit breaker is now HALF_OPEN, attempting request...');
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure(error);
throw error;
}
}
onSuccess() {
if (this.state === 'HALF_OPEN') {
this.successCount++;
// After 3 successful requests in HALF_OPEN, close the circuit
if (this.successCount >= 3) {
console.log('Circuit breaker is now CLOSED after successful requests');
this.failures = 0;
this.successCount = 0;
this.state = 'CLOSED';
}
} else {
this.failures = 0;
this.state = 'CLOSED';
}
}
onFailure(error) {
this.failures++;
this.lastFailureTime = Date.now();
// Only count rate limit and server errors towards circuit breaking
const isCircuitBreakerError = error.message.includes('429') ||
error.message.includes('Rate limit') ||
error.message.includes('500') ||
error.message.includes('502') ||
error.message.includes('503');
if (isCircuitBreakerError && this.failures >= this.threshold) {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.timeout;
this.successCount = 0;
console.log(`Circuit breaker is now OPEN after ${this.failures} failures. Will retry at ${new Date(this.nextAttempt)}`);
}
}
getStatus() {
return {
state: this.state,
failures: this.failures,
nextAttempt: this.state === 'OPEN' ? new Date(this.nextAttempt) : null,
lastFailure: this.lastFailureTime ? new Date(this.lastFailureTime) : null
};
}
}
// Enhanced Dakota client with circuit breaker
class EnhancedDakotaClient extends DakotaApiClient {
constructor(apiKey, baseUrl) {
super(apiKey, baseUrl);
this.circuitBreaker = new DakotaCircuitBreaker({
threshold: 5,
timeout: 60000, // 1 minute
resetTimeout: 30000 // 30 seconds
});
}
async makeRequest(endpoint, options = {}) {
return this.circuitBreaker.call(async () => {
return super.makeRequest(endpoint, options);
});
}
getCircuitBreakerStatus() {
return this.circuitBreaker.getStatus();
}
}
// Usage example with circuit breaker
const dakota = new EnhancedDakotaClient('AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=');
// Make requests with automatic circuit breaking
for (let i = 0; i < 20; i++) {
try {
const response = await dakota.makeRequest('/customers');
console.log(`Request ${i + 1} succeeded`);
} catch (error) {
console.error(`Request ${i + 1} failed:`, error.message);
// Check circuit breaker status
const status = dakota.getCircuitBreakerStatus();
if (status.state === 'OPEN') {
console.log('Circuit breaker is open, waiting before next attempt...');
await new Promise(resolve => setTimeout(resolve, 5000)); // Wait 5 seconds
}
}
}
5. Request Queuing and Throttling
For high-volume applications, implement request queuing to stay within rate limits:JavaScript
Copy
Ask AI
class DakotaRequestQueue {
constructor(apiClient, requestsPerMinute = 900) {
this.apiClient = apiClient;
this.queue = [];
this.processing = false;
this.interval = 60000 / requestsPerMinute; // ms between requests
this.lastRequest = 0;
}
async enqueue(endpoint, options = {}) {
return new Promise((resolve, reject) => {
this.queue.push({ endpoint, options, resolve, reject });
this.processQueue();
});
}
async processQueue() {
if (this.processing || this.queue.length === 0) {
return;
}
this.processing = true;
while (this.queue.length > 0) {
const { endpoint, options, resolve, reject } = this.queue.shift();
try {
// Ensure minimum interval between requests
const now = Date.now();
const timeToWait = this.interval - (now - this.lastRequest);
if (timeToWait > 0) {
await new Promise(r => setTimeout(r, timeToWait));
}
this.lastRequest = Date.now();
const response = await this.apiClient.makeRequest(endpoint, options);
resolve(response);
} catch (error) {
reject(error);
}
}
this.processing = false;
}
getQueueStatus() {
return {
queueLength: this.queue.length,
processing: this.processing,
estimatedWaitTime: this.queue.length * this.interval
};
}
}
// Usage example with request queue
const dakota = new DakotaApiClient('AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=');
const requestQueue = new DakotaRequestQueue(dakota, 900); // 900 requests per minute
// Queue multiple requests
const requests = [];
for (let i = 0; i < 100; i++) {
requests.push(
requestQueue.enqueue('/customers', {
method: 'POST',
body: JSON.stringify({
customer_type: 'individual',
name: `Customer ${i}`
})
})
);
}
// Process all requests with automatic rate limiting
const results = await Promise.allSettled(requests);
console.log(`Processed ${results.filter(r => r.status === 'fulfilled').length} requests successfully`);
Need Higher Limits?
If you need higher rate limits for your use case, contact our support team with:- Your current usage patterns and peak request volumes
- Expected request volume and growth projections
- Business justification for higher limits
- Timeline for when you need the increase
- Description of your rate limiting and retry strategies
Enterprise Rate Limits
For enterprise customers, we offer:- Higher base limits: Up to 5,000 requests per minute
- Burst allowances: Short-term higher limits for batch operations
- Dedicated rate limit monitoring: Real-time alerts and usage analytics
- Custom retry strategies: Optimized backoff algorithms for your use case