veriastra_
Developers

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 3–40 (one universal balance)
  • Quota headers: every response carries X-Credits-Remaining; unauthenticated calls carry X-RateLimit-Limit / Remaining / Reset instead
  • Fresh results: send "bypass_cache": true in the body to skip the cached copy and re-run the engine (same credit cost)

Try it now

veriastra — try itready

# runs against the live API from your browser · no key needed · guest tier: 5/minute

Node / TypeScript client

Zero dependencies, uses the platform fetch — so it runs on Node 18+, Bun, Deno, Cloudflare Workers and the browser alike.

npm install veriastra
import { Veriastra, VeriastraError } from "veriastra";

const veriastra = new Veriastra(process.env.VERIASTRA_API_KEY);

const result = await veriastra.dialCheck("+14155552671");
console.log(result.decision);   // "clear" | "caution" | "do-not-call"

try {
  await veriastra.hlr("+14155552671");
} catch (e) {
  if (e instanceof VeriastraError && e.isOutOfCredits) await topUp();
}

Every POST carries an Idempotency-Key, and a retry reuses the same one — so a retried call replays the first answer instead of being charged twice. Errors arrive as typed objects: branch on code, never on the message.

No key? Every single-lookup method also answers on the rate-limited keyless tier, so you can try the engines before you sign up.

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":"[email protected]"}'

# a replayed response carries:  Idempotent-Replay: true

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

Python
import requests

r = requests.post(
    "https://veriastra.com/api/v1/verify-email",
    headers={"Authorization": "Bearer ol_live_..."},
    json={"email": "[email protected]"},
)
print(r.json())
Node.js
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: "[email protected]" }),
  },
);
console.log(await res.json());
POST/api/verify-emailLive

Email Validation

Syntax, MX/DNS, SMTP mailbox probe, disposable & role detection, deliverability score.

Request
curl -X POST https://veriastra.com/api/verify-email \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]"}'
Response
{
  "email": "[email protected]",
  "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" }
  ]
}
POST/api/lookup-ipLive

IP Intelligence

Geolocation, ASN/ISP, datacenter & Tor detection, risk score. Omit 'ip' for the caller's own IP.

Request
curl -X POST https://veriastra.com/api/lookup-ip \
  -H "Content-Type: application/json" \
  -d '{"ip":"8.8.8.8"}'
Response
{
  "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)" }
  ]
}
POST/api/lookup-domainLive

Domain Intelligence

DNS/MX, HTTPS reachability, security headers, tech-stack detection, site health.

Request
curl -X POST https://veriastra.com/api/lookup-domain \
  -H "Content-Type: application/json" \
  -d '{"domain":"example.com"}'
Response
{
  "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, ...)" }
  ]
}
POST/api/lookup-phoneLive

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

Request
curl -X POST https://veriastra.com/api/lookup-phone \
  -H "Content-Type: application/json" \
  -d '{"phone":"(415) 555-2671","country":"US"}'
Response
{
  "signals": { "valid": true, "country": "US", "e164": "+14155552671" },
  "rows": [
    { "label": "Status", "value": "Valid" },
    { "label": "Country", "value": "US (+1)" },
    { "label": "Line type", "value": "Mobile (allocation)" },
    { "label": "Line type basis", "value": "Block allocated to a wireless carrier (...)" }
  ]
}
POST/api/carrier-lookupLive

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

Request
curl -X POST https://veriastra.com/api/carrier-lookup \
  -H "Content-Type: application/json" \
  -d '{"phone":"+14155552671"}'
Response
{
  "carrier": "PACIFIC BELL",
  "ocn": "9740",
  "rateCenter": "SNFC CNTRL",
  "lineType": "landline",
  "lineTypeBasis": "Block allocated to an incumbent wireline carrier (PACIFIC BELL)"
}
POST/api/fraud-scoreLive

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

Request
curl -X POST https://veriastra.com/api/fraud-score \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","ip":"8.8.8.8","phone":"+14155552671"}'
Response
{
  "score": 30,
  "level": "low",
  "reasons": ["IP is datacenter/hosting (possible proxy/VPN)"],
  "components": [ { "signal": "ip", "risk": 50 } ]
}
POST/api/bulkLive

Bulk / Async Jobs

Validate up to 10,000 items (email/ip/domain/phone) async. Returns a jobId; poll GET /api/bulk/{jobId}. Optional webhook.

Request
curl -X POST https://veriastra.com/api/bulk \
  -H "Content-Type: application/json" \
  -d '{"type":"email","items":["[email protected]","[email protected]"]}'
Response
{
  "jobId": "job_8e91a8a4ae34ef56",
  "status": "processing",
  "total": 2,
  "poll": "/api/bulk/job_8e91a8a4ae34ef56"
}
POST/api/keysLive

API Keys

Create an API key with a credit limit. GET /api/keys lists keys and usage.

Request
curl -X POST https://veriastra.com/api/keys \
  -H "Content-Type: application/json" \
  -d '{"label":"prod","creditsLimit":10000}'
Response
{
  "key": "ol_live_… (shown once)",
  "label": "prod",
  "creditsLimit": 10000
}
POST/api/feedbackLive

Feedback (reputation loop)

Report a real send outcome (bounced/delivered/complaint) so reputation self-corrects. Requires an API key; not charged.

Request
curl -X POST https://veriastra.com/api/feedback \
  -H "Authorization: Bearer ol_live_…" \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","outcome":"bounced"}'
Response
{
  "ok": true,
  "domain": "y.com",
  "outcome": "bounced"
}
POST/api/tech-stackLive

Tech Stack

Detect a website's technology stack from its homepage, matched against 5,000+ fingerprints we host. Every detection names its evidence. 2 credits.

Request
curl -X POST https://veriastra.com/api/tech-stack \
  -H "Authorization: Bearer ol_live_…" \
  -H "Content-Type: application/json" \
  -d '{"domain":"example.com"}'
Response
{
  "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 }
  ]
}
POST/api/business-lookupLive

Business Lookup

Business name + city + state → listing records with phone, website and address. Deep fetch on first ask (seconds), cached 7 days. 5 credits.

Request
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"}'
Response
{
  "matches": [
    {
      "name": "Piper Plumbing",
      "category": "hydraulic_equipment_supplier",
      "phone": "2144679091",
      "address": "5601 W Jefferson Blvd",
      "score": 100
    }
  ],
  "cached": false,
  "fetchMs": 23503
}
POST/api/email-enrichmentLive

Email Enrichment

Find a work email from name + company domain — SMTP-verified where the domain permits (not just a guess). 3 credits.

Request
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"}'
Response
{
  "found": true,
  "email": "[email protected]",
  "confidence": "high",
  "verified": true
}
GET/api/accountLive

Account

Usage and credit balance for the calling API key. Read-only; does not consume a request.

Request
curl https://veriastra.com/api/account \
  -H "Authorization: Bearer ol_live_…"
Response
{
  "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.csv

Both 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 retryable

The body was not valid JSON. Check the serialiser and the Content-Type header; resending the same bytes will fail identically.

missing_fieldnot retryable

A required field was absent. The endpoint's GET description lists what it expects.

invalid_fieldnot retryable

A field was present but unusable. Fix the value — this is a fault in the request, not a transient condition.

api_key_requirednot retryable

Send an API key as `Authorization: Bearer <key>`. Keys are created in the dashboard.

invalid_api_keynot retryable

The key was rejected: it may be revoked, mistyped, or from another environment. Issue a new one in the dashboard.

insufficient_creditsnot retryable

The 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 retryable

This product's answer is bought from a provider per call, so it is not included in the trial. Move to any paid plan and the same request will succeed. Every self-hosted product stays available during the trial.

rate_limitedretryablewait ~60s

Too many requests. Back off and retry — honour the Retry-After header when present, and prefer exponential backoff with jitter over a fixed interval.

proof_requiredretryable

The 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 retryable

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

provider_unavailableretryablewait ~30s

An upstream provider is failing or slow. Retry after a short delay; if it persists, the status page will say so.

not_foundnot retryable

No record exists for that identifier. Check the id — a job id belongs to the key that created it.

internal_errorretryablewait ~5s

Our fault. Retry once after a short delay; if it recurs, quote the X-Request-Id header when reporting it.

MCP

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.

Client config
{
  "mcpServers": {
    "veriastra": {
      "url": "https://veriastra.com/api/mcp",
      "headers": {
        "Authorization": "Bearer ol_live_..."
      }
    }
  }
}
Tools exposed
  • validate_phone
  • validate_email
  • validate_ip
  • validate_domain
  • enrich_email
  • carrier_lookup

Connected-data tools (hlr_lookup, search_intent, domain_seo) appear automatically when the account has them enabled.