> ## Documentation Index
> Fetch the complete documentation index at: https://developers.everhour.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate limits

> Request rate limits for the Everhour API and how to handle 429 responses.

The Everhour API enforces rate limits to ensure stability for all users.

## Current limits

The limit is **100 requests per 10 seconds** per API key. This value may vary with server load and is subject to change.

## Exceeded limit response

When you exceed the rate limit, the API returns:

```http theme={null}
HTTP/1.1 429 Too Many Requests
Retry-After: 10
```

The `Retry-After` header specifies how many seconds to wait before retrying.

## Handling 429 responses

Implement exponential backoff in your integration:

<CodeGroup>
  ```bash Bash/cURL theme={null}
  for attempt in 1 2 3 4 5; do
    response=$(curl -s -w "\n%{http_code}" https://api.everhour.com/projects \
      -H "X-Api-Key: YOUR_API_KEY")
    status=$(echo "$response" | tail -1)
    if [ "$status" != "429" ]; then break; fi
    sleep $(( 2 ** attempt ))
  done
  ```

  ```ruby Ruby theme={null}
  require "net/http"

  def request_with_retry(uri, headers, max_retries: 5)
    max_retries.times do |attempt|
      req = Net::HTTP::Get.new(uri, headers)
      resp = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
      return resp unless resp.code == "429"
      sleep(resp["Retry-After"]&.to_i || 2**attempt)
    end
    raise "Max retries exceeded"
  end
  ```

  ```python Python theme={null}
  import time
  import requests

  def request_with_retry(url, headers, max_retries=5):
      for attempt in range(max_retries):
          response = requests.get(url, headers=headers)
          if response.status_code == 429:
              wait = int(response.headers.get("Retry-After", 2 ** attempt))
              time.sleep(wait)
              continue
          return response
      raise Exception("Max retries exceeded")
  ```

  ```php PHP theme={null}
  function request_with_retry(string $url, array $headers, int $max_retries = 5): string {
      for ($attempt = 0; $attempt < $max_retries; $attempt++) {
          $ch = curl_init($url);
          curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
          curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
          $body = curl_exec($ch);
          $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
          curl_close($ch);
          if ($status !== 429) return $body;
          sleep(pow(2, $attempt));
      }
      throw new RuntimeException("Max retries exceeded");
  }
  ```

  ```java Java theme={null}
  import java.net.URI;
  import java.net.http.*;
  import java.time.Duration;

  HttpClient client = HttpClient.newHttpClient();

  HttpResponse<String> requestWithRetry(String url, int maxRetries) throws Exception {
      for (int attempt = 0; attempt < maxRetries; attempt++) {
          HttpRequest request = HttpRequest.newBuilder()
              .uri(URI.create(url))
              .header("X-Api-Key", "YOUR_API_KEY")
              .build();
          HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
          if (response.statusCode() != 429) return response;
          long wait = response.headers().firstValue("Retry-After")
              .map(Long::parseLong).orElse((long) Math.pow(2, attempt));
          Thread.sleep(wait * 1000);
      }
      throw new RuntimeException("Max retries exceeded");
  }
  ```

  ```javascript JavaScript theme={null}
  async function requestWithRetry(url, headers, maxRetries = 5) {
    for (let attempt = 0; attempt < maxRetries; attempt++) {
      const response = await fetch(url, { headers });
      if (response.status !== 429) return response;
      const wait = parseInt(response.headers.get("Retry-After") ?? String(2 ** attempt), 10);
      await new Promise(resolve => setTimeout(resolve, wait * 1000));
    }
    throw new Error("Max retries exceeded");
  }
  ```

  ```go Go theme={null}
  // import "fmt"; "math"; "net/http"; "time"
  func requestWithRetry(url string, apiKey string, maxRetries int) (*http.Response, error) {
      for attempt := 0; attempt < maxRetries; attempt++ {
          req, _ := http.NewRequest("GET", url, nil)
          req.Header.Set("X-Api-Key", apiKey)
          resp, err := http.DefaultClient.Do(req)
          if err != nil || resp.StatusCode != 429 {
              return resp, err
          }
          time.Sleep(time.Duration(math.Pow(2, float64(attempt))) * time.Second)
      }
      return nil, fmt.Errorf("max retries exceeded")
  }
  ```

  ```csharp .NET theme={null}
  async Task<HttpResponseMessage> RequestWithRetry(HttpClient client, string url, int maxRetries = 5) {
      for (int attempt = 0; attempt < maxRetries; attempt++) {
          var response = await client.GetAsync(url);
          if (response.StatusCode != System.Net.HttpStatusCode.TooManyRequests) return response;
          var wait = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt));
          await Task.Delay(wait);
      }
      throw new Exception("Max retries exceeded");
  }
  ```
</CodeGroup>

## Batch operations

If your use case requires a large volume of requests — such as a one-time data migration or bulk export — contact us before running it. We may be able to provide a more efficient data access method.

Reach us via chat inside your Everhour account or at [ask@everhour.com](mailto:ask@everhour.com).
