# Python Email Verification API Client — Ravn

Canonical: https://askravn.com/integrations/python

Integrations · Python

# Ravn from Python, with requests

POST JSON to https://api.askravn.com/v1/tools/<tool> with requests and a Bearer header, then branch on the status field: ok carries results, not_found is a free miss, and a 429 tells you how long to wait in its Retry-After header.
[Start free: 1,000 lookups](https://askravn.com/#start) [Read the docs](https://askravn.com/docs)

Setup
## Before you write code

- / 01
### Install requests
`pip install requests`. No Ravn SDK is needed; the API is plain JSON over HTTPS.
- / 02
### Put the key in the environment
`export RAVN_API_KEY=rw_live_…` locally, or your platform’s secret store in production. Never hard-code it.
- / 03
### Read the schemas
`GET https://api.askravn.com/v1/tools` returns every tool with its JSON Schema, so you can validate inputs before sending.
- / 04
### Paste the client below
It handles the three cases that matter: data, a free miss, and a wait.
Code
## ravn_client.py

```
import os
import time
import requests

API = "https://api.askravn.com/v1/tools/"
SESSION = requests.Session()
SESSION.headers["Authorization"] = "Bearer " + os.environ["RAVN_API_KEY"]

class RavnError(Exception):
def __init__(self, http, code, message):
super().__init__(f"{http} {code}: {message}")
self.http, self.code = http, code

def call(tool, payload, max_wait=300, tries=4):
"""Returns results dict/list, or None on a free not_found miss."""
for attempt in range(tries):
r = SESSION.post(API + tool, json=payload, timeout=60)
if r.status_code == 429:
wait = int(r.headers.get("Retry-After", "30"))
if wait > max_wait:  # 5-hour window is full: don't sleep for hours
raise RavnError(429, r.json()["error"]["code"], f"retry in {wait}s")
time.sleep(wait)
continue
if r.status_code == 503 and attempt < tries - 1:
time.sleep(2 ** attempt)
continue
body = r.json()
if r.status_code != 200:
err = body["error"]
raise RavnError(r.status_code, err["code"], err["message"])
return body["results"] if body["status"] == "ok" else None
raise RavnError(r.status_code, "gave_up", "retries exhausted")

if __name__ == "__main__":
v = call("validate_email", {"email": "jane@acme.com"})
if v is None:
print("no verdict")
elif v["status"] == "valid" and v["risk_score"] in ("A", "B"):
print("send")
elif v["status"] == "risky":
print("review:", v["reason"])  # e.g. catch_all_domain
else:
print("drop:", v["reason"])

for v in call("validate_emails", {"emails": ["a@acme.com", "b@acme.com"]}) or []:
print(v["email"], v["status"], v["risk_score"])
```

Recipes
## Patterns that hold up

- / 01
### Batch, don’t loop
For lists, send up to 1,000 addresses to `validate_emails` per call. Duplicates are counted once, and you make far fewer requests.
- / 02
### Connect a provider key for people calls
[people search](https://askravn.com/features/people-search-api) and other people, company and carrier tools run on your own provider key (People Data Labs or Twilio). Connect it with `ravn keys add pdl` (or `twilio`) so those calls do not return 403 `provider_key_required`; our own checks need no key.
- / 03
### Log the request id
Every response has `metadata.request_id`. Store it with your record; it is the fastest way for us to trace a question about a specific answer.
Good to know
## Reading the envelope
Every tool returns the same envelope: `status`, `results` and `metadata`. `metadata.fair_use.lookups_remaining_5h` tells you how much of the window is left, so a long job can slow itself down before it ever sees a 429. A 403 `provider_key_required` means a people, company or carrier tool needs your own provider key connected.
Errors come back with a non-200 status and `{"error": {"code", "message"}}`. The client above turns those into a `RavnError` with the code attached, so your code can catch `provider_key_required` differently from `invalid_input`. The full list is in [the error reference](https://askravn.com/docs#errors), and [the limits section](https://askravn.com/docs#limits) covers the 1 MB body cap.
FAQ
## Common questions.
[All questions →](https://askravn.com/faq)Can I run calls in parallel threads?Yes, but a small pool is enough, and batching with `validate_emails` usually beats threads. If you do go parallel, watch `lookups_remaining_5h` in the metadata and back off before the window runs out.Is there an official Python SDK?No, and you do not need one. Each tool is one POST with a JSON body, and the response shape is the same for all 25 tools, so the client above covers all of them.How long can Retry-After be?A short rate limit asks for seconds. If the 5-hour fair-use window is used up, it can be much longer. That is why the client raises instead of sleeping past `max_wait`; schedule the job to resume, or see [how the window works](https://askravn.com/fair-use).Which errors are safe to retry?Retry 429 after the wait and 503 with backoff. Do not retry 401, 403, 404, 413 or 422: they will fail the same way until you change the API key, connect a provider key, or fix the tool name or input.

## More in [integrations](https://askravn.com/integrations)
Every page uses the same account, plan and tools. [See pricing →](https://askravn.com/pricing)
- [Zapier Email Verification WebhookVerify and enrich emails in a Zap with the Webhooks by Zapier POST action and a Filter on the result, on Zapier plans that include Webhooks..](https://askravn.com/integrations/zapier)
- [Attio Contact Enrichment & VerificationVerify emails and fill work addresses for Attio people records: export a list to CSV, enrich with the Ravn CLI, and import the columns back..](https://askravn.com/integrations/attio)
- [Claude Contact Data ConnectorAdd Ravn to Claude as a custom connector and ask for verified emails, company facts and phone checks in plain English, on web, desktop and mobile..](https://askravn.com/integrations/claude)
- [Claude Code MCP for Email & Contact DataAdd the Ravn MCP server to Claude Code in one command, authenticate with /mcp, and install the Ravn skill so Claude Code can enrich files for you..](https://askravn.com/integrations/claude-code)
- [Clay Email Verification via HTTP APIAdd Ravn to a Clay table with the HTTP API enrichment: verify emails, find work addresses and pull company facts into new columns..](https://askravn.com/integrations/clay)
- [CSV Email Verification & EnrichmentVerify or enrich any CSV with npx ravn enrich: keeps every column, adds rw_* results, saves progress every 25 rows and resumes after a stop..](https://askravn.com/integrations/csv)
