Integrate email validation into your application with a simple REST API. Validate single addresses in real time or process entire lists in bulk.
The same validation runs over the Model Context Protocol, so Claude, Cursor, or any MCP client can call it directly —
OAuth sign-in, no API key. Point your client at
https://mcp.mailrook.com/mcp.
Validate an email address with a single request:
All API requests require a Bearer token in the Authorization header. Generate your API key from the dashboard after signing up.
/v1/validate/{email}
Validates one email address and returns detailed verification results. Typical response time is under 500ms.
email
The email address to validate (e.g. [email protected])
include
Comma-separated list of extra data to include. Currently supported: enrichment. When provided, an enrichment object is added to the response containing the same blocks as /v1/enrich. fields and include=category_detail work here too and apply to that block.
{
"data": {
"id": "673603dc-42da-474c-98cf-531ce11b1bf2",
"email": "[email protected]",
"normalized_email": "[email protected]",
"username": "elon",
"domain": "x.com",
"mx_record": "aspmx.l.google.com",
"provider": "google_workspace",
"score": 80,
"isv_format": true,
"isv_domain": true,
"isv_deliverable": true,
"isv_nocatchall": false,
"isv_nogeneric": true,
"isv_nodisposable": true,
"isv_nofreeemail": false,
"isv_nouniversity": true,
"result": "risky",
"reason": "catch_all",
"reason_label": "Catch-all domain (accepts all emails)",
"email_security": [],
"protection_level": "basic",
"domain_info": {
"has_spf": true,
"has_dmarc": true,
"has_website": true,
"domain_age_days": 8450,
"domain_created_at": "2003-03-17T00:00:00+00:00",
"domain_expires_at": "2026-03-17T00:00:00+00:00",
"registrar": "CSC Corporate Domains, Inc.",
"registrant_org": "X Corp.",
"registrant_country": "US",
"ns_hosts": [
"ns1.p09.dynect.net",
"ns2.p09.dynect.net"
],
"a_record_ip": "104.244.42.193",
"asn": "AS13414",
"asn_org": "Twitter Inc.",
"email_security": [],
"protection_level": "basic"
}
},
"code": 0,
"message": "ok"
}
{
"data": {
"id": "673603dc-42da-474c-98cf-531ce11b1bf2",
"email": "[email protected]",
"normalized_email": "[email protected]",
"username": "elon",
"domain": "x.com",
"mx_record": "aspmx.l.google.com",
"provider": "google_workspace",
"score": 80,
"isv_format": true,
"isv_domain": true,
"isv_deliverable": true,
"isv_nocatchall": false,
"isv_nogeneric": true,
"isv_nodisposable": true,
"isv_nofreeemail": false,
"isv_nouniversity": true,
"result": "risky",
"reason": "catch_all",
"reason_label": "Catch-all domain (accepts all emails)",
"email_security": [],
"protection_level": "basic",
"domain_info": {
"has_spf": true,
"has_dmarc": true,
"has_website": true,
"domain_age_days": 8450,
"domain_created_at": "2003-03-17T00:00:00+00:00",
"domain_expires_at": "2026-03-17T00:00:00+00:00",
"registrar": "CSC Corporate Domains, Inc.",
"registrant_org": "X Corp.",
"registrant_country": "US",
"ns_hosts": [
"ns1.p09.dynect.net",
"ns2.p09.dynect.net"
],
"a_record_ip": "104.244.42.193",
"asn": "AS13414",
"asn_org": "Twitter Inc.",
"email_security": [],
"protection_level": "basic"
},
"enrichment": {
"domain": "glossier.com",
"website": {
"url": "https://glossier.com",
"title": "Glossier",
"is_reachable": true,
"is_crawl_blocked": false
},
"tech_stack": {
"cms": "wordpress",
"ecommerce": null,
"pixels": [
"meta_pixel"
],
"analytics": [
"google_analytics"
],
"marketing": [],
"chat": [],
"affiliate": [],
"email_security": [
"proofpoint"
]
},
"social_links": {
"twitter": "glossier",
"instagram": "glossier",
"linkedin": "company/glossier"
},
"company": {
"name": "Glossier",
"industry": "Cosmetics",
"size": "201-500",
"type": "Private",
"founded": 2014,
"city": "New York",
"state": "NY",
"country_code": "US"
},
"category": {
"level": "niche",
"source": "website",
"sector": "beauty",
"sector_label": "Beauty & Personal Care",
"niche": "beauty.makeup",
"niche_label": "Makeup & colour cosmetics brand",
"customer_type": "b2c",
"customer_type_label": "Consumers",
"tags": [
"sells_online",
"d2c_brand"
],
"tag_labels": [
"Sells online",
"D2C brand"
],
"confidence": 0.93,
"reason": null,
"reason_label": null,
"checked_at": "2026-09-22T09:14:03+00:00",
"taxonomy_version": "2026-09-22"
},
"dns": {
"has_spf": true,
"has_dmarc": true,
"ns_hosts": [
"ns1.example.com"
],
"a_record_ip": "93.184.216.34",
"asn": "AS15133",
"asn_org": "Edgecast Inc."
},
"whois": {
"registrar": "RESERVED-Internet Assigned Numbers Authority",
"registrant_org": "IANA",
"registrant_country": "US",
"domain_created_at": "1995-08-14T00:00:00+00:00",
"domain_expires_at": "2025-08-13T00:00:00+00:00",
"domain_age_days": 10818
}
}
},
"code": 0,
"message": "ok"
}
/v1/validate/batch
Submit an array of email addresses for bulk validation. Returns a list ID that you can poll for results.
emails
Array of email addresses to validate
{
"data": {
"list_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"emails_count": 2
},
"code": 0,
"message": "ok"
}
/v1/validate/batch/upload
Upload a CSV or TXT file containing one email per line. Maximum file size is 10 MB.
file
A .csv or .txt file with one email address per line (max 10 MB)
{
"data": {
"list_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"emails_count": 150
},
"code": 0,
"message": "ok"
}
/v1/validate/list/{list_id}
Retrieve the results of a batch validation. Returns 202 while processing is in progress and 200 when complete.
list_id
The list ID returned from the batch submit or upload endpoint
{
"data": [],
"code": 0,
"message": "List is still processing: 45 of 100 emails done"
}
{
"data": [
{
"email": "[email protected]",
"isv_format": true,
"isv_domain": true,
"isv_deliverable": true,
"isv_nocatchall": true,
"isv_nogeneric": true,
"isv_nodisposable": true,
"isv_nofreeemail": true,
"isv_nouniversity": true,
"protection_level": "enterprise"
}
],
"code": 0,
"message": "ok"
}
/v1/enrich/{input}
Everything we know about the company behind a domain, without running a deliverability check: what it sells (category), its website and tech stack, the company record, DNS and WHOIS. Results are stored, so the same domain returns the same answer instantly on the next call.
input
An email address (e.g. [email protected]) or domain (e.g. example.com). When an email is provided, the domain is extracted automatically.
fields
Comma-separated list of blocks to return: website, tech_stack, social_links, company, category, dns, whois. Omit it and you get all of them. A block you leave out is absent from the response rather than null — and leaving out category is the one that makes a call measurably faster, because it is the only block behind a classifier call. An unknown value is a 422, so a typo never silently bills you for work you tried to skip.
include
Set to category_detail to add category.detail: the probabilities, the runner-up niches and the per-tag scores behind the verdict, for callers who want to apply their own thresholds. Absent (null) when the category did not come from the website.
{
"data": {
"domain": "glossier.com",
"website": {
"url": "https://glossier.com",
"title": "Glossier",
"is_reachable": true,
"is_crawl_blocked": false
},
"tech_stack": {
"cms": "wordpress",
"ecommerce": null,
"pixels": [
"meta_pixel"
],
"analytics": [
"google_analytics"
],
"marketing": [],
"chat": [],
"affiliate": [],
"email_security": [
"proofpoint"
]
},
"social_links": {
"twitter": "glossier",
"instagram": "glossier",
"linkedin": "company/glossier"
},
"company": {
"name": "Glossier",
"industry": "Cosmetics",
"size": "201-500",
"type": "Private",
"founded": 2014,
"city": "New York",
"state": "NY",
"country_code": "US"
},
"category": {
"level": "niche",
"source": "website",
"sector": "beauty",
"sector_label": "Beauty & Personal Care",
"niche": "beauty.makeup",
"niche_label": "Makeup & colour cosmetics brand",
"customer_type": "b2c",
"customer_type_label": "Consumers",
"tags": [
"sells_online",
"d2c_brand"
],
"tag_labels": [
"Sells online",
"D2C brand"
],
"confidence": 0.93,
"reason": null,
"reason_label": null,
"checked_at": "2026-09-22T09:14:03+00:00",
"taxonomy_version": "2026-09-22"
},
"dns": {
"has_spf": true,
"has_dmarc": true,
"ns_hosts": [
"ns1.example.com"
],
"a_record_ip": "93.184.216.34",
"asn": "AS15133",
"asn_org": "Edgecast Inc."
},
"whois": {
"registrar": "RESERVED-Internet Assigned Numbers Authority",
"registrant_org": "IANA",
"registrant_country": "US",
"domain_created_at": "1995-08-14T00:00:00+00:00",
"domain_expires_at": "2025-08-13T00:00:00+00:00",
"domain_age_days": 10818
}
},
"code": 0,
"message": "ok"
}
/v1/taxonomy/company-categories
The full value list behind the category block: every sector, every niche, every business-model tag and every customer type, with a human label and a one-line description each. Static, free, and unmetered — fetch it once to build a dropdown or a set of CRM properties. version changes only when a key is added, renamed or removed, so compare it against the taxonomy_version on any enrichment response to know whether your copy is current.
{
"data": {
"version": "2026-09-22",
"sector_count": 25,
"niche_count": 224,
"sectors": [
{
"key": "beauty",
"label": "Beauty & Personal Care",
"description": "\u2026 (one line per sector)",
"niches": [
{
"key": "beauty.skincare",
"label": "Skincare brand",
"description": "\u2026 (one line per niche)"
},
{
"key": "beauty.retailer",
"label": "Multi-brand beauty retailer",
"description": "\u2026"
},
"\u2026 11 more"
]
},
"\u2026 24 more sectors"
],
"tags": [
{
"key": "d2c_brand",
"label": "D2C brand",
"description": "\u2026"
},
"\u2026 12 more"
],
"customer_types": [
{
"key": "b2c",
"label": "Consumers",
"description": "Sells mainly to individual consumers and households"
},
{
"key": "b2b",
"label": "Businesses",
"description": "\u2026"
},
{
"key": "both",
"label": "Consumers and businesses",
"description": "\u2026"
},
{
"key": "not_applicable",
"label": "Not a seller",
"description": "\u2026"
}
]
},
"code": 0,
"message": "ok"
}
The enrichment object is returned by the /v1/enrich/{input} endpoint and optionally included in validation responses via ?include=enrichment.
| Field | Type | Description |
|---|---|---|
| domain | string | The domain that was enriched |
| category | ||
| category.level | string | How precisely the company was placed, and therefore which of the two keys below you can trust: niche (both), sector (sector only — two neighbouring niches split the vote), unclear (neither; see reason). Nothing is forced into a category to look complete. |
| category.source | string | What the answer was read from. website: the company's own homepage, the answer you want. domain_name: no usable page text, but the brand was recognised from the domain. company_record: the homepage said nothing, so the industry on our imported company record was mapped onto this taxonomy — coarser and older, never more than a sector or an obvious niche. none: nothing placed it. |
| category.sector | string|null | Sector key, e.g. beauty. The value a CRM "Industry" field wants. Null when level is unclear. |
| category.sector_label | string|null | Display name, e.g. "Beauty & Personal Care". Labels can be reworded; store and filter on the key. |
| category.niche | string|null | Niche key in sector.niche form, e.g. beauty.skincare — the sub-segment a sequence is written for. Set only when level is niche. |
| category.niche_label | string|null | Display name, e.g. "Skincare brand". |
| category.customer_type | string|null | Who they sell to: b2c, b2b or both. Null when unclear, and always null for a company_record answer, which carries no evidence about customers. |
| category.customer_type_label | string|null | Display name: "Consumers", "Businesses", "Consumers and businesses". |
| category.tags | array | Business-model tag keys that apply, e.g. ["sells_online", "d2c_brand"]. Independent of the sector and of each other: a D2C skincare brand and Sephora share a sector, and this is what separates them. Empty for a company_record answer. |
| category.tag_labels | array | The same tags as display names, in the same order. |
| category.confidence | number|null | 0–1 confidence in the niche. Null for a company_record answer — a lookup table has no probability behind it, and inventing one would invite comparison with a number that means something else. |
| category.reason | string|null | Why nothing was placed, when level is unclear: crawl_blocked, no_website, parked_page, personal_site, weak_homepage, too_vague, classifier_unavailable. The last one is temporary and worth retrying; the others are about the site itself. |
| category.reason_label | string|null | The same reason as a sentence you can show a user. |
| category.checked_at | string|null | When this category was determined (ISO 8601). Stored, so a repeat call returns the same answer and the same timestamp until the homepage is crawled again. |
| category.taxonomy_version | string | Version of the key list this answer uses. Compare with /v1/taxonomy/company-categories. |
| category.detail | object|null | Only with ?include=category_detail. Carries basis, evidence_score, niche_probability, sector_probability, the top three candidates and a probability per tag in signals, for applying your own thresholds. Null unless source is website or domain_name. |
| website | ||
| website.url | string|null | Resolved website URL |
| website.title | string|null | Page title from the website |
| website.is_reachable | boolean | Whether the website is reachable |
| website.is_crawl_blocked | boolean | Whether crawling is blocked by the website |
| tech_stack | ||
| tech_stack.cms | string|null | Detected CMS (e.g. wordpress, shopify) |
| tech_stack.ecommerce | string|null | Detected e-commerce platform |
| tech_stack.pixels | array | Tracking pixels detected (e.g. meta_pixel, tiktok_pixel) |
| tech_stack.analytics | array | Analytics tools (e.g. google_analytics, hotjar) |
| tech_stack.marketing | array | Marketing tools (e.g. hubspot, mailchimp) |
| tech_stack.chat | array | Chat/support widgets (e.g. intercom, zendesk) |
| tech_stack.affiliate | array | Affiliate/referral tools |
| tech_stack.email_security | array | Email security providers detected from MX records (e.g. proofpoint, mimecast) |
| social_links | ||
| social_links | object|null | Social media handles found on the website. Keys: twitter, instagram, facebook, youtube, tiktok, linkedin, github. Values are normalized handles/paths (e.g. "acme", "company/acme", "@acme"). |
| company | ||
| company.name | string|null | Company name |
| company.industry | string|null | Industry classification |
| company.size | string|null | Employee count range (e.g. 51-200) |
| company.type | string|null | Company type (e.g. Private, Public) |
| company.founded | integer|null | Year founded |
| company.city | string|null | City |
| company.state | string|null | State or region |
| company.country_code | string|null | ISO 3166-1 alpha-2 country code |
| dns | ||
| dns.has_spf | boolean|null | Domain has an SPF record |
| dns.has_dmarc | boolean|null | Domain has a DMARC record |
| dns.ns_hosts | array|null | Nameserver hostnames |
| dns.a_record_ip | string|null | Primary A record IP address |
| dns.asn | string|null | Autonomous System Number |
| dns.asn_org | string|null | ASN organization name |
| whois | ||
| whois.registrar | string|null | Domain registrar |
| whois.registrant_org | string|null | Registrant organization |
| whois.registrant_country | string|null | Registrant country code |
| whois.domain_created_at | string|null | Domain registration date (ISO 8601) |
| whois.domain_expires_at | string|null | Domain expiration date (ISO 8601) |
| whois.domain_age_days | integer|null | Domain age in days since registration |
The category block answers four questions about the
company behind a domain, read from its own homepage. They are separate on purpose: a D2C skincare brand and
Sephora share a sector, and it is the other three that tell you which pitch to send.
25 values — what a CRM "Industry" field wants. beauty, software, food.
224 values, keyed sector.niche so the sector always falls out of the niche. This is the level a sequence is written for: a skincare brand and a salon-supply distributor are not the same prospect.
b2c Consumers · b2b Businesses · both Consumers and businesses · not_applicable Not a seller
13 independent yes/no business-model flags, cutting across every sector. A brand can be a makeup brand and premium and sold direct; these are three answers, not one category.
Read level before anything else: it tells you which keys are safe to use. Nothing is forced into a category to make the response look complete.
| level | Meaning |
|---|---|
| niche | Both sector and niche are set and safe to route on. Around 90% of domains with a readable homepage land here. |
| sector | sector is set, niche is null: two neighbouring niches split the vote — a soap-and-skincare brand, a furniture maker that is also a store. Use the sector; ?include=category_detail names both candidates if you want to pick one. |
| unclear | Neither is set, and reason says why. Better an honest blank than a wrong industry in your CRM. A classifier_unavailable reason is the only temporary one — retry that domain later. |
source tells you how much the answer is worth, which
level alone cannot. A sector read off a homepage this month
and a sector mapped from a LinkedIn industry field typed years ago are both "sector" — and you may well want to treat them differently.
| source | Meaning |
|---|---|
| website | Read from the company's own homepage — title, description, headlines, first paragraph. The answer you want, and the most current description of the business that exists. |
| domain_name | The site blocked our crawler or had no usable text, but the brand was recognised from the domain itself. Held to a stricter confidence bar than a page read, because there is nothing to check it against. |
| company_record | The homepage said nothing usable, so the LinkedIn-style industry on our company record was mapped onto this taxonomy. A fallback: coarser, older, usually sector-level, and always with confidence: null, tags: [] and customer_type: null, because an industry label is no evidence about those. |
| none | Nothing placed it. level is unclear and reason says why. |
| Key | Label | On when |
|---|---|---|
| sells_online | Sells online | The site itself sells: product pages with prices, add-to-cart, checkout, or paid online booking. An e-commerce platform such as Shopify or WooCommerce in `tech` is strong evidence. |
| d2c_brand | D2C brand | Own-brand physical goods (cosmetics, apparel, food, gadgets, supplements) sold to shoppers on the brand's own site, whether or not also sold in stores. |
| multibrand_retailer | Multi-brand retailer | Multi-brand shop, marketplace, department store, pharmacy chain, grocery, distributor: most products carry other companies' brand names. |
| marketplace | Marketplace | Third-party sellers, creators, hosts, drivers or vendors list on it (Etsy, Airbnb, Upwork, Amazon Marketplace). |
| saas | SaaS | Software product with plans, pricing, login, features, integrations, or API as the core offering. |
| mobile_app | Mobile app | The site mainly promotes downloading its app; the app is the product. |
| services_agency | Services / agency | Agencies, consultants, law or accounting firms, studios, contractors, freelancers, clinics, salons. |
| manufacturer_wholesale | Manufacturer / wholesale | Factory, contract manufacturer, ingredients supplier, wholesale-only, "trade customers", minimum order quantities, industrial catalogue. |
| local_physical_business | Physical locations | Addresses, opening hours, "visit us", store locator, local city named as the service area, appointments in person. |
| nonprofit_public | Nonprofit / public | Charity, NGO, foundation, public agency, municipality, public school or university, religious organisation, association. |
| restricted_category | Restricted category | Pornography, casino or betting, cigarettes or vapes, cannabis or CBD products, guns and ammunition, crypto trading or tokens. |
| natural_clean_positioning | Natural / clean | Natural/organic/clean/vegan/non-toxic is the headline claim across the range (clean beauty, organic food, natural deodorant). |
| premium_luxury | Premium / luxury | Luxury language, high prices, designer, couture, bespoke, prestige, "finest", flagship boutiques. |
All 24 sectors below, plus other for pages that are not a
business at all (parked, personal, unidentifiable). Consumer categories go deep because that is where a generic industry label fails hardest; B2B
categories are broad by design. Fetch the full list with descriptions from
/v1/taxonomy/company-categories.
beauty
fashion
health
wellness
food
home
electronics
gaming
media
software
internet
marketing
finance
retail
travel
automotive
sports
pets
family
education
realestate
services
industrial
public
sector,
niche and
tags; the
*_label twins are display strings and may be reworded at any time.
taxonomy_version (currently
2026-09-22) changes only when a key is
added, renamed or removed — never for a wording change — so comparing that one string is enough to know whether your copy is stale.
Want to see it on your own domains first? Try the free category check.
Both /v1/enrich/{input} and /v1/validate/{email}?include=enrichment support third-party enrichment providers for deeper data. Pass provider API keys via the X-Enrich-Providers header as a JSON object:
Provider keys are never stored — they are only used for the current request. More providers will be added over time.
| Field | Type | Description |
|---|---|---|
| id | string | Unique identifier for this validation |
| string | The email address that was validated | |
| normalized_email | string | Normalized form of the email (dots removed, +tags stripped, alias domains canonicalized) |
| username | string | Local part of the email (before @) |
| domain | string | Domain part of the email (after @) |
| mx_record | string|null | Primary MX record for the domain |
| provider | string|null | Detected email provider (e.g. gmail, google_workspace, microsoft_365, outlook, yahoo, protonmail, icloud, zoho, yandex, mailru, proofpoint, mimecast, aol, fastmail). Null if unknown. |
| score | integer | Overall quality score from 0 to 100 |
| isv_format | boolean | Email has valid syntax (RFC compliant) |
| isv_domain | boolean | Domain exists and has valid DNS records |
| isv_deliverable | boolean|null | Mail server accepts email for this address. Null if unable to verify. |
| isv_nocatchall | boolean|null | Domain is not a catch-all (accepts mail for any address) |
| isv_nogeneric | boolean | Not a generic/role address (e.g. info@, support@) |
| isv_nodisposable | boolean | Not a disposable/temporary email address |
| isv_nofreeemail | boolean | Not from a free email provider (e.g. Gmail, Yahoo) |
| isv_nouniversity | boolean | Not from an educational institution domain |
| result | string | Overall result: deliverable, undeliverable, risky, or unknown |
| reason | string | Reason code: accepted_email, rejected_email, invalid_format, invalid_domain, disposable_email, catch_all, unable_to_verify, sender_blocked |
| reason_label | string|null | Human-readable reason label (e.g. "Mailbox not found"). Null for accepted_email. |
| email_security | array | Email security gateways detected from MX records (e.g. proofpoint, mimecast, barracuda). Empty array if none detected. |
| protection_level | string | Synthesized email protection level: none (no gateway or DMARC enforcement), basic (SPF + strict DMARC policy), or enterprise (dedicated security gateway like Proofpoint or Mimecast). |
| domain_info | object|null | Domain WHOIS and DNS information. Null if domain data is not yet available. |
| domain_info.has_spf | boolean|null | Domain has an SPF record |
| domain_info.has_dmarc | boolean|null | Domain has a DMARC record |
| domain_info.has_website | boolean|null | Domain has a reachable website |
| domain_info.domain_age_days | integer|null | Domain age in days since registration |
| domain_info.domain_created_at | string|null | Domain registration date (ISO 8601) |
| domain_info.domain_expires_at | string|null | Domain expiration date (ISO 8601) |
| domain_info.registrar | string|null | Domain registrar |
| domain_info.registrant_org | string|null | Registrant organization |
| domain_info.registrant_country | string|null | Registrant country code |
| domain_info.ns_hosts | array|null | Nameserver hostnames |
| domain_info.a_record_ip | string|null | Primary A record IP address |
| domain_info.asn | string|null | Autonomous System Number |
| domain_info.asn_org | string|null | ASN organization name |
| domain_info.email_security | array | Email security gateways (same as top-level email_security) |
| domain_info.protection_level | string | Protection level (same as top-level protection_level) |
Calls are metered in credits. Every authenticated response carries your balance, so an integration can pace itself without a second request.
| Operation | Credits | Notes |
|---|---|---|
| GET /v1/validate/{email} | 1 | Per address. |
| GET /v1/validate/{email}?include=enrichment | 3 | The validation plus the enrichment lookup. |
| GET /v1/enrich/{input} | 2 | Per domain, whatever fields you ask for — the company category costs no more than the rest, and a domain already in our index costs the same as a cold one. |
| GET /v1/taxonomy/company-categories | 0 | Free and unmetered. It is a static list; fetch it once and cache it against its version. |
| POST /v1/validate/batch | 1 | Per unique address. The list is de-duplicated first, and the whole batch is charged up front — a batch you cannot afford is rejected with 402 and nothing is charged. |
| POST /v1/validate/batch/upload | 1 | Per unique address, charged up front, same as the JSON batch. |
| GET /v1/validate/list/{list_id} | 0 | Reading results is free — the batch was already charged on submission, so polling costs nothing. |
Returned on every authenticated response, including the 402. Credits are spent soonest-to-expire first: the daily allowance, then the plan allowance, then purchased credits.
| Header | Type | Description |
|---|---|---|
| X-Credits-Remaining | integer | Everything spendable right now: today's free allowance, plus whatever the plan has left this period, plus purchased credits. This is the one number to poll — it is what runs out. |
| X-Credits-Free-Today | integer | Of that total, how much is today's free allowance. Resets at midnight UTC. |
| X-Credits-Plan | integer | Of that total, how much of the plan's monthly allowance is left this billing period. Absent on accounts with no plan. |
| X-Credits-Paid | integer | Of that total, the purchased balance. Purchased credits never expire and are spent last. |
| X-Plan | string | Key of the active plan. Absent on accounts with no plan. |
The three bucket headers add up to X-Credits-Remaining; read the total unless you need to know which bucket a call will draw from. Headers for a bucket you do not have are omitted rather than sent as zero, so treat a missing header as "not applicable", not as "empty".
Once your balance cannot cover a call, it returns 402 and nothing is charged. The body says which allowance ran out and when it comes back, so an integration can back off until then rather than retrying into a wall:
{
"data": null,
"code": 402,
"message": "Insufficient credits to validate",
"details": {
"reason": "free_allowance_exhausted",
"plan": null,
"daily_free_allowance": 25,
"free_resets_at": "2026-01-02T00:00:00+00:00",
"hint": "Your free allowance for today is used up and it resets at midnight. A plan gives you a monthly allowance instead.",
"billing_url": "https://mailrook.com/billing"
}
}
| details.reason | Meaning |
|---|---|
| free_allowance_exhausted | No plan on the account and today's free allowance is spent. It comes back at free_resets_at. |
| plan_allowance_exhausted | The plan's allowance for this billing period is spent and there is no purchased balance to fall back on. |
Every response carries the same envelope: data, a numeric code, and a message. On success code is 0; on failure it mirrors the HTTP status, and data is null.
| HTTP Status | Meaning |
|---|---|
| 200 | Success. |
| 201 | Batch accepted. The body carries the list_id to poll. |
| 202 | Batch still processing. The message reports how many of the addresses are done; poll again. |
| 401 | Missing or invalid API key. |
| 402 | Out of credits. See the credits section below — the body says which allowance ran out and when it comes back. |
| 404 | Unknown list id, or no such endpoint. |
| 422 | Invalid email format, unresolvable domain, or a missing required field. |
| 500 | Internal server error. |
API requests are rate-limited per account. If you exceed your limit, you will receive a 429 response. Contact us if you need higher limits for your use case.