> Reverse Contact API documentation. This is the Markdown copy of https://docs.reversecontact.com/sdk/python/async-tasks
> 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

# Async tasks

> Wait for live profiles, Find Email, Verify Email and Find Phone tasks in Python, resume polling by id, or parse webhook deliveries from the API.

- Last verified: 2026-11-04

`fetch.person_live`, `fetch.company_live`, `contact.find_email`, `contact.verify_email` and `contact.find_phone` start a task and return a task handle right away. `task.id` is the task's `webhookId`.

Call `task.wait()` to poll the task until it ends. When the task succeeds, the method returns its final state in `data` (`status`, `result`, `credits_consumed`). When the task fails, the method raises `TaskFailedError`. Polling is free and does not count toward your rate limit.

**Live fetch**

```python [Python SDK]
import os

from reversecontact import TaskFailedError, TaskTimeoutError, ReverseContact

with ReverseContact(api_key=os.environ["RC_API_KEY"]) as client:
    task = client.fetch.person_live(url="https://social.com/in/janedoe")
    # Save the id first: after a restart, client.tasks.wait(id) resumes without paying for a new task.
    print("Task id:", task.id)

    try:
        done = task.wait(timeout=1800)
        if done.data is not None and done.data.result is not None:
            print(done.data.result.first_name, "credits:", done.data.credits_consumed)
    except TaskFailedError as error:
        print("Task failed:", error.error_code, "credits:", error.credits_consumed)
    except TaskTimeoutError as error:
        print("Still running, resume later with client.tasks.wait:", error.task_id)
```

How `wait()` behaves:

- Each poll asks the API to hold the answer for up to 20 seconds (`poll_wait`, 0 to 20), so the result arrives as soon as the task ends. Two polls start at least `Retry-After` seconds apart, 2 seconds when the header is missing.
- `timeout` is the overall deadline in seconds, 1800 (30 minutes) by default. When the deadline passes, `wait()` raises `TaskTimeoutError` with `task_id` and `last_status`. The task may still finish. Resume polling with `client.tasks.wait(task_id)`. The synchronous client can exceed the deadline by the length of the poll in progress.
- A task that ends with `status` `errored` raises `TaskFailedError`, with `error_code`, `credits_consumed`, `result` and `not_found`. The `not_found` field is `True` for `data-not-found`, `person-not-found`, `company-not-found`, `email-not-found` and `phone-not-found`.
- The SDK calls `on_poll` with each state it reads.
- The SDK never starts a new task after a failure. Your code decides whether to start another task, since the new task can be billed again.

`client.tasks.retrieve(task_id, wait=20)` reads one state of a task without waiting for the end. [Polling](https://docs.reversecontact.com/guides/polling) describes the task lifecycle and how long a result stays available.

### Webhook delivery instead of polling

Pass `webhook_url` when you create the task. The API sends the result to that HTTPS URL as `{webhookId, data}`.

Use `parse_webhook_delivery` to parse the decoded body, a JSON string or bytes. The helper checks the delivery's shape and returns `webhook_id`, `data` and `is_error`. The `is_error` field is `True` when `data.errorCode` is a non-empty string. A malformed delivery raises `ReverseContactError`. The helper does not validate the profile itself.

**Webhook delivery**

```python [Python SDK]
import os

from reversecontact import ReverseContact, parse_webhook_delivery

with ReverseContact(api_key=os.environ["RC_API_KEY"]) as client:
    task = client.fetch.person_live(
        url="https://social.com/in/janedoe",
        webhook_url="https://your-app.com/webhooks/reversecontact",
    )
    print("Store this id to match the delivery:", task.id)

# Call this from the route behind webhook_url with the raw request body.
def handle_webhook(body: bytes) -> None:
    delivery = parse_webhook_delivery(body)
    if delivery.is_error:
        print(delivery.webhook_id, "failed:", delivery.data)
    else:
        print(delivery.webhook_id, "result:", delivery.data)
    # Answer within 10 seconds, then process the data: deduplicate on webhook_id.
```

You can still poll a task created with `webhook_url` by calling `task.wait()`. See [Webhooks](https://docs.reversecontact.com/guides/webhooks) for delivery failures and retries.

## Read one state from a task handle

`task.retrieve(wait=20)` returns one typed `Result[TaskSnapshot[T]]`. The profile or contact result keeps the type of the method that created the task. Unlike `wait()`, `retrieve()` returns an `errored` state without raising `TaskFailedError`.

**Python SDK**

```python [Python SDK]
from reversecontact import ReverseContact

with ReverseContact() as client:
    task = client.contact.find_phone(url="https://social.com/in/janedoe")
    state = task.retrieve(wait=20)
    if state.data:
        print(state.data.status, state.data.result)
```

After a restart, use [client.tasks](https://docs.reversecontact.com/sdk/python/tasks) to retrieve a task or wait for the result by id. `client.tasks.wait(task_id)` returns the endpoint-specific `result` as decoded JSON. The original `Task` handle parses that result into its typed model.

[client.fetch](https://docs.reversecontact.com/sdk/python/fetch) and [client.contact](https://docs.reversecontact.com/sdk/python/contact) list the task-creating methods. Bulk tasks and List operations use their own helpers, described in [Lists and Bulk enrichment](https://docs.reversecontact.com/sdk/python/lists-and-bulk).

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

## FAQ

### How do I wait for a live fetch or Find Email result in Python?

Save `task.id` and call `task.wait()`. Polling is free and waits up to 1800 seconds by default. Resume after a restart with `client.tasks.wait(task_id)`.

### What happens when a Python SDK task fails?

`task.wait()` raises `TaskFailedError` for an `errored` task. The exception includes `task_id`, `error_code`, `credits_consumed`, `result` and `not_found`.

### Does TaskTimeoutError cancel the enrichment task?

`TaskTimeoutError` ends local waiting, without cancelling the remote task. Resume polling with the exception's `task_id`.

### Can I use webhooks and polling together in Python?

Pass `webhook_url` to a live fetch or contact method and parse deliveries with `parse_webhook_delivery`. The same task remains available through `task.wait()`.
