StormGTM API
Barometer reviews leads over a JSON API at https://stormgtm.com. Call it with curl, the Node client, the stormgtm CLI, or any MCP client. A condensed version for models lives at /llms.txt.
Quickstart
Every account starts with 100 free credits. Create a key in the dashboard, then:
export STORMGTM_API_KEY=sgtm_live_...
curl https://stormgtm.com/v1/check \
-H "authorization: Bearer $STORMGTM_API_KEY" \
-H "content-type: application/json" \
-d '{
"email": "jane.doe@acme.io",
"context": { "name": "Jane Doe", "company": "Acme" }
}'The typed Node client wraps every endpoint. Pass baseUrl as below, or call clientFromEnv() to read STORMGTM_API_KEY and STORMGTM_API_URL.
npm install @stormgtm/client
import { StormGTM } from "@stormgtm/client";
const stormgtm = new StormGTM({
apiKey: process.env.STORMGTM_API_KEY!,
baseUrl: "https://stormgtm.com",
});
const result = await stormgtm.check({
email: "jane.doe@acme.io",
tier: "deep",
context: { name: "Jane Doe", company: "Acme", githubLogin: "janedoe" },
});
const send = result.verdict === "deliverable" && result.policy.allowed;
console.log(send ? "send" : "skip", result.score, result.reasons);
await stormgtm.reportOutcome({ email: "jane.doe@acme.io", kind: "delivered" });The stormgtm CLI ships with the client. check exits with code 2 when a lead is undeliverable, so it drops into shell pipelines. CSV batches read an email column plus any context columns, such as name, company, or githubLogin.
npm install -g @stormgtm/client export STORMGTM_API_KEY=sgtm_live_... export STORMGTM_API_URL=https://stormgtm.com stormgtm me stormgtm check jane.doe@acme.io --name "Jane Doe" --company Acme stormgtm check jane.doe@acme.io --github janedoe --deep --json stormgtm batch leads.csv --wait stormgtm outcome jane.doe@acme.io bounced
Add the MCP server to Claude Desktop (claude_desktop_config.json) or Cursor (~/.cursor/mcp.json, or .cursor/mcp.json in a project), then restart the app.
{
"mcpServers": {
"stormgtm": {
"command": "npx",
"args": ["-y", "@stormgtm/mcp"],
"env": {
"STORMGTM_API_KEY": "sgtm_live_...",
"STORMGTM_API_URL": "https://stormgtm.com"
}
}
}
}With Claude Code:
claude mcp add stormgtm \ --env STORMGTM_API_KEY=sgtm_live_... \ --env STORMGTM_API_URL=https://stormgtm.com \ -- npx -y @stormgtm/mcp
Authentication
Send Authorization: Bearer sgtm_live_… on every request. Create and revoke keys in the dashboard; a key is shown once, when you create it. All request and response bodies are JSON.
STORMGTM_API_KEY | Your key, sgtm_live_…. Read by the Node client, the CLI, and the MCP server. |
STORMGTM_API_URL | Base URL. Set it to https://stormgtm.com. |
POST /v1/check
Check one lead synchronously. Fast checks usually finish in 1–4 seconds; SMTP servers that stall can take up to ~25 seconds.
{
"email": "jane@acme.io", // required
"tier": "fast" | "deep", // default fast (1 credit); deep = 5 credits
"smtp": true, // set false to skip the SMTP probe
"context": { // all optional; more context, better verdicts
"name": "Jane Doe", "firstName": "Jane", "lastName": "Doe",
"company": "Acme", "companyDomain": "acme.io", "title": "CTO",
"source": "github", "sourceUrl": "https://github.com/acme/api",
"githubLogin": "janedoe", "linkedinUrl": "…", "notes": "…"
},
"policy": { … } // optional; overrides your account default
}{
"id": "chk_…",
"email": "Jane@Acme.io", "normalized": "jane@acme.io",
"verdict": "deliverable" | "risky" | "undeliverable" | "unknown",
"score": 0-100,
"tier": "fast",
"reasons": [{ "code": "smtp_accepted", "impact": "positive", "weight": 45, "detail": "RCPT accepted by aspmx.l.google.com" }],
"facts": { "mx": "yes", "provider": "google", "smtp": "accepted", "catchAll": false, "spf": true, "dmarc": true, … },
"policy": { "allowed": true, "violations": [] },
"billable": true,
"credits": 1
}Verdicts
deliverable | Mailbox confirmed or strongly evidenced. Send. |
risky | Likely delivers but unconfirmed: catch-all, role inbox, or weak evidence. Send carefully or enrich. |
undeliverable | Will bounce, or must not be mailed. Drop it. |
unknown | Greylisted, timed out, or no signal. Free. Retry later or use a batch (batches retry automatically). |
policy.allowed is separate from the verdict: a lead can be deliverable and still outside your policy. Gate sends on both.
Reasons
The score starts at 50 and each reason adds its weight. A fatal reason makes the lead undeliverable regardless of the rest.
| Code | Weight | Meaning |
|---|---|---|
syntax_invalid | fatal | Not a valid address |
placeholder_local / placeholder_domain | fatal | you@, firstname.lastname@, example.com, company.com |
no_reply | fatal | noreply@, mailer-daemon@, notifications@ |
disposable_domain | fatal | Throwaway providers (120k+ domains) |
domain_typo | fatal | gmial.com, acme.con — facts.suggestion holds the fix |
no_mx / null_mx | fatal | Domain cannot receive mail |
smtp_rejected | fatal | Mail server refused the mailbox |
history_bounced | fatal | A hard bounce was reported for this address |
role_account | −20 | info@, sales@, support@ — caps the verdict at risky |
smtp_catch_all | −15 | Domain accepts every address; mailbox not confirmed |
smtp_accepted | +45 (+15 on Yahoo and gateways) | Mail server accepted RCPT TO |
not_catch_all | +5 | A random address was rejected, so acceptance is meaningful |
history_replied / history_delivered | +50 / +35 | Outcomes you or others reported |
github_profile_email / github_commit_email | +35 / +30 | Address is public on GitHub or authored commits |
web_mention | +20 (+28 on own domain) | Exact address found on the public web |
name_pattern_match / mismatch | +12 / −12 | Local part vs. the lead's name |
llm_verdict | −20 … +20 | Reviewer model on ambiguous results (deep tier) |
Policy
Set a default policy on your account, or send policy with a check or batch to override it. Violations come back in policy.violations and never change the score.
"policy": {
"blockTlds": ["ru", "cn"],
"blockDomains": ["qq.com"],
"blockLocals": ["npm"],
"blockRoleAccounts": true,
"blockFreeMail": false,
"blockAnonymous": true,
"blockSocialHosts": true,
"blockCatchAll": false
}POST /v1/batches
Queue up to 10,000 leads. Greylisted or silent servers are retried after 5, 15, and 45 minutes before settling as unknown.
{
"leads": [{ "email": "jane@acme.io", "context": { "name": "Jane Doe" } }, …],
"tier": "fast",
"policy": { … },
"webhookUrl": "https://you.example/hooks/leads" // optional, HTTPS only
}
→ 202 { "id": "bat_…", "status": "queued", "total": 2, "maxCredits": 2 }Poll GET /v1/batches/:id?offset=0&limit=100 for status and results. When done we POST {"event":"batch.completed","batch":{"id","total","completedAt","summary"}} to your webhook.
POST /v1/outcomes
Report what happened after you sent. Bounces make future checks of that address undeliverable; replies and deliveries raise confidence. Addresses are stored hashed.
{ "email": "jane@acme.io", "kind": "bounced" | "delivered" | "replied" | "opened" | "complained",
"occurredAt": "2026-10-01T12:00:00Z", "detail": "550 5.1.1" }
// or many at once
{ "outcomes": [ … up to 1000 … ] }Other endpoints
GET /v1/me | Account, credit balance, pricing, 30-day usage. |
GET /v1/checks?limit=50&before=… | Recent checks. |
GET /v1/checks/:id | One stored result. |
GET /v1/batches | Recent batches. |
Errors
Errors look like {"error":{"code":"insufficient_credits","message":"…"}}. Codes: missing_api_key, invalid_api_key (401), insufficient_credits (402), invalid_request (422, with issues), batch_too_large (413).
Agents and MCP
Give your agent the MCP server (stormgtm-mcp) with STORMGTM_API_KEY set, and it gets check_lead, check_batch, batch_status, report_outcome, and credits tools. Setup is in the Quickstart.
The rule we recommend agents follow:
- Send only when
verdictisdeliverableandpolicy.allowedis true. risky: enrich with more context andtier: "deep", or ask a human.unknownis free: retry later or use a batch.- Report bounces and replies to
/v1/outcomes.