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

# Rate Limits

> Understand rate limiting, headers, and retry strategies for the Encrata API.

## Limits by endpoint

Rate limits are applied **per API key**, not per IP. Each key has its own independent counter per endpoint.

| Endpoint              | Limit       | Window   |
| --------------------- | ----------- | -------- |
| `/api/agent/lookup`   | 60 requests | 1 minute |
| `/api/agent/validate` | 60 requests | 1 minute |
| `/api/agent/breaches` | 60 requests | 1 minute |
| `/api/agent/ip`       | 60 requests | 1 minute |
| `/api/agent/phone`    | 60 requests | 1 minute |
| `/api/agent/domain`   | 30 requests | 1 minute |
| `/api/agent/company`  | 30 requests | 1 minute |
| `/api/agent/google`   | 30 requests | 1 minute |
| `/api/agent/darkweb`  | 30 requests | 1 minute |
| `/api/agent/monitors` | 60 requests | 1 minute |
| `/api/agent/lists`    | 60 requests | 1 minute |
| `/api/webhooks/*`     | 30 requests | 1 minute |

<Note>
  Enterprise plans get 10× the above limits (e.g. 600/min for lookup, 300/min for domain).
</Note>

## Response headers

Every response includes rate limit headers:

| Header                  | Description                           | Example      |
| ----------------------- | ------------------------------------- | ------------ |
| `X-RateLimit-Limit`     | Max requests in the current window    | `60`         |
| `X-RateLimit-Remaining` | Requests remaining                    | `42`         |
| `X-RateLimit-Reset`     | Unix timestamp when the window resets | `1716134460` |

When you're rate limited (HTTP 429), an additional header is returned:

| Header        | Description                 | Example |
| ------------- | --------------------------- | ------- |
| `Retry-After` | Seconds until you can retry | `12`    |

## Handling rate limits

### Exponential backoff

The recommended strategy is exponential backoff with the `Retry-After` header:

<CodeGroup>
  ```python Python theme={"dark"}
  import time
  import requests

  def lookup_with_retry(email, api_key, max_retries=3):
      url = "https://encrata.com/api/agent/lookup"
      headers = {"Authorization": f"Bearer {api_key}"}

      for attempt in range(max_retries):
          resp = requests.post(url, headers=headers, json={"email": email})

          if resp.status_code == 429:
              retry_after = int(resp.headers.get("Retry-After", 2 ** attempt))
              time.sleep(retry_after)
              continue

          return resp.json()

      raise Exception("Max retries exceeded")
  ```

  ```javascript JavaScript theme={"dark"}
  async function lookupWithRetry(email, apiKey, maxRetries = 3) {
    const url = "https://encrata.com/api/agent/lookup";

    for (let attempt = 0; attempt < maxRetries; attempt++) {
      const resp = await fetch(url, {
        method: "POST",
        headers: {
          Authorization: `Bearer ${apiKey}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ email: email }),
      });

      if (resp.status === 429) {
        const retryAfter = parseInt(resp.headers.get("Retry-After") || 2 ** attempt);
        await new Promise((r) => setTimeout(r, retryAfter * 1000));
        continue;
      }

      return resp.json();
    }

    throw new Error("Max retries exceeded");
  }
  ```

  ```go Go theme={"dark"}
  func lookupWithRetry(email, apiKey string, maxRetries int) (map[string]interface{}, error) {
      url := "https://encrata.com/api/agent/lookup"

      for attempt := 0; attempt < maxRetries; attempt++ {
          body, _ := json.Marshal(map[string]string{"email": email})
          req, _ := http.NewRequest("POST", url, bytes.NewBuffer(body))
          req.Header.Set("Authorization", "Bearer "+apiKey)
          req.Header.Set("Content-Type", "application/json")

          resp, err := http.DefaultClient.Do(req)
          if err != nil {
              return nil, err
          }
          defer resp.Body.Close()

          if resp.StatusCode == 429 {
              retryAfter, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
              if retryAfter == 0 {
                  retryAfter = 1 << attempt
              }
              time.Sleep(time.Duration(retryAfter) * time.Second)
              continue
          }

          var result map[string]interface{}
          json.NewDecoder(resp.Body).Decode(&result)
          return result, nil
      }

      return nil, fmt.Errorf("max retries exceeded")
  }
  ```
</CodeGroup>

### Best practices

<CardGroup cols={2}>
  <Card title="Respect Retry-After" icon="clock">
    Always use the `Retry-After` header value instead of a fixed delay.
  </Card>

  <Card title="Spread requests" icon="chart-scatter">
    Distribute requests evenly across the window rather than bursting.
  </Card>

  <Card title="Use bulk endpoints" icon="layer-group">
    For high-volume lookups, use bulk endpoints instead of individual calls.
  </Card>

  <Card title="Monitor remaining" icon="gauge">
    Check `X-RateLimit-Remaining` to preemptively slow down before hitting limits.
  </Card>
</CardGroup>
