Documentação · Python
Python tem várias bibliotecas HTTP boas. Recomendamos httpx (suporta async + sync com mesma API) ou requests (padrão, mais conhecido). O fluxo é em lote: envia os emails, guarda o id e consulta até fechar. Os exemplos abaixo usam httpx — adapte trivialmente pra requests.
pip install httpx
# ou:
poetry add httpx
uv add httpxPasso 1 · POST /api/v1/batch
Endpoint assíncrono. Manda de 1 a 100.000 emails num POST e recebe 201 com o id do lote. O campo name é opcional e serve pra você achar o lote depois.
# emailchecker.py
import os
import time
import httpx
API_KEY = os.environ['EMAILCHECKER_API_KEY']
BASE_URL = 'https://app.emailchecker.email/api/v1'
# um client só: keep-alive durante todo o polling
client = httpx.Client(
base_url=BASE_URL,
headers={'Authorization': f'Bearer {API_KEY}'},
timeout=30.0,
)
def unwrap(r: httpx.Response) -> dict:
"""Devolve data[] ou levanta com a mensagem real da API."""
body = r.json()
if r.is_error:
raise RuntimeError(f'EmailChecker {r.status_code}: {body.get("error")}')
return body['data']
def submit_batch(emails: list[str], name: str | None = None) -> str:
"""POST /api/v1/batch — de 1 a 100.000 emails. Devolve o id do lote."""
payload: dict = {'emails': emails}
if name is not None:
payload['name'] = name
return unwrap(client.post('/batch', json=payload))['id']
batch_id = submit_batch(['joao@empresa.com.br', 'contato@outra.com.br'], 'minha-lista')
print('lote enviado:', batch_id)Passo 2 · GET /api/v1/batch/{id}
Não enviamos webhook: o jeito de saber que terminou é consultar o lote até status virar completed. Só aí o array emails vem preenchido, com result, score, reason e os atributos isv_*. O limite é de 300 req/min e 10.000/hora por key — um poll a cada 5 segundos sobra.
# poll.py — não existe webhook: consulte o lote até ele fechar
def wait_for_batch(batch_id: str, interval: float = 5.0, timeout: float = 1800.0) -> dict:
"""GET /api/v1/batch/{id} até status == 'completed'."""
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
r = client.get(f'/batch/{batch_id}')
if r.status_code == 429: # 300 req/min e 10.000/hora por key
time.sleep(60)
continue
batch = unwrap(r)
if batch['status'] == 'completed':
return batch
if batch['status'] in ('failed', 'cancelled'):
raise RuntimeError(f'lote {batch["status"]}')
time.sleep(interval) # pending | processing
raise TimeoutError('lote não terminou dentro do tempo')
batch = wait_for_batch(batch_id)
print(batch['size'], 'emails, finalizado em', batch['finished_at'])
# emails[] só vem preenchido com status == 'completed'
for e in batch['emails']:
# result: 'deliverable' | 'undeliverable' | 'risky' | 'unknown'
if e['result'] == 'deliverable' and e['score'] >= 80:
print(e['email'], e['score'], e['reason'], e['is_free'])Pitfalls
Erros sutis que só aparecem em produção. Conheça antes de subir o código.
Tanto requests quanto httpx retornam o objeto Response em qualquer status — um 422 não levanta exceção por conta própria. Sem checar r.is_error (ou chamar r.raise_for_status()) você lê {"data": null, "error": "..."} como se fosse sucesso e estoura num KeyError bem longe da causa.
Usar a função de módulo httpx.post() cria um client novo (e um pool TCP novo) por request — péssimo num loop de polling. Instancie um httpx.Client() no módulo (ou um with httpx.Client() as client:) e reaproveite; o keep-alive corta latência e file descriptors.
Chamar client.get() sync dentro de uma corrotina bloqueia todo o loop durante o polling, anulando o ganho do asyncio. Dentro de async use httpx.AsyncClient com await, e await asyncio.sleep() no lugar de time.sleep(), que congela o FastAPI inteiro.
O httpx aplica timeout de 5s por padrão e levanta httpx.ReadTimeout calado no meio de um POST com dezenas de milhares de emails. Passe timeout=30.0 explícito no client e, se o POST estourar, cheque antes se o lote foi criado em vez de reenviar às cegas e duplicar.
Mais linguagens
Comece grátis com 500 créditos. Sem cartão, sem compromisso.