API reference

The VerifAi v1 REST API lets you verify a single address, enrich a contact record, read your credit balance, grade a list you manage (/v1/lists) and evaluate any list for free (/v1/evaluate) — each returns a single JSON object directly. Larger lists are handled asynchronously as jobs: see Bulk & async jobs for file uploads (/v1/files), batch validation (/v1/validate/batch) and job status (/v1/jobs). Every endpoint authenticates with a bearer API key.

Base URL

All paths below are relative to:

https://ratifai.app/hook/FlowClick/FlowVerify/v1

POST/v1/validate

Verifies a single email address and returns a verdict with per-field provenance and an evidence trail.

Request body

FieldTypeRequiredDescription
emailstringYesThe address to verify.
depthstringNoHow deep to probe: syntax, mx_only or full. Defaults to full. Your plan’s maximum applies — a deeper request is degraded to what your plan allows and reported in depth_applied.
idempotency_keystringNoMakes the call safe to retry. May also be sent as the Idempotency-Key header, which wins if both are present. See Authentication.

Response

FieldTypeDescription
emailstringThe address that was checked.
verdictstringdeliverable, undeliverable, risky or unknown.
reasonstringMachine-readable reason code for the verdict.
depth_requestedstringThe depth you asked for.
depth_appliedstringThe depth actually run after applying your plan limit.
billablebooleanWhether this result consumed a credit.
credits_chargedinteger0 or 1. See How billing works.
checksobjectPer-field results: syntax, role_address, disposable, typo_suggestion, mx, smtp, catch_all. Each carries an origin (provenance).
mail_providerstringThe identified mail provider, when known.
enrichmentobjectProvider classification and free_provider flag.
evidencearrayChronological record of what was observed, each with at, step and detail.
request_idstringUnique id for this request, also returned in the X-Request-Id header.

Example

curl
curl https://ratifai.app/hook/FlowClick/FlowVerify/v1/validate -H 'Authorization: Bearer YOUR_API_KEY' -H 'Content-Type: application/json' -d '{"email":"support@github.com","depth":"full"}'
Python
import requests

resp = requests.post(
    "https://ratifai.app/hook/FlowClick/FlowVerify/v1/validate",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={"email": "support@github.com", "depth": "full"},
)
print(resp.json())
JavaScript
const resp = await fetch(
  "https://ratifai.app/hook/FlowClick/FlowVerify/v1/validate",
  {
    method: "POST",
    headers: {
      "Authorization": "Bearer YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ email: "support@github.com", depth: "full" }),
  },
);
const data = await resp.json();
console.log(data);
Response
{
  "email": "support@github.com",
  "verdict": "unknown",
  "reason": "probe_failed_open",
  "depth_requested": "full",
  "depth_applied": "full",
  "billable": false,
  "credits_charged": 0,
  "checks": {
    "syntax": { "valid": true, "origin": "checked" },
    "role_address": { "value": true, "origin": "checked" },
    "disposable": { "value": false, "origin": "checked" },
    "typo_suggestion": null,
    "mx": { "records": ["github-com.mail.protection.outlook.com"], "origin": "checked" },
    "smtp": { "accepted": false, "status": "unknown", "origin": "failed_open" },
    "catch_all": { "value": false, "origin": "skipped" }
  },
  "mail_provider": "Microsoft 365",
  "enrichment": {
    "mail_provider": { "value": "Microsoft 365", "class": "cloud", "origin": "checked" },
    "free_provider": { "value": false, "origin": "checked" }
  },
  "evidence": [
    { "at": "2026-08-05T18:02:40Z", "step": "syntax_check", "detail": "address is well-formed" },
    { "at": "2026-08-05T18:02:40Z", "step": "mx_lookup", "detail": "1 MX records for github.com" },
    { "at": "2026-08-05T18:02:40Z", "step": "smtp_probe", "detail": "mailbox probe result: unknown" }
  ],
  "request_id": "req_1785952959868"
}

POST/v1/enrich

Enriches and normalizes a contact record. Supply any subset of the fields below; VerifAi returns cleaned, standardized values with per-field provenance. Every returned field carries a sibling <field>_origin set to checked when it was derived from a supplied input, or skipped when its source input was absent. Enrichment is synchronous and charges nothing.

Request body

All fields are optional; send whichever you have.

FieldTypeRequiredDescription
emailstringNoAn email address to normalize and classify.
namestringNoA person’s name to split and canonicalize.
phonestringNoA phone number to normalize to E.164 and describe.
addressstringNoA postal address to standardize.
companystringNoA company name to normalize.
websitestringNoA website URL to normalize.

Response

Each value field is accompanied by a <field>_origin sibling (checked or skipped). When an input is absent, its derived fields come back empty with _origin of skipped.

FieldTypeDescription
normalized_emailstringThe canonical form of the supplied email.
email_domainstringThe domain part of the email.
is_corporatestring"true" or "false" — whether the email is a corporate (non-free) domain.
name_firststringGiven name parsed from name.
name_laststringFamily name parsed from name.
name_canonicalstringThe canonicalized full name.
phone_e164stringThe phone number in E.164 form.
phone_nationalstringThe phone number in national format.
phone_area_codestringThe area code.
phone_timezonestringIANA timezone inferred from the number.
company_normalizedstringThe normalized company name.
company_legal_formstringThe legal form, e.g. Inc.
address_standardizedstringThe standardized postal address.
website_normalizedstringThe normalized website URL.
completeness_scorestringA 0–100 score of how complete the record is.
dedupe_keystringA stable key for de-duplicating the record.
<field>_originstringProvenance sibling for each field: checked or skipped.
request_idstringUnique id for this request.

Example

curl
curl https://ratifai.app/hook/FlowClick/FlowVerify/v1/enrich -H 'Authorization: Bearer YOUR_API_KEY' -H 'Content-Type: application/json' -d '{"email":"john.smith@acmecorp.com","name":"John Smith","phone":"+14155552671","company":"Acme Corporation"}'
Response
{
  "normalized_email": "john.smith@acmecorp.com",
  "normalized_email_origin": "checked",
  "email_domain": "acmecorp.com",
  "email_domain_origin": "checked",
  "is_corporate": "true",
  "is_corporate_origin": "checked",
  "name_first": "John",
  "name_first_origin": "checked",
  "name_last": "Smith",
  "name_last_origin": "checked",
  "name_canonical": "John Smith",
  "name_canonical_origin": "checked",
  "phone_e164": "+14155552671",
  "phone_e164_origin": "checked",
  "phone_national": "(415) 555-2671",
  "phone_national_origin": "checked",
  "phone_area_code": "415",
  "phone_area_code_origin": "checked",
  "phone_timezone": "America/Los_Angeles",
  "phone_timezone_origin": "checked",
  "company_normalized": "Acme Corporation",
  "company_normalized_origin": "checked",
  "company_legal_form": "Inc",
  "company_legal_form_origin": "checked",
  "address_standardized": "",
  "address_standardized_origin": "skipped",
  "website_normalized": "",
  "website_normalized_origin": "skipped",
  "completeness_score": "75",
  "completeness_score_origin": "checked",
  "dedupe_key": "12f1f4a7...",
  "dedupe_key_origin": "checked",
  "request_id": "req_01J..."
}

GET/v1/credits

Returns your current plan, credit balance and the start of the next billing period. Reading your balance is always free and never rate-limited beyond the standard per-key limit.

Response

FieldTypeDescription
planstringYour current plan name.
credits_balanceintegerRemaining monthly plan allowance.
payg_creditsintegerPay-as-you-go credits, which never expire.
balanceintegerTotal spendable credits (credits_balance + payg_credits).
included_allowanceintegerValidations included with your plan each month.
periodstringThe current billing period, YYYY-MM.
period_resetstringDate the monthly allowance resets, YYYY-MM-DD.
statusstringAccount status, e.g. Active.
request_idstringUnique id for this request.

Example

curl
curl https://ratifai.app/hook/FlowClick/FlowVerify/v1/credits -H 'Authorization: Bearer YOUR_API_KEY'
Python
import requests

resp = requests.get(
    "https://ratifai.app/hook/FlowClick/FlowVerify/v1/credits",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(resp.json())
JavaScript
const resp = await fetch(
  "https://ratifai.app/hook/FlowClick/FlowVerify/v1/credits",
  { headers: { "Authorization": "Bearer YOUR_API_KEY" } },
);
const data = await resp.json();
console.log(data);
Response
{
  "plan": "Clean Pro",
  "credits_balance": 24991,
  "payg_credits": 0,
  "balance": 24991,
  "included_allowance": 25000,
  "period": "2026-08",
  "period_reset": "2026-09-01",
  "status": "Active",
  "request_id": "req_1785952911305"
}

GET/v1/lists

Returns a health report for one of your lists — grade, verdict distribution, deliverable percentage, decay since the last check, and the enrichment lift we have added. Health is computed from validation results you already have, so it never re-validates a single address and never costs a credit. The list is identified by its id as a query parameter, the same convention as /v1/jobs (the webhook path exposes no trailing path segments). Requesting a list your key does not own returns 404 list_not_found — never a 403, which would confirm the id exists.

Query parameters

ParameterTypeRequiredDescription
idstringYesThe list id, e.g. LST-00001.

Grade bands

The letter grade maps the list’s deliverable percentage to A–F using published bands. The thresholds live in editable configuration (kv namespace fv_config, key grade_bands), not in code, so you can reproduce any grade from your own numbers:

GradeDeliverable %
A≥ 90
B≥ 75
C≥ 60
D≥ 40
F< 40

Response

FieldTypeDescription
gradestringThe overall grade, AF, from the bands above.
totalintegerRecords in the list.
distributionobjectCounts by verdict: deliverable, undeliverable, risky, unknown.
deliverable_pctnumberDeliverable records as a percentage of the total.
decayobjectChange since the previous health check: since_last_check_days, newly_undeliverable, decay_pct. On a list checked only once it is { "status": "not_enough_history" } — decay needs a prior check to compare against.
enrichment_liftobjectField coverage before (as you supplied it) vs after enrichment, per family (phone, company, address, name_parsed), plus fields_added, records_improved and lift_pct.
risksarrayFlags present in the list, any of disposable_domains, role_addresses, typo_domains.
computed_atstringWhen the report was computed.
billablebooleanAlways false — health is free.
request_idstringUnique id for this request, also in X-Request-Id.

Example

curl
curl 'https://ratifai.app/hook/FlowClick/FlowVerify/v1/lists?id=LST-00001' -H 'Authorization: Bearer YOUR_API_KEY'
Response
{
  "grade": "B",
  "total": 9000,
  "distribution": { "deliverable": 7412, "undeliverable": 981, "risky": 402, "unknown": 205 },
  "deliverable_pct": 82.4,
  "decay": { "since_last_check_days": 94, "newly_undeliverable": 143, "decay_pct": 1.6 },
  "enrichment_lift": {
    "before": { "phone": 1204, "company": 880, "address": 402, "name_parsed": 6100 },
    "after":  { "phone": 5840, "company": 7102, "address": 3990, "name_parsed": 9820 },
    "fields_added": 18166,
    "records_improved": 8104,
    "lift_pct": 50.5
  },
  "risks": ["disposable_domains", "role_addresses", "typo_domains"],
  "computed_at": "2026-08-06T00:00:00Z",
  "billable": false,
  "cost": "free - computing list health never uses credits",
  "request_id": "req_1785974460465"
}

POST/v1/evaluate

The free List Evaluator. Point it at a list of addresses and it tells you what is wrong with the list before you spend anything. It runs at syntax depth only — it makes no DNS or SMTP calls, writes nothing, and never costs a credit. It is the one endpoint you can call without an API key.

Because it is public it is rate limited. Called anonymously (no key) it is limited to 10 requests per minute per IP and a maximum of 5,000 addresses per request — over the cap returns 413 payload_too_large. Send a bearer key and your plan’s per-key limit applies instead and the size cap is lifted; for very large lists use /v1/files.

Request body

Send either a JSON object { "emails": [ ... ] } (or { "addresses": [ ... ] }), or a raw CSV / newline-delimited list — the address column is auto-detected and a header row is ignored.

Response

FieldTypeDescription
totalintegerAddresses found in the body.
uniqueintegerDistinct addresses (case-insensitive).
exact_duplicatesintegertotal minus unique.
syntactically_invalidintegerAddresses that are not well-formed.
disposableintegerAddresses on known disposable / burner domains.
role_addressesintegerRole mailboxes such as info@, sales@, admin@.
typo_domainsintegerAddresses on likely-mistyped domains.
suggested_correctionsarrayPer typo domain, the correction we would suggest: { domain, suggestion }.
domain_distributionarrayThe most common domains: { domain, count }, up to five.
top_domain_concentration_pctintegerShare of the list held by those top domains.
gradestringAn overall grade using the same published bands as /v1/lists.
quality_pctnumberThe percentage the grade is based on.
limitationsstringWhat this check cannot tell you — namely whether the mailboxes actually exist.
upgradestringHow to confirm deliverability (/v1/validate or /v1/files).
billablebooleanAlways false.
request_idstringUnique id for this request.

Example

curl (no API key required)
curl https://ratifai.app/hook/FlowClick/FlowVerify/v1/evaluate -H 'Content-Type: application/json' -d '{"emails":["a@gmail.com","a@gmail.com","info@x.com","t@mailinator.com","z@gmial.com","bad@nodot"]}'
Response
{
  "total": 6,
  "unique": 5,
  "exact_duplicates": 1,
  "syntactically_invalid": 1,
  "disposable": 1,
  "role_addresses": 1,
  "typo_domains": 1,
  "grade": "D",
  "quality_pct": 50.0,
  "top_domain_concentration_pct": 100,
  "domain_distribution": [{ "domain": "gmail.com", "count": 2 }, { "domain": "x.com", "count": 1 }, { "domain": "mailinator.com", "count": 1 }, { "domain": "gmial.com", "count": 1 }, { "domain": "nodot", "count": 1 }],
  "suggested_corrections": [{ "domain": "gmial.com", "suggestion": "gmail.com" }],
  "limitations": "This free evaluation checks address format and list hygiene only. It cannot tell you whether these mailboxes actually exist or accept mail - that requires a verification run.",
  "upgrade": "To confirm deliverability, run these addresses through POST /v1/validate (single) or POST /v1/files (bulk), which probe MX and the SMTP mailbox.",
  "depth": "syntax",
  "billable": false,
  "request_id": "req_1785974430429"
}

Errors

Every error uses one envelope — { error: { code, message, doc_url }, request_id } — and its doc_url links to a stable anchor on the Errors page.

HTTPCodeMeaning
400invalid_requestThe request was malformed.
400file_emptyAn uploaded file has no data rows.
400batch_too_largeThe emails array exceeds 10,000; use /v1/files.
401invalid_api_keyMissing, malformed or expired key.
401revoked_api_keyThe key has been revoked.
402insufficient_creditsNo credits left for a billable check.
404not_foundNo such resource for your account.
404job_not_foundNo job with that id exists for your account.
404list_not_foundNo list with that id exists for your account.
409job_not_readyThe job result is not ready yet; keep polling.
413list_too_largeAn uploaded file exceeds your plan’s list-size limit.
413payload_too_largeThe request body has too many addresses for /v1/evaluate; reduce it or use /v1/files.
422no_email_columnNo email column could be detected in an uploaded file.
429rate_limitedPer-key rate limit exceeded; see Retry-After.
500internal_errorSomething failed on our side; safe to retry. A billable check that errors is not charged.

Credits

You spend one credit per answer. Unknown results and syntax-depth checks are free. The full rules are on How billing works; read your live balance any time with /v1/credits. Bulk jobs are metered per billable row as they run.

This reference is also published as a machine-readable OpenAPI document.

Continuous hygiene — POST /v1/hygiene

Register a list for continuous hygiene, turn it off, or check its status. When a list is registered, VerifAi re-validates it automatically on your chosen cadence and sends a signed hygiene.decay webhook whenever addresses go bad, with the count, the addresses, and what it would have cost to send to them. A record is “under management” when it belongs to a list with an active hygiene registration; re-checking those records is free and unlimited. Registration itself costs no credits. Adding a new record to a registered list is a new record and meters normally.

Body: list_id (required), action = register | unregister | status (default status), cadence = 30|60|90 days (register only, default 30). Hygiene requires a paid plan; Clean allows one hygiene list up to 25,000 records, Clean Pro and above are unlimited in count up to their size cap. Over a limit returns 403 naming the limit and the upgrade; a list that is not yours returns 404.

curl -X POST https://ratifai.app/hook/FlowClick/FlowVerify/v1/hygiene \n  -H "Authorization: Bearer $VERIFAI_KEY" \n  -H "Content-Type: application/json" \n  -d '{"list_id":"LST-00007","action":"register","cadence":"30"}'

Returns the current state, e.g. {"list_id":"LST-00007","hygiene":"active","cadence_days":30,"next_run":"...","record_count":1234,"plan":"Clean Pro","under_management_free":true}

Email finder — POST /v1/find

Find a specific named person’s work email from their name and company domain. VerifAi determines the domain’s address pattern cheapest-source-first — its own verified corpus (addresses we have actually probed, not scraped guesses), then a learned-pattern cache, then generic ranked patterns (first.last, flast, first, f.last, firstlast, lastf, first_last) — and probes candidates over SMTP, stopping at the first deliverable. Probes per find are capped by your plan (5 by default).

Catch-all honesty. A catch-all domain accepts every address, so probing cannot confirm any one of them. VerifAi detects catch-all first and returns the most likely address with verified:false, a stated confidence and a reason — never a confident-looking address that bounces.

Bundled, not per hit. A find deducts zero credits. It is gated by plan (paid plans) and by the same per-key, per-minute rate limit shared across REST and MCP. The finder locates a named person’s address; it will not enumerate a domain, and sweeping many names hits the rate limit.

Body: first_name (required), last_name (required), and either domain or company (a company name is resolved to a domain, echoed back as domain_used). pattern_source is corpus, learned_cache or generic.

Example

curl
curl https://ratifai.app/hook/FlowClick/FlowVerify/v1/find -H 'Authorization: Bearer YOUR_API_KEY' -H 'Content-Type: application/json' -d '{"first_name":"Dan","last_name":"Ludowise","domain":"example.com"}'

A verified result — an address a mail server confirmed:

{
  "email": "dan.ludowise@example.com",
  "found": true,
  "verified": true,
  "confidence": 0.93,
  "pattern": "first.last",
  "pattern_source": "corpus",
  "pattern_evidence_count": 41,
  "candidates_probed": 1,
  "alternatives": [
    "dludowise@example.com"
  ],
  "reason": "Pattern inferred from 41 previously verified addresses at this domain; candidate accepted by the mail server.",
  "catch_all": false,
  "corpus_hit": true,
  "request_id": "req_01J...",
  "domain_used": "example.com"
}

On a catch-all domain, an honest best guess instead of a false positive:

{
  "email": "jane.doe@example.com",
  "found": true,
  "verified": false,
  "confidence": 0.35,
  "pattern": "first.last",
  "pattern_source": "generic",
  "pattern_evidence_count": 0,
  "candidates_probed": 0,
  "alternatives": [],
  "reason": "This domain is catch-all: it accepts all mail, so no address can be confirmed by probing. Returning the most likely address from generic evidence (unverified).",
  "catch_all": true,
  "corpus_hit": false,
  "request_id": "req_01J...",
  "domain_used": "example.com"
}

A find that confirms nothing returns found:false with a reason — it does not return a guess dressed as a result. The full field reference is in the OpenAPI document.

← All docs