Spaw is a verification API: the data you send is mostly other people's — their email addresses, their phone numbers, their IP addresses, where they live. This page states, specifically, what happens to it, one product at a time, because the answer is not the same for all six. It only describes measures that are in place today. Its companion is /trust, which covers the company rather than the lookups: the certifications Spaw does not hold, who processes what and where, the standing data processing addendum — which used to be one line on this page saying it was available on request — the answers a security questionnaire asks for, and where to send a vulnerability.
Transport
All traffic to spaw.co, including the API, the dashboard, the form widget, and the MCP server, is served over HTTPS. Plain HTTP requests are redirected.
Calling the API from a browser
The API answers cross-origin requests from any origin, and the browser endpoints authenticate with a publishable key plus an Origin check against the list you configure — so the allowlist that matters is the one on your own key, not one held here.
Six response headers are exposed to browser JavaScript, because the documentation tells you to read them: X-Request-Id (quote it to support), X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (what to back off on), Retry-After (what a 429 says to wait) and Idempotent-Replayed (whether a repeated create was the original answer replayed rather than a second job). Preflight responses are cacheable for 24 hours, so a widget that posts JSON pays one preflight rather than one per lookup.
No cookie ever authenticates the API — it is a bearer token, or a publishable key in the body — so credentialed cross-origin requests are not accepted. Only /api/* and Sanctum's CSRF-cookie route are cross-origin readable at all; the dashboard and the marketing pages are not.
API keys
- Secret keys (
sk_live_…) are shown once at creation and stored hashed. Spaw cannot show a secret key again; if you lose it, create a new one and revoke the old one. Revocation is immediate. - Key scopes. A secret key can be limited to any of the seven scopes —
email,phone,ip,address,entity,consistency,account— and a call outside them is refused with403 KEY_SCOPE_DENIEDbefore any work runs. A key can also carry an allowlist of source addresses or CIDR ranges, and a daily credit cap that stops a batch part-way through rather than letting one request spend past it. - Publishable keys (
pk_…) are made for the browser and are stored and displayed in plain text by design. They can run the four browser lookups — email, phone, IP and postal address — only from the origins you list, and can be limited with a daily credit cap. Their responses deliberately carry nometablock, so a page visitor never sees the account's credit balance. A key can also require a Cloudflare Turnstile token per lookup, which stops scripted abuse without ever blocking a real form submission. - Rate limits. Each product family has its own bucket, so a burst of one never starves another: 5 requests per second per key on the email and consistency endpoints, which do DNS and a mailbox handshake, and 50 per second on the phone, IP, address and business endpoints, which read local data. The browser endpoints allow 20 requests a minute per address, and the free tools have their own per-visitor throttles.
- Request ids. Every response carries an
X-Request-Idheader, repeated inside the JSON body, so a support question can be matched to the exact request. The request log behind it records the endpoint, the status, the error code and the cost — never the value you looked up.
The addresses you verify
- An address is used to answer your query and for nothing else. Spaw does not sell submitted data, build shared datasets from it, or contact the addresses.
- No message is ever sent to an address you check. The mailbox check is an SMTP handshake that ends before any message data is transmitted.
- Only addresses that reach the mailbox step are sent to the mailbox verification partner. Addresses at disposable domains, typo-squat domains, and domains with no working mail server are answered from DNS and open lists alone and never leave Spaw.
- Email verification is the one product that keeps a history row: the address, its verdict, reason and risk score, and what it cost. It is your own usage log, it is deleted after 30 days, and it is what the dashboard's history, exports and bounce-feedback matching read.
- Bounce and complaint outcomes you report are your own account's record, with one deliberate exception: the share of reported sends that bounced is counted per domain across every account, and a domain says nothing until it has at least five outcomes. What crosses accounts is that ratio for a domain, never an address and never who reported it.
- The six
@spaw.testtest addresses answer fixed verdicts and are never logged.
Phone, address and business lookups
- Nothing about the lookup is stored. No history row, no log line carrying the number, the address or the identifier. What a lookup leaves is keyed and never holds the value: a seven-day marker that makes a repeat of the same value free (a phone number whose live check you paid for gets a second one, for the check); for a phone number, the traffic counts in the next point; and, when a live carrier check runs, the network's answer, held for a day under a keyed hash of the number so a repeat does not ask again.
- Your own phone traffic is counted, not recorded. For single and browser phone lookups, each number is kept as a truncated keyed hash under the account's own key, filed under keyed hashes of its 1,000-number range and of the client address the caller sent, for 60 minutes; and the account's lookups per country and hour are kept as counts, with no number, for 30 days. They answer the account's own
range_seen_by_you,client_velocityanddestination_new_for_youand its owner's pumping and enumeration alerts, and are never read for another account. Erasing a number takes it out of every range count. - They are answered on the server. A phone number is answered from libphonenumber and the regulators' allocation tables held here, except the opt-in live carrier-network check, which sends that one number to a carrier-network partner; a postal address from the format table, the postcode directory, the national address registers and the open company registers synced here; a business identifier from register copies downloaded and queried here. Apart from that one opt-in check, no third-party processor sees any of the three, and the
sourcesblock of every response names each dataset and the date it was synced. - The premium checks are off. A live carrier check on a phone number, a deliverability check on a postal address, and listing the addresses at a postcode would each send the value to a licensed partner. None is configured, so none runs: the response says the check did not run rather than guessing, and nothing is sent anywhere.
- Two public datasets name phone numbers, and they are Spaw's own. The complaints the US Federal Trade Commission publishes about unwanted calls are downloaded each weekday and kept for 90 days: the number named as the caller, the day, whether it was a recorded message and the subject, never anything about the consumer who complained. Three on two different days raise that number's risk for every account, although caller ID is easily spoofed. And the numbers public SMS-receiving websites publish are downloaded daily; one the sites stop showing is kept for up to 730 days after it was last seen, scoring as disposable for the first 90 of them (a number the lists still carry scores as disposable for 365 days after it was last seen), and as a weaker stale listing after that. Erasing a number deletes its complaints and any listing the sites no longer publish.
- Bulk runs keep the list you uploaded and the result file written from it as the only copies of what you sent; both are deleted with the job after 30 days.
- Monitored lists are the one place a value is stored on purpose, because re-checking it later is the whole point: a saved list holds its numbers, addresses or identifiers and each one's most recent answer. A list nobody runs any more is deleted along with everything it stored, 365 days after its last run; a list in active use is never touched, because every run resets that clock. Every monitor payload reports its own
prunes_after_days. - Outcome reports you send about a phone number or a postal address are stored per account, hold the value you reported, and are pruned after 180 days. They shape your own later lookups. One exception, narrow and a count only: once three or more separate accounts report abuse for the same number inside 30 days, that number's risk rises for everyone, and what other accounts read is the number of accounts, never the reports. Address feedback has no such exception and never will: a phone number is a handset and an IP is a network endpoint, but a postal address is where somebody lives, and a shared "post fails here" database would be a database about residences.
- Business verification returns no registered address, deliberately. An identifier does not say whether a company or a sole trader stands behind it, so the product answers what the register says about the entity and nothing about where it can be found.
IP lookups
- An IP address is personal data and is handled as such. Nothing about an IP lookup is stored: no history row, no log line with the address. The traces are a keyed seven-day marker that makes a repeat of the same address free, and, under the same hash, the count behind
seen_by_you. privacy: trueon a lookup, or?privacy=1on a GET, skips both. Every such call is a fresh, billable lookup with no trace kept.- IP lookups never leave the server. Every signal is a local database or a compiled feed, so no third-party processor sees the address. The one exception is opt-in: asking for
abuse_contactsends the address to the Internet registry's public RDAP service (via rdap.org) to read the network's abuse mailbox, and that answer is cached per allocation. Thesourcesblock of every response names each dataset consulted and the date it was synced. - Bulk IP runs keep the input list and the result file as the only copies of what you sent; both are deleted with the job after 30 days.
- Outcome reports you send to
POST /api/v1/ip/feedbackare the one thing stored on purpose, and even they never hold the address: each is a salted SHA-256 hash of the canonical address with the outcome and a timestamp, kept for 90 days and then pruned. Your reports shape only your own later lookups, except that once three or more accounts report the same address its risk rises for everyone; what other accounts read is that count alone, never the reports.
Cross-signal consistency
A consistency call stores exactly what its legs store and nothing more: the email leg writes its history row, the phone, address and IP legs write nothing. The comparison itself touches no dataset, no database and no network — it is arithmetic over fields the four pipelines already computed, which is why it is free — and its result is returned to you and not retained.
Retention
- Email lookup history is kept for 30 days, then deleted automatically. Bulk job files, for every product, follow the same window, and so does the API request log.
- Repeat-lookup markers last 7 days; they are what makes a repeat free.
- Phone velocity counts last 60 minutes and the per-country counts of phone lookups 30 days; neither holds a number.
- The FTC complaint data is kept for 90 days from the day each complaint was filed, and a disposable-number listing for up to 730 days after a public list last showed the number. Both are public data about numbers, not anything you sent, and both go with an erasure of the number.
- Monitored lists are deleted 365 days after their last run, with everything they stored. A list still being run is never pruned.
- Suppression lists and feedback are stored per account. A suppression entry you imported, or one recording a spam complaint, an opt-out or an abuse report, stays until you remove it (an erasure of the number also deletes an abuse entry you did not import); one a lookup or your reports of failed deliveries added also leaves when a later answer or report clears it. Suppression entries a lookup or your reports of failed deliveries added are re-verified rather than trusted once they are older than 90 days; imports, complaints, opt-outs and abuse reports never go stale. Outcome reports are pruned after 180 days, except IP reports, which are hashed and pruned after 90.
- The IP addresses your own account has been seen from — a signed-in browser or an API key — are kept for 180 days from when each was last seen, and are the one address store held in the clear rather than hashed. They are a security record read by staff when somebody writes in about their account, never shown to another customer and never joined to the addresses you verify. The privacy policy carries the full entry.
- The credit ledger is kept for the life of the account as your billing record.
- Deleting your account from the settings page removes the account and everything attached to it: keys, history, jobs, monitors, suppression lists and feedback. Two parts of that were only true on paper until 2026-09-09. Each bulk run's uploaded list and result file is now deleted from disk with the account — a database cascade fires none of the application's own cleanup, so those files were being orphaned with no row left pointing at them, and the files are the personal data, not the rows. And secret API keys are now destroyed with the account too: their table is polymorphic rather than a foreign key, so it did not cascade, and rows holding a key's name, its token hash, its scopes and the allowlist of a customer's own server addresses were surviving the account indefinitely. Any live subscription is cancelled at the same time, so a closed account stops being charged.
What the API promises, and what it does not
The OpenAPI document at /openapi.yaml is the contract. Not a description of it — the contract itself: a test fails the build if a route exists that the document does not describe, if an operation describes a route that does not exist, or if a typed error code appears in one and not the other. What you read there is what runs.
Additive changes ship without notice, and your integration has to tolerate them. New endpoints, new fields on an existing response, new values in an enum, new typed error codes, and new optional request parameters are all additive, and all of them will happen. A client that rejects an unknown field, or that treats an unrecognised reason or error.code as a crash rather than as a value it has not seen yet, will break on a change that breaks nobody else. Match on the codes you handle and let the rest fall through.
Breaking changes get a new path. Removing a field, changing the type or meaning of one, renaming an enum value, tightening validation, or changing what an endpoint costs is a breaking change, and it does not happen to /api/v1. It happens at a new version prefix, with both live at the same time.
Nothing is deprecated today. When something is, it will be marked deprecated in the OpenAPI document, keep working, carry a Sunset header naming the date it stops, and be announced in the changelog — which has a permalink per release and an Atom feed, so you can follow it without watching the page.
Availability
There is no numeric uptime commitment, and we would rather say so than publish a figure we cannot stand behind.
What there is, is the measurement. /status publishes, live and without authentication: the state of every component behind an answer, ninety days of measured availability, the age and version of every dataset the answers are read from, and the median and 95th-percentile latency per endpoint group over the last hour and the last day, with the success rate beside them. Below those, dated rather than live, it publishes what the weekly quality measurements found. GET /api/v1/status/data returns everything in the first list as JSON, unauthenticated and free, so you can watch it from your own monitoring rather than trusting a page; the quality measurements are page copy and are deliberately not in that payload, because they are a weekly figure and a monitor polling for one would be polling for last Sunday.
Three things are worth knowing about how that is reported. The percentiles cover requests we actually answered: a refusal that never reached a lookup — an empty balance, a rate limit, a validation failure — returns in a millisecond or two and is not evidence that anything is fast, so those are counted in the success rate instead. And a component nobody has switched on reads as not configured rather than degraded, so an optional check that is off does not pretend to be an outage.
The third is the ninety-day availability figure, and it is the one to read carefully. It is counted from our own timings — one anonymous row per lookup request, kept a week and rolled up into a daily count before it is deleted — and for each UTC day it reports the share of the requests we recorded that were not answered with a fault on our side. Refusals stay out of it on the same grounds as the percentiles. The limits are worth stating plainly, because they are the limits of any measurement taken from inside. It only counts requests that reached this server and were recorded. An outage that took the service off the network records nothing at all: if it lasted a whole day, that day is blank and a blank day is never counted as a good one — but if it lasted an hour, the other twenty-three are all that is left, 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, which is the likeliest cause of a real burst of 5xx — takes those failures out of the figure along with everything else, because the recorder is allowed to fail silently rather than break a request. So read it as a floor on the bad days rather than as proof of a good one. It is a measurement of the requests we answered, not a claim about the time we were reachable, it is not an SLA, and the incident log is where an outage is actually described, because a person writes that.
The quality measurements are a different kind of number again, and the same care applies. Two of them live only on that page. The first pools the delivery outcomes customers reported back to us against the verdicts we had given those addresses, and it is not an accuracy rate — we do not have one. It counts what customers chose to send us, and a bounce breaks a send and gets filed while a delivery is silence, so it is biased upwards by an amount nobody can measure; it is blind in the other direction too, because an address we called undeliverable that would have been fine is never sent to and never reported. So it is published beside the thing that shows how small a slice it is: how many addresses were verified in the same window, and how many of those same addresses anyone came back about. No share is published at all until at least a hundred reported outcomes stand behind that verdict, from at least three accounts, with no single account more than half of them — a pooled figure drawn from one customer's rows is that customer's mail performance on our website, whatever we call it. The second measurement takes identifiers out of each installed business register's own file and asks for them back through the ordinary lookup: it is a check on our own plumbing, it says nothing about whether a register is complete or right, and a register whose file measures nothing is named as unchecked rather than scored. The other three products' measurements are published in full on their own product pages, so each figure lives in one place.
Incidents are written up by hand at /incidents, with an Atom feed if you would rather subscribe than watch a page. That history carries what we found and recorded, which is the same limit as above said another way.
If a dependency we do not run is down, the component that uses it says so on that page rather than the answer quietly getting worse.
Reporting
Report a security issue, or ask anything about how your data is handled, at [email protected]. A report is acknowledged by a person within five business days; the disclosure policy is on /trust and machine-readable at /.well-known/security.txt.
If Spaw holds something about you and you never signed up here — because a customer verified your address, or reported that a message to it bounced — the erasure form at /privacy/data-request deletes it, and names beside the form the few things it leaves alone, such as a customer's own import of the address. It confirms by mail to the address and runs immediately.