API reference

Base URL https://veritas.rohnelt.dev. Authenticate with Authorization: Bearer <api key>. Every response is JSON.

Quickstart

# 1. create an account (returns your key once)
curl -X POST https://veritas.rohnelt.dev/v1/signup \
  -H 'Content-Type: application/json' \
  -d '{"email":"you@company.com"}'

# 2. use it
curl -H "Authorization: Bearer $KEY" \
  "https://veritas.rohnelt.dev/v1/email/verify?email=john.doe@gmail.com"

POST /v1/signup

Unauthenticated. Body: email (required), name, company, country, plan, referral_code, locale. Send an Idempotency-Key header to make retries safe.

GET|POST /v1/email/verify

ParamType Notes
emailstringrequired
deepbooladds SPF/DMARC lookups; billed as 2 units
dnsbooldefault true; set false for syntax-only (still 1 unit)
{
  "email": "john.doe@gmail.com",
  "status": "deliverable",        // deliverable | risky | undeliverable | unknown
  "sub_status": "verified",
  "score": 87,                     // 0-100
  "normalized": "johndoe@gmail.com",
  "has_mx": true, "mx_hosts": ["gmail-smtp-in.l.google.com"],
  "is_disposable": false, "is_role": false, "is_free_provider": true,
  "gibberish_score": 0.17, "did_you_mean": null,
  "reasons": ["MX present (5 host(s))", "free consumer provider"],
  "meta": {"units_charged": 1, "units_remaining": 249}
}

POST /v1/email/batch

Body {"emails": ["a@x.com", "b@y.com"]}. Billed one unit per row. Duplicate rows inside a batch are resolved once and reused, so deduping your list before sending costs you nothing extra. Row cap follows your plan.

GET|POST /v1/identity/validate

type = cpf | cnpj | iban | vat | card | isbn | ean13 | ein, plus value. Card numbers are never echoed back in full and are not written to logs. /v1/identity/autodetect infers the type.

GET|POST /v1/phone/validate

phone, plus optional country as an ISO code (US, BR) or a calling code (1, +55). Pass it for any list that is not already in +E.164 - a bare national number is ambiguous and we say so rather than guessing a country. Returns E.164, ISO country, line type and national formatting, and splits out an extension (x22, ext. 105, #7) instead of counting it as digits.

POST /v1/phone/batch

Body {"phones": [...], "country": "US"}. Billed one unit per row, with duplicates inside a batch resolved once. Row cap follows your plan. The response adds a summary by line type, a by_country breakdown and a valid_rate, which is what a list sanitisation run actually wants back.

What the phone checks do and do not prove

North American numbers are checked against the numbering plan itself, so an impossible area or exchange code is rejected rather than passed through, and Canada, Puerto Rico and the Caribbean are reported as themselves instead of as the United States. Line type is answered only where the national plan encodes it; NANP does not, so those come back fixed_line_or_mobile. None of this proves a line is connected - that needs an HLR lookup, which is a different product and a per-lookup fee.

Account

EndpointPurpose
GET /v1/accountplan, usage, projected invoice
POST /v1/account/keysissue another key
DELETE /v1/account/keys/<id>revoke a key
GET /v1/account/invoicesinvoice history with line items
POST /v1/account/planself-serve upgrade or downgrade
POST /v1/account/welcomere-send the onboarding email

Account endpoints are rate limited but never consume quota.

Language

The site answers in the language your browser asks for via Accept-Language; ?lang=en|pt-BR|es overrides it. API responses stay in English - error codes and field names are identifiers, not copy. Lifecycle emails go out in the language captured at signup; send locale on /v1/signup to set it explicitly.

Currency

Prices are published in USD, EUR, GBP and BRL. The currency is guessed from the region in your browser's Accept-Language and can be forced with ?currency=usd|eur|gbp|brl. It is fixed on the account at signup - send currency on /v1/signup to set it explicitly - and every _cents field the API returns is in it. These are market prices, not conversions: the same plan costs less in BRL than the dollar rate would imply, and that is deliberate.

Errors

StatusCode Meaning
400missing_parameterbad request
401invalid_api_keykey missing, wrong or revoked
402quota_exceededfree quota used up - upgrade to continue
413payload_too_largebody above the configured limit
429rate_limitedrespect the Retry-After header
500internal_errorincludes a request_id

OpenAPI document