GTMCLI API Docs
Email validation and email finder API built for GTM teams. Validate emails, find anyone's work email by name + company, and submit batch jobs with up to 100K contacts.
Base URL
https://api.gtmcli.comAuthentication
All API requests require a Bearer token in the Authorization header. Create API keys in the dashboard.
curl https://api.gtmcli.com/v1/validate \ -H "Authorization: Bearer gtm_live_your_key_here"
Rate Limits
Rate limits depend on your plan tier. Headers are included in every response:
| Header | Description |
|---|---|
| X-RateLimit-Limit | Requests per second |
| X-RateLimit-Remaining | Daily quota remaining |
| X-RateLimit-Tier | Your current plan tier |
| X-RateLimit-Reset | Unix timestamp when daily limit resets |
Plan Tiers
| Tier | Req/sec | Daily Limit | Batch Size |
|---|---|---|---|
| Free | 5 | 100 | 10 |
| Starter | 25 | 10,000 | 50 |
| Growth | 50 | 100,000 | 100 |
| Scale | 100 | 500,000 | 500 |
POST /v1/validate
Validate a single email address in real-time.
Request
curl -X POST https://api.gtmcli.com/v1/validate \
-H "Authorization: Bearer gtm_live_your_key" \
-H "Content-Type: application/json" \
-d '{"email": "ceo@apple.com"}'Response
{
"email": "ceo@apple.com",
"status": "valid",
"sub_status": "",
"domain_info": {
"domain": "apple.com",
"provider": "other",
"mx_records": ["mx-in-hfd.apple.com", "mx-in-ma.apple.com"],
"is_catchall": false,
"spf": {
"raw": "v=spf1 include:_spf.apple.com ~all",
"valid": true,
"includes": ["_spf.apple.com", "_spf-txn.apple.com"],
"providers": ["google"],
"policy": "softfail"
}
},
"cached": false,
"ms": 465,
"verified_at": "2026-04-06T19:01:08Z"
}Status Values
| Status | Meaning | Safe to Send? |
|---|---|---|
| valid | SMTP verified, mailbox exists | Yes |
| invalid | Mailbox does not exist or domain has no MX | No |
| risky | Catch-all, disposable, or role-based address | Use caution |
| unknown | Could not determine (SMTP timeout or error) | Retry later |
Sub-Status Values
| Sub-Status | Description |
|---|---|
| catchall | Domain accepts all addresses (can't confirm individual mailbox) |
| disposable | Temporary/throwaway email service (mailinator, guerrillamail, etc.) |
| role_based | Generic address like info@, support@, admin@ |
| no_mx | Domain has no mail server configured |
| syntax_error | Invalid email format |
| smtp_reject | Mail server explicitly rejected the address |
| smtp_timeout | Mail server did not respond in time |
| mailbox_full | Mailbox exists but is full |
POST /v1/validate/batch
Validate up to 500 emails synchronously. For larger lists, use the async jobs API.
Request
curl -X POST https://api.gtmcli.com/v1/validate/batch \
-H "Authorization: Bearer gtm_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"emails": [
"ceo@apple.com",
"fake@notreal.com",
"test@mailinator.com"
]
}'Response
{
"results": [
{ "email": "ceo@apple.com", "status": "valid", ... },
{ "email": "fake@notreal.com", "status": "invalid", "sub_status": "no_mx", ... },
{ "email": "test@mailinator.com", "status": "risky", "sub_status": "disposable", ... }
],
"count": 3,
"ms": 1250
}Async Jobs (Batch up to 100K)
For large lists, submit an async job. The API processes emails in the background and you can poll for progress or receive a webhook when complete.
1. Submit a Job
curl -X POST https://api.gtmcli.com/v1/jobs \
-H "Authorization: Bearer gtm_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"emails": ["email1@example.com", "email2@example.com", ...],
"webhook_url": "https://your-app.com/webhook" // optional
}'// Response
{
"id": "481b3095-4cca-4222-a9cf-126a118c0e4b",
"status": "pending",
"total_emails": 50000,
"processed": 0,
"progress_pct": 0,
...
}2. Check Progress
curl https://api.gtmcli.com/v1/jobs/481b3095-... \ -H "Authorization: Bearer gtm_live_your_key"
{
"id": "481b3095-...",
"status": "completed",
"total_emails": 50000,
"processed": 50000,
"valid_count": 32150,
"invalid_count": 12400,
"risky_count": 4800,
"unknown_count": 650,
"progress_pct": 100,
"completed_at": "2026-04-06T19:25:00Z"
}3. Get Results (paginated)
curl "https://api.gtmcli.com/v1/jobs/481b3095-.../results?offset=0&limit=1000" \ -H "Authorization: Bearer gtm_live_your_key"
{
"job_id": "481b3095-...",
"status": "completed",
"total": 50000,
"offset": 0,
"limit": 1000,
"results": [
{
"email": "email1@example.com",
"position": 0,
"status": "valid",
"sub_status": "",
"domain": "example.com",
"provider": "google",
"is_catchall": false,
"spf_raw": "v=spf1 include:_spf.google.com ~all"
},
...
]
}4. List All Jobs
curl https://api.gtmcli.com/v1/jobs \ -H "Authorization: Bearer gtm_live_your_key"
Webhook Payload
If webhook_url is set, a POST is sent when the job completes:
{
"job_id": "481b3095-...",
"status": "completed",
"total_emails": 50000,
"valid_count": 32150,
"invalid_count": 12400,
"risky_count": 4800,
"unknown_count": 650,
"completed_at": "2026-04-06T19:25:00Z"
}POST /v1/find — Email Finder
Find someone's email address given their name and company domain. The API tries up to 16 naming conventions (first.last, flast, firstl, etc.) and validates each via SMTP until it finds a match. It learns which convention each domain uses, so subsequent lookups are faster.
Request
curl -X POST https://api.gtmcli.com/v1/find \
-H "Authorization: Bearer gtm_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Tim",
"last_name": "Cook",
"domain": "apple.com"
}'Response
{
"email": "tcook@apple.com",
"status": "found",
"convention": "flast",
"confidence": 1,
"domain_info": {
"domain": "apple.com",
"provider": "other",
"mx_records": ["mx-in-hfd.apple.com", ...],
"is_catchall": false,
"spf": { "raw": "v=spf1 include:_spf.apple.com ~all", ... }
},
"attempts": 5,
"ms": 6484
}Status Values
| Status | Meaning |
|---|---|
| found | Email verified via SMTP — the address exists |
| catchall | Domain accepts all addresses — returned the best guess convention but can't confirm |
| not_found | Tried all conventions, none were valid |
Conventions Tried (in order)
| Convention | Example (John Smith) | Est. Usage |
|---|---|---|
| first.last | john.smith@ | ~35-40% |
| first | john@ | ~15-20% |
| firstlast | johnsmith@ | ~10-12% |
| first_last | john_smith@ | ~5-7% |
| flast | jsmith@ | ~5-7% |
| firstl | johns@ | ~4-5% |
| last.first | smith.john@ | ~3-4% |
| first.l | john.s@ | ~2-3% |
| f.last | j.smith@ | ~2-3% |
| last | smith@ | ~1-2% |
| + 6 more rare conventions (lastfirst, last_first, lastf, first-last, f_last, last-first) | ||
How Convention Learning Works
When an email is found, the API records which convention worked for that domain. Future lookups for the same domain try the known convention first, often finding the email on the first attempt. The more lookups you do, the faster it gets.
POST /v1/find/batch — Batch Email Finder
Find emails for up to 50 contacts in one request.
Request
curl -X POST https://api.gtmcli.com/v1/find/batch \
-H "Authorization: Bearer gtm_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"contacts": [
{"first_name": "Tim", "last_name": "Cook", "domain": "apple.com"},
{"first_name": "Satya", "last_name": "Nadella", "domain": "microsoft.com"},
{"first_name": "Jeff", "last_name": "Tucker", "domain": "opflow.io"}
]
}'Response
{
"results": [
{"email": "tcook@apple.com", "status": "found", "convention": "flast", "attempts": 1, "ms": 450},
{"email": "satyan@microsoft.com", "status": "found", "convention": "firstl", "attempts": 3, "ms": 2100},
{"email": "jeff@opflow.io", "status": "found", "convention": "first", "attempts": 1, "ms": 12}
],
"count": 3
}Domain Intelligence
Every validation response includes domain_info with data that gets richer over time as we see more emails from the same domain.
| Field | Description |
|---|---|
| domain | The email domain |
| provider | Email provider: google, microsoft, yahoo, zoho, proton, fastmail, mimecast, or other |
| mx_records | Mail server hostnames |
| is_catchall | Whether the domain accepts all addresses |
| spf.raw | Raw SPF record |
| spf.providers | Detected services from SPF includes (google, sendgrid, hubspot, etc.) |
| spf.policy | SPF enforcement: fail (-all), softfail (~all), neutral (?all), pass (+all) |
Error Handling
All errors return JSON with an error field:
// 401 Unauthorized
{ "error": "invalid api key" }
// 400 Bad Request
{ "error": "email is required" }
// 429 Too Many Requests
{ "error": "rate limit exceeded, upgrade your plan for higher limits" }
// 405 Method Not Allowed
{ "error": "method not allowed" }Quick Start Examples
Python — Validate
import requests
resp = requests.post(
"https://api.gtmcli.com/v1/validate",
headers={"Authorization": "Bearer gtm_live_your_key"},
json={"email": "test@example.com"}
)
result = resp.json()
print(f"{result['email']}: {result['status']} ({result['sub_status']})")Python — Find Email
import requests
resp = requests.post(
"https://api.gtmcli.com/v1/find",
headers={"Authorization": "Bearer gtm_live_your_key"},
json={"first_name": "Tim", "last_name": "Cook", "domain": "apple.com"}
)
result = resp.json()
if result["status"] == "found":
print(f"Found: {result['email']} (convention: {result['convention']}, {result['attempts']} attempts)")
elif result["status"] == "catchall":
print(f"Catch-all domain, best guess: {result['email']}")
else:
print("Email not found")Python — Batch Find
import requests
contacts = [
{"first_name": "Tim", "last_name": "Cook", "domain": "apple.com"},
{"first_name": "Satya", "last_name": "Nadella", "domain": "microsoft.com"},
{"first_name": "Jeff", "last_name": "Tucker", "domain": "opflow.io"},
]
resp = requests.post(
"https://api.gtmcli.com/v1/find/batch",
headers={"Authorization": "Bearer gtm_live_your_key"},
json={"contacts": contacts}
)
for r in resp.json()["results"]:
print(f"{r.get('email', 'N/A'):30} {r['status']:10} {r.get('convention', ''):15} {r['ms']}ms")JavaScript / Node.js
// Validate
const resp = await fetch("https://api.gtmcli.com/v1/validate", {
method: "POST",
headers: {
"Authorization": "Bearer gtm_live_your_key",
"Content-Type": "application/json",
},
body: JSON.stringify({ email: "test@example.com" }),
});
const result = await resp.json();
console.log(result.status, result.sub_status);
// Find email
const findResp = await fetch("https://api.gtmcli.com/v1/find", {
method: "POST",
headers: {
"Authorization": "Bearer gtm_live_your_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
first_name: "Tim",
last_name: "Cook",
domain: "apple.com"
}),
});
const found = await findResp.json();
console.log(found.email, found.convention);Google Sheets (Apps Script)
function validateEmail(email) {
const resp = UrlFetchApp.fetch(
"https://api.gtmcli.com/v1/validate",
{
method: "post",
contentType: "application/json",
headers: { Authorization: "Bearer gtm_live_your_key" },
payload: JSON.stringify({ email }),
}
);
return JSON.parse(resp.getContentText()).status;
}
function findEmail(firstName, lastName, domain) {
const resp = UrlFetchApp.fetch(
"https://api.gtmcli.com/v1/find",
{
method: "post",
contentType: "application/json",
headers: { Authorization: "Bearer gtm_live_your_key" },
payload: JSON.stringify({
first_name: firstName,
last_name: lastName,
domain: domain
}),
}
);
const result = JSON.parse(resp.getContentText());
return result.status === "found" ? result.email : result.status;
}