Cold Leads

Перенесення перевірки e-mail з Hunter або Apollo в Cold Leads

Для кого
Команди, які зараз перевіряють e-mail через Hunter або Apollo
Проблема
Код перевірки повний параметрів і значень статусу, специфічних для постачальника. Якщо замінити ендпоінт, не зіставивши їх, непомітно зміниться те, на які адреси надсилає ваш конвеєр.
Рішення
Зіставте кожен параметр і поле відповіді з відповідником у Cold Leads, залиште на місці те, чого Cold Leads не замінює (дані про людей, пошук за доменом), і скористайтеся невеликим адаптером, щоб наявний код зберіг свою форму.
Що ви отримаєте
Зіставлення поле за полем, Python-адаптер, який повертає поля в стилі Hunter із даних Cold Leads, і таблиця вартості Cold Leads для 1 000, 10 000 і 50 000 перевірок на місяць.

Адреси, ідентифікатори та результати в прикладах ілюстративні. Домен example.com зарезервовано для документації, тож реальна перевірка цих адрес поверне invalid.

Приклади коду однакові для всіх мов, коментарі в них — англійською.

Що переноситься, а що лишається

  • Переноситься: перевірка окремих адрес (Hunter Email Verifier) — на POST /api/v1/verify, списки — на POST /api/v1/verify/bulk (до 5 000 адрес у завданні), а пошук імовірної адреси за іменем і доменом (Hunter Email Finder) — на POST /api/v1/find.
  • Лишається: Domain Search від Hunter і вебджерела, на яких ґрунтуються його результати, а також People Search, People Enrichment і Bulk People Enrichment від Apollo. Cold Leads не має бази людей чи компаній; GET /api/v1/leads шукає лише серед контактів у вашій власній CRM Cold Leads.
  • Публічний API Apollo не має окремого ендпоінта перевірки; email_status приходить разом із даними про людину. Якщо ви лишаєте Apollo як джерело даних, адреси, які він повертає, можна перевірити в Cold Leads перед надсиланням.

Заміна Email Verifier на POST /api/v1/verify

HunterCold LeadsПримітка
GET /v2/email-verifier?email=…POST /api/v1/verify з JSON-тілом {email}Cold Leads приймає POST із JSON-тілом.
Параметр запиту api_key, заголовок X-API-KEY або Authorization: BearerЗаголовок x-api-key або Authorization: BearerКлючі в рядку запиту не приймаються.
Параметра часу немає; 202, поки перевірка ще триваєtimeout_ms, від 1 000 до 30 000Cold Leads відповідає, коли перевірку завершено або ліміт часу вичерпано (тоді risky, причина timeout).
data.status validstatus valid з останньою причиною okvalid зі smtp_unreachable означає, що скриньку не перевірено.
data.status invalidstatus invalid, причина mailbox_missing, no_mx або syntax
data.status accept_allstatus risky, причина catch_all, catch_all true
data.status disposabledisposable true (status risky або invalid, якщо домен не має MX)Вбудований список із 40 одноразових поштових доменів.
data.status webmailВідповідника немаєАдреси вебпошти перевіряються так само, як будь-які інші.
data.status unknownstatus risky, причина smtp_unknown або timeout
data.scorescore (від 0 до 100)Інша шкала; ухвалюйте рішення за причинами, а не за числом.
data.regexpreasons містить syntax, якщо перевірку синтаксису не пройдено
data.mx_recordsmx: перший MX-хост або null, якщо домен не має ні MX-, ні A-запису (причина no_mx)
data.smtp_serversmtp_unreachable, якщо жоден поштовий сервер не був досяжний через SMTPok, catch_all і mailbox_missing означають, що поштовий сервер відповів на перевірку одержувача.
data.smtp_checkостання причина ok або catch_allПоштовий сервер прийняв адресу.
data.accept_allcatch_allМає сенс лише тоді, коли поштовий сервер відповів (остання причина ok, catch_all або mailbox_missing).
data.gibberish, data.block, data.sourcesВідповідника немає

Заміна Email Finder на POST /api/v1/find

HunterCold LeadsПримітка
domaindomainURL скорочується до хоста (https://www.example.com/about стає example.com).
companyВідповідника немаєДомен обов’язковий.
first_name, last_namefirst, lastДіакритика прибирається, а інші нелатинські символи відкидаються, тож спершу транслітеруйте імена.
full_namenameДілиться за першим пробілом на first і last.
linkedin_handle, max_durationВідповідника немає
data.emailemailnull, якщо не вдалося побудувати жодного кандидата, наприклад коли домен не має MX.
data.scoreconfidence (від 0 до 100)
data.verification.statusmethodverified — лише коли поштовий сервер підтвердив скриньку; pattern — припущення за поширеними шаблонами адрес; none — коли нічого не знайдено.
data.accept_allcatch_all
data.position, twitter, linkedin_url, phone_number, company, sourcesВідповідника немає
(немає)candidatesАдреси, побудовані за поширеними шаблонами, кожна зі статусом своєї перевірки, якщо її перевіряли.

Адаптер для прямої заміни

Якщо ваш код розгалужується за назвами полів Hunter, ці дві функції повертають ту саму структуру з даних Cold Leads, а також сиру відповідь у полі coldleads. Зіставлення навмисно консервативне: адреса, скриньку якої не перевірено (smtp_unreachable), стає unknown, а не valid.

hunter_adapter.py
# pip install httpx
# Drop-in helpers for code written against Hunter's email-verifier and email-finder.
# They call Cold Leads and return Hunter-style fields, plus the raw Cold Leads answer.
import os

import httpx

cold = httpx.Client(
    base_url="https://coldleads.app/api/v1",
    headers={"x-api-key": os.environ["COLDLEADS_API_KEY"]},  # the key goes in a header, never in the URL
    timeout=40.0,
)

# a mail server answered the SMTP probe only when the last reason is one of these
PROBED = {"ok", "catch_all", "mailbox_missing"}


def email_verifier(email: str) -> dict:
    r = cold.post("/verify", json={"email": email, "timeout_ms": 20000})
    r.raise_for_status()
    c = r.json()
    last = c["reasons"][-1]
    if c["disposable"] and last not in ("syntax", "no_mx"):
        status = "disposable"
    elif last == "ok":
        status = "valid"
    elif last == "catch_all":
        status = "accept_all"
    elif last in ("mailbox_missing", "no_mx", "syntax"):
        status = "invalid"
    else:
        # smtp_unreachable (mailbox not checked), smtp_unknown or timeout: not verified
        status = "unknown"
    return {
        "email": c["email"],
        "status": status,
        "score": c["score"],
        "regexp": last != "syntax",
        "disposable": c["disposable"],
        "mx_records": c["mx"] is not None,
        "smtp_check": last in ("ok", "catch_all"),
        "accept_all": c["catch_all"] if last in PROBED else None,  # None: not tested
        "coldleads": c,
    }


def email_finder(domain: str, first_name: str = "", last_name: str = "", full_name: str = "") -> dict:
    body = {"domain": domain, "first": first_name, "last": last_name}
    if full_name and not (first_name or last_name):
        body = {"domain": domain, "name": full_name}
    r = cold.post("/find", json=body)
    r.raise_for_status()
    c = r.json()
    return {
        "email": c["email"],
        "score": c["confidence"],
        "accept_all": c["catch_all"],
        # Cold Leads says "verified" only when a mail server confirmed the mailbox; a pattern guess is not verified
        "verification": {"status": "valid" if c["method"] == "verified" else None},
        "coldleads": c,
    }


if __name__ == "__main__":
    print(email_verifier("anna@example.com"))

Відмінності в поведінці, які варто врахувати

  • Коди помилок: Cold Leads відповідає 429 у разі перевищення ліміту запитів (rate_limited, з Retry-After: 60) і коли відкрито забагато масових завдань (too_many_jobs), а 402 no_credits — коли закінчилися кредити. Hunter документує 403 для свого ліміту запитів і 429 для ліміту використання, тож обробку помилок доведеться переписати, а не просто перейменувати.
  • Ліміт запитів: 120 запитів на хвилину на ключ. Для списків одне масове завдання замінює до 5 000 окремих викликів.
  • Кожен виклик коштує 1 кредит, зокрема й для результатів із 30-денного кешу.
  • SMTP-перевірка скриньки та accept-all виконується лише тоді, коли Cold Leads може відкрити SMTP-з’єднання з поштовим сервером одержувача. reasons кожного результату показують, чи вона відбулася: smtp_unreachable означає, що скриньку не перевірено.

Ліміти та вартість

Перевірок на місяцьКредитиВартість Cold Leads на місяць
1 0001 000$99: тариф Business, який містить 10 000 кредитів
10 00010 000$99
50 00050 000$299: $99 плюс 40 додаткових пакетів по 1 000 кредитів по $5

API є лише в тарифі Business, який можна оплачувати й щороку ($999). Виклики пошуку адреси списують ті самі кредити, 1 за виклик. Місячні кредити рахуються за календарний місяць (UTC) і використовуються раніше за куплені пакети. Ціни вказано в доларах США; може додаватися податок.

Питання

Чи є в Cold Leads аналог Domain Search від Hunter або пошуку людей в Apollo?

Ні. Cold Leads не збирає даних про людей. GET /api/v1/leads шукає лише серед контактів, які ви вже зберігаєте у своїй CRM Cold Leads.

Чи працюватимуть мої наявні пороги оцінки?

Ненадійно, бо шкали різні. Натомість ухвалюйте рішення за статусом і останнім кодом причини: ok — надсилати, catch_all і smtp_unknown — переглянути, mailbox_missing, no_mx і syntax — відкинути, а smtp_unreachable позначає адреси, скриньку яких не перевірено.

Чи можна перевірити експорт з Apollo в Cold Leads?

Так. Надішліть адреси масовими завданнями до 5 000 і опитуйте результати або скористайтеся Python-скриптом для CSV-файлів на цьому сайті. Кожна адреса коштує 1 кредит.

Чи дешевші результати з кешу?

Ні. Результат із 30-денного кешу позначається cached: true і коштує 1 кредит, як і будь-який інший виклик.