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.com

Authentication

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:

HeaderDescription
X-RateLimit-LimitRequests per second
X-RateLimit-RemainingDaily quota remaining
X-RateLimit-TierYour current plan tier
X-RateLimit-ResetUnix timestamp when daily limit resets

Plan Tiers

TierReq/secDaily LimitBatch Size
Free510010
Starter2510,00050
Growth50100,000100
Scale100500,000500

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

StatusMeaningSafe to Send?
validSMTP verified, mailbox existsYes
invalidMailbox does not exist or domain has no MXNo
riskyCatch-all, disposable, or role-based addressUse caution
unknownCould not determine (SMTP timeout or error)Retry later

Sub-Status Values

Sub-StatusDescription
catchallDomain accepts all addresses (can't confirm individual mailbox)
disposableTemporary/throwaway email service (mailinator, guerrillamail, etc.)
role_basedGeneric address like info@, support@, admin@
no_mxDomain has no mail server configured
syntax_errorInvalid email format
smtp_rejectMail server explicitly rejected the address
smtp_timeoutMail server did not respond in time
mailbox_fullMailbox 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

StatusMeaning
foundEmail verified via SMTP — the address exists
catchallDomain accepts all addresses — returned the best guess convention but can't confirm
not_foundTried all conventions, none were valid

Conventions Tried (in order)

ConventionExample (John Smith)Est. Usage
first.lastjohn.smith@~35-40%
firstjohn@~15-20%
firstlastjohnsmith@~10-12%
first_lastjohn_smith@~5-7%
flastjsmith@~5-7%
firstljohns@~4-5%
last.firstsmith.john@~3-4%
first.ljohn.s@~2-3%
f.lastj.smith@~2-3%
lastsmith@~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.

FieldDescription
domainThe email domain
providerEmail provider: google, microsoft, yahoo, zoho, proton, fastmail, mimecast, or other
mx_recordsMail server hostnames
is_catchallWhether the domain accepts all addresses
spf.rawRaw SPF record
spf.providersDetected services from SPF includes (google, sendgrid, hubspot, etc.)
spf.policySPF 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;
}