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
| Param | Type | Notes |
|---|---|---|
| string | required | |
| deep | bool | adds SPF/DMARC lookups; billed as 2 units |
| dns | bool | default 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
| Endpoint | Purpose |
|---|---|
| GET /v1/account | plan, usage, projected invoice |
| POST /v1/account/keys | issue another key |
| DELETE /v1/account/keys/<id> | revoke a key |
| GET /v1/account/invoices | invoice history with line items |
| POST /v1/account/plan | self-serve upgrade or downgrade |
| POST /v1/account/welcome | re-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
| Status | Code | Meaning |
|---|---|---|
| 400 | missing_parameter | bad request |
| 401 | invalid_api_key | key missing, wrong or revoked |
| 402 | quota_exceeded | free quota used up - upgrade to continue |
| 413 | payload_too_large | body above the configured limit |
| 429 | rate_limited | respect the Retry-After header |
| 500 | internal_error | includes a request_id |