Skip to content

Changelog

What changed in Spaw, newest first. Every entry names the endpoints and fields it touched, so you can search this page for the thing you noticed, and every entry has a page of its own you can link.

atom feed · /changelog.atom

2026-10-08

Relay aliases scored on what they show, 23 more alias domains, and Amazon SES told apart from the rest of AWS

  • mailbox_confidence on a relay alias stays at 50. It used to add the relay operator's DMARC, SPF, mail provider and domain age, and to score the alias's name, which says nothing about whether one alias exists, so a made-up alias at bravealias.com scored 75. Now only evidence against the alias moves it: a bounce rate of 50% or more among the sends reported for the domain takes 20 off, and a plus-tag takes 10. A sending policy with a min_mailbox_confidence above 50 may now hold catch-all relay aliases it used to let through; the default policy sets no floor.

  • 23 more alias domains answer as relays. They are:

    • Surfshark Alternative ID: ten domains, such as flounderfantasy.com and dontsp.am.
    • IronVest Masked Email: opayq.com, and Abine's older abinemail.com, blurmail.net, blurme.net and maskmemail.com.
    • Erine.email: erine.email and erine.eu.
    • Alias Email: bumpmail.io.
    • Guard Email: m.guardemail.com and m1.guardemail.com.
    • Addy.io: 4wrd.cc and addy.to.
    • 33Mail: 33m.co.

    Several sat on the open burner list, so an address there could answer disposable with the 80-point weight and no mailbox check. They now answer is_relay true with their relay_service. Cloaked's identity domains are not listed, because Cloaked keeps identity mail in its own inbox unless the owner turns forwarding on. Lookups already logged were relabelled for the email statistics.

  • Only Amazon's inbound mail hosts read as amazon-ses. Any mail host named under amazonaws.com used to answer mx_provider amazon-ses and mx_provider_type transactional, including an EC2 instance, a load balancer or an SES Mail Manager gateway. Now only inbound-smtp.{region}.amazonaws.com, which SES receiving and WorkMail use, does; the rest answer other. Domain monitors start a fresh provider baseline for the domains they last saw as amazon-ses, so the relabelling sends no alert. The mx-provider-patterns version is 2026-10-08.

  • A delivery report no longer says a domain is not catch-all. An address your account reported delivered answers as a confirmed mailbox. Its catch_all used to read false, even at a domain known to accept every address. It is now null, because the report says nothing about the rest of the domain. The verdict, score and billing are unchanged.

  • Domain pages describe relay domains as alias services. The domain checker pages called a relay domain an organisation's own domain, and called Amazon SES "a transactional or application setup". They now name the relay service and say it forwards each alias to its owner's inbox.

  • The forwarding-services dataset version is 2026-10-08.

2026-10-07

Brave Email Aliases and eight more alias domains answer as relays

  • Brave Email Aliases are relays. An address at bravealias.com, where the Email Aliases built into the Brave browser are issued, answers is_relay true with relay_service "Brave Email Aliases", domain_category relay and the 5-point relay weight. It used to answer is_relay false and domain_category other. The servers that receive Brave's alias mail accept every address at the domain, so a Brave alias still typically answers risky with reason catch_all: the mailbox handshake cannot tell a live alias from a made-up one.
  • Sign in with Apple's new relay domain is flagged before the switch. Apple said in August 2026 that new Sign in with Apple addresses will be issued on private.icloud.com from later this year. Addresses there answer like those at privaterelay.appleid.com, with relay_service "Sign in with Apple private relay". The flag belongs to that exact name, so ordinary icloud.com addresses are unchanged.
  • ExpressMailGuard is a relay. clivaro.io, a shared alias domain of ExpressVPN's ExpressMailGuard, answers with relay_service "ExpressMailGuard", and so do the dedicated subdomains the service hands out under it.
  • More alias domains of services Spaw already knew. mailer.me and addymail.com answer as Addy.io, silomails.com and slmails.com as SimpleLogin, and passmail.com and pass2mail.com as Proton Pass.
  • mailer.me addresses are no longer disposable. mailer.me is on the open burner list Spaw syncs every week, so an Addy.io alias there could answer disposable, with the 80-point weight and no mailbox check. A relay is never disposable, so these addresses are now checked and scored like any other relay.
  • A sending policy that blocks relays now blocks these addresses too. The default policy does not block the relay signal.
  • Lookups already logged were relabelled, so the email statistics count these addresses as relays. The verdict and score each lookup was answered and billed with are unchanged. The forwarding-services dataset version is 2026-10-07.

2026-10-02

iCloud and Proton mailboxes are not relays, LEIs meet their Companies House record, and honest location and freshness grades

  • iCloud Mail and Proton Mail addresses are mailboxes, not relays. Every address at icloud.com, me.com, mac.com, proton.me, protonmail.com, protonmail.ch and pm.me used to answer is_relay true with a relay_service, domain_category relay and the 5-point relay weight. A sending policy that blocks relays blocked them too. They now answer like any free mailbox provider: is_relay false, relay_service null, domain_category webmail. iCloud+ Hide My Email aliases do live on icloud.com, but nothing in the domain separates them from a real iCloud mailbox, so none is flagged. Proton Pass aliases have their own domains and still answer as relays. Lookups already logged were relabelled, so the email statistics do not count these addresses as relays.
  • Sign in with Apple private relay addresses are flagged. An address at privaterelay.appleid.com answers is_relay true with relay_service "Sign in with Apple private relay". Such an address only accepts mail from the sender domains the app registered with Apple.
  • A LEI is now compared with its Companies House record. The register key that links a LEI to its UK company number was stored cut short, so no LEI was ever matched with its Companies House record. LEI answers had no agreements, named only the GLEIF source, and could never raise name_differs_across_registers or status_differs_across_registers. A LEI with a Companies House cross-reference now answers with both records, the agreements between them and either flag where they differ. The same fault had cut long Companies House company categories and very long GLEIF legal names. The categories that can be rebuilt were restored at once; the rest are stored whole from the next weekly register update.
  • location_confidence answers low for anycast, mobile-carrier and satellite addresses. An anycast address is served from many places at once, and a mobile or satellite network declares the location of its gateway or ground station, not the user's. Such an address used to be graded high when its operator declared the location, or by its country's measured agreement. It now answers low whenever is_anycast, is_mobile or is_satellite is true, whatever the location source. Operator-declared locations for other networks are still graded high. The consistency check withholds the IP-to-address distance for these addresses, as it does for any location not graded high.
  • /status calls a dataset current only when its latest scheduled update landed. Each dataset's freshness window now follows its own schedule plus two days' grace, so one missed run shows as stale within days instead of weeks. The lists curated by hand get a review window and are no longer shown as unchecked: the sanctions and FATF lists, the satellite and mobile network lists, and the address formats and rules. The FATF window counts from the plenary the lists reflect. The North Rhine-Westphalia address register is now dated by its publisher's release, not by the day it was synced.
  • An address longer than mail can be sent to answers invalid_syntax. The limit is 254 octets with the domain converted to punycode, the longest path RFC 5321 allows. An internationalized domain grows when it is converted, so a 129-character address could be 315 octets long; such an address used to be answered and billed, and its history row could then fail with an error. It is now invalid_syntax and free, and an address of exactly 254 octets is still checked.
  • The email suppression list compares addresses exactly. josé@ and jose@ are different mailboxes, but the list used to treat them as one entry: importing the second spelling was skipped, and removing one could remove the other. They are now two entries. A batch of delivery reports is also recorded all or nothing, so a provider's webhook retry no longer stores part of a batch twice.
  • Flemish place lists are no longer cut. Some Flemish postcodes name a list of sub-municipalities up to 162 characters long, which the addresses-at-a-postcode answer cut at 120. They are now returned whole, and the stored rows were repaired.
  • A long API key name no longer breaks lookups made with it. The usage label api · <key name> is now cut to 100 characters, which every column that records it can hold.

2026-10-02

New pricing: a longer ladder, yearly plans, five IP lookups to the credit and a bigger free tier

  • A bigger free tier. Every account now gets 100 free credits at the start of each month (it was 10), and confirming the email address adds a one-off bonus of 500 (it was 25). Unused free credits still accumulate, and balances already on accounts are untouched. The low-balance warning now fires at 50 credits.
  • The ladder goes to a million credits a month. Plans are Starter 1,000 credits for $7 a month, Growth 5,000 for $29, Scale 25,000 for $119, Volume 100,000 for $349 (was $399), and three new tiers: Pro 250,000 for $749, Business 500,000 for $1,249 and Enterprise 1,000,000 for $1,999, which is $2.00 per 1,000. One-time packs are 1,000 for $9, 5,000 for $39, 25,000 for $149, 100,000 for $449 (was $499), and the new 250,000 for $899 and 1,000,000 for $2,499. Above a million credits a month, ask us at /support?topic=volume.
  • Priority support covers Scale and every tier above it. Pro, Business and Enterprise carry it from the start, as Scale and Volume already did; the free tier, Starter and Growth have email support. Priority puts a request at the top of the support queue and is not a response-time promise.
  • Every plan can be billed yearly, at ten times the monthly price: Enterprise is $19,990 a year, $1.67 per 1,000. A yearly plan still deposits its credits once a month, on each monthly anniversary of the year's start, not all at once. A monthly plan can move to yearly at any time from the Billing page and the year starts that day; a bigger yearly plan starts a new year that day and credits the unused part of the old one. A yearly plan cannot be switched to monthly from the dashboard.
  • Pack credits never expire; plan credits roll over up to three months' allowance and stay usable for 60 days after you cancel. Credits a plan deposits are tracked apart on the balance and spent first. While a plan is active, a deposit that would take them above three months of the plan's allowance expires the excess at once. After a plan ends, the plan credits left stay usable for 60 days past the end of the last paid period, then expire. Each expiry is its own ledger entry with the reason plan_credits_expired, and the Billing page shows the cap or the date the plan credits stay usable until. Pack, free grant, signup bonus and automatic top-up credits never expire.
  • Automatic top-ups on a plan buy a block of the plan. An account with an active plan tops up with one block the size of its plan at the plan's own rate, and those credits never expire. Accounts without a plan still buy the pack they chose. The top-up line has to sit below the block and the monthly ceiling has to cover it.
  • IP lookups cost a fifth of a credit. One credit now buys five answered IP lookups. The lookup that opens a block reports credits_used 1 and leaves four prepaid on the account; the next four report credits_used 0. Every IP answer carries meta.ip_lookups_prepaid, the prepaid lookups left after it, on single lookups, each batch item and the batch, the consistency IP signal and the MCP tools. A ledger row with the reason ip_lookup now stands for five IP lookups, and the Usage page counts it that way. Bulk IP runs, IP monitors and the IP leg of a consistency check bill the same way, and a bulk preview's credits_upper_bound for an IP list is a fifth of its distinct rows, rounded up. ASN and range lookups still cost 1 credit each.
  • Unallocated IP space is free, as the pricing page already said. It used to be billed. Reserved and private ranges, unknown addresses and repeats within seven days stay free, and none of them uses a prepaid lookup.
  • An email answer whose mailbox check was uncertain is free. A risky answer with the reason unverified used to cost 1 credit unless the handshake partner was down. It now costs nothing whatever the cause: greylisting, a timeout, or a server that would not say. It leaves no seven-day repeat marker, so a later lookup is a fresh one: the uncertain result is reused for up to 15 minutes (refresh and max_age do not set it aside), then the mailbox is asked again, and the lookup bills only once the mailbox is confirmed. A re-check behind callback_url bills 1 credit when it confirms the mailbox and nothing while it still cannot say; a re-check the balance cannot pay for ends with a final callback that is not settled, carries no data and says meta.stopped_reason: "insufficient_credits". Catch-all, role and other definite risky answers still cost 1 credit.
  • Undeliverable email verdicts are free within a fair-use allowance. An undeliverable answer that needed a mailbox check is free up to 500 in any 30 days, or the number of deliverable and risky email answers billed in those 30 days when that is more. Past the allowance each one costs 1 credit and answers meta.fair_use_billed: true. Verdicts settled without a mailbox check (bad syntax, no working mail server, a username the provider never allows), suppressed addresses, test addresses and repeats are always free and never counted. The rule is the same on single lookups, batches, bulk runs, monitors and the browser endpoint, and the Billing page shows how much of the allowance has been used.
  • A mailbox the handshake reports invalid without a finer reason now answers mailbox_not_found. These answers used to come back unverified. They are now undeliverable with no smtp_reason, and count toward fair use like any undeliverable answer that needed a check.
  • The licensed add-ons are repriced, and neither is enabled. The live carrier-network check would add 4 credits to the base credit, 5 in all (it was 3 extra), and the list of addresses at a postcode from a licensed partner would cost 25 credits per answered list (it was 2). The address deliverability check stays at 3 extra credits. The live carrier-network check (hlr: true) is not enabled on the service: hlr_checked answers false and nothing extra is charged. The street listings from open registers still cost 1 credit per answered postcode.
  • Every customer-facing statement follows. The pricing page and its Markdown twin, the rate card in the API reference and the OpenAPI document, the getting-started guide, the product and docs pages, the comparison pages, the press kit, the glossary, the MCP tool descriptions, the welcome and low-balance emails, the terms, llms.txt and the three client libraries' notes all state the new rules.

2026-09-27

Phone line types from carriers' filings, a number formatter, and pages for every phone source, signal, carrier and state

  • Line types from what carriers file. Phone lookups now type the numbers of 28 more carriers from what each files with the FCC. In North America a number's line type cannot be read from the number itself, so these numbers now answer from the carrier holding the block. Wireless carriers' numbers answer line_type mobile: Boost Mobile, T-Mobile's former Powertel companies, Bell Atlantic Mobile (now Verizon Wireless), Liberty Mobile in Puerto Rico and the US Virgin Islands, Southern Linc, STPCS Joint Venture, and Fido in Canada. Numbers of VoIP providers that sell numbers, SIP trunks or programmable voice now answer line_type voip and is_virtual: true. That adds the virtual signal (+40) to risk_score, so such a number that also carries a signal worth more than 20 now scores above 60 and is refused by the default SMS policy. Cable companies' phone lines and wireline carriers still answer fixed_line_or_mobile. On NANPA's file of 25 September 2026, the share of allocated US thousands-blocks with a settled line type rises from 63.8% to 67.0%.
  • Time zones on GET /api/v1/phone/block. timezones now lists only zones in the range's own country: the +1 709 report no longer names America/Puerto_Rico. For North American codes it reads every exchange rather than twelve sampled numbers, so +1 867 names all five of its zones.
  • Fictional ranges have a length. A number a digit short or long of one of Ofcom's drama ranges is no longer taken for a fictional number. It now answers valid: false with the numbering plan's own reason and is free, where it answered valid and fictional and was charged: invalid_number for 020 7946 001, a digit short, and too_long for 020 7946 00181.
  • A free phone number formatter. /tools/phone-number-formatter takes up to 200 numbers and gives each in E.164, national and international form, with its country, the numbering plan's line type, time zones and local time, and whether it fits the plan. Numbers that don't fit get the lookup's reason and did-you-mean suggestion. Results can be copied as CSV, and the numbers are not stored. The page also publishes two tables derived from libphonenumber 9.0.37: every region whose trunk prefix is not 0 or whose numbers keep a leading 0 after the calling code (the North American 1, the 8 of Russia and Kazakhstan, Hungary's 06, Italy's kept 0), and every international call prefix other than 00. It explains why +44 (0)20 is wrong, citing ITU-T E.123.
  • A page for every phone data source. /tools/phone-number-lookup/sources gives every publisher a phone answer reads its own page: the ten regulators whose allocation tables are synced (NANPA, CNAC, Ofcom, ARCEP, ACMA, ACM, NAD, the CRT, UKE and MIMIT), libphonenumber, the two SMS-receiving lists and the FTC's reported-calls files. Each page names the files and their format, how often they are published and refreshed here, the terms as the publisher states them, which numbers a table is complete for, and what the installed copy holds as counts by status and distinct holders, never rows or names. It also says what the table cannot tell you. The index lists the regulators whose tables are not synced (Germany, Spain, Brazil, Ireland, Denmark and Switzerland), each with a dated reason, and has a Markdown twin at /tools/phone-number-lookup/sources.md, also carried in llms-full.txt. The phone product page's data-sources table links each dataset to its publisher's page, and its Licence column is now Terms and states what each publisher actually publishes: no terms of use from NANPA, CC0 for ACM's register, Ofcom copyright with acknowledgement, terms not confirmed for ACMA and the CRT. The status page lists NANPA's NPA report, the table of every North American area code, synced daily and dated by the date NANPA puts on the file.
  • A page for every phone risk signal. Each of the 18 phone signals, from fictional (+80) to reported_unwanted_calls (+20), has its own reference page at /docs/signals/phone/{signal} with a Markdown twin: the weight, the dataset it is read from, the field it reads, whether the default SMS policy blocks on it, what it cannot tell you, what to do, and an example answer. /docs/signals now has an email section and a phone section, so a phone reader no longer lands on the email weight: disposable is +80 on an email answer and +70 on a phone one. The IP and address weights stay in their guides until they get pages of their own. The risk-score table in the phone intelligence guide and its Markdown twin show each signal's slug, linked to its page and anchored as #signal-{slug}.
  • North American area codes. Area-code pages now show NANPA's facts about the whole code: the state or province, the codes sharing its area, when it went into service, how local calls are dialled and whether relief planning is under way. They also show the rate centres its codes are assigned for, the codes and thousands-blocks assigned in the last 12 months and those not yet in service, and every assigned central office code with its rate centre and the code holder's network brand. Every table is original allocation, and there is no page per code. A code no number can ring in yet says so in NANPA's terms, with NANPA's dates, instead of "line types unknown": codes whose every assigned code waits for its effective date (357, 483, 837, 861), overlays in service with no code assigned (465, 471, 748 and seven more, now indexed) and planned codes (273), each with whether libphonenumber accepts its numbers yet. New and upcoming North American area codes, with a Markdown twin, lists from NANPA's NPA report the codes put in service in the last 24 months, those assigned but not yet in service and those in relief planning, each with its overlay, its central office code counts, its first dated code and whether libphonenumber accepts its numbers yet.
  • States and provinces. Each US state and Canadian province with more than one area code, about 50, has a page at /tools/phone-number-lookup/countries/{us|ca}/{state}: its area codes with overlays and in-service dates from NANPA's NPA report, the allocated central office codes counted by rate centre or exchange area, the pooled share (US), the codes and thousands-blocks assigned in the last twelve months, and the codes by network brand, as original allocation and as shares of the regulator's table.
  • Network carriers. /tools/phone-number-lookup/carriers has a page for each of about 45 brands in the US, Puerto Rico, Canada, the UK and France: the legal entities and codes the regulator lists, the numbers they were allocated (counted in numbers, as a share of the regulator's table), how a lookup reads those numbers, thousands-blocks held inside other brands' pooled US codes, assignments by year and the area codes with most of the brand's numbers. The carrier on a lookup is the block's original holder, not the caller or the network serving the number today. The pages are counted weekly from the NANPA, CNAC, Ofcom and ARCEP tables, on Mondays after the phone feed sync, and listed in sitemap-phone-carriers.xml.
  • Phone statistics from public data. /statistics/phone, with a Markdown twin and a CSV of Spaw's derived counts under CC BY 4.0, counts where the caller IDs consumers report to the FTC sit in the US numbering plan. On 26 September 2026, 60.8% of the 489,940 US-area-code numbers reported between 28 June and 23 September sat in blocks NANPA lists under VoIP providers, against 19.7% of all allocated US numbers; both shares are lower bounds, because they count only holders on Spaw's classification list. Toll-free numbers are 12.3% of reported caller numbers but 49.3% of those reported at least three times on two days. The page also gives the FTC counts by month, the codes and thousands-blocks NANPA, CNAC and ARCEP assigned in the last 7, 30 and 365 days, and SMS-receiving listings by calling code. Every FTC figure carries three statements: this is not the Do Not Call Registry, it says nothing about whether you may call or text a number, and the reports are unverified and caller ID is often spoofed.
  • Country pages. Each region's page now says how its numbers are dialled and written, read from libphonenumber's numbering metadata: the trunk prefix, the international prefix (and, where the metadata allows several and prefers none, every prefix it allows), the number lengths per line type, the digits every mobile number starts with, whether mobile and fixed-line numbers share ranges, and one number written nationally, internationally and as dialled from the US and the UK. SMS-receiving listings are counted for the region's own numbers, never worldwide or per calling code, with the listings that still score disposable first. The countries index lists each region's trunk and international prefix, and a page for a region with no synced table says what its carrier answer is: the range's original network in libphonenumber's carrier data, which a port never updates.
  • France, the Netherlands and the UK by the plan's own codes. France has one page per zone, 01 Île-de-France to 05 South-west France, named from ARCEP's plan, which since 2023 allows a 01 to 05 number anywhere in metropolitan France, and pages for its 08x and 09 ranges. The Netherlands has a page per area code (010, 0111, 0229 Hoorn …) and one 06 page. Old two-digit pages redirect where all their blocks moved to one page, and France's 06 and 07 mobile ranges no longer have indexed pages, because ARCEP's table misses part of them. The UK's non-geographic ranges have pages named by type, such as "074 mobile numbers" and "070 personal numbers": 03x, 055, 056, 070, 071 to 079, 080, 084, 087, 090, 091 and 098. The United Kingdom's country page lists its 07 numbers by original range holder, counted in numbers from Ofcom's table: VodafoneThree 38.0%, Virgin Media O2 28.9% and EE 27.5% of the allocated mobile numbers, with notes on 070 personal numbers, 076 pagers and the Crown Dependencies' 07 prefixes. Where a regulator's blocks differ in size, area-code pages rank holders and brands by the numbers their blocks cover rather than by rows, with a numbers column: on France's 01 that puts Orange at about 59% of the numbers, where the rows showed 4.5%. The GET /api/v1/phone/block report still counts rows. Outside North America and the UK's area codes, a range page with fewer than 100,000 allocated numbers still renders but is no longer indexed.

2026-09-27

Fictional phone ranges in France, Australia and Ireland, and new phone guides, terms and comparisons

  • Fictional ranges in France, Australia and Ireland. Phone lookups now flag the numbers ARCEP, ACMA and ComReg reserve for films, television and other creative works, as they already did for the North American 555-01XX block and Ofcom's drama ranges. The ranges are ARCEP's audiovisual blocks 01 99 00, 02 61 91, 03 53 01, 04 65 71 and 05 36 49, plus 06 39 98, which sits in Mayotte's mobile range and is dialled +262 639 98 xx xx; ACMA's (02), (03), (07) and (08) 5550 xxxx and 7010 xxxx, its thirty listed 0491 57x mobiles, 1800 160 401, 1800 975 707 to 711 and 1300 975 707 to 711; and ComReg's 089 011 0000 to 089 011 0999. A number in one of them answers valid: true with is_fictional: true and the fictional signal (+80). The default SMS policy refuses it with blocked_by: "fictional", and it costs one credit like any valid number. Before this change the five French geographic blocks answered unassigned_block and +262 639 98 answered invalid_number, both free, while the Australian and Irish numbers answered valid, unflagged and ok_to_send: true. Ireland's 020 code is recorded but still answers invalid_number, because ComReg gives it no line type. The fictional-ranges dataset version is 2026-09-27, and each range now names its publisher's document and the day it was read.
  • Country pages count reserved single numbers apart. The fictional-ranges line on a country page counts ranges and single numbers separately (Australia: 8 ranges and 41 single numbers), and ARCEP's +262 639 98 is counted on Mayotte's page, not Réunion's.
  • Phone guides, terms, comparisons and integrations. Eight new guides: an area code is not a time zone, fictional and test phone numbers, how to store phone numbers in E.164, number pooling, SMS delivery error codes, SMS pumping and the default SMS policy, UK number ranges from 01 to 09 and what FTC unwanted-call complaints can and cannot say about a number. The glossary gains fifteen phone terms, from number pooling and rate centre to SMS pumping and the live carrier check, and the phone docs' field table, area-code pages and country pages link them. /compare/phone adds Veriphone, Abstract Phone Validation, ClearoutPhone, numlookupapi, IPQualityScore Phone Validation and Loqate Phone Validation, each read from the vendor's own pages on 27 September 2026, and every phone comparison gains an SMS pumping controls row. The HubSpot, Klaviyo, Typeform, Pipedrive, Salesforce, ActiveCampaign and Airtable integration guides now cover phone numbers, and a new Sign-in and one-time codes category has recipes for Firebase Authentication, Supabase Auth, Amazon Cognito and Auth0 that check a number before a code is sent. The guide on why US numbers cannot be split into mobile and landline no longer says every UK 07 number is a mobile: only 071 to 075 and 077 to 079 are.

2026-09-26

Phone places named from the regulators' tables, prefixes as dialled, and Northern Mariana Islands numbers answered again

  • Northern Mariana Islands numbers stop answering unassigned_block. NANPA files the islands' +1 670 codes under the state code NN, which the sync read as the United States. A lookup of a Northern Mariana Islands number then found no block of its own in NANPA's table, and because that table lists every assigned code, the number answered valid: false with unassigned_block. The codes are now filed under MP, so from the first NANPA sync after this release those numbers answer from their own blocks, with the holder as carrier, and the islands' country page lists +1 670.
  • Cleaner region names. Where region on POST /api/v1/phone falls back to the number's NANPA rate centre, the name no longer carries NANPA's billing zones and district areas: "New York City Zone 01, NY" reads "New York City, NY" and "Los Angeles DA 06, CA" reads "Los Angeles, CA", and names are cased properly (Fremont-Newark, McKinney, La Grange). A number in one of Washington, DC's zones in Maryland or Virginia now answers what the numbering metadata says, such as "Maryland", not "Washington Zone 03, MD". Three misspellings in the numbering metadata are corrected wherever a place is shown: "Chtamstths, VI" reads "Charlotte Amalie (St. Thomas), VI", "British Colombia" reads "British Columbia" and "Lódź" reads "Łódź".
  • A North American place the data agrees on, and a new places list. GET /api/v1/phone/block names a North American area code after a city only when most of its sample numbers are geocoded to that city and a rate centre agrees, or when one rate centre holds half or more of the allocated blocks. Otherwise place is the state, province or territory ("Montana"; "Nova Scotia and Prince Edward Island"), where it used to be whichever town the samples happened to name. The new places array lists up to three rate centres (NANPA) or exchange areas (CNAC) holding the most allocated blocks, filled only when place names a state, province or territory and empty everywhere else. Both describe where the regulator recorded the codes, not where subscribers are.
  • Prefixes as people dial them. GET /api/v1/phone/block accepts a prefix written with its trunk prefix, the 0 of the UK, France, the Netherlands, Australia and New Zealand or the North American 1: gb/020, gb/0161 and us/1212 answer the report of gb/20, gb/161 and us/212, with that stored prefix, its charge and its repeat marker. Italian, Polish and Mexican prefixes keep their leading digits. A miss on such a form names both spellings.
  • The area-code and country pages. Area-code pages lead with the number as dialled inside the country, name mobile, freephone and premium-rate ranges by type instead of "area code", and say "area code" only where the prefix is the plan's own area code: Sydney's 029 sits inside the 02 area, so its page reads "029 numbers". They sit under their country in a breadcrumb and are dated by the tables they cite, never by the day they were rendered, and their structured date is the day what the page shows last changed, the same date the sitemap gives. They no longer name Polish and Dutch holders listed without a legal form, such as sole traders, Polish partnerships whose names carry their partners' names, or the holders of single Dutch numbers, which the API's block report still returns; the holder table's last row adds up the rest of the regulator's table, the smaller holders included, and no network is credited with blocks the page does not attribute to it. /gb/020, /us/787 (Puerto Rico's page), the Saint Martin and Saint Barthélemy pages and a bare /tools/phone-number-lookup/us redirect to their canonical pages. Country pages list every area code with a page instead of the first 24, grouped by state, province or code length, and Guernsey, Jersey and the Isle of Man say their numbers sit in the UK +44 plan and are answered from Ofcom's table.
  • Also. The phone product page, the phone guide and API reference, the OpenAPI document, the MCP tools, the SDKs, the integrations, the phone comparison pages and the phone guides now state the live carrier-network check's status in one sentence: it is not enabled on the service, hlr_checked answers false and nothing extra is charged. The status page calls the UK numbering table stale after ten days without a new copy instead of thirty, and addresses left over from the domain's previous owner answer 410 Gone.

2026-09-25

Suppressions you import, spam complaints and abuse reports stay until you remove them

  • A lookup no longer clears what you put on the list. A recovered verdict used to remove an email suppression entry whoever added it, so an address you had imported, or one a complaint had put there, came off the list the next time it was verified afresh: a single lookup, the MCP tool, a visitor typing it into the form on your site, or a batch, bulk or monitor run once the entry was more than 90 days old. A verdict that is not undeliverable now clears only an entry an earlier undeliverable verdict added. Imports stay until you remove them, and an entry a bounce report added stays until you remove it or report a delivery to that address.
  • An undeliverable verdict no longer relabels your entries. It used to rewrite an imported or reported entry as an automatic one, which the next recovered verdict would then clear. It now only renews the entry, which keeps a bounce report's entry past the 90-day staleness check, and leaves its source and reason as they were.
  • Importing an address already on the list makes it yours. It keeps the verdict stored with it and is counted as already_suppressed, as before, but becomes an import, so no later lookup or delivery report takes it off. Importing an address again is harmless and never needed to keep it listed.
  • Stale entries are still re-checked, imports never. Batch, bulk and monitor runs still verify an entry a verdict or a bounce report added afresh once it is untouched for 90 days, instead of trusting it. An import is never stale, on any of the three lists, and is served for as long as it is listed. A verdict or lookup answer an import kept from before you imported it is a measurement, though, and nothing measures an import again: it is served for 90 days after the entry was last renewed, and after that the import answers suppressed like any other, so an answer months old is never repeated as today's.
  • Phone and postal imports work the same way. Importing a number or an address that a lookup or your own reports already put on the list keeps its reason, and any stored answer, but makes it an import, so a later valid lookup, delivery or verified report no longer takes it off. And a moved, refused or returned report about a postal address you imported no longer takes it over: it used to relabel the entry as a report, which the next delivery report then cleared.
  • A spam complaint stays until you remove it. A complaint is the recipient's objection, not a fact about the mailbox, so it is now treated like an import. A later delivery report no longer clears it, a later bounce report no longer relabels it as a bounce (which a delivery then cleared), a complaint reported beside a bounce in one batch wins whichever comes first, and it never goes stale, so batch, bulk and monitor runs keep serving it after 90 days instead of re-verifying the address. An erasure request from the address itself keeps it as well, as it keeps a phone number's opt-out, because deleting it would put the address back in front of your next campaign; the privacy policy and the trust page say so.
  • A phone abuse report stays until you remove it. An abused report puts the number on your phone suppression list with reason abuse, and that entry used to leave once the report was more than 90 days old and a delivery was reported after it — or it was relabelled undelivered by later failures, which a delivery then cleared. Now no delivery, verification, failure or lookup clears or relabels it, and it never goes stale, so batch, bulk and monitor runs keep serving it. The reported_abuse risk signal on a single lookup still counts reports from the last 90 days, as before.
  • Removing an abuse entry sticks. Only an abused report in the feedback call that carries it now adds a number to the list with reason abuse, as only an opt-out in the call adds an opt-out. A removed abuse entry used to come back with the number's next report of any kind — delivered or undelivered — while the old abuse report was still inside its 90 days. A later delivered, verified or undelivered report no longer brings it back as an abuse entry; only a new abuse report does (an opt-out, or failures that would suppress any number, still add it with their own reason).
  • An opt-out, abuse report or complaint about an entry you imported is kept. An email complaint used to leave an imported entry exactly as it was, so it still went stale 90 days after the import, and a phone abuse report turned an imported number into an entry of your own reports, which a later delivery could clear once the report was 90 days old. The report's reason — opted_out, abuse or complained — is now written onto the import, which stays an import: it never goes stale, meta.opted_out answers true, and an erasure keeps it. An opt-out still replaces an abuse reason, never the other way round. A report that justifies the entry without standing for good — a permanent failure, repeated failed deliveries, an email bounce, post reported moved, refused or returned — leaves an import's reason alone, and a delivery or verification still never clears an import. A report that says nothing against the number or the address — a failure your SMS provider puts on the sender's side, or a late delivery receipt dated before the failures — renews nothing and never puts back an entry you removed. An import never goes stale, on any of the three lists.
  • Drama numbers stop answering unassigned in batch, bulk and monitor runs. Before Ofcom's drama ranges answered valid where Ofcom's own table lists the block as protected, a lookup of such a number — 020 7946 0018 was the documented example — put it on your phone suppression list as unassigned_block, and batch, bulk and monitor runs kept serving that stored answer. Such an entry is now removed the next time a run meets it, and the number is looked up afresh: valid, with is_fictional true.
  • Time zones, precisely. Round four's note that timezones names the zones of the number's own country holds wherever the time-zone database files a zone under that country: a Palestinian number answers Asia/Gaza and Asia/Hebron rather than the Europe/Bucharest the numbering data gives, whose daylight-saving dates differ. The database files none under Kosovo, Ascension Island or Tristan da Cunha, so their numbers answer the zone the numbering data gives, Europe/Belgrade or Atlantic/St_Helena, which keeps the same clock.

2026-09-24

A business park named after its road is found under either spelling

  • Both spellings read the same companies. A business park that takes its road's name — Ferry Road Office Park, Stone Road Business Park, Green Lane Business Park — is filed under the park and under the road, so companies_registered answers the same whichever way the address is written. Measured on the Companies House copy of 1 September 2026: 7 Ferry Road, Preston PR2 2YH read 10 companies and the park spelling 80; both now read 90. 55 Stone Road, Stoke-on-Trent ST4 6SR read 15 and now reads 361, the same as 55 Stone Road Business Park; 6 Green Lane, London SE9 3TL read 2 and now reads 182. Where the count crosses the published threshold of 25, is_mail_drop answers true with mail_drop_source: company_cluster for the road spelling too.
  • Only inside a full postcode. The road spelling is filed only when the address carries a full British postcode, so a park is never merged with a road of the same name elsewhere in a town or a postcode district. The designators read are OFFICE PARK, BUSINESS PARK, BUSINESS CENTRE and BUSINESS VILLAGE, after a name ending in a road word; a park whose name is not a road (Viewpoint Office Village) is still found by its own name.
  • One filing, one count. A filing is counted once, never once per spelling: the district pages' company tally and the cluster threshold see the same number as the lookup.
  • Mailbox operators are not widened. A mail-drop branch listed under a park still flags only that listing; it is not copied onto the road, because an operator's listing answers is_mail_drop on its own, with no threshold between it and the answer.
  • Known limit. A park that numbers its units rather than its buildings can share a count with the same number on its road: at Stamford PE9 2PR, 1 West Street reads 6 companies, 5 of them filed at unit 1 of West Street Business Park. That is below the threshold, so it raises no flag.

2026-09-24

A building before its number, the street behind a care-of line, and a town under another of its names

  • A building named in front of its number and street. Richmond House 50 Tontine Street, Blackburn BB1 7ED now reads Richmond House as the building and 50 Tontine Street as the number and street, as the comma form already did; the whole line used to be the street, with no house number. The split needs a name of two words or more that ends in a building word — House, Building, Court, Point, Quay, Wharf, Park and the like — followed by a number and a street that ends in a street type, in a country that writes the number first, so a street such as Mill House Close is never cut. A unit written in front reads the same way: Unit 3 Morris Park 37 Rosyth Road is Unit 3 of Morris Park, at 37 Rosyth Road.

  • The street on a care-of line. In separated fields, a care-of line that carries the street keeps the street and drops the name: C/O CSC CLS (UK) LIMITED 5 CHURCHILL PLACE above 10TH FLOOR reads 5 Churchill Place with the unit 10TH FLOOR, where the whole line used to be set aside and the address was left with no street. An ordinal street (c/o ACME Corp 623 26th Ave) and one whose type comes first (c/o Smith LLP 1345 Avenue of the Americas) are read too, and so, in any country, is a street parted from the name by a comma (c/o Ola Hansen, Karl Johans gate 22 reads Karl Johans gate 22). Wherever the name can be told from the street it never becomes the street, and a care-of line with no street on it is set aside once a town or postcode is given, as it already was in a written address.

  • A town after a floor. 167-169 Great Portland Street 5th Floor London, W1W 5PF reads London as the locality, where it used to take London for the dependent_locality and leave the town empty. That happens only when no other town line is written, and a room or building named after the floor — Lion Suite, Lion House — is never the town.

  • States in Mexico and India. A Mexican state written by its INEGI code, abbreviation or name — Jal., N.L., Edo. Méx. — and an Indian state written by its name are read as the administrative_area, before or after the postcode, a full name when it stands on a line of its own: Avenida Juárez 100, Guadalajara, Jal. 44100 reads Guadalajara in Jal. and matches the postcode on both, where Jal. used to be taken for the town, and 1 MG Road, Bengaluru, Karnataka 560001 reads Bengaluru in Karnataka. A state whose full name is also a large city's — Puebla, Durango, Oaxaca, Delhi — and Mexico City are left to be read as the town.

  • An Irish county is not an Eircode. A postcode with no digit in it is no longer read as one, so 12 Main Street, Naas, Co. Kildare reads Naas in Co. Kildare with no Eircode, where it used to take KILDARE for the postcode, and a town written alone after the street — Athlone, New Ross — stays the town. A real Eircode (D02 X285) still reads, and Bermuda's letter-only codes (HM BX) are the one exception.

  • Unit lines as each country writes them. A German 3. OG or EG, a Danish 2. tv, a Spanish 3º B, an Italian Scala B after the town and a French Bât. B are the unit, where they used to be read as a district or as the region, and a French Résidence is a building: Calle Mayor 5, 3º B, 28013 Madrid answers unit 3º B. An Austrian Stiege 3, Top 7 is the unit too, but only in Austria: in Germany and Switzerland Stiege is a street name, so Stiege 12, 48739 Legden reads the street Stiege at number 12. Studio is a unit word when a number or a single letter follows it (Studio 16, 5 Marine Crescent), while Studio Rd stays a street; a box line keeps the street written below it (CS 70001 above 12 rue Bichat); and a building number written before a unit carries to the street on the next line.

  • Unit and number, the way they are written. In Australia and New Zealand a slash is unit over number: 3 / 1234 Pacific Highway, Pymble NSW 2073 answers unit 3 and house_number 1234, where the number used to be 3 / 1234, and Level 3/123 Collins Street, Melbourne VIC 3000 reads unit Level 3 at 123 Collins Street. An Austrian Hauptstraße 3/2/12 reads number 3 with unit 2/12, and a Russian ул. Тверская, д. 1 reads the street and house number 1. A company name above a numbered street is the organisation, and a numbered street is never taken for the town: Knoll Buchler Phillips Limited, 84 Grosvenor Street, W1K 3LN reads 84 Grosvenor Street.

    13 of the parser corpus's 18 known limits are closed. It now holds 238 written addresses, 6 of them known limits with today's answer and a note.

  • Company counts, re-keyed. Re-keying the whole Companies House copy of 1 September 2026 with the new parser, the way the company index files it, 4,221,495 companies key to a building, up from 4,109,723. Of the 110,663 that had no key before and have one now, 46,438 gained it through a building named in front of its number, 41,041 through a unit written before a building and 10,153 through a care-of line. Richmond House, 50 Tontine Street BB1 7ED goes from 89 companies to 307, 5 Churchill Place E14 5HU from 1,921 to 2,640, Carpenter Court, 1 Maple Road SK7 2DH from 11 to 865, Lake House, 2 Port Way PO6 4TY from 7 to 772 and Lytchett House, 13 Freeland Park BH16 6FA from 142 to 1,711, and the filings keyed to a street that is really a care-of line fall from 4,356 to 1,601. Brønnøysund's file and the five US business registers go through the same separated fields and lose 1,114 Norwegian entries and 632 American ones: every Norwegian one a lone care-of line, and each of the 107 American ones sampled a lone care-of or attention line.

  • Other names of a town. A town written under another name of the same place now matches its postcode: Munich for 80331 (München), Cologne, Vienna, Prague, Lisbon, Rome, Milan, Brussels, Bucharest, Moscow and Kiev each scored locality_mismatch and now read locality_matches_postcode: true. The names come from GeoNames' list of places of 15,000 people or more (CC BY 4.0), compared inside one country only; a name that another listed place of that country also carries is left out, and so are airport codes. Another name is matched against the postcode's own places, or the name of a one-city region, never against its neighbours', and a town's only where the postcode's centroid lies within 30 km of the point GeoNames gives the place, so Bristol written for Briston's NR24 2AD and Berlin for Perlin's 19209 are refused. A name the directory also lists as a town of its own more than 30 km away counts only when it is the place's own name cut short — Biberach for Biberach an der Riß, Yarmouth for Great Yarmouth — so Perlin, a village near Schwerin that GeoNames lists among Berlin's names, is refused for a Berlin code too. An answer confirmed through another name names geonames-cities in its sources, dated by the day the cities file was installed; one written in the directory's own spelling, München, does not. Measured on 24 September 2026 across every installed directory, Great Britain's included, as it stood before the GeoNames re-read below, writing 23,683 of the list's towns in those countries under their GeoNames names: the postcode nearest the town accepts it 77.6 percent of the time, up from 72.5; a wrong postcode 15 to 40 km away 56.0 percent, up from 55.6, every gain within 30 km of the town at a code that lists it under another of its names or in its one-city region; and one 40 to 150 km away 36.2 percent, unchanged but for a single Bangkok code, which Bangkok's one-city region accepts. Of 32,700 real pairs of a postcode and its own first place none changed, and of 25,867 wrong pairs none was newly accepted. A wrong name — Pressburg, Bratislava's German name, for a Budapest code — is still refused.

  • A town written as its one-city region. Where the region a postcode is filed under is a single city, the city's name written as the town reads confirmation.locality: postcode_area, reported and never scored. Mexico City was the only one; Delhi, DKI Jakarta, Bangkok, Bogotá, Moscow, Saint Petersburg, Bucharest, Seoul and Korea's other metropolitan and special self-governing cities — Busan, Incheon, Daegu, Gwangju, Daejeon, Ulsan and Sejong — now join it, and so does Tokyo for a code filed under one of its special wards, 7,510 of the directory's 8,747 Tokyo codes. Tokyo for 100-0005, Jakarta for 10110, Seoul, Busan, Sejong, Bangkok and Bogotá scored locality_mismatch and no longer do, while Tokyo written for a Hachiōji code (192-0001) still is: the Tama cities and the islands are not the city. Bucharest is the municipality alone, not Ilfov around it, and Bucharest written as the region for 010011 no longer scores region_mismatch. A region that merely holds a city of its name — Bremen, São Paulo, New York — is never one city.

  • Region spellings for Indonesia, Korea and Colombia. The directory files Indonesian postcodes under the statistics office's province numbers alone, so any province written by name scored region_mismatch. All 34 provinces now answer to their number, ISO code, Indonesian and English names and the usual short forms (Jawa Barat, West Java, Jabar), and the four Papuan provinces created in 2022 read as inside the one they were split from. Korea's 17 regions, which the directory stores in Hangul, answer to their romanised, English and Hangul names, and Bogotá to its capital-district spellings. Across every installed code, the 8,266 Indonesian codes written with their province's Indonesian name went from region_mismatch to a match, and so did the 34,249 Korean codes written with the short names (Seoul, Gyeonggi), and the 22,273 of them the romanised ISO names had failed.

  • Danish towns without their district letter, and postcodes in two regions. GeoNames writes a Danish postal town with the letter or name of its district — København K, Aarhus C, Viby J, Nykøbing F, Brenderup Fyn — so København, Copenhagen or Aarhus written on its own scored locality_mismatch. The directory now lists the bare town beside it, and does the same for eleven Dutch codes and one in Liechtenstein whose town carries letters that tell it from a namesake (Bergen L, Mauren FL). A postcode GeoNames files under two regions now keeps both, where it kept only the first: 3644 lies in New South Wales and Victoria, so Cobram East VIC 3644 no longer scores region_mismatch, and nor does Queanbeyan's 2620 written with New South Wales. Measured on 24 September 2026, before and after the directory was re-read from GeoNames with the new reader: the 605 Danish codes whose town carries such a suffix, written with the bare town, go from 599 locality_mismatch to none, and Copenhagen written for the 446 codes of København from 446 to 2 — 0900 and 0999, which GeoNames places in North Jutland, beyond the 30 km another name reaches; 314 codes in 25 countries are filed under two regions or more, and written with a region after the first they go from 269 region_mismatch to none.

  • Eircode routing keys that cross a county line. GeoNames files each routing key under a single province, so an address in a county across the line from the key's town scored region_mismatch. Which counties a key reaches now comes from the Property Services Regulatory Authority's Residential Property Price Register, as last updated on 23 September 2026 and read on the 24th: 250,671 of its 807,724 sales carry an Eircode, and a county counts for a key once it holds at least ten of that key's sales. Fourteen keys reach a county of another province, or are filed under the wrong one: A82, A91, E32, E41, E53, F91, H12, H91, N37, N39, N41, P14, R42 and X91. For those, every county the key reaches, and that county's province, answers administrative_area_matches_postcode: null, neither confirmed nor refused; the province GeoNames files the key under answers null rather than true; and a county the key does not reach is still refused unless it lies in that province — and for P14, which GeoNames files under Leinster though none of its sales is there, even then: Athlone's N37 written with Roscommon no longer scores, written with Kerry it still does, and Crookstown's P14 written with Kildare now does. D11 and D15 reach into Meath, 17 of D11's 3,132 sales and 149 of D15's 7,075, so neither confirms County Dublin any longer; Dublin 11 and Dublin 15 still do. Written with each sale's own county, the register's addresses now score region_mismatch 330 times, down from 3,644, and 10,035 D11 and D15 sales written with Dublin (3,115 and 6,920) read null where they read true, measured on the same sales that set the ten-sale line. Only the facts derived from it ship — the counties of those fourteen keys, and D11's and D15's — and the register is credited in the footer and in the address guide's table of datasets.

  • Known limits.

    • 1-1 Marunouchi, Chiyoda-ku, Tokyo, 100-0005 keeps 1-1 on the street line: Japan's format writes the block number after the name, as the Japanese script does, and the number is split off only once the formatter writes it first for an address in Latin letters.
    • 10 downing street london sw1a2aa, written on one line with no separators, keeps the town on the street line: only a register can say where the street ends.
    • A building, court or park named in front of a number, above a street address the parser does not take for one (Innovation Centre 1 Liverpool Science Park over 131 Mount Pleasant, Aftermarket House 5 Marlin Office Village over 1250 Chester Road Castle Bromwich), is keyed at its own number rather than the street's. Of the 185 filings the whole file re-keys, 138 move onto a road OS Open Names lists in the postcode's district and 8 move off one.
    • A care-of line in a country that writes the number after the street, with no comma between the name and the street, cannot be cut, because where the name ends and the street begins cannot be read: written as an address, c/o Firma AS Storgata 5, 0155 Oslo is set aside and answers missing_street, and in separated fields the whole line stays the street, c/o Firma AS Storgata at number 5.
    • A Mexican state's full name run on after the town on the same line (Guadalajara Jalisco 44100) stays with the town, because towns end in state names too (Miguel Hidalgo, Dolores Hidalgo); written on a line of its own (Guadalajara, Jalisco 44100) it is the state.
    • The directory carries no names for Indonesia's regency codes, so Jakarta Pusat written for 10110 still scores locality_mismatch, even though DKI Jakarta now matches the region.
    • Cundinamarca for a Bogotá code, and a Russian region written in English (Moscow Oblast), still score region_mismatch.
    • Another name that the directory also lists as a town of its own more than 30 km away does not count unless it is the place's own name cut short: Calicut is not taken for Kozhikode at 673001, because a Calicut in the Andamans has a code of its own, and reads postcode_area, unscored; Llanelly for Llanelli's SA15 1AB scores locality_mismatch, because the directory also lists a Llanelly in Monmouthshire. Nor does another name at a postcode GeoNames places more than 30 km from its town: Helsingfors for Helsinki's 00002, which it places at Hattula.
    • A code GeoNames files under a region by mistake answers to that region too: Bulgaria's 8543, a Burgas code, is also filed under Montana, so Монтана written for it is accepted.
    • A county that holds fewer than ten of a key's sales is still refused: Mullingar's N91 written with Cavan, which holds 4, scores region_mismatch. The register lists residential sales only, so a cross-border area with no recorded sale stays unseen.

2026-09-24

Phone intelligence round four, from an SMS policy to Poland's and Italy's number blocks

  • 49 fields. POST /api/v1/phone, the batch, the browser endpoint, monitors and the MCP verify-phone tool now answer sixteen more fields after roaming: carrier_code, carrier_brand, block_pooled, block_assigned_at, region_level, local_time, utc_offsets, in_call_window, did_you_mean, range_seen_by_you, client_velocity, destination_new_for_you, complaints_90d, complaints_last_at, ok_to_send and blocked_by. The published weights gain five signals: range_burst 40, numbers_per_client 30, disposable_range 30, international_network 30 and reported_unwanted_calls 20. The full guide is at /docs/phone-intelligence.
  • An SMS policy decides ok_to_send. Every phone answer ends with ok_to_send and blocked_by from your account's SMS policy: the countries you send to or never send to, the line types you refuse, the signals that block, a ceiling on the score, whether a number your suppression list holds as opted out is blocked (block_opted_out) and, if you want one, a live check that reached the handset. Set it with GET, PUT and DELETE /api/v1/phone/policy or the SMS policy tab on the phone page. The defaults block fictional, disposable, reported_abuse, reported_abuse_widely and premium_rate, any score over 60 and a number your suppression list holds as opted out, and send to every country and line type; blocked_by names the rule that decided, in a fixed order that starts with invalid. It is a fraud and deliverability decision, not consent and not a do-not-call scrub, and a country list is a business choice about where you send, not a verdict on a country. The guide Gate SMS verification codes with the SMS policy walks through a signup flow.
  • Signals from your own traffic. range_seen_by_you counts the distinct numbers your account looked up in the same 1,000-number range in the last hour, and five or more add range_burst (+40). client_velocity does the same for one client address: send client_ip on POST /api/v1/phone, which is counted and never looked up or billed, and a browser lookup counts the visitor's address on its own; five or more add numbers_per_client (+30), unless Spaw's own IP data places the address on a mobile carrier or a corporate proxy, where many people share one. destination_new_for_you says whether the country is new for your account in the last 30 days. Only single and browser lookups are counted, a browser answer carries the three fields null, and no other account's lookups are ever read. The account owner is emailed about range bursts, destination spikes and enumeration runs, and the Usage page shows the last seven days of phone lookups by country. destination_new_for_you and the spike alert start once a week of the account's own lookups has been counted, and until 14 days have been counted a spike is measured against the days counted so far; the enumeration alert watches from the first counted hour.
  • Who holds the block. carrier_code is the regulator's code for the block holder: the NANPA or CNAC operating company number, ARCEP's operator code, UKE's operator ID. carrier_brand names the network behind a legal name — AT&T for New Cingular Wireless PCS — from a bundled, versioned map cited as carrier-brands in sources. block_pooled marks North American codes split into thousands-blocks that other carriers may hold, 71.8% of the assigned codes in NANPA's file of 24 September 2026. In the United States the lookup reads NANPA's daily thousands-block report too, so in a pooled code carrier, carrier_code, carrier_brand, a settled line_type and is_virtual describe the holder of the number's own block of a thousand, and a block the pool holds free answers unassigned_block; in Canada, and for a block the report does not settle, they describe the code holder. block_assigned_at is the date the regulator assigned the code or range, from NANPA, CNAC and ARCEP, or in a pooled US code the date NANPA assigned the number's own block; it is never the code's date for a pooled code. GET /api/v1/phone/block adds each holder's brand, a brand breakdown, the pooled count and the span of assignment dates, and now answers Guernsey, Jersey and the Isle of Man from the UK table.
  • Poland and Italy. Phone lookups read UKE's numbering tables for Poland and MIMIT's assigned-numbering workbook for Italy, synced weekly: the operator behind Polish and Italian fixed and mobile blocks, a Polish number in the space the regulator holds in reserve answered unassigned_block from that row itself, and mobile ranges assigned to messaging platforms flagged is_virtual. Neither table lists every block, so a number missing from one is never called unassigned. The footer credits both, UKE with its retrieval date on the status page.
  • SMS-receiving numbers, kept as they age. Two open lists of the numbers SMS-receiving websites publish are merged and synced daily now. A listed number scores disposable (70) for a year after it was last seen; one that leaves them is kept at its last-seen date for two years and scores disposable for 90 days after it was last seen, then disposable_stale (40), instead of vanishing. disposable_range (+30) flags a number that is not listed itself in a 1,000-number range holding ten or more listings that still score disposable (still listed and seen within the year, or dropped and seen within 90 days); GET /api/v1/phone/block reports the same count under disposable, and +1 202 555 0113 is its test number, the eighth.
  • Complaints about callers. For North American numbers, complaints_90d and complaints_last_at count the consumer complaints in the Federal Trade Commission's published reported-calls data that name the number as the caller over 90 days, and reported_unwanted_calls (+20) fires at three or more complaints on at least two days. The reports are unverified and caller ID is often spoofed: this is context about a caller, not proof of wrongdoing, and not a do-not-call scrub.
  • The clock and a typo check. region_level says how specific region is: locality, area or country. local_time gives the time at the number now where its time zones agree, utc_offsets their current offsets, and in_call_window answers when you send call_window ("09:00-20:00" in the number's local time) on POST /api/v1/phone or /phone/batch — clock arithmetic, not compliance advice. On an invalid answer to a number sent with a country, did_you_mean suggests the one valid reading of what was typed, such as a missing + or a foreign international prefix left in; it is never applied, and the answer stays free. A reading in a range reserved for fiction is offered even where the regulator's table lists its block as protected, as the lookup answers such a number valid. international_network (+30) scores the international-network and satellite codes +870, +881, +882 and +883, on top of voip for the last two.
  • SMS outcomes that say what failed. Delivery receipts from the SMS providers whose webhooks Spaw reads are classified by the provider's error code, and POST /api/v1/phone/feedback takes an optional failure_class: permanent, transient, not_sms_capable or sender_side. Only failures about the number count against it: one permanent failure suppresses it at once, a transient or unclassified one after two, and a carrier filter, an unregistered campaign or a sender limit is recorded without scoring your recipient. GET /api/v1/phone/feedback/summary splits failures by class. Two more outcomes: verified (the recipient entered the code) counts as a delivery, and the summary's verified_share reads as your code conversion, overall and per block; opted_out never scores, keeps the number on your suppression list with reason opted_out until you remove it, and a single lookup reports it as meta.opted_out: true and, under the default SMS policy (block_opted_out, on by default; an OTP sender may switch it off), answers ok_to_send: false with blocked_by: "opted_out". Honouring an opt-out stays your obligation. Every feedback endpoint, for all four products, refuses an occurred_at more than five minutes in the future.
  • Suppression entries stay until you say. A valid lookup now clears only an entry an unassigned block added. An import, an opt-out, an abuse report, or an entry your own failure reports added stays — including when somebody types the number into your public form — until you remove it or, for a failure report, a later delivery or verification clears it.
  • Bulk runs. Bulk phone runs mark every repeated number (is_duplicate, duplicate_of_row), add up a finished run in a summary (verdicts, reasons, line types, the top countries and carriers, signals, suppressed rows, what the live check found, and the rows your SMS policy held back), and download as deduped, mobile, ok or blocked as well as by validity and risk. The result file carries every new field a run fills, which leaves out the three own-traffic fields and in_call_window.
  • Monitors. Phone monitors save each number once, in E.164, read against the monitor's country where it has no calling code: two spellings of one number are one entry, counted and billed once, the 500-number limit counts distinct numbers, and an entry that does not parse is kept as typed and answers invalid. Existing monitors were converted in place and keep their baselines, and results on GET /api/v1/phone/monitors/{id} is keyed by E.164. A monitor now flags numbers that still answer but changed — listed by an SMS-receiving website, reported for abuse by three or more accounts, risen to high risk, a new line type, and with the live check ported or served by another network — in their own email, in the webhook's flags and on the monitor itself. A line type is compared only with one read from the same basis, so a type now read from another block holder, or from the number's own thousands-block, sets a new baseline rather than a flag. A number on your suppression list answers suppressed, which never counts as decay. Phone monitors stay weekly or monthly: most regulator tables refresh weekly (NANPA's and the SMS-receiving lists daily), a weekly run already catches a number that stopped being valid or started being listed within days, and with the live check a daily run would pay for the network check seven times a week.
  • Fixes. Phone inputs accept a full-width plus, a 00 international prefix and up to 64 characters everywhere. Ofcom's drama ranges answer valid and fictional, including 07700 900xxx, 01632 960xxx and 0115 496 0xxx, and including where Ofcom's own table lists the drama block as protected. The Northern Ireland drama range is 028 9649 6xxx: real Belfast numbers on 028 9018 0xxx no longer read as fictional. timezones names the zones of the number's own country: a Jamaican mobile answers America/Jamaica instead of every zone of +1, and a Lithuanian or Moroccan number its own zone instead of a neighbour's. A fictional number is never sent to the live check. A repeated live check is free while the network's day-long answer is held; after that the repeat asks the network again and bills the premium, reserved first and refused with 402 when the balance cannot cover it, and resuming a live-check bulk run more than a day after it stopped does the same for the rows it had answered. The base credit is charged once per number every seven days whether or not a lookup asked for the live check: a check after a plain lookup of the same number costs only the check, and a plain lookup after a check costs nothing. The hlr-live-status source is dated when the network answered. UK 084 and 087 numbers answer shared_cost (+20) instead of premium_rate (+60), as Ofcom's numbering plan classes them as service numbers with a capped charge, so the default SMS policy, which blocks premium_rate, no longer blocks them; 0871 to 0873 still fall under Ofcom's premium-rate-services rules, so add shared_cost to blocked_line_types to keep refusing them. Erasing a phone number also drops the shared live-check answer, its FTC complaints and any listing the SMS-receiving sites no longer publish.
  • Clients. The Python, Node and PHP SDKs (0.5.0, unreleased) carry the new fields and the SMS policy calls, and their phone lookup takes client_ip and call_window. The Zapier, Make, n8n and Google Sheets connectors return the new fields, with call window and client IP inputs where a step can use them. The footer credits UKE, MIMIT, the FTC and the second SMS-receiving list.

2026-09-23

Address verification fixes: postcodes no directory lists, ZIP+4, company counts without the dissolved, and a parser that finds the town

  • A postcode the directory never lists is not evaluated, not refused. Great Britain's directory leaves out Northern Ireland's BT postcodes, because reusing them needs a Land & Property Services licence, and the release carries no British Forces Post Office numbers, no BF1 codes and no non-geographic BX codes. The directory is complete, so until today every one of those answered valid: false with postcode_not_found. They now answer postcode_status: null: valid, with no town or county comparison, no centroid, confirmation.postal_code: "pattern", and no register match, because Ordnance Survey's Open Names covers Great Britain only as well. 1 High Street, Belfast, BT1 5GS is a valid street address with a risk score of 0, and an organisation above a BX code needs no street at all: HM Revenue and Customs, BX9 1AS is valid with the organisation separated. Boxes get the same treatment from the other side. A directory built from places — GeoNames, and every directory a register builds — does not list Postfach PLZs or APO ZIPs, so Postfach 10 01 01, 50441 Köln and PSC 1234 Box 5678, APO AE 09204 are no longer refused by one. The British directory, which does list box and large-user codes, and the French copy, which carries La Poste's business codes, still refuse a box code they lack.

  • A ZIP+4 is matched on its ZIP everywhere. The United States directory is complete, and it used to be asked for all nine digits, so 1600 Amphitheatre Parkway, Mountain View, CA 94043-1351 answered postcode_not_found. The directory now looks a ZIP+4 up by its ZIP, and so do the building registers, the Census Bureau's number ranges, the company counts, the mail-drop lists and the registered-agent list, on both sides of every match: a filing or a branch published with a ZIP+4 is found by an address written with the plain ZIP, and the reverse. postal_code_formatted keeps all nine digits, and premise_key is the same for either spelling. It matters most in Connecticut, whose business file writes a ZIP+4 on 150,652 of its 439,769 filings, 34 percent; 43 Anvil Drive in Avon now reads the same company count written as 06001 or as 06001-3218. The street listings follow: GET /v1/address/streets and the open half of GET /v1/address/postcode answer a ZIP+4 with every street in its ZIP and label the streets with the ZIP rather than the nine digits, every spelling of one ZIP is one charge for the week, and a test address written 99999-0001 is still a test address.

  • companies_registered counts only companies that still exist. Every register was audited against its own file:

    • Colorado's file lists every entity the state has ever registered. Of its 3,105,488 rows, 2,219,686 belong to entities that still exist — in good standing, behind on a periodic report (which the Secretary of State says does not mean a business has stopped operating), of a type that files no report, or whose agent has resigned — and the other 885,802, among them the dissolved, withdrawn, revoked, merged and converted, are no longer counted. 1500 North Grant Street in Denver read 136,102 companies in the 22 September compile and reads 122,145.
    • New York's file held one row per address per document filed, so a company was counted once per filing, under agents it had since replaced, and after it dissolved: its 869,311 registered-agent rows with a street belonged to 542,338 entities. The register now reads the Department of State's active-corporations file, 4,284,739 active entities on 23 September, each counted once, 873,062 of them with a registered agent's street. 28 Liberty Street reads 53,904 rather than 89,029, and 111 Eighth Avenue, where agents that have since moved once filed, 128 rather than 81,625.
    • Oregon's file lists active registrations only, but 1,819 of them named their agent more than once, spelled two ways at one address; each now counts once.
    • France already counted active establishments only. The 2,429,502 active ones whose owner has opposed publication read "[ND]" in every address cell, and they were filed under placeholder keys no real address matches; they are now left out altogether, so no count at a real French address moves — 60 rue François Ier in Paris still reads 31,270.
    • Connecticut and Hawaii (active entities only), Norway (a deleted entity leaves the file) and Companies House (a copy of the live register, with no dissolved company in it; a company in liquidation or under a strike-off notice still holds its registered office and still counts) were checked and are unchanged.
    • An agent who writes a building name, a suite or "C/O" on the first line and the street on the second is now read at the street, when only the second line carries a house number: 15,065 New York rows, 6,261 Colorado and 4,995 Oregon. 99 Washington Avenue in Albany, which many filings write as "One Commerce Plaza", now reads 4,794.

    Across the eight registers the local compile went from 23,946,643 company records over 9,652,828 addresses on 22 September to 20,812,823 over 9,478,132 today.

  • The parser finds the town, the organisation and the county.

    • A lone line before the state and ZIP, or before the postcode, is the town: Springfield, IL 62701, Southampton, SO14 2AQ and Paris, 75001 answer missing_street, as 75001 Paris already did, where they used to take the town for the street and pass. A line that carries a number, a road or building word, a unit or a box stays the street (Rose Cottage, SO14 2AQ is still a street address), and nothing changes in a country whose format has no town line, such as Singapore.
    • Where the line's shape leaves it open, the installed data decides: a place the postcode directory lists for the code or around it makes the line the town, and a street a register's listing files there makes it the street. Strand, WC2R 0RL is a valid street address confirmed by Open Names, and so are Piccadilly, Whitehall and Deansgate written the same way, while Mountain View, CA 94043, whose name ends in a street type, answers missing_street with Mountain View as the town, as Beverly Hills and Oak Park do. Where the data says both or neither, the shape still decides. Nothing new is reported in changes: the line was read, not moved.
    • A floor is the unit, not the street: 5th Floor, 167-169 Great Portland Street, London W1W 5PF reads 167-169 Great Portland Street with unit 5th Floor, and so does the floor written after the street or between the number and the street. Ground Floor Flat, 3rd Floor North, Level 3 and The Basement are units too, every unit an address carries is kept in the order written (5th Floor, Suite 500), and a building named with its number above a numbered street is the building: Tower 42, 25 Old Broad Street reads 25 Old Broad Street. In today's local compile the Companies House snapshot places 29,852 companies at 167-169 Great Portland Street and 27,998 at 86-90 Paul Street, and each of those spellings finds the same count. Across the whole snapshot no filing is keyed to a street that is only a floor any more, and 688 are keyed to a street with the word floor somewhere in it.
    • An organisation line needs evidence — a word such as Ltd, PLC, Bank or HM, or an acronym above a town written in mixed case — so HMRC, BX9 1AS is valid with organization HMRC while asdf, BX1 1AA answers missing_street. DVLA, Swansea, SA99 1AR separates the organisation too, and still answers missing_street: only a BX code stands in for the street.
    • A numbered box written after the street on the same line is the delivery point: 123 Main St P.O. Box 5, Springfield, IL 62701 answers po_box: "PO BOX 5", no street and box_line_moved, as PO Box 5 alone does.
    • An Irish Co. Mayo or Co. Kerry line after the town is the county, and the town stays the town: Main Street, Castlebar, Co. Mayo reads Castlebar in Co. Mayo.
    • Where a country writes the area after the town — Mexico, Italy, Spain, Turkey, Argentina, Chile and others — a line after the postcode's line is the area: Avenida Madero 1, Centro, 06000 Ciudad de México, CDMX reads Ciudad de México and CDMX, and İstiklal Caddesi 1, 34433 Beyoğlu, İstanbul reads Beyoğlu and İstanbul.
    • A / splits a line only where the postcode leads the town and never after a bare number, so CS 70001 / 01001 Bourg-en-Bresse CEDEX is a box above its town while 3 / 1234 Pacific Highway, Pymble NSW 2073 keeps its unit and number.
    • A care-of line that carries the street keeps it: C/O 39 Castle Street, Leicester LE1 5WN reads 39 Castle Street, where it used to read Leicester as the street.
    • Unit words match whole words only, so Stevenage, Flagstaff, Levelland and Unity are towns again rather than an STE, FL, LEVEL or UNIT unit.

    The parser corpus now holds 212 written addresses, 18 of them known limits with today's answer and a note.

  • Real streets are not placeholders. The shapes that mark a placeholder — no vowel, one letter repeated, a keyboard run — are now judged on the street without its type, never on the folded key that turned MG Road into MGRD and Bridge Street into BRST, which made the shape rules flag 1,013 of about 380,000 street names in Ordnance Survey's Open Names, and MLK Blvd besides. W counts as a vowel in Welsh (Bwlch, Cwmffrwd), R and L in Czech and Slovak (Tvrz). A part a dataset has confirmed is never judged at all: the comune of None under 10060, Jbphh for Joint Base Pearl Harbor–Hickam, CDMX for Mexico City. Measured over about 925,000 street names from the installed registers, 10 still flag.

  • Regions written the way people write them. Mexico's 32 states answer to their INEGI number, ISO code, abbreviation or name — CDMX, CMX, DF and Distrito Federal all mean Mexico City — and Ireland's provinces to their name, letter or Irish name, where any spelling but the directory's own used to score region_mismatch; Indian states answer to their ISO codes old and new, and a Spanish province with two official names to either. An Eircode routing key is filed under its province, so a county inside that province answers administrative_area_matches_postcode: null rather than a mismatch, a Dublin district key (D01–D24, D6W) confirms County Dublin (Dublin 2, Co. Dublin, D02 X285 matches), and a county in another province is still refused. Mexico City is a single city, so Ciudad de México written as the town of a Mexico City postcode is accepted, while any other state's name is still a wrong town.

  • The town check knows the municipality. GeoNames lists a colonia, a post office or a street under many postcodes instead of the town. For eighteen countries whose file carries it, the directory now also lists the municipality or city from the dump's own administrative columns: Guadalajara 44100, Mumbai 400001, Makati 1200, Sapporo 060-0001, Chiyoda-ku 100-0005 and București 030031 match their postcodes. Bulgarian cells holding a Cyrillic and a Latin name are split (Plovdiv 4000, Sofia 1000), as are Spanish ones holding two official names; every Singapore code lists Singapore; and Metro Manila's capital district lists Manila. Measured on the 9,903 towns of 15,000 people or more in those countries, the postcode nearest each town now names it 55.6 percent of the time, up from 32.8. The cost is leniency: a code 15 to 40 km away now names the town 10.7 percent of the time, up from 1.2, so a wrong town nearby is accepted more often than before.

  • CEDEX, box lines and Canada Post's stations. A CEDEX after the town is business-mail routing: 10 rue de Rivoli, 75384 Paris CEDEX 08 keeps its street and answers address_type: bulk_mail with po_box null, where it used to be read as a box and lose the street; a CS or TSA line is still the box. A street line that opens with a box written the short way — GD, P O Box, Apdo — becomes the box under the operator's name (GENERAL DELIVERY, PO BOX, APARTADO) and no longer stays the street as well, and a box on its own line beside a street keeps both and is written once in formatted, under the street, never again as a district. Canada Post's station stays on the box: GD STN A Calgary AB T2P 2M2 reads GENERAL DELIVERY STN A in Calgary, and PO Box 45 Stn LCD 1, Toronto ON M5W 1A1 keeps STN LCD 1 whole.

  • The sandbox takes an empty optional field. The published sandbox key answered 403 SANDBOX_VALUE_NOT_ALLOWED to the test order the Shopify page prints, because the empty address_line2 a checkout sends reaches the API as null. An empty or null optional field now counts as absent; a value of any other type is still refused, and an empty field never makes an address that is not a test address answerable.

  • Two data pages, and fuller district pages. Address data sources lists every address register and postcode directory installed — publisher, licence, declared coverage and what partial means there, the levels it answers at, its size and version, how often it is refreshed — with the company registers and mailbox operators counted but not named, and a Markdown twin. Great Britain postcodes in numbers counts the live and terminated postcodes in the ONS directory, with the Channel Islands and the Isle of Man counted apart, the codes introduced and terminated in each of the last 24 complete months, and a row per postcode area. Each British district page now also lists its postcode sectors, the twelve nearest districts, every road Open Names files under it (named roads first, then numbered ones) and how many companies Companies House lists with a registered office there, with, from a hundred up, how many sit at addresses 25 or more companies share: WC2H has 91,283, of which 89,741. A page headlines the place Open Names most often lists first rather than the region, and the 26 Channel Islands and Isle of Man districts have pages of their own, apart from every Great Britain figure.

  • Measured on real business addresses. The address product page now publishes a recall table: every Sunday a seeded sample of up to 2,000 addresses from each installed company register goes through the ordinary lookup, uncharged and unlogged, and the page prints how far each got — the building, the street, the postcode only, nowhere, no register to ask, or refused as written. In the local run of 23 September 2026, 1,441 of 2,000 Companies House filings matched a street. It measures the matcher on addresses other people wrote, skewed urban and towards formation agents, and it is not coverage.

  • Street listings read the register that files the postcode. Belgium's three regional registers share its postcodes, and the streets and postcode endpoints used to ask only the first, Flanders, so every Brussels and Walloon postcode listed nothing: the streets endpoint answered 404 and the postcode endpoint an empty list. They now try each register in turn and name the one that answered: 1000 Brussels lists 1,394 streets and 4000 Liège 857, and 1,143 Belgian postcodes can be listed, up from 517.

  • Known limits. Where neither the directory nor a street listing settles a lone line, its shape still decides: London Wall, EC2M 5QD is read as the town and answers missing_street, because Open Names files London Wall under EC2Y, and Canary Wharf, E14 5AB still passes as a street called Canary Wharf. And only a BX code stands in for a street; other large-user codes do not.

2026-09-20

The sign-up form now runs the email lookup on your address

  • The address is checked while you are still looking at the field. Leave the email box on /register and the form runs the same lookup POST /api/v1/email answers, and says what came back: a likely misspelling is offered as one click that rewrites the field (gmial.com for gmail.com, and acme.co for a domain you would otherwise have to spot yourself), a mailbox the handshake confirms gets a quiet line saying so, and an address that cannot receive mail gets the reason before the form is submitted rather than after. It is advisory — nothing is disabled and the button always works, because the address is verified for real when the account is created.
  • Only an undeliverable address is refused. A domain publishing no mail servers or a null MX record, mail servers that do not resolve, a username the provider does not issue, a mailbox the provider reports as missing or closed: in each case the confirmation link cannot arrive, and that link is the account — the API refuses every request from an unconfirmed address, and the starting credits land on the click. Everything the product calls risky still opens an account, exactly as before: a role address, a free provider, a catch-all domain, a domain registered last week. Those are answers this product sells, not grounds to turn a customer away at its own door. A disposable address is refused by the rule that has always refused it, in its own words.
  • The refusal you read is the refusal you get. The line under the field and the error on submit are one sentence from one answer, so fixing what the hint told you cannot lead to a second refusal worded differently — and the answer is reused across both, so signing up costs one mailbox check rather than two.
  • A check that cannot run opens the door. If DNS, the registration data or the mailbox handshake cannot be reached, the form stops objecting and the sign-up goes through. Refusing a paying customer because something upstream is down is worse than the unconfirmable address the check exists to catch, so it is never the outcome.
  • Changing the address on your profile is checked the same way — with one exception that has always applied to the disposable check and now applies here too: the address your account already holds always passes. A mailbox that goes quiet for an afternoon must not lock you out of the one screen you would correct it from.

2026-09-17

Address intelligence round three: the decision, the placeholders, the units, and what the parser did to your input

  • One boolean per address, decided once. Every address answer now ends with ok_to_ship and blocked_by, decided by a shipping policy the account keeps: the signals that block whenever they fire, a ceiling on the risk score, and whether a national register must have confirmed the building. The order is fixed — an address that cannot stand as written first (blocked_by: invalid), then the first blocked signal in the published weights' order, then the score ceiling (risk_score), then the register requirement (unconfirmed) — and the defaults block undeliverable, placeholder and vacant and any score over 60, require no register, and leave a mail drop at 50 to pass, because whether a parcel may go to a mailbox shop is a merchant's call. GET, PUT and DELETE /api/v1/address/policy read, change and reset it, free and unlogged; the Shipping policy tab on the verification page edits the same three rules with checkboxes beside the weights; batch, bulk, monitored lists, the browser endpoint and the five test addresses all apply the account's policy, the guest demo the defaults. The guide at /guides/gate-a-checkout-with-the-shipping-policy suggests three policies and says why not_in_register deserves thought before it is blocked.
  • Placeholders are named for what they are. "asdf", "test", "n/a", "no address", "same as above", "123 Fake Street", a row of x's, a keyboard run, four letters with no vowel: looks_placeholder is true when the street or the town of a valid address is one of those, by WHOLE name only — "Test Valley Road" in Hampshire and "Sample Road" fold to keys the list does not carry and are never flagged, and a short real town like Rye or Ely is never judged. A true answer carries the placeholder signal at 60, cites the bundled address-placeholders list in sources, and is blocked by the default shipping policy. It is not a typo detector: a misspelt real street still answers street_suggestion.
  • A suite at a single-family home. unit_check reads the flat or suite you wrote against the units a register lists at the building it found: ok, missing (several units listed, none written, weight 20 as unit_missing) or unexpected (a flat or suite written at a building the register lists as a single residence, weight 30 as unit_unexpected), and null when the register did not find the building, does not say how many units it has, or lists one unit at a non-residential building, where a suite is ordinary. A private-mailbox number never counts as a unit, so a PMB is scored once. The Austrian register counts dwellings, the Belgian one carries box numbers and the Australian G-NAF lists flats as rows; the United States National Address Database fills its unit column on about fourteen percent of rows and therefore never calls a building single.
  • How often you have seen this address. seen_by_you counts your own account's lookups of the same address inside a seven-day window, with the first sighting — kept under the premise digest, never the address, and never anybody else's lookups. A free repeat still counts, the window closes from its first sighting, the guest demo and the test addresses answer null, and it carries no weight; what three lookups in a day should mean is a rule for your policy, not for the score. There is still no cross-account address reputation, and there will not be one.
  • The address as its operator prints it, as lines, and in its own script. postal_code_formatted writes the postcode the way its operator does — the British space, the Dutch space, the Canadian and Irish spaces, the Polish, Brazilian, Japanese and Portuguese hyphens, the ZIP+4 hyphen, the Luxembourg, Latvian, Lithuanian and Moldovan prefixes, plus Malta, Azerbaijan, Bermuda, Brunei, Somalia, Iran, the Cayman Islands, Lebanon and the American territories — and null when the postcode is missing or of the wrong shape. formatted_lines is the same rendering as formatted, one line per entry, for a label printer or a form with a field per line; a named building now stands on its own line above the street. formatted_local hands a Japanese, Chinese, Taiwanese, Hong Kong, Macao or Korean address written in Han, kana or Hangul back in that script and in the country's own largest-first order, 〒 kept, and stays null for a Latin-written one, because transliterating would be inventing characters the customer never typed.
  • Three new weights, seventeen in all. placeholder 60 joins private_mailbox at the top below undeliverable; unit_unexpected 30 sits with the 30s and unit_missing 20 with the 20s. The docs page, its twin and the product page carry the same seventeen rows in the same order, test-enforced.
  • A parser corpus, and what it found. 128 written addresses across 28 countries now run through the parser on every test run, each with the parts it must produce: the shapes fixed last round (country lines, care-of lines, building names, number ranges) and the ordinary ones. The first run disagreed on nine; five were parser gaps and are fixed — a spaced letter after a trailing number ("Musterstr. 12 a"), Singapore's "#20-01" unit written without a space, Korea's trailing house number, an Emirati office line in a country with no postcode and no town line, and a lone postcode-and-town line — beside the Italian province code after the town ("00100 Roma RM"). Twelve cases are recorded as known limits with today's answer and a note, capped at a fifth of the corpus, so the file documents rather than hides them: Irish counties, Turkish provinces, German, Danish and Spanish floor lines, French résidence and bâtiment lines, the Austrian door triple, Japanese block numbers, fractional American numbers, addresses written on one line with no separator, Indian states before the PIN, Russian house lines.
  • Directionals and repetition indices fold. "1201 W Peachtree St NE" now finds a register row filed as "West Peachtree Street", and the reverse, because every street is keyed under its variants — as written, without a leading directional, without a trailing one, without both — at query time and, from today, at compile time; "9 North Elm Street" finds "Elm Street". French "6 bis rue de la Paix" reads as number 6 bis, the way the Base Adresse Nationale files it, with ter, quater and quinquies alongside. One caveat: a register compiled before today answers the typed-side variants only until it is recompiled, which its next weekly sync does.
  • Postcode directories learn from the registers, and from the Census. Ten complete registers — Switzerland, Austria, Czechia, Norway, the three Belgian regions, Luxembourg, Australia and Latvia — now add, as they compile, every town they write under each postcode and a centroid to the country's directory, beside the directory's own rows and never in their place: a register of buildings never lists the box-only and large-recipient codes, so replacing the directory would have turned real box addresses into postcode_not_found. Coverage moves to complete only when an operator's published total is configured and the union reaches it, and none is configured yet. Measured on the Czech register: 38 codes added to the dump's 2,694 and 2,639 given the register's places. The United States, with no complete register, gets its extra place names from the Census Bureau's ZCTA-to-place file, public domain: 28,668 of 41,488 ZIPs gained places in 3.5 seconds, 94043 now lists Mountain View, Palo Alto and Sunnyvale, and "1 Main Street, Palo Alto, CA 94043" answers a matching town. It does not list Moffett Field or Hollywood — the file describes the places whose land a ZIP touches, not the names the postal service accepts — so an American town mismatch is still reported and still never scored.
  • A summary on every finished bulk run. The address bulk payload now carries summary, built from the same cells the CSV holds: rows and duplicates, valid and invalid with their reasons, the risk split, the top ten countries and mail-drop operators, the address types, how far each row was followed into a register, how many rows carried each of the seventeen signals, the placeholders, and blocked — the rows the shipping policy refused. The dashboard's bulk panel shows it under each run; the webhook body is unchanged.
  • A page for every British postcode district. Each of the 2,872 outward codes with data behind it — 2,752 of them indexable — has a page under the address checker at /tools/address-checker/gb/districts: the postcode area and its name, the places Open Names lists for the district's postcodes, the live and terminated postcode counts, the streets filed under the district with the first twenty, a centroid, the datasets and their versions with the Open Government Licence attribution, and a hand-off into the checker with the district prefilled. SW1A lists 145 live postcodes and 33 streets; RG5, 504 and 285. A page says whether codes are in use, never who is there; a road is filed under one district; and there is no open register of buildings in Britain, so exists stays null there. Northern Ireland has no pages, because BT postcodes need a licence the open directory does not carry.
  • The round-trip report stopped failing on what it cannot measure. The weekly self-check that sends each register's own rows back through the pipeline now lists a register it cannot run under unmeasured with the reason — the British register of streets is keyed by district, whose rows the lookup refuses as a postcode before any match; the TIGER range table is managed by its own command and has no raw file to sample — instead of scoring them nought and failing every Saturday.
  • More registers. Germany gets its first open registers, a Land at a time, from the three Länder that publish house coordinates a fetcher can read: Berlin (Datenlizenz Deutschland Zero 2.0, 402,523 buildings), North Rhine-Westphalia (Datenlizenz Deutschland Zero 2.0, 4,503,290 buildings, keyed by municipality because the file carries no postcode) and Rhineland-Palatinate (Datenlizenz Deutschland Namensnennung 2.0, 1,404,472 buildings), all compiled on 17 September and round-tripping at 100 percent; each is partial, because a Land is not the country, so a German address the installed Länder do not carry answers exists: null, never false, and the other Länder either sell their data or publish nothing a fetcher can read. Brazil gets the IBGE's 2022 census address list, CNEFE, as twenty-seven state registers under one dataset — free public data with attribution to IBGE, partial because it is a 2022 snapshot — with a position, a building use and a unit from the complement fields for every point; verified on Roraima, 135,417 buildings at 100 percent, and the national compile is about 226 million rows and two and three-quarter hours. To read it, Portuguese street types and titles now fold onto one spelling, so RUA and R, AVENIDA and AV, SANTA and STA agree on both sides of a match. And the Dutch BAG, CC0 and complete, is in the register list: the parser reads the 3.6-gigabyte archive of archives through a new nested-archive reader, verified on a fixture built to the extract's shape, with the compile of the real file under way as this entry is written. Each register names its licence in the answer's sources and its coverage claim in register_coverage.
  • Deliberately not built. An American town mismatch still carries no weight, for the reason above. A register compiled before today answers only the typed-side directional variants until it is recompiled. The unit check counts a register's units and never matches them, because "Apt 7", "7", "Wohnung 7" and "bus 7" rarely agree across a register and a customer. And there is still no cross-account address reputation: seen_by_you is your own account's traffic and nobody else's.

2026-09-16

Address intelligence round two: what a register knows about a building, and what the parser did to your input

  • The parser stopped losing four kinds of address. A last line that only repeats the country ("10 Downing Street, London, SW1A 2AA, United Kingdom", "…, CA 94043, USA", "…, 10115 Berlin, Germany") used to become the town, with the real town demoted to a dependent locality; it is now set aside, and when no country was sent and the line names exactly one, it becomes the country. A care-of or attention line ("c/o Jane Smith" above "221B Baker Street") used to become the street; it is dropped, because the product is about places. A building name above a numberless street ("Rose Cottage, High Street") used to become the street with the street demoted; it is now building, a new field that is also accepted as a separated input and as a list column headed building, building name, premise or house name. And a house-number range ("71-75 Shelton Street") used to fold to a number no register lists; it is now asked as written and then by either end, in the registers, the company register and the mailbox lists alike. Each of these moves is recorded in a new changes list — country_line_removed, care_of_removed, building_separated, organization_separated, administrative_area_folded, box_line_moved, private_mailbox_moved_to_unit — so the answer says what it did to your input.
  • 53 fields, and a map of who confirmed what. Beside building: confirmation, an object naming for each of the postcode, the street, the house number, the town and the region which dataset stood behind it (postcode_directory, postcode_area, register, range, company_register, mail_drop_list, pattern), unconfirmed where a dataset could have and did not, or null where nothing could check; administrative_area_matches_postcode; premise_key, the hex digest of the building that every spelling of one address shares and that feedback and suppressions are filed under; building_use and is_multi_unit as the register declares them; street_suggestion; and changes. The guide at /guides/how-to-read-the-address-confirmation-map reads the map field by field; unconfirmed is never wrong.
  • Postcode directories for a hundred countries. The GeoNames postal-code dump, CC BY 4.0, installs one directory per country beside Britain's ONS directory: 105 countries and 1,097,486 codes from the 16 September copy, with the places each code is written with, the region and its code, and a centroid. Fifteen directories — the United States, Germany, France, Spain, Italy, Austria, Switzerland, Belgium, Denmark, Norway, Finland, Slovakia, Japan, Mexico and India — reach their operators' published totals and are recorded complete, so a code they lack answers postcode_not_found; the rest are partial and deny nothing. Canada, the Netherlands and Ireland publish only the forward sortation area, the four-digit district and the routing key, so those three match on their prefix and are partial whatever their size. Two consequences: administrative_area_matches_postcode compares the written state or province with the directory's, and a disagreement carries a new region_mismatch weight of 30; and locality_matches_postcode now checks the town against every place the directory lists for the code, then — before scoring anything — against every place in the postcode's area, so a post town written for a settlement reads confirmation.locality: postcode_area with no weight. The United States dump carries only each ZIP's default city, so an alternate city name there is reported as not matching and never scored.
  • Great Britain gets its streets and its towns. Ordnance Survey's Open Names, Open Government Licence, is installed as a register of streets keyed by postcode district: 999,065 road rows compile to 876,830 street keys in a 7.3-megabyte index, Welsh names indexed beside English ones. A British street the file lists answers match_level: street with confirmation.street: register; exists stays null because a register of streets lists no buildings, and a miss costs nothing because a road that runs through several districts is filed under one of them (87.3 percent of 49,106 Companies House street lines matched). The same file's postcode rows gave 1,742,466 of 1,749,109 installed British postcodes their places, so locality_matches_postcode answers in Great Britain for the first time, lenient to the region and the borough. The country page for GB says all of this in its own words.
  • A register now says where a building stands. The premise index keeps, beside every key, what the register publishes about the building: its position to about 1.2 metres, its use where the register categorises buildings, and its unit rows. A premise match answers latitude and longitude from the register with location_source: register, falling back to the postcode centroid as before; building_use reads residential, commercial, mixed, industrial, institutional, agricultural or other, and is_multi_unit is true at two or more listed units, false only where a register declares its units and lists none (the Austrian register counts dwellings), and null otherwise. National grids are converted in code — Swiss LV95, the Austrian Gauss-Krüger zones, Czech Křovák, Norwegian UTM 33 — and each conversion was checked against proj4js: the transverse Mercator and Helmert paths agree to between 0.02 and 0.06 metres, the Swiss approximate formula to between 0.4 and 2.3 metres. Recompiling the Swiss, Austrian, Czech and Norwegian registers grew each premise index from eight to fifteen bytes a key — the Swiss one from 25.8 to 48.1 megabytes — and the Federal Palace in Bern, the Storting and Oslo City Hall each answered within about twenty metres of their reference positions. A register compiled before this answers as it did until it is recompiled.
  • Seven more national registers, all open. Belgium's BeST address register in its three regional files (CC BY 4.0: Brussels 451,111 buildings, Wallonia 1,663,329, Flanders 2,798,435, every language spelling of a street indexed), Luxembourg's national addresses (CC0, 357,776 keys, filed under both spellings of the postcode), Latvia's State Address Register (CC BY 4.0, 322,198 buildings), Australia's G-NAF (CC BY 4.0 with its end-user clause against mailing lists; verified on the real tables, full compile pending), Denmark's national addresses (the agency's free geographic data; parser verified on fixtures, full compile not yet run) and, when a LINZ key is configured, New Zealand's street addresses (CC BY 4.0). Each names its licence in the answer's sources and its coverage claim in register_coverage. The Dutch BAG is not among them: its extract is a 3.6-gigabyte archive of archives that the reader cannot yet open, and it will come when it can.
  • United States address ranges. The Census Bureau's TIGER/Line address-range tables, public domain, compile into a range table consulted after the National Address Database has missed: a number inside a range published for its street answers a new match_level: range with confirmation.house_number: range and exists null, and a street the table knows whose number falls outside every range answers street with a new number_out_of_range weight of 20. A street the table does not know costs nothing, because the table is partial by measurement: not every edge carries a range and a street on a ZIP boundary can be filed under its neighbour. Delaware compiled in 23 seconds to 139,683 ranges on 24,424 streets; the national run is a few hours and about a gigabyte, started by hand.
  • Street suggestions, and a streets endpoint. Every register sync now keeps the streets it lists under each postcode by name. An address followed no further than its postcode answers street_suggestion, the register's spelling of the closest street (Banhofstrasse becomes Bahnhofstrasse, Limatquai becomes Limmatquai), and GET /api/v1/address/streets?country=CH&postal_code=8001 lists every street the register holds under a postcode — 200 in central Zürich — for one credit when it answers and nothing when it does not, with a seven-day repeat. GET /api/v1/address/postcode/{postcode} answers the same streets from the open registers wherever a listing is installed, at one credit, and keeps the licensed partner's full list for Great Britain and Ireland when one is configured.
  • Registered agents are named for what they are. A bundled list of registered-agent and formation services' own published offices — seventeen operators on 16 September, from the Delaware and Wyoming agents to the London formation addresses — answers mail_drop_source: registered_agent with the agent's name in mail_drop_provider, taking precedence over the company-cluster heuristic. The weight is the mail-drop 50, because nobody lives there; the guide says why a company registered there has done nothing unusual.
  • Three new weights, fourteen in all. region_mismatch 30, street_not_found 20 (a complete register of streets has no such street) and number_out_of_range 20 join the published table; locality_mismatch keeps its 30 but fires only when the town is found nowhere in the postcode's area and only where the directory lists every place a code is written with.
  • Five test addresses, a fields option, a ninth comparison. The houses numbered 1 to 5 on Spaw Test Street, Springfield, IL 99999 answer a valid address, a mail drop, an address a complete register lacks, a terminated postcode and — written "5 Spaw Test Street PMB 5" — a private mailbox, free, unlogged and recognised by every path including the sandbox key. fields on POST /api/v1/address and the browser endpoint keeps only the keys you name. And the cross-signal consistency check emits a ninth comparison, phone_address_region, the state a North American number was issued in against the address's region — evidence only, never a flag, because people keep their numbers when they move.
  • Deliberately not built. No cross-account address reputation, still: a postal address is where somebody lives, and a shared "post fails here" database would be a database about residences. No Dutch register yet, for the reason above. And no licensed delivery-point answers: is_deliverable, is_cmra, is_vacant and is_residential stay null until a partner is enabled, and building_use from a cadastre is never presented as the postal service's residential indicator.

2026-09-13

A sending policy, monitored domains, and the decision the answer now makes

  • The answer makes the decision now: ok_to_send and blocked_by, 46 fields in all. The score was always arithmetic, and the decision was always yours to write as an if-chain. A sending policy writes it once: the signals that block whenever they fire, a ceiling on the score, and a floor for a catch-all or unverified mailbox's confidence, applied in that order after the verdict. An undeliverable verdict never passes; blocked_by names the rule that stopped anything else — undeliverable, the blocking signal's own name, risk_score or mailbox_confidence. Until an account sets one, the published defaults apply (disposable, likely_typo, parked_domain, mx_blocklisted and no_reply block, and so does any score over 60), and the guest demo, the sandbox key and the test addresses answer with those. The policy belongs to the account and reaches every channel it is billed for — single and batch lookups, bulk runs (two more result columns and a blocked count in the summary), monitors, the form widget and the dashboard. GET, PUT and DELETE /api/v1/email/policy read, change and reset it; the Sending policy tab on the verification page edits the same three rules.
  • Monitored domains. A domain's mail setup drifts: a provider is switched, a DMARC policy loosened, a registration lapses into a parking page, a mail host lands on a blocklist. POST /api/v1/email/domain-monitors saves up to 500 domains and re-checks them daily, weekly or monthly through the domain lookup, billed the same way — one credit when a domain accepts mail, nothing when it is dead, repeats inside seven days free, so a daily cadence costs at most a credit per domain a week. Each domain answers ok, flagged, dead or invalid with every flag that fired: parked, blocklisted, expiring, no_spf, no_dmarc, and five compared with the previous run (provider_changed, spf_changed, dmarc_changed, dkim_lost, mta_sts_lost). The first run baselines; after that an alert goes out only for a domain that was ok and no longer is, or gained a flag it did not have, by email and, when set, by the same signed webhook the other monitors post. List, get, update, run and delete endpoints, a Domains tab on the verification page, and the SDKs carry it.
  • fields on the single and browser endpoints. A comma-separated list of the keys you want, answered in the answer's own order; unknown names are ignored, sources stays only when named, meta is never narrowed. With 46 fields and a batch of fifty, an answer should be as wide as the caller needs.
  • The bulk summary on the dashboard. A finished run's verdict and risk splits, its most frequent reasons, providers and domains, and how many rows carried each signal, drawn beside the run.
  • A second handshake path. When the first handshake path is unavailable — the circuit breaker open, a transport failure — a second one now answers the same probe, so a partner outage costs latency rather than verdicts. Neither is named anywhere a reader can see, as before.
  • Two more free tools. A DKIM checker at /tools/dkim-checker lists the common selectors that publish a key; an MTA-STS checker at /tools/mta-sts-checker reads MTA-STS, TLS-RPT and BIMI beside the SPF all-qualifier and the DMARC pct and reporting address. Both run on the domain report the domain tools already share.
  • Typo suggestions read more than the big providers. did_you_mean now draws on the thousand most-linked domains and the domains an account verifies most, so acme.co is corrected to acme.com; a domain outside the provider list is suggested only when its own mail servers accept mail. The bundled first-name list gained the MIT male list, so a lone male first name reads as a first name rather than a surname.
  • Bounce reports feed the not-found memory. A reported bounce whose provider text says the mailbox does not exist (a 5.1.1 code, "user unknown" and its kin) is recorded the way a probe's not-found answer is, so a later mailbox that exists again reads as recycled from day one; a one-off command backfills the last six months of stored reports.
  • Measured reasons and confidence. The status page's measured block gains two tables: the delivered share per verdict reason, and per mailbox-confidence bucket (under 40, 40 to 69, 70 and over), each published only under the same floors as the verdict figures — a hundred outcomes from three accounts, none holding more than half — and never called accuracy.

2026-09-12

What the username and the domain look like, and two signals read from traffic

  • Seventeen more fields on every email answer, 44 in all. About the username, with no network call: local_part_pattern names its shape (first.last, flast, a lone first name, digits, a plain word), first_name and last_name carry the name tokens the bundled name lists recognised — read from the address itself, never appended from outside, and never a promise of correctness — and looks_generated marks a hex string, a UUID, a placeholder word or a name glued to a run of digits. About the domain: domain_category sorts relay, disposable, webmail, academic, government and military domains from the rest; mx_provider_type says whether the provider hosts mailboxes, is a security gateway, a transactional relay, a web host or the domain's own servers; mx_blocklisted and mx_network_type run the mail hosts through the threat blocklists and network feeds the IP product already installs; domain_expires_at and parked read the registration's end and the parking nameservers. About the mailbox: role_type names the family a role address belongs to, previously_invalid and previously_invalid_at say whether a confirmed mailbox once answered "no such mailbox", and widely_seen says whether three or more other accounts looked the address up inside a day. risk_signals lists every weight that fired with the dataset it was read from, so the score is always recomputable from the answer; it is empty on an undeliverable verdict, where the verdict is the answer.
  • Relay addresses are real mailboxes. Firefox Relay, SimpleLogin, Addy.io, DuckDuckGo Email Protection and their kind sit on the open burner list because they hand out addresses on demand, and Spaw used to report them as disposable. An address there is a real person's real mailbox reached through a name they control and often kept for years, so it now answers is_relay: true with the service in relay_service, disposable is forced false, and the score carries 5 for the withheld identity instead of 80.
  • Four new reasons, all risky and all billed like any risky answer: parked_domain (the nameservers belong to a parking or for-sale service and the domain still accepts mail: whoever collects that mail is not your customer), mx_blocklisted (the mail hosts resolve into a Spamhaus DROP netblock or the abuse.ch botnet list — a burner signal no domain list has caught yet), no_reply (a noreply, donotreply or mailer-daemon shape, recognised by pattern even when the role list lacks the spelling; mail to it is discarded by design) and recycled_mailbox (the mailbox exists now and once answered that it did not — the shape of a recycled address, the spam-trap kind included). Every one has its own page under https://spaw.co/docs/reasons.
  • Twenty-one published weights. New rows: parked domain 60, blocklisted mail network 60, recycled mailbox 30, generated username 20, no-reply 20 (on top of the role row), widely seen 20, catch-all behind a security gateway 15 (instead of the catch-all 30, because behind Proofpoint, Mimecast or Barracuda a catch-all is the gateway's design rather than a squat), registration ending within 30 days 10, relay 5. The twelve rows that were there are unchanged. mailbox_confidence also reads whether a username follows the naming convention seen in the domain's confirmed deliveries: +15 when it does, −10 when a name shape breaks it, from pooled outcome counts per domain and never from an address.
  • The domain endpoint answers 31 keys. Beside what it answered before: domain_category, mx_provider_type, mx_blocklisted and mx_network_type; the SPF all qualifier and the DNS lookup count the policy needs (over ten is a permanent error at every receiver); the DMARC pct tag and whether aggregate reports are collected; the common DKIM selectors that publish a key; MTA-STS, TLS-RPT and BIMI; the registration's expiry, last change, registrar and whether the registrant is withheld; parked, is_relay and relay_service. All free DNS and the RDAP record already fetched, cached for a day so the twenty TXT queries behind the DKIM block are paid once per domain.
  • Bulk runs finish with a summary. A finished run carries rows and duplicates, the verdict and risk splits, every reason and domain category seen with its row count, the ten most frequent providers and domains, and how many rows carried each of the disposable, relay, role, catch-all, parked, blocklisted, previously-invalid, widely-seen and generated signals — built from the same rows the result file holds, so the two can never disagree. The result file carries the new columns in the answer's order, with risk_signals flattened to signal:weight pairs; the webhook body is unchanged.
  • What is stored, and what is deliberately not built. widely_seen is a keyed hash of the address with the account ids that saw it and when, kept for the day; the "no such mailbox" memory behind previously_invalid is a date under a keyed hash, kept for a year. Both are read cross-account as counts and dates only, neither says who asked, and both are erased with the address through the existing erasure route; the privacy page says so. Not built, on purpose: breach-exposure data, which would send every address to a third party; account enumeration through other sites' password-reset endpoints, which is exactly the enumeration this product tells its customers not to do; bought spam-trap lists, which no vendor can legitimately sell; and an opaque learned score in place of the published weights.
  • Every client follows. The Node SDK types the 44-field result, the 31-key domain answer and the bulk summary; the Python and PHP clients pass the new fields through; the Zapier app (1.9.0), the Make modules, the Google Sheets functions (0.9.0) and the MCP tools describe and map them.

2026-09-12

IP intelligence round four, from corporate gateways to your own lists

  • is_corporate_proxy and corporate_proxy name the egress of a corporate secure web gateway from the cloud enforcement node ranges Zscaler publishes for its eight clouds. An employee behind one reaches the Internet from the gateway's address, so the answer now says workplace rather than datacenter: no weight, the datacenter weight is skipped for it like relay egress and verified crawlers, and the consistency endpoint withholds its country comparisons for it.
  • Three more named VPN lists — IVPN, AirVPN and the VPN Gate volunteer relays — join Mullvad, NordVPN and Private Internet Access, and Cloudflare WARP is answered by inference as Cloudflare's own network outside the CDN ranges it publishes, with privacy_service reading Cloudflare WARP (inferred) so nobody mistakes an inference for a list. GitHub's hosted runner ranges join the datacenter feeds as hosting_provider GitHub Actions.
  • The Tor feeds sync hourly now, and each sync rebuilds a thirty-day exit history: tor_last_seen dates the address's last sighting as an exit, and an address that left the list inside the last seven days carries a recently_tor weight of 40, because exits rotate daily and yesterday's exit is still the same machine.
  • sanctions names the OFAC country programmes that name the country, comprehensive ones marked apart, and fatf_list says whether the FATF lists it under a call for action or increased monitoring as of its June 2026 plenary. Both come from a curated file that names its sources and dates, carry no weight, and are facts about a place rather than a verdict about a person.
  • timezone now resolves for split countries and regions — Texas, Canada, Australia, Brazil, Russia, Indonesia — from the nearest place in the GeoNames list inside the located country, and timezone_source says whether the region, the country or the nearest city settled it. Send browser_timezone with a lookup, from the server endpoints or the browser endpoint, and timezone_matches says whether the visitor's clock agrees with the network's.
  • connection_type names what positive evidence says the address is attached to — corporate_proxy, hosting, satellite, mobile, or residential from a dynamic reverse-DNS name — and is_mobile and is_satellite come from curated positive-only operator lists (Starlink, Viasat and HughesNet; T-Mobile, Verizon Wireless, AT&T Mobility, EE, Three, Jio, Airtel, China Mobile, Telcel and more). Null never means residential. With hostname: true, hostname_kind and hostname_domain say what the naming convention says and which registrable domain the name belongs to.
  • Your own lists: /api/v1/ip/lists and the dashboard's new Lists tab keep up to 20 allow or deny lists of up to 5,000 addresses or prefixes each, applied to every lookup the account is billed for. An allow-list hit zeroes the score with a single allowlisted signal saying why, a deny-list hit tops it at 100, deny is checked first, and account_list names the list and the entry that matched.
  • seen_by_you counts how many times your account looked the address up in the last seven days and when it first did — velocity, kept under the same keyed hash as the repeat marker, never the address — and privacy: true skips it along with the marker.
  • A fields option on the single-address endpoints keeps only the keys you name, so a page that wants a country and a currency receives two keys rather than sixty; the two GET endpoints answer with Cache-Control: private, max-age=300. Monitored lists accept CIDRs of up to 256 addresses and expand them under the 500 cap.
  • Bulk result files carry the new columns at their response positions, and a finished run's payload adds a summary: rows, reserved addresses, the top countries and networks, how many rows carried each signal, how many hit one of your lists, and the risk split, added up from the same cells the CSV holds.

2026-09-12

Hosts seen attacking, as their own signal

  • Every IP answer carries is_attack_source and attack_source: whether an attack-source list names the address, and which one. The lists are Emerging Threats' compromised-hosts list, the CINS Army list and blocklist.de, and they name single hosts seen attacking honeypots and sensors in the last days rather than netblocks or operators, which is why they sit beside is_blocklisted rather than inside it. The flag is tri-state like every other, and the three lists cover IPv4 today.
  • A hit weighs 50, between your own reports at 60 and a datacenter range at 40, because an entry says a machine was misbehaving this week, not that a netblock belongs to a criminal operation. risk_signals carries it as attack_source with the dataset attack-source-lists, the bulk CSV gained the two columns after blocklist, and a finished bulk run's summary counts how many rows carried it.
  • The lists are refreshed hourly, like the botnet list and the Tor exits, and a monitored address that newly appears on one is flagged attack_source and named in the alert as "seen attacking, listed by" the list.
  • The publishers' terms, as published on 12 September 2026 and repeated on the product page: Emerging Threats' open ruleset, which its compromised-hosts rules are built from, is BSD-licensed and is credited as that licence asks; CINS says its Army list is "provided as a simple text file, with which you can parse and use in any way you see fit"; blocklist.de calls itself a free and voluntary service and offers its export files "as they are", with no licence text.

2026-09-10

One upload for every signal, a hero that answers, and a reference you can run

  • The cross-signal check takes a list now. It was strictly one record at a time, so scoring a signups export meant four separate uploads to four pages and a spreadsheet join afterwards — and the comparison nobody else computes was the one thing you could not get for a list. There is a fifth bulk run type: one upload, one header row, and the parser works out which column is the email, which is the phone, which is the IP and which are the address fields by composing the four detectors that already existed. A row where only two of the four resolve is a perfectly good check of those two and is run as one.
  • It bills exactly as the single call does, leg by leg, and the comparison stays free — so a row costs between nothing and four credits depending on what its columns reached and what answered. The preview prices it on the columns actually mapped, so a file that only reaches two signals is quoted at two a row rather than four. It goes through the same preview-then-start flow as the other four, with no one-shot upload and no paste box: a pasted line cannot say which signal it is, and a mis-read column costs four times more here than anywhere else.
  • The landing page stops playing a recording. The hero has been auto-rotating four canned lookups with no input at all, on the busiest page on the site — while five real widgets and five throttled guest endpoints existed and were mounted on every marketing page except that one. Each tab now holds the live widget for its signal, seeded from the lookup it used to play, so the panel still shows a full answer the instant it loads and runs a real one the moment you ask.
  • One of the recorded values it paints at rest was wrong, which only mattered once the live answer landed on top of it: mailinator.com is a free provider and the record said it was not. A test now runs each recorded value through the real pipeline, so the page cannot show a number and then correct itself in front of the visitor.
  • The endpoint reference runs. A hundred-odd pages you could read and none you could try. Six operations — the ones that cost at most a credit, that a guest can already run free, and whose demo answers the same shape the page documents — now have a console on their own page. It says what the call will cost before you press anything, never runs on load, and every other operation says why it has none rather than silently missing one.
  • A run does everything the endpoint does, and the page says so: the answer is logged where that endpoint logs it, and a verdict that suppresses an address or a number suppresses it here too. The first version claimed it "changes nothing", which was not true of three of the six and would have had somebody delete them.
  • The consistency run also got what the other four have had for weeks and it did not: a completion email when it finishes or stops, a place on the dashboard's live strip, its row in the published list of webhook events, and its uploaded file deleted with the account. That last one is the third time this exact gap has appeared — a database cascade fires no application code, so a new run type's files outlive the account unless something deletes them by hand.

2026-09-10

See what we parsed before you pay for it

  • Choosing a file used to be starting a paid run. Upload, and the list was parsed, the columns guessed, the job created and the work queued in one request — the first time you saw what we made of your file was on the invoice. It is two steps now. The first parses the list and shows you the delimiter we read, which column filled which field and whether a heading named it or we guessed, the first rows as we parsed them, the repeats, your balance and the most the run can cost. The second starts it, and takes corrections.
  • That matters most for addresses. The column headings we recognise are English, so a German or French export fell through to a heuristic that picks "the column with the most address-shaped values" — and a wrong guess on forty thousand rows was a wrong guess you paid for. Every column now has a select beside it with its first value, and correcting one re-reads the file through the corrected mapping.
  • The cost is called an upper bound, not an estimate, everywhere it appears — rows come back free for reasons no preview can know in advance, so the real figure is always the one on the finished job. Two ways that promise was wrong when it landed, both caught in review: correcting a column changed which values repeat, and the ceiling was still quoting the guess; and a privacy IP run writes no repeat marker at all, so the repeats it was subtracting as free were going to be billed. A draft you never start is deleted after a day, and every read path enforces that whether or not the sweeper has reached it.
  • Automatic top-ups, with a ceiling you set. Running out of credits means your own signup form starts rejecting people, and until now the only warning was the balance email. You can now have a pack bought automatically when the balance falls below a line you choose — strictly opt-in, off by default, and hard-capped by a monthly ceiling we never exceed. Reach the ceiling and the answer is the ordinary out-of-credits refusal, not one more charge: "a hard stop, never a surprise bill" is a promise this site makes in four places and it still holds.
  • Every automatic charge is on the usage page as its own ledger entry and confirmed by email. It is charged straight to the card rather than invoiced, unlike a pack you buy yourself, and both the email and the page now say so — they had said the opposite.
  • A trust page. The data processing addendum was a bullet under an unrelated heading, subprocessor disclosure was one vague sentence, and there was no questionnaire answer set, no security.txt and no statement about encryption, backups, access or breach notification. /trust carries all of it: the subprocessors by role and region, a standing DPA, the answers a security review actually asks for, and an RFC 9116 security.txt with a response window. It holds no certifications and says so plainly rather than implying an audit that has not happened.
  • A way to be forgotten for people who never signed up. Abuse reports about a number or an address raise the risk other customers see, which means this service holds data about people who have no account here — and the only "forget" in the whole codebase was a cache invalidation. There is a form under /privacy now, verified by email so it cannot be used against somebody else's data, an admin action, and a command behind both that sweeps every table holding that identifier and leaves an audit row proving it happened.
  • One thing it deliberately does not delete: a suppression entry a customer imported themselves. That is their own do-not-mail list, and erasing it would do the opposite of what the person asking wants — it would un-suppress them and the next campaign would reach them again. The first version deleted those; the rule against it has been written down since the feedback system shipped.
  • The link the form emails is now one per mailbox rather than one per spelling. Keyed on what was typed, mia+1@, mia+2@ and m.i.a@ each opened their own window on the same inbox — so the only thing standing between a public form and a way to mail a stranger repeatedly was not standing anywhere.
  • A page per country for the address formats we hold. Two hundred and seven countries of write order, local terminology, required fields and postcode shape had been sitting in a data file, published nowhere. Each country that has enough to say now has its own page, gated by a catalog so the ones that do not are left out rather than filled with prose. Every page says what this service can actually check there, which for most of them is the format and the postcode and not a register lookup.

2026-09-10

Your team shares one balance, one set of keys, and one way out

  • An account can have more than one person on it. Everything about the product assumed one login owned one balance, so a team either shared a password or bought a second account and reconciled two bills. An account now holds the credit balance and the API keys, and the people on it — an owner, admins and members — spend the one balance and use the one set of keys. The monthly free grant is per account, not per head, which is what it always said on the pricing page.
  • The team page says what is shared and what is not, because not everything is. Per-resource ownership deliberately did not move: your bulk runs, your lookup history and your suppression lists stay yours and stay private from your colleagues. A customer who read "shared account" as "shared everything" would be wrong about three things they care about, so the page lists them rather than leaving it to be discovered. The ledger does answer "where did my credits go" account-wide, with a spend column per member.
  • Inviting is a signed link that consumes nothing when it is opened. Corporate mail scanners fetch every link in an inbound message, and a one-step accept would be burned by a robot before the human ever clicked it. Only the token's hash is stored, so a database dump hands over no live invitation, and withdrawing one kills the link immediately. Nothing in the flow says whether an address already has an account here.
  • Before you accept, the page prices it: the balance you are leaving behind, the keys that change hands, and how many scheduled monitors move with you. Accepting is a move, not a merge — the account you own stays exactly as it is, dormant, holding its balance and its whole history, and it is what you come back to if you leave the team.
  • Leaving settles what you leave behind, whichever of the three ways out you take. Being removed, leaving, and accepting an invitation somewhere else are three different doors and only two of them settled anything. Your secret keys on a team you have left are deleted, because a secret key is the account's credential and somebody who has gone must not walk away with one that works. Your publishable keys are not: those are embedded in the customer's own site and revoking one takes their widget down, so they are repointed at the owner instead.
  • Your scheduled monitors come with you. They were stamped with whoever was paying at the time they were made and nothing ever revisited that, so a list belonging to somebody who had left went on running hourly and billing the team they left — for good, while they could still add rows to it. They now move to whichever account you are on, so the bill follows the person who controls the spend. Nothing pauses and nothing is deleted.
  • A daily check that can actually be cleared. There is a command that asserts the whole thing — every person on exactly one account, every row that spends money pointing at a live one, every monitor billing an account its owner is actually on — and it runs at 03:35 every night. It had one hole worth naming: it did not know about the dormant account somebody leaves behind when they join a team, so the first accepted invitation would have failed it permanently. An alarm that can never be cleared is worse than no alarm.

2026-09-10

Webhooks that cannot be replayed, and pages that only say what they know

  • Webhook signatures carry a timestamp now, and the old header has an end date. A signature over the body alone never goes stale: anyone who captures one delivery — a log aggregator, a proxy, an error report pasted into a ticket — can post those exact bytes at your endpoint next year and your check still passes. Every delivery now also carries X-Spaw-Signature-V2, written t={unix seconds},v2={hex}, where the hex is the HMAC-SHA256 of v2:{t}:{raw body} — so the timestamp is signed as well as sent, and a receiver that refuses anything more than five minutes from its own clock narrows a replay from forever to five minutes.
  • Both headers go out until 1 March 2027, and only the timestamped one after that. Nothing about your secret, your URL or the payloads changes when the old one goes. Move your check any time before then: all three clients ship it as one call, and POST /api/v1/webhooks/test now hands back both signatures and the timestamp, so you can check new code against a real signed delivery before anything depends on it. It had been publishing only the header being retired — the one field it told you to trust was the one that stops arriving.
  • A new API key hands you a command that key can run. The panel that shows a secret key once now picks the request from the key's own scopes — the free email test address, or the free phone test number — instead of always printing an email lookup, so a key narrowed to one product is no longer given a request that answers 403 KEY_SCOPE_DENIED for the rest of its life; a key that can run neither is told so, and what it would get back if it tried. A key pinned to an address allowlist keeps the command but not the "send" button, because a request from your browser would be refused — the allowlist doing its job. The button really does use the key, so pressing it finishes the dashboard checklist rather than pretending to.
  • Every bulk run inside the retention window is reachable from the dashboard. The panels showed ten and stopped, while the runs themselves are kept for thirty days — so an eleventh run was billed, finished, on disk, and unreachable except through the API. There is a pager now, with one-click jumps to the newest and oldest runs, and starting a run from an older page returns you to the page the new run is actually on, so its progress ticks over instead of sitting behind a page you had no reason to turn.
  • The comparison pages say what they actually know. A row a vendor's own pages carry no figure for now reads "not on the pages read" rather than "not published" — a claim about our reading, not about the vendor, because two of the vendors compared here block automated readers and do publish the prices those rows were denying. Where that is the case the page says so beside that vendor. Each product also has its own comparison hub now, reachable from the footer, and a product added with no comparison written for it fails by name instead of quietly inheriting another product's page.
  • The measured-quality section on /status states only what it can prove. The figure weighing customer-reported outcomes against our own verdicts counts the same population on both sides, so it can no longer print a share above 100% by dividing reports by lookups. A bounce share appears only once at least a hundred reported outcomes stand behind that verdict, from at least three accounts counted for that verdict alone, with no single account holding more than half of them — and the page says which floor is holding a figure back. A business register whose release file reads back as no identifiers at all is a failed check named on the page, not a silent clean sheet.
  • The privacy policy and the terms stop saying things the billing code does not do. An automatic top-up is charged straight to the card and raises no invoice — the terms claimed every payment raised one, and sent you to support with an invoice number that cannot exist. Both now say what the billing page has always said, the refunds route works for a charge that has only a date and an amount, and the switch that stops automatic top-ups is named as being on the billing page, which is where it is.
  • The terms no longer promise an email and a ledger entry for every top-up: when the payment processor's answer is lost to us, a charge can end with no credits, no entry and no mail, and the page says so rather than hoping. Two things that made that worse are fixed: the code that reconciles exactly this case was listening for a payment event the payment endpoint was never subscribed to, and so was the code that tells you a subscription payment failed. Both are live now.
  • On the privacy side the retention table gained the three stores it was missing — your account, your API keys, and the pending erasure request the form on that page holds for an hour — and names the erasure route for a postal address and an IP as it already did for a phone number. Every window it publishes, in the table and in the prose around it, is now pinned to the configuration that does the deleting. Two of them were published with nothing behind them at all.
  • Smaller things. The signed-in tab strip fades on whichever edge still has tabs behind it, measured rather than always on, and keyboard focus keeps clear of that fade instead of parking underneath it. And every code block on the site was copying its own "Copy" button label into the command — pasted into a shell, that ran the command and then reported Copy: command not found.

2026-09-10

A monitor that tries again, and a webhook you can ask for again

  • A monitored list whose run dies is checked again within the hour, not next month. A verification run that loses its worker — a deploy, a restart, a host that goes away — used to leave the schedule where it stood, because the schedule is pushed forward before the run starts so an hourly tick can never run a slow monitor twice and bill you twice. The cost was that a monthly list went unwatched for another month, while the dashboard showed the run before it as if nothing had happened.
  • A run that dies is now recorded as one, and the list is re-checked about 45 minutes later — the sweep ticks hourly, so in practice between 45 minutes and about an hour and three quarters. Exactly once: a list that is broken rather than unlucky waits for its normal cadence instead of burning a run every 45 minutes, and pressing "Run now" while that re-check is pending no longer resets the count, or the row, which used to say "we have stopped trying" while the next run was forty minutes away. A run that stopped for lack of credits is untouched by any of this — it stopped for a reason a timer cannot fix.
  • The re-check window is derived rather than picked: it is longer than the longest a run may take, so a re-check can never fall due while the attempt it replaces might still be going. And a monitor row whose run never finished no longer prints a green "all clean" it has no results to stand on — a failed run has no counts, and an absent count was reading as zero.
  • A bulk run's completion webhook can be asked for again. Press Send webhook again beside the run on any Bulk runs tab, or call POST /api/v1/{email,phone,ip,address,consistency}/bulk/{jobId}/webhook/redeliver. Until now a 100,000-row run whose endpoint happened to be down for four minutes was finished, billed, on disk — and your pipeline never learned, with polling the only way back.
  • The body is byte-for-byte the body the first delivery carried. The signature is stamped afresh, because a receiver refuses anything whose timestamp is more than five minutes old. The delivery is queued rather than made while you wait: one attempt against an endpoint that is down takes half a minute, and that is precisely the endpoint you are asking about — so the call answers 202, the run reads webhook_status: "pending" until a worker has an answer, and then delivered or failed with what your endpoint said. Free, and capped at ten a minute.
  • In the same release, cancelling a run that was still queued now fires its completion webhook with status: "cancelled" — which the documentation had always promised and the code had never done, so a redelivery for such a run would have been its first delivery while every page called it a repeat.
  • A monitor's stopped_reason is documented now, on all five products: insufficient_credits and key_spend_cap where a run stopped part-way, run_failed where it died, and run_failed_retrying where it died and the schedule has been pulled in — with retry_at beside it saying when. A summary carrying either failure reason has no counts in it at all, so a missing count means unknown rather than zero.

2026-09-10

Invoices, ninety days of uptime, and a key you can use before you sign up

  • The first request in the documentation now runs. Every generated sample printed sk_live_…, so evaluating this API meant creating an account, confirming an email and minting a key before you could see a single real response. There is a published sandbox key now. It answers a handful of read-only lookups for the sample values the documentation prints — the test addresses, the fiction-block numbers, the sample IP and postal address — and refuses everything else with a typed error that names what it would have answered for.
  • It is a credential everybody has, so its limits are the point: no account behind it, no credits spent, nothing written that lasts, no arbitrary value looked up, and its own small budget counted per source address. Anything that would cost something — a callback URL, a forced refresh, the live carrier check, the deliverability check — is refused rather than ignored. meta.sandbox is true and credits_remaining is null, because there is no balance it could be about.
  • Businesses can get an invoice. Checkout collected no tax number and no billing address, and a credit pack produced a receipt rather than an invoice — so an EU or UK company could not get the reverse-charge document its accountant needs. Both now ask for a tax number (optional: a private buyer has none) and a billing address, and a pack raises a real invoice. Past invoices are listed on the billing page with the number, the date and the total each one states.
  • A customer who only ever bought credit packs could not reach the billing portal at all. The "Card and invoices" button sat inside a branch that only rendered for subscribers, so a pack customer had no way to update their card and no way to read past invoices. It is reachable by anyone with a card on file now.
  • Ninety days of measured availability, and a straight account of what it cannot see. /status had no memory: no uptime figure, no incidents, no feed, and the raw data was deleted after a week before anything could roll it up. There is a daily rollup now, a ninety-day strip on the status page and the same figures on the status endpoint.
  • Read it as a floor on the bad days, never as proof of a good one — and the page says exactly that, at some length. It only counts requests that reached this server and were recorded. A whole day of outage is blank, and blank is never counted as good; but an hour of outage leaves the other twenty-three, and the day can read as a clean 100%. A partial outage is the common case and this figure is close to blind to it. A fault that also stops the recording write — a database incident, the likeliest cause of a real burst of 5xx — takes those failures out of the number along with everything else. There is an incident log because that is where an outage is actually described, by a person, and a feed at /status.rss to follow it.
  • The widgets ask for something now. All four ended at the verdict with the nearest sign-up button a page-height away. There is a footer under each: "Copy as cURL" writing the real request for the value just typed, a link to the full response fields, and the way to get a key. A test replays every command the footer offers against the real API, and another pins each widget's own default to the command being replayed — so a widget cannot quietly start copying something that fails.
  • The landing page and the product pages read as one site again. They had drifted into two design languages, one with uppercase section eyebrows and one without; the rule already said which wins, and the eyebrow text is folded into the headings rather than dropped.
  • Three things found while checking the above, all in work from this same day. A value that was not a string — {"email": ["a"]} — made the sandbox throw rather than refuse, so a credential printed in our own documentation could force a 500 on the most-read endpoint on the site. Sandbox calls were being kept out of the request timings entirely, which was right for the latency percentiles and quietly took sandbox failures out of the availability figure that exists to catch them; they are recorded and flagged now, so each number sees what it should. And the availability strip's own caveat was wrong in the direction that flatters us — it described a whole-day outage and left the reader to assume it covered a partial one.
  • Smaller corrections: the invoice list said every row carries a tax number and a billing address, which is not true of anything bought before today; the billing page was making an outbound call to Stripe on every tab focus, which also made the local credit balance wait on it; the consistency product page still said "four signals" in six more places; and the rate-limit reference page did not know about the sandbox budget, so a reader meeting it had nowhere to look it up.

2026-09-10

A support page, a glossary, and the work nobody could reach

  • Support is a page now, not a mailto. Writing to us meant an email address in the footer, with nothing said about what happens next. /support is a form that, when you are signed in, already knows your account, your plan, your balance and your five most recent request ids — the things support would otherwise have to ask you for, and the ones you would otherwise have to go and look up. It says plainly what happens next, including what it does not: no auto-reply, no ticket portal, and no response-time commitment we have not measured. What "priority support" on the paid plans actually buys is stated in one sentence rather than implied.
  • Filed requests are kept for 180 days and then deleted whether or not anyone read them, and the privacy policy now says so — a form that stores a message, a reply-to address and the account it came from is a store of personal data, and the policy's collection and retention sections are exhaustive lists.
  • Thirty terms have a page each. Every machine value already had one — 58 verdict reasons, 18 error codes — while the words around them lived only inside guide bodies, where nothing could link to them. Catch-all, greylisting, null MX, implicit MX, ASN, CIDR, E.164, NPA/NXX, LEI, PMB, mail drop, delivery point and the rest are now at /glossary, each opening with its definition, each true of this product specifically rather than of the dictionary, and each naming the field or reason it maps to.
  • Pricing goes above the top of the ladder, and shows its working. The ladder stopped at 100,000 credits and offered nothing beyond it: somebody with a million rows a month read to the bottom and left. There is a way to ask now. Beside it, an estimator: put in a month's volume and the share of it you expect to be free — invalid rows, undeliverable verdicts and 7-day repeats all cost nothing — and it names the cheapest tier that covers it and what the two ways of buying work out at.
  • It compares them honestly, which took two goes. A pack is bought once and a plan is charged monthly, so the first version subtracted the two prices and told you the plan saved you money in most of the ladder — while the pack it was comparing against covered three or four months. It now spreads the pack over the months its credits actually last, which is what makes the two comparable, and says which is cheaper only where that is true.
  • The comparison pages cover the whole product. All nine were email vendors, and the table itself was email-shaped — every competitor compared on an SMTP check, whatever they sold. There are twenty-one now across five products, each compared on the axes that product is actually judged on: a phone vendor on where its line type comes from and how much of the numbering plan it carries, an address vendor on the registers it holds, a business vendor on the registers behind an identifier. /compare is grouped by product, and each page carries the date its own figures were read rather than one date for all of them.
  • Twenty-five guides, and every product page links its own. Nineteen guides had been written and not one product or documentation page linked a single one, so the only way to find them was the index. Guides now declare the product they are about, the index groups by it, a guide's "related" reading prefers its own product instead of whatever was published most recently, and every product and docs page carries a rail of its own. Six are new: business verification and cross-signal consistency had none at all, and no other marketing surface either.
  • You can send yourself a webhook. Proving your signature check works meant queueing a real, billed bulk run and waiting for it to finish. POST /api/v1/webhooks/test posts a sample payload for any documented event to a URL you name, signed the way a real delivery is, and hands back the secret, the signature and the exact bytes the two were computed over, so you can verify your own HMAC against ours before anything depends on it. Free, and in all three clients.
  • The email callback had no private-address guard. Every webhook we send refuses a URL whose host resolves onto a private or reserved address — the check runs at delivery rather than when the URL is saved, because a name that resolved publicly can be repointed later. The callback_url on an email lookup was the exception: that path had its own copy of the delivery code, written before the guard existed, and never gained it. It goes through the same signer as everything else now.
  • The guard also missed one address of its own: ::, the IPv6 unspecified address, which the network stack routes to the local machine exactly as 127.0.0.1 does, and which the loopback entry beside it does not cover.
  • Every free tool and browser endpoint has its own rate budget. A guest has no key for the throttle to hash, so all six tool demos shared one allowance and so did the four browser endpoints — while each page and each documentation section quoted its own number. A checkout form checking an address and a phone number was spending one budget twice. Each is separate now, which is what makes the published number true, and a test refuses the next unauthenticated route that joins a shared one.
  • The Requests page says "every API call this account made" and now means it: the request log and the public latency figures shared one early return, so a call on any route outside the five lookup groups wrote no row at all. The latency table is still lookups only, deliberately — a free endpoint whose duration is mostly your own server's would make a published percentile mean something else.
  • Smaller corrections, all of them a number that had quietly stopped being true: the consistency endpoint takes five signals and raises nine flags, not four and seven; /pricing names three answers that cost more than one credit, not two; the glossary's own first draft misstated the phone feed count, the heaviest address weight and the PO box rule, all three of which are published elsewhere on the site and now agree.

2026-09-09

Three fixes: partial bulk results, monitor baselines and key spend caps

  • A failed bulk run's results are downloadable. A run that fails part-way keeps the rows it did write, and those rows were billed, so they now download exactly like the rows of a run you cancelled part-way. Until today they were unreachable: work you had paid for was thrown away with the run that failed.
  • A run that failed before writing anything answers a new 409 JOB_FAILED — see /docs/errors/JOB_FAILED. It used to answer RESULTS_NOT_READY, which asks you to poll the job until it reports completed, for a job that had already stopped and never would. Nothing was billed for a run like that.
  • A monitor that runs out of credits no longer loses its place. Every monitor compares each entry against the previous run to decide whether to alert you. A run that stopped on credits used to discard the stored answers for everything it had not reached, so the next run read those entries as seen for the first time — and a first sighting only sets the baseline, it never emails. Whatever changed in between was never reported.
  • Worst case, on the business monitor: a company struck off the register read as unverifiable rather than left_register, and the single most important event a monitor exists to catch passed silently. The stored answers are now carried forward for every entry a stopped run did not reach, so the next run compares against the last real answer.
  • A key's daily credit cap now holds inside a batch. The cap was checked once, before the request ran, so a single batch could spend far past it — a cap of 10 did not hold. A batch now stops at the cap, keeps everything it has already paid for, and answers 200 with meta.stopped_reason: "key_spend_cap" beside the existing "insufficient_credits"; those two remain the only values.
  • A call that starts with the cap already spent still answers 429 KEY_SPEND_CAP_REACHED and runs nothing, as before. Bulk runs and monitor runs bill in the background, are never counted against a key, and are deliberately unaffected by the cap once they are under way.
  • Every API failure now answers in the documented shape. The reference has always promised one envelope — success: false with a code, a message and a request_id — but only four failures kept that promise. A wrong job or monitor id answered a raw framework message that named an internal class, carried no code to branch on and no request id to quote at support; a wrong HTTP verb and an unhandled failure did the same. Those now answer 404 NOT_FOUND, 405 METHOD_NOT_ALLOWED (with an Allow header) and 500 SERVER_ERROR in the same envelope as everything else, each with its own reference page.
  • A record belonging to another account answers exactly as one that never existed, deliberately: telling the two apart would confirm that somebody else holds that id. And on a 500 alone we say plainly that a failure landing after the lookup ran leaves the credit spent — every other failure bills nothing.
  • The MCP server's nine tools are listed everywhere again. verify-entity and check-consistency shipped yesterday and the pages that list the tools were not updated with them: the documentation said seven, the integrations page said four, and the registry entry named three of the five products. All four now agree, and a test pins them to what the server actually registers.
  • A dataset that stops refreshing now raises an alarm. Only the phone feeds warned when a sync failed; the other eleven could fail every night and nobody would learn until an answer was wrong. A daily sweep now reads the same list the status page publishes and reports any dataset that is missing or older than its own refresh cadence allows, naming the command that fixes it. It checks the age of the data rather than the success of a job on purpose: if the scheduler itself stops, no job runs to report anything, and that is the failure that matters most.
  • Failed completion webhooks now record what came back — the HTTP status your endpoint answered with, or the transport failure when it answered at all — so a wrong URL, an expired credential and a fault at your end are no longer the same word. The response body is never stored.
  • You get told before the balance runs out, and when a card fails. Until now an empty balance was something you discovered as a 402 in your own traffic, and a failed subscription payment was not surfaced at all. There is now a warning email as the balance approaches the line, an amber banner in the dashboard while it is under it, and an email when a payment fails saying what happens next — your credits and keys are untouched while the card is retried. The warning is sent at most once a day, and again if the balance climbs back over the line and falls a second time.
  • Uploading a file larger than the per-run limit used to report the truncated number as though it were the whole file. It now says how many rows were left out.
  • A monitor run that fails now says so. The scheduler moves a monitor's next run forward before it starts, so a slow one can never be run twice and billed twice — but that meant a run which died had already moved the schedule on, and left the previous run's figures on screen. The monitor looked healthy while it had skipped a cycle. A failed run is now recorded and shown as such, the saved answers it never got to are kept, and the panels name the reason a run stopped rather than assuming it was credits.
  • Entity monitors were rate-limited ten times more tightly than the other four products — their scope allowed 50 requests a second and the routes were getting 5, because one pattern was missing a wildcard the other three had.
  • Deleting your account now really removes your files. Every table belonging to an account already went with it, which made the deletion look complete — but a database cascade fires none of the application's own cleanup, so each bulk run's uploaded list and result file stayed on disk, orphaned, with no record left pointing at them. Those files are the addresses and numbers you sent us; the table rows never were. They are now deleted with the account, and any live subscription is cancelled at the same time so a closed account stops being charged.
  • The request id now leads somewhere. Every response has always carried an X-Request-Id header and repeated it in the error envelope, and until today that id led nowhere — you could quote it to support and nothing else. There is now a Requests page listing your own API calls, opening on the ones that were refused or failed, with the typed error code beside each and a box to paste an id into. GET /api/v1/account/requests/{id} reads the same row back, so a script that catches a failure can ask what happened without a human involved. Kept for 30 days.
  • The log holds no value you looked up. It names the endpoint, never the address, number or identifier the call was about — the promise that a phone or IP lookup writes no history row still stands, and the privacy policy describes this store rather than leaving you to find it.
  • Search, on every page. Press Cmd+K, or Ctrl+K, or the search button in the header. It covers everything published here — the documentation, every API endpoint, every reason and error code, the guides, the free tools, the integrations, the comparisons and each changelog entry — just under three hundred pages that until now could only be reached by knowing the URL. The whole index is fetched once and searched in the browser, so it answers as fast as you can type.
  • Business verification and cross-signal consistency have a dashboard. Both shipped with an API, docs, an MCP tool and SDK methods, and no way to try either without writing code — the API-key form even offered scopes for products the dashboard could not exercise. /verify/entity reads an identifier against the registers held here and carries a Monitors tab; /verify/consistency runs any of the four signals together and shows where they agree and where they do not, quoting both sides of every comparison. Both are in the signed-in navigation.
  • The consistency page states what it will not do: no score, no risk level, no verdict, no recommendation. A difference between two fields is a fact about the fields, not a judgement about a person — and where the IP is a VPN or datacenter exit the country comparisons are withheld and say why, because that country belongs to the network operator.
  • Business verification and cross-signal consistency have product pages. Both were sold only through their API docs. The consistency page leads with what it refuses to do, because that is the product: no score, no risk level, no verdict and no recommendation, and there will not be one. The email product page also caught up with the other three — it now publishes the same measured latency and the same table of the datasets behind an answer.
  • The site now sells what shipped. The landing page still led with email verification alone, its heading counted four endpoints, and a footnote said email, phone and IP were "live today" — directly beneath a card selling the fourth product, with a fifth and sixth already live. The hero, the heading, the search-engine description and the structured data now describe all six, business verification and cross-signal consistency have cards and footer links, and a test keeps the page from falling behind the next launch.
  • Pricing says what each signal costs. /pricing described email verdicts only, for a product billing six signals off one balance. It now carries a per-signal rate card — what earns a credit and what is always free, for each endpoint — and states plainly that the two optional premium checks are off until a licensed partner is configured, so they currently add nothing.
  • A page shared or crawled while the renderer is down now carries its own title. Every public page's title, description and canonical are written by the server-side renderer. When that renderer is not answering the page still works — the browser draws it — but the HTML that arrives carries only the site name, so a link posted to Slack, an AI assistant reading the page, and a crawler that does not run scripts all saw "SPAW.co" and one sentence, whatever page it was. The HTML now falls back to the page's own title and description, taken from the same list the sitemap and the search box are built from, so the two can never disagree. A rendered page is unaffected: its head still wins, and no page ever carries two.
  • /status now reports whether the renderer is answering, because nothing else did: the site stayed up and every link preview quietly went generic. It is listed on the status page as its own component and is in the sitemap.
  • The address page publishes the weekly index check. Every Saturday, rows are taken straight out of each installed national register's own file and sent back through the ordinary lookup. It is a check on our own plumbing — a parser that spells a street one way and an index that folds it another would answer nothing for a whole country, silently — and the result is now on /products/address-verification with the share of rows found again for each register. It is deliberately not called coverage: it says nothing about how many of a country's addresses a register carries, and we make no claim there.
  • The site navigates like an app again. Every link in the header — the logo, Docs, Pricing, Guides, and the eleven signed-in tabs — was a plain anchor, so each click tore down the page and downloaded the whole front end again, while the footer had been navigating properly all along. They now navigate in place and fetch the next page while the pointer is still on the link, so most clicks land on a page already in hand. The docs sidebar does the same.
  • A first-run dashboard. The dashboard rendered exactly the same thing to an account that had just signed up and one running in production. A new account now sees what is left to do — create a key, make the first call — and the checklist disappears for good once that call lands.
  • The footer is a way around the site. It now carries the reason reference, the error codes, the MCP server, both OpenAPI formats and llms-full.txt alongside the docs, in place of a link to a scroll position on the front page. There is a live service-status pill beside the logo, and the twelve-line block of data licences — which we are obliged to publish and will keep publishing in full — is behind a "Data sources and licences" disclosure instead of outweighing the navigation above it. The signed-in pages, which had a copyright line and one email address, now carry the same way out.
  • Failures look like the rest of the site, and carry the id. A 500, a 503, an expired form and a rate-limit refusal all rendered the framework's own grey page. Each now has a page of its own that says what happened, whether anything was billed, and what to do next — and the 500 prints the request id, so someone hitting it in a browser can quote the same number an API caller reads out of the error envelope.
  • Confirmations reach every page. The component that draws a confirmation was mounted only inside the signed-in shell, so a message flashed to the front page, the docs, the pricing page or a tool was thrown away silently. Deleting your account was the worst case: it worked, signed you out, and said nothing at all. It now confirms.
  • Keyboard and screen-reader gaps. Every page now opens with a skip link, so a keyboard user is not walked through the logo, five nav links and eleven tabs before reaching the page. The mobile menu closes on Escape and hands focus back, and closes when you navigate instead of covering the page you just opened. The signed-in tab strip scrolls the tab you are on into view. The theme switcher announces as one setting with three values rather than three unrelated buttons.
  • A theme control for everyone. Light, dark and system have worked since day one, but the only switch was inside the account settings, behind sign-in and email verification — so a visitor reading the documentation could not override their operating system. The control is now in the header on every page.
  • Every Markdown twin (/docs/api.md and several hundred like it) now names the HTML page it is a twin of, so the two stop competing with each other in search. The docs twins and the About and Security pages also end with a link home, which the guides and comparisons already did.
  • A bulk run that stopped on credits can be resumed. Until today the only way out was to upload the file again: the run kept the list it was given, and nothing offered it back to you. Top up and press Resume, on any of the four products, and the run continues — it re-runs the list from the top, the rows it already verified come back as free repeats inside the seven-day window, and only the rows it never reached are charged. POST /api/v1/{product}/bulk/{id}/resume does the same from a script.
  • A run whose uploaded list has passed its retention window cannot be resumed and says so, rather than starting again with nothing. Neither can an IP run made in privacy mode: privacy keeps no record of the addresses checked, not even the repeat marker, so a second pass would charge for every row again — the run says that instead of quietly billing twice.
  • Usage answers what you spent. /usage listed credit movements and nothing else — there was no credits-spent figure anywhere in the product. It now filters by date, activity, channel and key, and summarises whatever you have narrowed to: spent, added, a per-activity breakdown and a bar per day. The figures cover the whole range you filtered, not the rows currently on screen, and the CSV download takes the same filters.
  • Spend is attributed to the exact key that made it. Ledger rows recorded a label — "api · Production" — which two keys of the same name share and a rename does not move. Each row now also carries the key's own id, so /usage can break spend down by key, GET /api/v1/account/ledger can be filtered to one with api_token_id, and the CSV carries the column too. A revoked key keeps its history under the name it was spending with. Work that runs in the background — bulk runs, monitors — is deliberately attributed to the run rather than to the key that scheduled it, and the table says so instead of leaving you to wonder why the numbers do not add up.
  • The API keys page showed "none" in the cap column for a key with no daily cap, throwing away the number of credits it had spent today — which the page below it was already showing for publishable keys. It now shows both.
  • The documentation says where you are again. The sidebar decided which entry was current by page, not by section, so on a page with ten anchors all ten lit up at once — a highlight that was not merely unhelpful but wrong. It now follows the section actually on screen. And on a phone, where the sidebar is hidden, a long reference page had no in-page navigation at all: there is now a row of the current page's sections above it.
  • Analytics now asks first. Google Analytics used to load on every page for everyone. It is not loaded at all until you say yes — nothing about a visit reaches Google before you answer — and the answer is remembered in your own browser, not on your account. The privacy policy says exactly that now, and carries a control to change your mind, because a policy that says you can and offers nowhere to do it is not a policy. Saying no changes nothing else about the site.
  • The privacy policy and the terms cover what we actually run. Both still described the product as email and domains "and — as they launch — phone numbers and IP addresses", while five signals and a cross-signal comparison were live. They now name all of them, and the privacy policy says plainly what each one keeps: a postal address lookup writes no history, but your suppression list and the delivery outcomes you report store the address as you wrote it rather than a hash, because both exist to be matched against a later lookup. A company lookup writes nothing at all and its answer never carries a street address — a registered address is very often a home address. Cross-signal consistency keeps nothing of its own: it writes whatever the signals it ran would have written, and the comparison itself is returned and never stored.
  • A bulk run tells you when it is over. Until now the only way to learn that a run had finished was to keep the tab open for its four-second poll: a 40,000-row list started before lunch reported to nobody. There is now an email — counts, spend and a link, never a row of your list. A run that stopped on credits or failed is always mailed, because those are the endings you have to act on; a clean run only when nobody could still plausibly be watching. Cancelling mails nobody: you were the one who cancelled it. And the dashboard now shows the runs still going, whoever on the account started them.
  • A run started from the dashboard can post to your endpoint when it finishes, which until now only a run started through the API could do. Webhooks are also refused outright when the URL resolves onto our own network — a customer-supplied URL fetched by our servers is the shape of a server-side request forgery, and the check runs at delivery rather than only when the URL is saved, because a name can be repointed afterwards.
  • Suppression lists can be searched and cleared by value. Removing one address from a 50,000-entry list meant paging through 500 requests to find its numeric id. All three lists now filter by value, source, reason and date, and DELETE /api/v1/{signal}/suppressions takes the values themselves and answers which were on the list. The search matches the way the entry was stored, so an address you wrote differently, a number in national form, or a different capitalisation all find the row they folded into.
  • A free IP lookup, and an index of every free tool. The IP checker was the only one of the five signals with no tool page, though the widget for it already existed. It is at /tools/ip-lookup, and /tools now lists all eight — from any one of them you can reach the others, which until now you could only do through the footer.
  • A browser can finally read the headers we tell it to read. There was no CORS policy, so the framework defaults applied: no exposed headers and no preflight cache. X-Request-Id — the id /docs/errors tells you to quote to support — was invisible to browser JavaScript, as were the rate-limit headers, Retry-After and Idempotent-Replayed. All six are exposed now, preflights are cacheable for a day (the form widget posts JSON, so it was paying a full extra round trip on every lookup a visitor typed), and the policy is written down on /security rather than left to be discovered.
  • Every release has its own link, and the changelog has a feed. A release could not be linked to a customer and there was no way to follow the product without checking the page. Each entry is now its own page with its own Markdown twin, and /changelog.atom is a real Atom feed a reader can subscribe to — advertised from the pages themselves, so a reader finds it without being told.
  • Monitors can post to your endpoint, and can be changed without being rebuilt. Mail was a monitor's only channel while bulk runs have posted webhooks for months; monitors now do too, signed the same way, and refused outright when the URL resolves onto our own network. And PATCH /api/v1/{signal}/monitors/{id} finally exists: adding one entry, renaming a list or changing its cadence meant deleting and recreating the monitor, which threw away the baseline every alert is judged against — a full cycle of silence at exactly the moment you were paying attention.
  • The Python, Node and PHP clients caught up with the API. They had fallen a long way behind: resuming a bulk run, removing suppressions by value, filtering a suppression list, the request log, api_token_id on the ledger, and the monitor webhooks and updates above were all reachable over HTTP and from none of the three. They are now, with the same surface in each. Two older gaps closed with them: the ledger's rows were being thrown away by the Python and Node clients before you ever saw them, and narrowing bulk IP results by risk level was documented and implemented but unreachable from every client.
  • A monitor now tells you which entries changed, not just how many. Three of the four panels stopped at the summary counts, so you could read that two addresses had decayed and never learn which two — while the API had been returning the per-entry map all along. Every monitor now expands to the last run's answer for each entry, worst first. The address monitor marks the entries that changed, because its run records that per entry; the email and phone monitors record only how many changed, so they show the status and do not guess.
  • Every answer names its sources on the page, not just in the raw response. "A verdict that names its sources" is the whole position, and on four of the five signals the line was only visible to somebody who opened "Show raw response" — the pages filtered it out of the field list and printed nothing. It is now on all five, in one wording.
  • A leaked API key is no longer an outage. The page told you to revoke the key and create another, which means every integration using it is down between the two. Rotate issues a replacement carrying the same scopes, cap, address allowlist and default country, and the old key keeps working for 24 hours so you can move each service across — or you can end it immediately with Revoke now. The pair shares one daily spend cap while both work, because a cap is a promise about your spend and not about a row.
  • New accounts start with more than the monthly grant, and get an email that says so. Confirming your address now adds a one-off credit bonus and sends a welcome message with your balance, a link to make a key and a request you can paste and run. The bonus is once per mailbox: closing an account and opening another with the same address does not pay again, which we keep track of as a one-way hash and nothing else — the privacy policy says so plainly.
  • Business identifiers can be checked in batches. Entity was the only signal with no batch endpoint. POST /v1/entity/batch takes up to 500 in one call and bills the way a single lookup does: only the ones that were found. An empty balance no longer abandons the identifiers that cost nothing.
  • Cross-signal consistency can compare a company. The endpoint that exists to cross-check signals could not cross-check the newest one. It now puts the registered name beside the organisation on the address, and the country of registration beside the country the address stands in — kept out of the overall country agreement on purpose, because where a company is registered is not where a person is. The name comparison is a normalised exact match and says so: accents, ampersands, apostrophes, initials and company-form words are taken out first, so a difference is a difference in the name rather than in how it was typed.
  • The request we print for your first call was missing a header, so pasting it answered "the email field is required". Fixed everywhere it appears.
  • What the API promises, written down. There was nothing anywhere saying what counts as a breaking change, what may appear without notice, or what would happen if something were deprecated — so an integration had to guess. /security now says it: the OpenAPI document is the contract and a test fails the build if it and the routes disagree; new fields, new enum values and new error codes ship without notice, so match on the codes you handle and let the rest fall through; a breaking change gets a new path rather than a quiet edit; nothing is deprecated today, and when something is it will carry a Sunset header and be announced in the changelog. The same page states plainly that there is no numeric uptime commitment — and points at the live measurement there is, including why a refused request is kept out of the latency percentiles.
  • A copy button on every code block. Around forty of them across the documentation, four generated samples per endpoint, and every block in the guides — all of them had to be selected by hand. The button leaves the shell prompt behind, so what you paste is a command that runs.
  • A privacy-forwarding address can open an account. Public burner lists carry the permanent alias services — Firefox Relay, Addy.io, SimpleLogin, DuckDuckGo Email Protection and the rest — and we were reading those lists at our own signup form, so a customer whose only address was a Firefox Relay or Addy alias was turned away at 422. Those are real mailboxes people keep for years, and for a privacy-minded customer they are often the only address on offer. They open an account now. What the API answers about those domains is deliberately unchanged: opening our own front door is not the same as changing the verdict you buy.
  • Knowing someone's email address no longer blocks their password reset. Asking for a reset link is limited by the address on purpose, so a botnet cannot fill one person's inbox from a thousand source addresses — but completing a reset shared that counter, so an attacker who knew nothing but a victim's address could spend the hourly allowance and leave the owner holding a valid link the form would no longer take. The two are separate now; completing a reset is limited by the source address, which is what the risk actually is.
  • Deleting your account deletes the devices it was signed in on. The confirmation says "your account and everything in it", and the sessions table — one row per browser, carrying the IP address and user agent each was last seen on, the same record the security page had just shown you — outlived the account by whatever was left of the session lifetime. Nothing else ever deleted those rows.
  • "Sign out everywhere else" is always offered. The button appeared only when the device list had rows in it. The list shows sessions that have been active recently, so a browser that chose to stay signed in and has not been used since drops off it and is still signed in — the button was being hidden in exactly the case somebody came to the page for. The dialog now says what it covers, including the devices no longer listed.
  • A malformed query parameter is a 422, not a 500. ?format[]=json on a bulk results download answered a server error — our fault, reported as something you could do nothing about — after the endpoint had already decided you were allowed to ask. It now answers the same validation error a misspelled value does.
  • format=json on a bulk download handles what CSV always could. A quoted cell holding a newline — a two-line address, a note — was being read as two rows, so the JSON came back with extra rows and the value cut in half while the CSV download of the same file was perfect. And a single byte that is not UTF-8, which an uploaded list routinely carries, raised mid-stream after the 200 and half the rows had already gone out. Both fixed, and a test now downloads one file both ways and requires them to agree.
  • A report you type into the dashboard stops disturbing everyone else's lookups. Dashboard reports are deliberately left out of the figures other accounts feel, and the code still threw away the shared cached count on every one of them — a count that could not have moved — making each click re-run the query behind everybody's lookups.
  • The line under the phone report buttons said repeated failures suppress a number, while one report of abuse suppressed it immediately. The button with the most consequence was the one described least accurately.
  • The privacy policy now covers the devices list and says what removes a row, and names the dashboard as the third way an outcome report reaches us alongside the API and your provider's bounce webhook — with the rule that separates it from the other two.
  • The three clients caught up again, with six operations. Listing your bulk runs in each of the four products (how a caller that lost a job id finds the run again), format=json on all four results downloads, the business-identifier batch, the regulator's number-range report, and the status endpoint — the one call that works before a key exists. Every batch item's index and input are documented and typed too: a batch stopped early answers only a prefix of the list, and the answer carries the normalised value, so those two are what line a result up with the row it came from.
  • A key's controls now hold over the MCP server. A secret key can carry a scope, a daily credit cap and an allowlist of addresses, and /mcp sat outside the middleware that enforces all three — so a key scoped to email alone, capped at ten credits and locked to one office address ran unrestricted lookups from anywhere as soon as an agent pointed at it. The documentation had been telling you the allowlist "makes a leaked key useless elsewhere". It does now. A refusal comes back as something the model can read and act on, not as a broken transport: an agent that asks for a product its key does not cover is told which scope it needs.
  • Agents can check a list. The MCP server had one batch tool, for email, and its input was literally named emails, so an agent checking two hundred phone numbers made two hundred calls. There are now batch tools for phone numbers, IP addresses and postal addresses on the same shape — twelve tools in all — and they bill and stop exactly as the REST endpoints do, keeping the rows already paid for when a balance runs out.
  • The form widget checks postal addresses. The browser endpoints for all four signals have been built and billed for weeks, and the widget on your own pages attached to email and phone inputs only — so the highest-value case, validating a shipping address at checkout, had no first-party widget at all. data-spaw-address now does it, taking its country from the field you name, and the spaw:result event says which of the three fired.
  • The widget waits for a country select to settle before it looks anything up. A keyboard user arrowing down a country list fires a change on every option they pass, and each is a different country, so the first version of this would have spent ten credits on nine countries nobody chose. And an answer for a country that has since been changed is now dropped rather than rendered: it described an address nobody was submitting.
  • Business identifiers reached the connectors. Zapier, n8n, Make and Google Sheets had every other signal — email, domain, phone, IP, address, suppressions and outcome reporting — and not this one, though all three SDK clients have covered it since it shipped. All four now verify a LEI or a company number, with the field lists, the caps and the caching each platform expects.
  • Those pages carry the caveat that matters more than anything else on them: the UK company file is a snapshot of the live register, so a struck-off company is removed from it rather than marked dissolved. A real dissolution arrives as found: false, not as the flag whose name suggests it — and on a LEI lookup it is quieter still, showing up as an empty agreements block while the register is still named. Filtering on the flag alone keeps you dealing with exactly the companies the feature exists to catch.
  • Business verification has a free tool. It was the only product with no way to try it without an account, though both registers are synced onto this server, so a public lookup is one database query with nobody to pay. /tools/company-lookup reads a company number or a LEI and answers the legal name, the register's own wording for its status, the jurisdiction and the incorporation date, each stamped with the date of the copy read. No registered address, deliberately — an identifier does not say whether a company or a sole trader stands behind it.
  • Every free tool now has its own rate budget, and so does every browser endpoint. All six tool demos shared one bucket per visitor, and so did the four widget endpoints, while each page and each documentation section promised its own number: checking an address and a phone number on one page quietly spent one allowance twice. Each is separate now, which is what makes the published number true. A test refuses any new guest-facing route that joins a shared bucket.
  • The endpoint reference shows the official clients. Every one of the API pages offered curl, fetch, requests and curl_setopt, and never spaw.verifyEmail(...), though all three clients cover every endpoint. All three now appear beside the four, generated from the same example the curl uses so they cannot drift apart — and the OpenAPI document you import into Postman, Scalar or Redocly carries them as x-codeSamples, where it previously arrived with a hundred example-free operations.
  • The samples are checked rather than trusted: a test fails if a sample names a method its client no longer declares, or passes a field the operation does not have, or quietly omits one the curl sample sends — which it was doing, dropping the address from the consistency call.
  • Smaller things: Billing has a place in the navigation and in the account menu instead of one link on the dashboard, the credit balance on the usage page is now the way to top it up, and the key-controls copy on /docs names all seven scopes rather than the four that existed when it was written.

2026-09-08

Business verification, and cross-signal consistency

  • POST /api/v1/entity is the fifth product: a Legal Entity Identifier read against the GLEIF Level 1 golden copy, or a UK company number read against the Companies House free company data product. Both are bulk files synced into a local table, so no third party sits in the request path. The guide is at /docs/business-verification.
  • It answers the legal name, whether the entity is still live (active, inactive, dissolved, with the register's own wording in status_detail), the country and jurisdiction of formation, the entity category and legal form, and the incorporation date. For a LEI it adds checksum_valid — the ISO 7064 MOD 97-10 check digit from ISO 17442 — plus registration_status and next_renewal_at.
  • A LEI can lapse while the company behind it is perfectly alive, so the two facts are separate: status is about the entity, registration_status is about the identifier, and lei_registration_not_current flags the difference.
  • Where a LEI names a Companies House number and both registers are installed, the two records are compared field by field. agreements quotes both sides and flags names name_differs_across_registers, status_differs_across_registers and dissolved_in_national_register. Names are compared on their letters and digits with the company-form words removed, so "ACME HOLDINGS LTD." and "Acme Holdings Limited" agree.
  • The checksum is reported beside the register answer rather than standing in for it. Measured against the published register: of 45,515 consecutive real LEIs, 45,512 satisfy the check digit, and all three that do not are annulled registrations.
  • No registered address is stored or returned, and none will be. A registered address is very often somebody's home, and an identifier alone does not say whether a limited company or a sole trader stands behind it. The product carries country and jurisdiction, which answer whether this is a real, live entity without holding a register of where people live.
  • Monitored lists: save up to 500 identifiers and have them re-checked weekly or monthly (no daily cadence — the UK company file is republished monthly). A run answers every identifier ok, one of left_register, dissolved, inactive, registration_lapsed and registers_disagree, or unverifiable where no register could answer at all.
  • left_register exists because of how the UK register actually works: the Companies House free product is a snapshot of the live register, so a company that is struck off is removed from the file rather than marked dissolved. A dissolution therefore reaches you as the identifier disappearing between runs, and reporting that as unverifiable would have let the single most important event pass silently. An email goes out only for an identifier that was fine last run and is not now; the first run only sets the baseline.
  • Every monitor now prunes itself, across all five products. A saved list that stops being re-checked is deleted along with the identifiers and the results it stored — an email monitor, a phone monitor, an IP monitor, an address monitor and a business monitor alike. A list in active use is never touched, because each run advances last_run_at, so the window only closes on one that has genuinely stopped. Retention is 365 days by default and configurable per product, prunes_after_days is on every monitor payload and meta.retention_days on every list endpoint, so the rule is visible in the API rather than only in the docs.
  • The business monitor is where the reasoning started: an identifier is often a company number but can equally be a sole trader's, and nothing in it says which. The same argument holds for an email address, a phone number, an IP and a postal address, which is why the rule was extended to the other four rather than left as a special case.
  • 1 credit when a register answers. A malformed identifier, one no register carries, an unsupported register and a register that is not yet installed are all free — and a register that is not installed says so, rather than reporting that the company does not exist.
  • Data: the LEI reference data is published under CC0 1.0 and refreshed daily; the Companies House file is Open Government Licence v3.0 and republished monthly, so an answer can be up to five weeks behind the live register. Every answer names both registers with the date of the copy, and /status lists them. Spaw is not affiliated with or endorsed by the organisation that publishes the LEI data.
  • POST /api/v1/consistency takes any subset of email, phone, ip and address, runs each through the product that owns it, and returns data.signals with all four keys always present — each holding that product's full answer, field for field, or null for a signal you did not ask for. The signals run in a fixed order (ip, phone, address, email) because a JSON object has no input order, and because email is the only leg that does DNS and a mailbox handshake. The guide is at /docs/cross-signal-consistency.
  • On top of them, a free consistency block: country_agreement over the phone, address and IP countries, and eight comparisons that each quote both sides — the signal, the field and the value that decided it — so nothing is asserted you cannot see the working for.
  • Seven flags, each with its own reference page: phone_country_differs_from_address_country, ip_country_differs_from_address_country, ip_country_differs_from_phone_country, disposable_email_from_datacenter_ip, disposable_email_from_anonymous_network, mail_drop_address_with_free_email_provider and disposable_phone_with_disposable_email. They sit in a flag field rather than a reason, because they describe two signals rather than one.
  • A comparison is emitted only when both of its signals answered. Where one side is null it answers not_compared with withheld: "missing_value" — a null means not evaluated, never no. Where the IP is a VPN exit, a relay or a datacenter egress, the country comparisons are withheld as low_confidence: that country belongs to the network operator, and reporting a difference would flag everyone who uses a VPN.
  • No score, no risk level, no verdict and no recommendation, and there will not be one. A difference between two fields is a fact about the fields, not a judgement about a person — people travel, move house, keep a foreign number and browse privately.
  • No new dataset, licence or sync command: every comparison is arithmetic over fields the four pipelines already computed. Each signal bills exactly as its own single lookup, meta.credits_used is the sum, and the analysis is free. Running out part-way keeps what was paid for and reports meta.stopped_reason.
  • The MCP server gained verify-entity and check-consistency, and the Python, Node and PHP SDKs gained check_consistency / checkConsistency. Keys carry a new consistency scope, and the endpoint has its own rate-limit bucket, since one call fans out to as many as four lookups.
  • A free /tools/card-number-checker joins the tool pages: brand, Luhn check digit and permitted length, computed entirely in the browser — no request, no endpoint, no log line, and the number never appears in the URL. It makes no issuer, bank, country or debit/credit/prepaid claim, and says plainly why: there is no free, openly-licensed, authoritative source for those, so it answers nothing rather than guessing.
  • Fixes: a mailbox probe lost to a provider outage is now free whatever names the verdict — a role address on an accepts-mail domain was still being charged during an outage, against the published rule. The French national address register (Base Adresse Nationale) is installed alongside the other five, and the Swiss register's coverage no longer claims Liechtenstein, which its file has never contained.

2026-09-05

Address verification: formats, boxes, registers and postcodes

  • POST /api/v1/address answers 45 fields: the address split into the parts its country writes, the country's facts, the address rendered the way that operator writes it, postal_code_valid and postal_code_type, postcode_status and locality_matches_postcode from the national postcode directory, address_type with the source that settled it, exists and match_level from the national address register with the coverage its publisher claims, the postcode centroid, companies_registered, is_mail_drop with the source that decided it, and a recomputable risk_score with its risk_signals. The full guide is at /docs/address-verification, and the product page at /products/address-verification has a live demo.
  • Send one written address or the separated fields, always with a country — the postal format is what the address is measured against, so a country the format table does not cover is refused rather than answered. Nine typed reasons say what is wrong, and every one of them is free.
  • address_type reads the postal operators' own naming in 52 countries: post-office boxes, private mailboxes at mail-receiving agencies, general delivery, rural routes, military mail, parcel lockers, and the business-volume arrangements that replace a street address such as the French CEDEX and the Dutch Antwoordnummer. A box replaces the street line; a private mailbox keeps it and moves its number into unit.
  • The data is free and licence-clean: postal formats for 206 countries (MIT), the national address registers of the United States (public domain), France (Licence Ouverte 2.0), Switzerland (© swisstopo), Austria, Czechia and Norway (CC BY 4.0), the ONS Postcode Directory for Great Britain (Open Government Licence v3.0 — Northern Ireland postcodes are excluded because their reuse needs a separate Land & Property Services licence), Companies House free company data and the Colorado, New York and Oregon business registries, and the branch lists mailbox operators publish themselves. The footer carries every attribution the licences ask for, and the status page lists each dataset with its sync date.
  • exists is deliberately cautious. It is true only on a building match, and false only when the register that answered claims to list every address. Where coverage is partial — the United States and France, whose registers are fed by whoever chose to take part — an address that is not in it answers null, and the not_in_register weight never applies. Absence from a partial register is not evidence that an address does not exist.
  • Two answers stay licensed and are not enabled: whether post is actually delivered to an address, with the postal service's own agency, vacant and residential flags, and which addresses sit at a UK postcode — every full UK address list derives from the Royal Mail Postcode Address File. The first answers deliverability_checked: false, the second answers 501 ADDRESS_LIST_UNAVAILABLE, and neither is charged or guessed. Their fields and billing are fixed now so nothing changes for your client when a partner is signed.
  • POST /api/v1/address/batch takes up to 100 addresses, POST /api/v1/address/public validates from a page with a publishable key, and the MCP server gained verify-address. The dashboard's new Address verification tab runs a single lookup written or as separated fields and shows every field, with "not evaluated" where a dataset is not installed.
  • Billing follows the same rules as the rest: 1 credit for a fresh answer that stands as written, nothing for one that cannot, and nothing for a repeat within seven days — the repeat marker is keyed on the folded address, so two spellings of one address are one lookup and one charge.
  • Nothing in the response says who lives or works at an address. The product is about places, and no field will ever be about people.

2026-09-05

Phone intelligence: block data, flags and a risk score

  • POST /api/v1/phone answers 33 fields: the E.164, national, international and tel: forms, the country's facts, line_type and carrier each with the source that settled it, the regulator's block_status, the is_fictional, is_virtual and is_disposable flags with the date a disposable listing was last seen, a recomputable risk_score with its risk_signals, and the fields of the live carrier-network check, which answers hlr_checked: false until a partner is configured. The full guide is at /docs/phone-intelligence.
  • Block data comes from NANPA's central office code report for the United States and its territories, the Canadian Numbering Administrator's table, and Ofcom's National Numbering Scheme for the UK and the Crown dependencies, synced weekly: a geographic number in a block nobody holds answers valid: false with reason unassigned_block, free, and a wireless or wholesaler block holder settles the North American fixed_line_or_mobile.
  • Three new reasons, each with its reference page: unassigned_block, emergency_number and short_number. Invalid numbers stay free.
  • Seven test numbers, +1 202 555 0100 to 0106, answer fixed results at no cost and leave no repeat marker, so integration tests never spend credits.
  • POST /api/v1/phone/batch takes up to 1,000 numbers, POST /api/v1/phone/bulk up to 100,000 with a signed webhook and CSV variants, and each item may carry its own country. POST /api/v1/phone/public and the data-spaw-phone attribute of the form helper validate numbers from a page with a publishable key.
  • The MCP server gained verify-phone; the n8n, Zapier, Make and Google Sheets connectors gained a phone operation; the Python, Node and PHP SDKs are at 0.4.0 with phone batch and bulk methods.
  • The footer credits Ofcom (Open Government Licence v3.0) and the open disposable-number list (MIT), as the licences ask, and the status page lists every phone dataset with its sync date.

2026-09-05

IP lookups check the Spamhaus and abuse.ch threat blocklists

  • POST /api/v1/ip now consults three public threat blocklists: Spamhaus DROP (netblocks hijacked or leased by spam and cybercrime operations, IPv4 and IPv6), Spamhaus ASN-DROP (the network operators behind them, matched by the address's ASN) and abuse.ch's Feodo Tracker list of active botnet C2 servers. is_blocklisted is the tri-state flag and blocklist names the list.
  • A blocklisted address adds 80 to risk_score, between unallocated space (100) and Tor (70). risk_signals carries it as blocklist with the dataset threat-blocklists, which also appears in sources with its sync date.
  • The lists are compiled locally like every other feed, so nothing about the lookup leaves the server. ASN-DROP can only answer once the ASN database resolves the address, so is_blocklisted stays null rather than false when neither a range list nor the ASN could be checked.
  • Bulk result files carry is_blocklisted and blocklist after hosting_provider; the connectors and the Node types know the two fields.
  • The Spamhaus lists sync daily with the other feeds; the botnet C2 list refreshes hourly.

2026-09-05

IP intelligence round three, from verified crawlers to your own abuse reports

  • is_crawler, crawler and crawler_kind name the search, AI and SEO crawlers from the ranges their operators publish for verification: Googlebot and Google's special crawlers and fetchers, Bingbot, GPTBot, ChatGPT-User, OAI-SearchBot, PerplexityBot, Perplexity-User, DuckDuckBot and AhrefsBot, synced daily. A crawler carries no risk weight and, like relay egress, never the datacenter weight.
  • registry, registry_country and allocated_at come from the RIR delegated statistics that were already synced: which registry allocated the block, the country it recorded and when. A block allocated weeks ago, or a registry country that disagrees with the geolocation, is worth a second look; neither is weighted.
  • City answers improve from every RFC 8805 geofeed discovered across the five registries, refreshed weekly (lookup:sync-geofeeds), and location_confidence says how far to trust a city: high when the operator declared it, otherwise graded from the weekly accuracy report's agreement for that country.
  • is_eea and privacy_regime settle consent decisions: the comprehensive privacy law where the address is, by region first (California ccpa, Quebec law25) and otherwise by country (gdpr across the EU and EEA, uk_gdpr, lgpd, pipeda, appi, pipl, popia, …); null when none is known.
  • POST /api/v1/ip/feedback records what an address turned out to be (abuse, fraud, bot, chargeback), free, stored only as a salted hash for 90 days. Your own reports score your own later lookups of the address (reported_abuse, +60); three or more accounts reporting an address make reported_abuse_widely (+40) for everyone. GET /api/v1/ip/feedback/summary counts your reports.
  • Monitored IP lists: register up to 500 of your own egress, mail-server or office addresses under /api/v1/ip/monitors, choose a daily, weekly or monthly cadence, and get an email the day one of them lands on Spamhaus DROP or a botnet list, becomes a Tor exit or scores high.
  • Two opt-in network extras on the single-address endpoints: network_details: true adds the registry's RDAP record (name, organisation, range, dates) from the same cached lookup as the abuse contact, and hostname: true adds the reverse DNS name with hostname_verified saying whether it forward-confirms. A verified name under a crawler operator's domain proves the crawler too.
  • Bulk result files carry the new columns at their response positions, and the Node, Python and PHP clients, the Zapier, n8n, Make and Google Sheets connectors and the MCP tool know the new fields and endpoints.

2026-09-04

IP lookups read the providers' own range feeds

  • POST /api/v1/ip now consults the published ranges of AWS, Google Cloud, Microsoft Azure, Oracle Cloud, DigitalOcean, Linode, Vultr, Cloudflare and Fastly next to the open X4BNet lists. hosting_provider names the provider when the address is inside its own feed.
  • Known VPN exits carry a name: privacy_service says Mullvad, NordVPN or Private Internet Access when that provider's server list contains the address.
  • is_relay flags iCloud Private Relay egress addresses from Apple's own list, with privacy_service set to iCloud Private Relay. Relay adds 10 to risk_score, and the datacenter weight is skipped for relay addresses because their egress sits on CDN infrastructure by design. Relay egress also appears in the aggregate VPN list, so is_vpn answers false there unless a VPN provider's own list names the address.
  • Where the network operator publishes the location itself (RFC 8805 geofeeds, Apple's relay list, VPN server lists), that declaration overrides the database answer and location_source says operator; when it disagrees with the database, region, latitude and longitude are null rather than a contradicting value.
  • asn_type classifies the network as hosting, isp, education or government from the flags and the operator name, and stays null when it cannot tell.
  • The datacenter, VPN and relay flags are evaluated for IPv6 wherever a feed covers it. Only the Tor exit list is IPv4-only, so is_tor stays null for IPv6.
  • sources gains relay-ranges; each group's version is the oldest sync date among its feeds. Every feed syncs daily.

2026-09-04

IP lookups by path, for the caller, in batches, in bulk and from the browser

  • GET /api/v1/ip/{ip} runs the same lookup as POST /api/v1/ip with the address in the path, and GET /api/v1/ip/me answers for the address the call came from.
  • Privacy mode: privacy: true in the body or ?privacy=1 on a GET keeps nothing about the lookup, not even the seven-day repeat marker; every such call is billed as fresh. Nothing about an IP lookup was ever logged or written to history; the security page now says so.
  • POST /api/v1/ip/batch looks up as many as 1,000 addresses in one call, in input order, with the paid partial results returned if the balance runs out mid-list.
  • Bulk runs: POST /api/v1/ip/bulk queues up to 100,000 addresses, with polling, cancellation, a CSV of every response field per row, an Idempotency-Key header and a signed bulk_ip_job.finished webhook, exactly like bulk email runs.
  • POST /api/v1/ip/public looks an address up from the browser with a domain-locked publishable key; leave ip out for the visitor's own address. Daily credit caps and Cloudflare Turnstile apply as they do for the email widget.
  • New fields on every IP answer: network (the prefix the location covers), continent, country_name, is_eu, timezone (only when the country or a single-zone region settles it), currency, calling_code, is_anonymous, and risk_signals, which lists every contributing signal with the weight applied.
  • IP endpoints have their own rate limit of 50 requests per second per key. The email endpoints keep 5.
  • The dashboard gained an IP intelligence page for single lookups, batches and bulk runs, and the docs gained an IP intelligence guide with three companion guides.
  • Three more signals from free data: is_unallocated (no regional registry has allocated or assigned the space, from the five RIRs' daily statistics; it scores 100), is_anycast (published CDN edge and public resolver ranges), and is_tor for IPv6 from the Tor Project's Onionoo relay data. abuse_contact: true adds the network's abuse mailbox from its RDAP record, looked up only on request.

2026-09-04

IP intelligence is live

  • POST /api/v1/ip and its GET, batch, bulk and browser forms are now a launched product: 1 credit per fresh answered lookup, reserved ranges and 7-day repeats free, 10 free credits a month on every account.
  • The product page runs a guest demo against the live service and publishes a measured-accuracy report: every week we sample the ranges whose operators publish a location and report how often the database agrees, per feed, plus what the named VPN lists add over the aggregate open list.
  • A public status page at /status (and GET /api/v1/status/data for monitors) reports the sync date of every dataset and measured API latency.
  • The footer credits DB-IP Lite (CC BY 4.0) for IP geolocation, as the licence requires.

2026-09-03

Documentation and public pages

  • The OpenAPI document is published at /openapi.json and /openapi.yaml.
  • Every docs page has a Markdown twin at the same URL with .md appended, and /llms-full.txt gathers all of them.
  • One reference page per endpoint, with request and response tables and code samples.
  • Reference pages for every verdict reason, SMTP reason, and API error code.
  • The free domain tools have permalinks: one page per domain for MX, SPF and DMARC, and disposable checks.
  • New public pages: pricing, about, security, and this changelog.

2026-09-03

Email verification required

  • New accounts confirm their email address before the dashboard, the API keys page, and every lookup surface open up. The verification email is sent at signup and can be resent from the verification notice.
  • API requests from an account that has not confirmed its address answer 403 EMAIL_NOT_VERIFIED with the usual error envelope. Nothing is charged and nothing is logged, so the same call can be retried once the address is confirmed. The MCP server answers the same way.
  • The browser form endpoint and the bounce webhook URLs keep working as before: their keys can only be created from a confirmed session.

2026-09-03

Unconfirmed mailboxes are now risky, and outages are free

  • A mailbox the SMTP handshake could not confirm (greylisting, a timeout, an unreachable server) now answers risky with reason: "unverified". It used to read as deliverable with smtp_checked: false, which hid the doubt behind the headline verdict. smtp_reason still names the cause and mailbox_confidence still estimates the odds; the risk weights are unchanged.
  • An answer Spaw could not obtain because its handshake partner was unavailable (smtp_reason: "provider_unavailable") is free, never cached, and probed again on the next lookup. Other unverified answers cost 1 credit like any risky verdict, and repeats stay free for 7 days.
  • The test address [email protected] now answers risky / unverified.
  • List monitors no longer count an unverified answer as decay, so a partner outage cannot trigger a false alert or erase a confirmed baseline.
  • The reason reference gained a page for unverified, and the field tables, guides and test-address table were updated to match.

2026-09-03

Monitors, history, and safe retries over the API

  • Monitors over the API: GET, POST and DELETE /api/v1/email/monitors, GET /api/v1/email/monitors/{id} with the last run's verdict per address, and POST /api/v1/email/monitors/{id}/run to re-verify now. The same 500-address lists, weekly or monthly cadences and decay alerts as the dashboard.
  • History over the API: GET /api/v1/email/history lists every lookup the account ran through any channel, newest first, with filters for verdict, address, date range and channel.
  • Bulk creation accepts an Idempotency-Key header. A retry with the same key and request replays the job it created, with Idempotent-Replayed: true and the same webhook secret, instead of queueing a second run; the same key with a different request answers 409 IDEMPOTENCY_KEY_REUSED.
  • Every new endpoint has a reference page under the API docs and a Markdown twin.

2026-09-03

Freshness controls and a per-call handshake timeout

  • POST /api/v1/email and POST /api/v1/email/batch accept refresh: true, which discards the answer from the seven-day repeat window and the shared mailbox answer, runs a real handshake again, and bills the lookup as fresh when it is answered.
  • max_age (seconds, 60 to 604800) does the same only when the previous answer is older than that, so a list can be re-checked at the cadence you choose without paying for answers that are still young.
  • The response says what happened: meta.refreshed is true whenever an earlier answer was discarded for the call.
  • timeout (seconds, 2 to 60) caps how long the mailbox handshake may wait for one call. A handshake that runs out of time answers unverified with smtp_reason: timeout instead of holding the request.
  • The Python, Node.js and PHP clients, the Zapier app and the Google Sheets functions expose the same options.

2026-09-03

Monthly plans

  • Monthly plans: Starter, Growth, Scale and Volume are now also sold as monthly subscriptions at $7, $29, $119 and $399 a month, 20 to 26 percent less per credit than the packs of the same size. The credits arrive on every paid invoice, land in the same balance as packs and the free grant, and never expire.
  • Start, switch, cancel and resume a plan from the Billing page. Moving to a bigger plan is charged today and deposits its credits at once; moving to a smaller one takes effect at the next renewal; cancelling keeps the plan until the end of the paid month and keeps the credits.
  • Stripe's customer portal is linked from the Billing page for card changes, invoices and receipts.
  • GET /api/v1/account reports the plan key: free, or starter, growth, scale, volume while a plan is paid up.
  • The pricing page and its Markdown twin show the pack and the plan side by side for every tier, and the usage page labels plan deposits as "Monthly plan".

2026-09-02

Verdict quality

  • Lenient address extraction: display names, mailto: links, quotes, trailing punctuation, and invisible characters are stripped before validation.
  • Provider username rules reject local parts that Gmail, Outlook, Yahoo, iCloud, AOL, and Proton would never issue.
  • A curated typo-squat map turns gmail.con and similar domains into a likely_typo verdict with a suggestion.
  • New MX sanity statuses: implicit_mx for domains that rely on the A-record fallback and mx_unresolvable for MX hosts that point nowhere usable.
  • Disposable detection now also matches on the MX hosts, so a fresh burner domain hosted by a known operator is caught; local allow and deny overrides beat waiting for the upstream list.
  • Role and free-provider lists sync weekly from their open sources.
  • smtp_reason carries the provider detail: a full mailbox is risky, a disabled mailbox is undeliverable.
  • The risk score adds a missing SPF record and the domain's registration age, read from RDAP.
  • mailbox_confidence estimates the odds of an unverified mailbox.
  • The response grew from 21 to 26 fields.

2026-09-02

Reliability, feedback loop and billing

  • A shared mailbox answer cache across customers, so the same address is probed once.
  • Bulk runs process in chunks, and each chunk writes its own part file before results are stitched.
  • Provider balance alerts warn before the mailbox partner runs dry.
  • Six @spaw.test test addresses answer fixed verdicts at no cost and are never logged.
  • Every response carries X-Request-Id, repeated in meta.request_id and error.request_id.
  • Publishable keys accept a daily credit cap (429 KEY_SPEND_CAP_REACHED once it is spent).
  • A circuit breaker keeps a struggling mailbox provider from slowing every lookup.
  • Batch requests send their mailbox probes concurrently.
  • A bounce feedback API (POST /v1/email/feedback) and webhook targets for Postmark, Amazon SES, Mailgun, and SendGrid; bounces join the suppression list, deliveries clear it, and the dashboard shows measured accuracy.
  • smtp_checked_at, the 27th response field, records when the mailbox was last probed.
  • Catch-all domains are remembered for a week, and an account's own delivered addresses count as verified for 90 days.
  • Credit packs are sold through Stripe Checkout from the Billing page.
  • callback_url and callback_secret on a lookup re-check an unverified answer after 5 and 20 minutes and POST the settled verdict, signed.
  • Bulk uploads keep the file's own columns, add the verdict columns, and offer ?variant=deliverable|risky|undeliverable downloads.
  • Publishable keys can require a Cloudflare Turnstile token per lookup.

2026-08-31

Batch, bulk, domains, monitors, MCP and free tools

  • POST /v1/email/batch verifies up to 50 addresses in one request.
  • POST /v1/email/bulk queues up to 100,000 addresses, with an HMAC-signed completion webhook, cancellation, and CSV results.
  • GET /v1/email/domain/{domain} answers MX, provider, SPF and DMARC, registration age, and list membership for a whole domain.
  • A per-account suppression list: undeliverable verdicts are remembered, and existing lists can be imported.
  • Scheduled list monitors re-verify a list on a cadence and alert when its deliverability decays.
  • Publishable keys (pk_…) power a browser endpoint locked to allowed origins.
  • An MCP server lets AI agents verify addresses with an existing API key.
  • Four free tools without signup: email checker, MX lookup, SPF and DMARC checker, and disposable domain checker.
  • The documentation became a multi-page area with a getting-started guide, an email verification guide, and the API reference.
  • The product is now Spaw at spaw.co.

2026-08-30

Email verification API launch

  • POST /v1/email verifies one address: RFC syntax, live MX resolution, open disposable and role lists, typo suggestions, SPF and DMARC records, and a 0 to 100 risk score built from published weights.
  • An SMTP mailbox handshake runs on every lookup whose domain accepts mail. No message is ever sent.
  • MX provider classification names the infrastructure behind a domain (Google, Microsoft, Proofpoint, and others).
  • Secret API keys (sk_live_…) are created from the dashboard, shown once, and stored hashed.
  • Credits bill answers only: a deliverable or risky verdict costs 1 credit, undeliverable and invalid answers are free, and a repeat within 7 days is free.
  • The dashboard playground runs the same lookups as the API.
  • spaw-form.js, a form helper for the browser, checks addresses on blur, offers a did-you-mean correction, and never blocks a submit.