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 and live lookups 2–10 (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

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: 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": "name@example.com"},
)
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: "name@example.com" }),
  },
);
console.log(await res.json());
POST/api/screenLive

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

Request
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"}'
Response
{
  "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" }
}
POST/api/dial-checkLive

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

Request
curl -X POST https://veriastra.com/api/dial-check \
  -H "Authorization: Bearer ol_live_…" \
  -H "Content-Type: application/json" \
  -d '{"phone":"+16502530000"}'
Response
{
  "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."
}
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":"name@example.com"}'
Response
{
  "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" }
  ]
}
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":"(650) 253-0000","country":"US"}'
Response
{
  "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" }
  ]
}
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 "Authorization: Bearer ol_live_…" \
  -H "Content-Type: application/json" \
  -d '{"phone":"+16502530000"}'
Response
{
  "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)"
}
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":"x@y.com","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: 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.

Request
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"]}'
Response
{
  "jobId": "job_8e91a8a4ae34ef56",
  "status": "processing",
  "total": 2,
  "poll": "/api/bulk/job_8e91a8a4ae34ef56"
}
POST/api/keysLive

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

Request
# dashboard → API Keys → Create key
# the request it sends (session cookie, not an API key):
POST /api/keys  {"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":"x@y.com","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": "jane.doe@example.com",
  "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 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 retryable

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

The 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 ~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
  • › 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.