API Documentation
One REST API for email, IP, domain, and phone validation. JSON in, JSON out, with the evidence behind every verdict.
Getting started
- Base URL:
https://veriastra.com/api— or pin a version with/api/v1(what v1 guarantees) - Auth: pass
Authorization: Bearer <api-key>(free tools are unauthenticated & rate-limited) - Content-Type:
application/json - Credits: core validations = 1 credit; enrichment and live lookups 2–10 (one universal balance)
- Quota headers: every response carries
X-Credits-Remaining; unauthenticated calls carryX-RateLimit-Limit / Remaining / Resetinstead - Fresh results: send
"bypass_cache": truein the body to skip the cached copy and re-run the engine (same credit cost)
Try it now
# runs against the live API from your browser · no key needed · guest tier: 5/minute
Calling it from Node / TypeScript
No SDK to install: the platform fetch is enough, on Node 18+, Bun, Deno, Cloudflare Workers and the browser alike.
async function veriastra(path, body, idempotencyKey) {
const res = await fetch(`https://veriastra.com/api/v1/${path}`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VERIASTRA_API_KEY}`,
"Content-Type": "application/json",
// Reuse the same key on a retry: the first answer is replayed, not re-charged.
...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}),
},
body: JSON.stringify(body),
});
const data = await res.json();
if (!res.ok) throw Object.assign(new Error(data.error), { code: data.code, retryable: data.retryable });
return data;
}
const { decision } = await veriastra("dial-check", { phone: "+16502530000" });Errors arrive as typed objects: branch on code, never on the message. retryable says whether the same request can succeed later.
Machine-readable spec
The whole API is described in OpenAPI 3.1 at /api/openapi.json. Point Swagger UI or Scalar at it, or generate a typed client in your language of choice.
For Postman there is a ready-made collection at /api/postman — every endpoint, auth already wired to an apiKey variable, and each request body pre-filled with a working example. Import the link, paste a key, press Send. It is generated from the spec above on every request, so it cannot fall out of step with the API.
# generate a client (any openapi-generator target) npx @openapitools/openapi-generator-cli generate \ -i https://veriastra.com/api/openapi.json \ -g typescript-fetch -o ./veriastra-client
Each operation carries an x-credits extension with its cost, so you can budget a workload straight from the spec.
Safe retries
A request that times out at the network layer leaves you unable to tell whether we processed it. Send an Idempotency-Key header and retrying is safe: the first response is stored for 24 hours and replayed verbatim, without charging a second credit.
curl -X POST https://veriastra.com/api/v1/verify-email \
-H "Authorization: Bearer ol_live_…" \
-H "Idempotency-Key: order-4417-attempt" \
-H "Content-Type: application/json" \
-d '{"email":"name@example.com"}'
# a replayed response carries: Idempotent-Replay: trueUse any string you can regenerate for the same logical operation, up to 255 characters: an order id, a job id, a UUID you keep with the record. Keys are scoped to your API key and to the endpoint, so the same string on two endpoints is two separate operations. Only successful responses are stored; a 4xx or 5xx stays retryable, because the condition behind it may have changed.
Webhook signatures
Bulk jobs POST their result to the webhook URL you supply. Every delivery is signed, so you can prove it came from us before you act on it:
X-Veriastra-Signature: t=1785580802,v1=6f1c…
// signed value is `${t}.${rawBody}` — verify in Node:
const crypto = require("node:crypto");
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
const age = Math.abs(Math.floor(Date.now()/1000) - Number(parts.t));
if (age > 300) return false; // replay window
const expected = crypto.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}Your signing secret is unique to each API key. Read it from GET /api/account (field webhookSecret). Verify against the raw request body, before parsing it: re-serialised JSON will not match. The timestamp is inside the signed value, so it cannot be edited to replay an old body under a new time.
Limits & caching
Without a key you get 5 requests per minute and 50 per day, per IP. That tier exists so the free tools work and you can try an endpoint, not as a production API. With a key there is no per-minute ceiling: your limit is the credit balance, and X-Credits-Remaining tells you where you stand after every call.
Hitting the guest limit returns 429 with Retry-After in seconds. An exhausted balance returns 402.
After the first few guest calls of a day, the API answers 428 with a proof-of-work challenge: find a solution such that SHA-256 of challenge:solution starts with difficulty zero bits, then retry with X-Veriastra-Proof: challenge:solution. It costs a browser about a tenth of a second and each challenge is single-use. This keeps the free tools usable by people rather than by proxy pools — requests with an API key are never challenged.
Results are cached per data type (email ~1 day, IP ~7 days, domain ~3 days, phone ~30 days) and a cache hit is marked with "cached": true. It still costs a credit, because the answer is the product. When you need to see a change that just happened, add "bypass_cache": true and the engine re-runs; the fresh answer replaces the cached one.
import requests
r = requests.post(
"https://veriastra.com/api/v1/verify-email",
headers={"Authorization": "Bearer ol_live_..."},
json={"email": "name@example.com"},
)
print(r.json())const res = await fetch(
"https://veriastra.com/api/v1/verify-email",
{
method: "POST",
headers: {
Authorization: "Bearer ol_live_...",
"Content-Type": "application/json",
},
body: JSON.stringify({ email: "name@example.com" }),
},
);
console.log(await res.json());/api/screenLiveDecision for a job: lead, signup, application
Email, phone and IP read together and answered as the decision the job needs: crm-lead → write · write-flagged · discard, signup → allow · challenge · block, application → work · verify-manually · drop. 1 credit per identifier you send, 2 at most. Optional name is screened against the sanctions lists at no extra credit; optional appetite: strict · balanced · lenient.
curl -X POST https://veriastra.com/api/screen \
-H "Authorization: Bearer ol_live_…" \
-H "Content-Type: application/json" \
-d '{"purpose":"signup","email":"test@mailinator.com","ip":"185.220.101.1"}'{
"purpose": "signup",
"decision": "block",
"reasons": [
"Nothing supplied can be reached (email). There is no way to confirm or recover this account.",
"Disposable email",
"IP is a Tor exit node"
],
"channels": [ { "channel": "email", "reach": "no", "risk": 80 }, { "channel": "ip", "risk": 100 } ],
"risk": { "score": 90, "level": "high" }
}/api/dial-checkLiveDecision for a number: dial check
Should this number be dialled? clear · caution · do-not-call, from line type, complaint history and your own suppression list, with a reason code on each finding. 2 credits.
curl -X POST https://veriastra.com/api/dial-check \
-H "Authorization: Bearer ol_live_…" \
-H "Content-Type: application/json" \
-d '{"phone":"+16502530000"}'{
"decision": "clear",
"reasons": [
{ "code": "landline-b2b", "weight": "context", "text": "Landline. If it is a business's line, ..." }
],
"summary": "Nothing found against this number in the checks below."
}/api/verify-emailLiveEmail Validation
Syntax, MX/DNS, SMTP mailbox probe, disposable & role detection, deliverability score.
curl -X POST https://veriastra.com/api/verify-email \
-H "Content-Type: application/json" \
-d '{"email":"name@example.com"}'{
"email": "name@example.com",
"deliverable": true,
"score": 80,
"rows": [
{ "label": "Syntax (RFC 5322)", "value": "Valid", "tone": "ok" },
{ "label": "MX record", "value": "mx.example.com", "tone": "ok" },
{ "label": "Disposable", "value": "No", "tone": "ok" }
]
}/api/lookup-ipLiveIP Intelligence
Geolocation, ASN/ISP, datacenter & Tor detection, risk score. Omit 'ip' for the caller's own IP.
curl -X POST https://veriastra.com/api/lookup-ip \
-H "Content-Type: application/json" \
-d '{"ip":"8.8.8.8"}'{
"ip": "8.8.8.8",
"signals": { "country": "United States", "asn": 15169, "datacenter": true, "risk": 50 },
"rows": [
{ "label": "Location", "value": "Mountain View, California, United States" },
{ "label": "ISP / Org", "value": "Google LLC" },
{ "label": "Proxy / VPN / Tor", "value": "Likely proxy/VPN (datacenter)" }
]
}/api/lookup-domainLiveDomain Intelligence
DNS/MX, HTTPS reachability, security headers, tech-stack detection, site health.
curl -X POST https://veriastra.com/api/lookup-domain \
-H "Content-Type: application/json" \
-d '{"domain":"example.com"}'{
"domain": "example.com",
"signals": { "reachable": true, "https": true, "hasMx": true, "healthScore": 100 },
"rows": [
{ "label": "Tech stack", "value": "Next.js, React" },
{ "label": "Security headers", "value": "5/5 (HSTS, CSP, ...)" }
]
}/api/lookup-phoneLivePhone Validation
Validation, E.164 formatting, country, line type with its basis, and the carrier holding the number's block. Live-network answers (DNC, HLR) need a telecom data source.
curl -X POST https://veriastra.com/api/lookup-phone \
-H "Content-Type: application/json" \
-d '{"phone":"(650) 253-0000","country":"US"}'{
"signals": { "valid": true, "country": "US", "e164": "+16502530000", "city": "Mountain View", "state": "CA" },
"rows": [
{ "label": "Status", "value": "Valid" },
{ "label": "Line type", "value": "Landline (allocation)" },
{ "label": "Line type basis", "value": "Block allocated to an incumbent wireline carrier (...)" },
{ "label": "Carrier", "value": "MCIMETRO ACCESS TRANSMISSION SERVICES LLC - CA (assigned thousands-block)" },
{ "label": "Spam / reputation", "value": "No FTC complaints on record" }
]
}/api/carrier-lookupLiveCarrier Lookup
Assigned carrier, OCN, rate center and line type for US/CA numbers. The assigned carrier is not the current one for a ported number — that needs a live HLR query.
curl -X POST https://veriastra.com/api/carrier-lookup \
-H "Authorization: Bearer ol_live_…" \
-H "Content-Type: application/json" \
-d '{"phone":"+16502530000"}'{
"carrier": "MCIMETRO ACCESS TRANSMISSION SERVICES LLC - CA",
"ocn": "7240",
"rateCenter": "MT VIEW",
"lineType": "landline",
"lineTypeBasis": "Block allocated to an incumbent wireline carrier (MCIMETRO ACCESS TRANSMISSION SERVICES LLC - CA)"
}/api/fraud-scoreLiveFraud Score
Unified 0-100 fraud risk from combined email, IP, and phone signals. Rule-based: every score returns its reasons and per-signal risk. 2 credits — this call runs four engines. Supplying an email probes the mailbox live, so expect seconds, not milliseconds.
curl -X POST https://veriastra.com/api/fraud-score \
-H "Content-Type: application/json" \
-d '{"email":"x@y.com","ip":"8.8.8.8","phone":"+14155552671"}'{
"score": 30,
"level": "low",
"reasons": ["IP is datacenter/hosting (possible proxy/VPN)"],
"components": [ { "signal": "ip", "risk": 50 } ]
}/api/bulkLiveBulk / Async Jobs
Validate up to 10,000 items (email/ip/domain/phone) async. Returns a jobId; poll GET /api/bulk/{jobId}. Optional webhook: a public http(s) URL, or the request is refused. For a small list answered in the same response (50 phones or IPs, 25 emails, 15 domains), POST /api/batch.
curl -X POST https://veriastra.com/api/bulk \
-H "Authorization: Bearer ol_live_…" \
-H "Content-Type: application/json" \
-d '{"type":"email","items":["a@b.com","c@d.com"]}'{
"jobId": "job_8e91a8a4ae34ef56",
"status": "processing",
"total": 2,
"poll": "/api/bulk/job_8e91a8a4ae34ef56"
}/api/keysLiveAPI Keys
Keys are created on the API Keys page of the dashboard, which calls this endpoint with your signed-in session; a key cannot mint another key. Each key spends your account balance, up to its own cap.
# dashboard → API Keys → Create key
# the request it sends (session cookie, not an API key):
POST /api/keys {"label":"prod","creditsLimit":10000}{
"key": "ol_live_… (shown once)",
"label": "prod",
"creditsLimit": 10000
}/api/feedbackLiveFeedback (reputation loop)
Report a real send outcome (bounced/delivered/complaint) so reputation self-corrects. Requires an API key; not charged.
curl -X POST https://veriastra.com/api/feedback \
-H "Authorization: Bearer ol_live_…" \
-H "Content-Type: application/json" \
-d '{"email":"x@y.com","outcome":"bounced"}'{
"ok": true,
"domain": "y.com",
"outcome": "bounced"
}/api/tech-stackLiveTech Stack
Detect a website's technology stack from its homepage, matched against 5,000+ fingerprints we host. Every detection names its evidence. 2 credits.
curl -X POST https://veriastra.com/api/tech-stack \
-H "Authorization: Bearer ol_live_…" \
-H "Content-Type: application/json" \
-d '{"domain":"example.com"}'{
"domain": "wordpress.org",
"byCategory": {
"CMS": ["WordPress"],
"Web servers": ["Nginx"],
"Tag managers": ["Google Tag Manager"]
},
"detections": [
{ "name": "WordPress", "evidence": "meta:generator", "version": "6.5" },
{ "name": "PHP", "evidence": "implied by WordPress", "implied": true }
]
}/api/business-lookupLiveBusiness Lookup
Business name + city + state → listing records with phone, website and address. Deep fetch on first ask (seconds), cached 7 days. 5 credits.
curl -X POST https://veriastra.com/api/business-lookup \
-H "Authorization: Bearer ol_live_…" \
-H "Content-Type: application/json" \
-d '{"name":"Piper Plumbing","city":"Dallas","state":"TX"}'{
"matches": [
{
"name": "Piper Plumbing",
"category": "hydraulic_equipment_supplier",
"phone": "2144679091",
"address": "5601 W Jefferson Blvd",
"score": 100
}
],
"cached": false,
"fetchMs": 23503
}/api/email-enrichmentLiveEmail Enrichment
Find a work email from name + company domain — SMTP-verified where the domain permits (not just a guess). 3 credits.
curl -X POST https://veriastra.com/api/email-enrichment \
-H "Authorization: Bearer ol_live_…" \
-H "Content-Type: application/json" \
-d '{"first_name":"Jane","last_name":"Doe","company_domain":"example.com"}'{
"found": true,
"email": "jane.doe@example.com",
"confidence": "high",
"verified": true
}/api/accountLiveAccount
Usage and credit balance for the calling API key. Read-only; does not consume a request.
curl https://veriastra.com/api/account \ -H "Authorization: Bearer ol_live_…"
{
"label": "prod",
"credits": { "limit": 10000, "used": 320, "remaining": 9680 },
"requests": 412
}Reading a finished job
GET /api/bulk/{jobId} returns status and results as JSON. For a large job, two other shapes avoid buffering the whole thing:
# one JSON object per line, streamed — process record 1 while 9,000 is still arriving
curl -H "Authorization: Bearer $KEY" \
"https://veriastra.com/api/v1/bulk/$JOB?format=ndjson" | while read -r line; do
echo "$line" | jq -r '.input'
done
# a spreadsheet, or a file for the compliance folder
curl -H "Authorization: Bearer $KEY" \
"https://veriastra.com/api/v1/bulk/$JOB?format=csv" -o results.csvBoth are streamed with the same ownership check as the JSON form: a job created with a key is readable only by that key.
Error codes
Branch on code, never on the message: the code is a stable contract, the message is human text we may reword. Every error body also carries retryable and a recovery sentence, so a client does not have to infer intent from a status code.
invalid_jsonnot retryableThe body was not valid JSON. Check the serialiser and the Content-Type header; resending the same bytes will fail identically.
missing_fieldnot retryableA required field was absent. The endpoint's GET description lists what it expects.
invalid_fieldnot retryableA field was present but unusable. Fix the value — this is a fault in the request, not a transient condition.
api_key_requirednot retryableSend an API key as `Authorization: Bearer <key>`. Keys are created in the dashboard.
invalid_api_keynot retryableThe key was rejected: it may be revoked, mistyped, or from another environment. Issue a new one in the dashboard.
insufficient_creditsnot retryableThe balance is exhausted. Top up or wait for the plan to renew; the same request will succeed afterwards. Do not retry in a loop.
paid_plan_requirednot retryableThis product's answer is bought from a provider per call, so the free plan and trials do not include it. Move to any paid plan and the same request will succeed. Every self-hosted product stays available on the free plan.
records_plan_requirednot retryableAdding to the do-not-call record (access log, suppression list, audited scrubs) is part of the Compliance, Growth and Professional plans. Reading and exporting records you already hold keeps working.
compliance_plan_requirednot retryableThe live registry check is part of the Compliance plan, whose credits cover the provider's per-call price. Move to Compliance and the same request will succeed.
rate_limitedretryablewait ~60sToo many requests. Back off and retry — honour the Retry-After header when present, and prefer exponential backoff with jitter over a fixed interval.
proof_requiredretryableThe free tier asks for a proof-of-work challenge after the first few daily lookups. Solve the challenge in the response and resend, or use an API key to skip it entirely.
not_configurednot retryableThis product is not switched on for your account or is awaiting a provider credential on our side. Retrying will not change that — get in touch.
not_foundnot retryableNo record exists for that identifier. Check the id — a job id belongs to the key that created it.
internal_errorretryablewait ~5sOur fault. Retry once after a short delay; if it recurs, quote the X-Request-Id header when reporting it.
MCP Connector: let your agents verify data
Veriastra ships as a Model Context Protocol server. Claude, Cursor and any MCP client can call the same engines as the REST API: same credits, same results, same signed receipts. Point your client at the endpoint below with your API key.
{
"mcpServers": {
"veriastra": {
"url": "https://veriastra.com/api/mcp",
"headers": {
"Authorization": "Bearer ol_live_..."
}
}
}
}- › validate_phone
- › verify_email
- › lookup_ip
- › lookup_domain
- › screen_sanctions
- › verify_business
- › fraud_score
Those seven only. Bulk, DNC registry, scrapers and connected-data tools are not in the MCP list.