Email Check
Get Email Check Result
Poll an email check and read the answer for each product
GET
/
api
/
v1
/
public
/
verify
/
{job_id}
curl -X GET "https://app.gethuntd.com/api/v1/public/verify/8f4c2a90-6c1e-4b58-9a2f-1d3e5b7c9011" \
-H "X-API-Key: hntd_abc12345_yoursecretkey"
const jobId = '8f4c2a90-6c1e-4b58-9a2f-1d3e5b7c9011';
const response = await fetch(
`https://app.gethuntd.com/api/v1/public/verify/${jobId}`,
{ headers: { 'X-API-Key': process.env.HUNTD_API_KEY } }
);
const job = await response.json();
if (job.status === 'completed') {
for (const [source, answer] of Object.entries(job.sources)) {
console.log(source, answer.is_user); // true, false, or null if that source failed
}
}
import requests
import os
job_id = '8f4c2a90-6c1e-4b58-9a2f-1d3e5b7c9011'
response = requests.get(
f'https://app.gethuntd.com/api/v1/public/verify/{job_id}',
headers={'X-API-Key': os.environ['HUNTD_API_KEY']}
)
job = response.json()
if job['status'] == 'completed':
for source, answer in job['sources'].items():
print(source, answer['is_user']) # True, False, or None if that source failed
{
"success": true,
"job_id": "8f4c2a90-6c1e-4b58-9a2f-1d3e5b7c9011",
"email": "dana@acme.com",
"status": "completed",
"sources": {
"gong": { "status": "done", "is_user": true, "checked_at": "2026-09-12T08:14:22.000Z", "cached": true },
"extend": { "status": "done", "is_user": false, "checked_at": "2026-09-15T09:04:51.000Z", "cached": false }
}
}
{
"success": true,
"job_id": "8f4c2a90-6c1e-4b58-9a2f-1d3e5b7c9011",
"email": "dana@acme.com",
"status": "running",
"sources": {
"gong": { "status": "done", "is_user": null, "checked_at": null, "cached": true },
"extend": { "status": "running", "is_user": null, "checked_at": null, "cached": false }
}
}
{
"success": false,
"error": {
"code": "INVALID_API_KEY",
"message": "API key is missing, invalid, or has been revoked."
}
}
{
"success": false,
"error": {
"code": "JOB_NOT_FOUND",
"message": "No such verification job."
}
}
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded — 60 requests per minute. Retry in 23s."
}
}
Availability: Agency plan, on
https://app.gethuntd.com. Other plans receive
403 ENDPOINT_NOT_ON_PLAN. Polling is free but counts toward your API key’s rate limit.curl -X GET "https://app.gethuntd.com/api/v1/public/verify/8f4c2a90-6c1e-4b58-9a2f-1d3e5b7c9011" \
-H "X-API-Key: hntd_abc12345_yoursecretkey"
const jobId = '8f4c2a90-6c1e-4b58-9a2f-1d3e5b7c9011';
const response = await fetch(
`https://app.gethuntd.com/api/v1/public/verify/${jobId}`,
{ headers: { 'X-API-Key': process.env.HUNTD_API_KEY } }
);
const job = await response.json();
if (job.status === 'completed') {
for (const [source, answer] of Object.entries(job.sources)) {
console.log(source, answer.is_user); // true, false, or null if that source failed
}
}
import requests
import os
job_id = '8f4c2a90-6c1e-4b58-9a2f-1d3e5b7c9011'
response = requests.get(
f'https://app.gethuntd.com/api/v1/public/verify/{job_id}',
headers={'X-API-Key': os.environ['HUNTD_API_KEY']}
)
job = response.json()
if job['status'] == 'completed':
for source, answer in job['sources'].items():
print(source, answer['is_user']) # True, False, or None if that source failed
{
"success": true,
"job_id": "8f4c2a90-6c1e-4b58-9a2f-1d3e5b7c9011",
"email": "dana@acme.com",
"status": "completed",
"sources": {
"gong": { "status": "done", "is_user": true, "checked_at": "2026-09-12T08:14:22.000Z", "cached": true },
"extend": { "status": "done", "is_user": false, "checked_at": "2026-09-15T09:04:51.000Z", "cached": false }
}
}
{
"success": true,
"job_id": "8f4c2a90-6c1e-4b58-9a2f-1d3e5b7c9011",
"email": "dana@acme.com",
"status": "running",
"sources": {
"gong": { "status": "done", "is_user": null, "checked_at": null, "cached": true },
"extend": { "status": "running", "is_user": null, "checked_at": null, "cached": false }
}
}
{
"success": false,
"error": {
"code": "INVALID_API_KEY",
"message": "API key is missing, invalid, or has been revoked."
}
}
{
"success": false,
"error": {
"code": "JOB_NOT_FOUND",
"message": "No such verification job."
}
}
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded — 60 requests per minute. Retry in 23s."
}
}
Overview
Retrieve the progress of an email check and, once it’s finished, the answer for each source. Returns200 for any job that exists: read status to know whether it’s done.
Reading the Result
For each source,is_user is the answer:
is_user | Meaning | What to do |
|---|---|---|
true | This person uses the product | Use the answer |
false | We checked, and this person does not use the product | Use the answer. It’s a real “no”, not a missing value. |
null | No answer: that source’s status is failed | Submit again if you still need it |
Only read
is_user once the job’s status is completed. Until then it’s null for every
source, even ones that have already finished. All answers are released together.checked_at is when the check actually ran, so a cached answer (cached: true) keeps its original
date and can be older than the job.
Job Status
| Status | Description |
|---|---|
queued | Nothing has started yet |
running | At least one source is being checked |
completed | Every source is done or failed. Answers are filled in now. |
Polling Strategy
- Wait until the
poll_aftertime from the submit response (skip this if it wasnull) - Poll every 5 seconds, backing off toward 60 seconds
- Stop when
statusiscompleted
const BASE = 'https://app.gethuntd.com/api/v1/public';
const HEADERS = { 'X-API-Key': process.env.HUNTD_API_KEY };
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function waitForResult(job) {
// job is the response from Submit Email Check
if (job.poll_after) {
await sleep(Math.max(0, new Date(job.poll_after) - Date.now()));
}
for (let delay = 5_000; ; delay = Math.min(delay * 2, 60_000)) {
const res = await fetch(`${BASE}/verify/${job.job_id}`, { headers: HEADERS });
if (res.status === 429) {
await sleep(Number(res.headers.get('Retry-After') ?? 60) * 1000);
continue;
}
const view = await res.json();
if (!view.success) throw new Error(`${view.error.code}: ${view.error.message}`);
if (view.status === 'completed') return view.sources;
await sleep(delay);
}
}
import os
import time
from datetime import datetime, timezone
import requests
BASE = 'https://app.gethuntd.com/api/v1/public'
HEADERS = {'X-API-Key': os.environ['HUNTD_API_KEY']}
def wait_for_result(job):
# job is the response from Submit Email Check
if job['poll_after']:
start = datetime.fromisoformat(job['poll_after'].replace('Z', '+00:00'))
time.sleep(max(0, (start - datetime.now(timezone.utc)).total_seconds()))
delay = 5
while True:
res = requests.get(f"{BASE}/verify/{job['job_id']}", headers=HEADERS)
if res.status_code == 429:
time.sleep(int(res.headers.get('Retry-After', 60)))
continue
view = res.json()
if not view['success']:
raise RuntimeError(f"{view['error']['code']}: {view['error']['message']}")
if view['status'] == 'completed':
return view['sources']
time.sleep(delay)
delay = min(delay * 2, 60)
A job queued for hours usually isn’t stuck: checks only run Monday to Friday, 09:00 to 18:00 in
your account’s timezone. There is no webhook for this endpoint, so for long waits save
job_id
and check back from a scheduled job.Errors
| HTTP | Error Code | Description |
|---|---|---|
| 401 | INVALID_API_KEY | API key is missing, invalid, or has been revoked |
| 403 | ENDPOINT_NOT_ON_PLAN | Your plan doesn’t include email checks |
| 403 | SUBSCRIPTION_INACTIVE | Your workspace has no active subscription. Contact your account manager. |
| 404 | JOB_NOT_FOUND | No such job for your organization. A job belonging to another organization also returns 404. |
| 429 | RATE_LIMIT_EXCEEDED | Over your API key’s per-minute limit, which includes polls. Wait Retry-After seconds. |
| 500 | INTERNAL_ERROR | Something broke on our side. Safe to retry. |
| 503 | ORG_KIND_UNAVAILABLE | Your plan briefly couldn’t be confirmed. Retry. |
Authorizations
API key in format: hntd_{id}_{secret}. Create one in Settings → API & webhooks. Authorization: Bearer is also accepted.
Path Parameters
The job ID returned by Submit Email Check
Example:
"8f4c2a90-6c1e-4b58-9a2f-1d3e5b7c9011"
Response
The job's progress, and its answers once completed
Available options:
true Example:
"8f4c2a90-6c1e-4b58-9a2f-1d3e5b7c9011"
Example:
"dana@acme.com"
completed means every source is done or failed, and the answers are filled in.
Available options:
queued, running, completed One entry per source slug.
Show child attributes
Show child attributes
⌘I
curl -X GET "https://app.gethuntd.com/api/v1/public/verify/8f4c2a90-6c1e-4b58-9a2f-1d3e5b7c9011" \
-H "X-API-Key: hntd_abc12345_yoursecretkey"
const jobId = '8f4c2a90-6c1e-4b58-9a2f-1d3e5b7c9011';
const response = await fetch(
`https://app.gethuntd.com/api/v1/public/verify/${jobId}`,
{ headers: { 'X-API-Key': process.env.HUNTD_API_KEY } }
);
const job = await response.json();
if (job.status === 'completed') {
for (const [source, answer] of Object.entries(job.sources)) {
console.log(source, answer.is_user); // true, false, or null if that source failed
}
}
import requests
import os
job_id = '8f4c2a90-6c1e-4b58-9a2f-1d3e5b7c9011'
response = requests.get(
f'https://app.gethuntd.com/api/v1/public/verify/{job_id}',
headers={'X-API-Key': os.environ['HUNTD_API_KEY']}
)
job = response.json()
if job['status'] == 'completed':
for source, answer in job['sources'].items():
print(source, answer['is_user']) # True, False, or None if that source failed
{
"success": true,
"job_id": "8f4c2a90-6c1e-4b58-9a2f-1d3e5b7c9011",
"email": "dana@acme.com",
"status": "completed",
"sources": {
"gong": { "status": "done", "is_user": true, "checked_at": "2026-09-12T08:14:22.000Z", "cached": true },
"extend": { "status": "done", "is_user": false, "checked_at": "2026-09-15T09:04:51.000Z", "cached": false }
}
}
{
"success": true,
"job_id": "8f4c2a90-6c1e-4b58-9a2f-1d3e5b7c9011",
"email": "dana@acme.com",
"status": "running",
"sources": {
"gong": { "status": "done", "is_user": null, "checked_at": null, "cached": true },
"extend": { "status": "running", "is_user": null, "checked_at": null, "cached": false }
}
}
{
"success": false,
"error": {
"code": "INVALID_API_KEY",
"message": "API key is missing, invalid, or has been revoked."
}
}
{
"success": false,
"error": {
"code": "JOB_NOT_FOUND",
"message": "No such verification job."
}
}
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded — 60 requests per minute. Retry in 23s."
}
}