Rate Limiting

Clemta APIs implement rate limiting to ensure fair usage and system stability. This page explains how rate limiting works across all our APIs.

Rate Limit Types

Clemta APIs employ different rate limiting strategies depending on the endpoint:
  1. API Key-based Rate Limiting: Applied to most endpoints
  2. Resource-based Rate Limiting: Applied to specific resources (e.g., per company ID)

Rate Limit Headers

All API responses include headers that provide information about rate limits:

Common Headers

X-RateLimit-API
string
Displays your API key’s rate limit (e.g., “100 requests per minute”)
X-RateLimit-Remaining
integer
Number of requests remaining in the current time window
X-RateLimit-Limit
integer
Maximum number of requests allowed per minute
X-RateLimit-Reset
integer
Unix timestamp when the rate limit resets
X-RateLimit-Type
string
Indicates which rate limit type is being applied (“api_key” or “resource”)
When a rate limit is exceeded, you’ll also receive:
Retry-After
integer
Seconds to wait before making another request (typically 60 seconds)

Rate Limit Errors

When a rate limit is exceeded, the API will respond with:
  • HTTP Status Code: 429 Too Many Requests
  • Error Code: RATE_LIMIT_EXCEEDED
  • Error Message: “Rate limit exceeded”
{
  "success": false,
  "message": "Rate limit exceeded",
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "details": "Too many requests. Please try again later."
  }
}

Best Practices

1

Implement exponential backoff

Use a retry strategy with exponential backoff when you receive 429.
2

Monitor your usage

Track X-RateLimit-Remaining and slow down before hitting limits.
3

Use conditional requests

Use If-Modified-Since for GET endpoints.
4

Use pagination

Paginate list endpoints to reduce load.

1. Implement Exponential Backoff

exponential_backoff.go
package main

import (
	"fmt"
	"math"
	"net/http"
	"strconv"
	"time"
)

func makeRequestWithRetry(url string, apiKey string, maxRetries int) (*http.Response, error) {
	client := &http.Client{}
	for attempt := 0; attempt < maxRetries; attempt++ {
		req, err := http.NewRequest("GET", url, nil)
		if err != nil {
			return nil, err
		}
		req.Header.Set("X-API-KEY", apiKey)

		resp, err := client.Do(req)
		if err != nil {
			if attempt == maxRetries-1 {
				return nil, err
			}
			continue
		}

		if resp.StatusCode == http.StatusTooManyRequests { // 429
			retryAfter := resp.Header.Get("Retry-After")
			var delay time.Duration
			if retryAfter != "" {
				if seconds, err := strconv.Atoi(retryAfter); err == nil {
					delay = time.Duration(seconds) * time.Second
				}
			}
			if delay == 0 {
				ms := math.Min(1000*math.Pow(2, float64(attempt)), 60000)
				delay = time.Duration(ms) * time.Millisecond
			}
			fmt.Printf("Rate limited. Waiting %v before retry %d\n", delay, attempt+1)
			time.Sleep(delay)
			continue
		}

		return resp, nil
	}
	return nil, fmt.Errorf("max retries exceeded")
}

2. Monitor Rate Limit Headers

monitor_headers.go
package main

import (
	"fmt"
	"net/http"
	"strconv"
	"time"
)

func makeRequestWithMonitoring(url string, apiKey string) (*http.Response, error) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("X-API-KEY", apiKey)

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	remaining := resp.Header.Get("X-RateLimit-Remaining")
	reset := resp.Header.Get("X-RateLimit-Reset")
	limit := resp.Header.Get("X-RateLimit-Limit")
	fmt.Printf("Rate limit: %s/%s remaining\n", remaining, limit)

	if remaining != "" && reset != "" {
		if r, err := strconv.Atoi(remaining); err == nil && r < 10 {
			if rs, err := strconv.ParseInt(reset, 10, 64); err == nil {
				wait := (rs * 1000) - time.Now().UnixMilli()
				if wait > 0 {
					fmt.Printf("Waiting %dms before next request\n", wait)
					time.Sleep(time.Duration(wait) * time.Millisecond)
				}
			}
		}
	}
	return resp, nil
}

3. Use Conditional Requests

conditional_requests.go
package main

import (
	"fmt"
	"net/http"
)

func makeConditionalRequest(url string, apiKey string, lastModified string) (*http.Response, error) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("X-API-KEY", apiKey)
	if lastModified != "" {
		req.Header.Set("If-Modified-Since", lastModified)
	}

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode == http.StatusNotModified { // 304
		fmt.Println("No changes since last request")
	} else {
		fmt.Printf("Last-Modified: %s\n", resp.Header.Get("Last-Modified"))
	}
	return resp, nil
}

Example Rate Limit Headers

Successful Request

X-RateLimit-API: 100 requests per minute
X-RateLimit-Remaining: 95
X-RateLimit-Limit: 100
X-RateLimit-Reset: 1640995200
X-RateLimit-Type: api_key

Rate Limited Request

X-RateLimit-API: 100 requests per minute
X-RateLimit-Remaining: 0
X-RateLimit-Limit: 100
X-RateLimit-Reset: 1640995200
X-RateLimit-Type: api_key
Retry-After: 60