> Reverse Contact API documentation. This is the Markdown copy of https://docs.reversecontact.com/sdk/python/errors-and-retries
> Every page is listed at https://docs.reversecontact.com/llms.txt, every endpoint with its credit cost and status at https://docs.reversecontact.com/catalog.json

# Errors and retries

> Handle Python SDK exceptions, understand automatic retries and rate limits, and reuse idempotency keys safely for Find Email and Find Phone.

- Last verified: 2026-11-04

SDK-defined errors inherit from `ReverseContactError`. If a response cannot be parsed into the declared model, Pydantic can also raise `ValidationError`. Errors returned by the API inherit from `APIError`, with `status`, `code`, `message`, `details`, `request_id`, `retry_after`, `headers` and `body`.

| HTTP status | Error class |
| ----------- | ----------- |
| `400` | `BadRequestError` |
| `401` | `AuthenticationError` |
| `402` | `InsufficientCreditsError` |
| `403` | `PermissionDeniedError` |
| `404` | `NotFoundError` (a lookup with no match returns `None` instead) |
| `409` | `ConflictError` |
| `410` | `GoneError` |
| `422` | `UnprocessableEntityError` |
| `429` | `RateLimitError`, or `DailyLimitError` for a daily limit |
| `451` | `UnavailableForLegalReasonsError` |
| `5xx` | `InternalServerError` |
| Any status with `KEY_CREDIT_LIMIT_REACHED` | `KeyCreditLimitError` |

A network failure raises `APIConnectionError`. If an attempt runs out of time, the SDK raises `APITimeoutError`, a subclass of that error.

The SDK retries a request up to 4 times after the first attempt, 5 attempts in total:

- For a `429` from the per-minute rate limit, the SDK first checks `error.details.retryAfter` for the retry delay, then the `Retry-After` header. If neither supplies a delay, the SDK backs off.
- For a `500`, `502`, `503` or `504`, the SDK backs off exponentially: 2, 4, 8, then 15 seconds, plus up to 250 milliseconds of jitter. If the response sends `Retry-After`, the SDK uses that delay instead.
- If the requested wait exceeds 60 seconds, the SDK raises the error at once instead of waiting.
- The SDK retries network failures and timed-out attempts on `GET` requests. For a `DELETE`, the SDK also retries a transport failure when no HTTP response was received. For a `POST`, the SDK retries only when the connection was never established. This prevents sending a request twice when the API may already have billed it. Find Email and Find Phone are exceptions, protected by their idempotency key (see below).

The SDK never retries `DailyLimitError` (the daily limits, `DAILY_LIMIT_EXCEEDED` and `DAILY_EXTRACTION_LIMIT_EXCEEDED`), `KeyCreditLimitError` (an API key credit limit) or `InsufficientCreditsError`. Other `4xx` errors are not retried either, except `409 IDEMPOTENCY_KEY_IN_USE` for Find Email and Find Phone. To resolve these errors, change the request, key or account, or wait until the next day. Set `max_retries=0` to turn retries off.

**Handle errors**

```python [Python SDK]
import os

from reversecontact import APIError, DailyLimitError, KeyCreditLimitError, ReverseContact

with ReverseContact(api_key=os.environ["RC_API_KEY"]) as client:
    try:
        client.enrich.company(domain="acme.com")
    except (DailyLimitError, KeyCreditLimitError) as error:
        # Not retried: pause the work until the limit resets.
        print("Limit reached:", error.code, "retry after", error.retry_after, "seconds")
    except APIError as error:
        print(error.status, error.code, error.message, "request id:", error.request_id)
```

[Error handling & retries](https://docs.reversecontact.com/guides/errors-retries) lists every error code of the API.

## Safe retries on Find Email and Find Phone

Sending the same Find Email, Verify Email or Find Phone request twice would start two jobs, and both can be billed. `contact.find_email`, `contact.verify_email` and `contact.find_phone` send an `Idempotency-Key` header with a new UUID on each call. Each automatic retry of that call reuses the key. For 24 hours, the API returns the first response for a repeated key and body, without starting or billing another lookup.

To retry safely after your process restarts, choose the key yourself, store it with the request, and pass it as `idempotency_key`. `job.replayed` is `True` when the API returned the first response again.

**Idempotency key**

```python [Python SDK]
import os

from reversecontact import ReverseContact

with ReverseContact(api_key=os.environ["RC_API_KEY"]) as client:
    job = client.contact.find_email(
        full_name="Jane Doe",
        company_domain="acme.com",
        email_type="any",
        idempotency_key="crm-contact-4821-email",
    )
    print(job.id, "first response returned again" if job.replayed else "new lookup")

    done = job.wait()
    emails = done.data.result if done.data and done.data.result else []
    for email in emails:
        if email is not None:
            print(email.value, email.type)
```

Use 1 to 255 printable ASCII characters without spaces, and a new key for each new lookup. A key that is empty, longer than 255 characters, or contains a space or a non-ASCII character raises `ReverseContactError` before any request is sent.

The SDK retries `409 IDEMPOTENCY_KEY_IN_USE` after its `Retry-After` delay. The SDK does not retry `422 IDEMPOTENCY_KEY_REUSED` (same key, different body) or `400 INVALID_IDEMPOTENCY_KEY`. After 24 hours, the same key can start and bill a new lookup. To read a job you already started, call `client.jobs.wait(job_id)`.

## List and Bulk retry identifiers

`lists.create()` and `bulk.enrich_persons()` generate an `Idempotency-Key` when you omit one and reuse that key on automatic retries. `lists.import_signals()` generates an `operation_id` when omitted. Save your own identifiers to continue the same work after a restart. See [Lists and Bulk enrichment](https://docs.reversecontact.com/sdk/python/lists-and-bulk).

These methods do not use the contact endpoints' special retry rule for ambiguous POST transport failures or `409 IDEMPOTENCY_KEY_IN_USE`. Use [client.contact](https://docs.reversecontact.com/sdk/python/contact) for the three contact methods protected by that rule.

`APIError` also exposes `http_response`, `alternative` and `reset_at`. `KeyCreditLimitError` adds `period`, `limit`, `used` and `required`. See [Timeouts and cancellation](https://docs.reversecontact.com/sdk/python/timeouts) for HTTP and overall job deadlines.

Return to the [Python SDK overview](https://docs.reversecontact.com/sdk/python).

## FAQ

### Does the reversecontact Python SDK retry failed requests?

The SDK retries per-minute rate limits and HTTP 500, 502, 503 and 504 up to four times by default. Daily limits, key credit limits and insufficient credits are not retried.

### How do I disable automatic retries in Python?

Pass `max_retries=0` to the client or an individual API method. That setting disables the request transport's automatic retries.

### How do I retry Find Email without creating another job?

Store a request-specific `idempotency_key` and reuse that key with the same body. Contact endpoints return the first response for 24 hours. Check `job.replayed` to identify a replay.

### Which Python exception represents an invalid API key?

An HTTP 401 response raises `AuthenticationError`, a subclass of `APIError` and `ReverseContactError`. Inspect `request_id`, `status`, `code` and `message` for diagnostics.
