Getting started
Spaw verifies the identifiers people type into forms, over a plain REST API and a dashboard. Six signals run off one key and one balance: email verification without sending mail, phone intelligence, IP intelligence, postal address verification, business verification against public registers, and a cross-signal consistency check that reports where the others agree and where they do not. Every answer names the datasets behind it, and the four scored signals publish the weights their 0 to 100 risk score is summed from. This guide takes you from a fresh account to your first lookup in a few minutes.
The first request does not wait for any of it. A published sandbox key answers the sample requests in these docs without an account, so you can see a real response before you decide to sign up — skip to your first request and paste it.
Create an API key
Past the sandbox key, every API request authenticates with a key created from the dashboard.
- 1
Create an account
Sign up with an email and password, then confirm the address from the email that arrives: the API refuses every request from an unconfirmed account. Confirming also credits a one-off signup bonus, on top of the free credits every account is granted at the start of each month. No card required.
- 2
Open the API keys page
Once signed in, choose API keys in the header. Name the key after where it will live — "Production", "Staging" — so your usage history stays readable.
- 3
Copy the key
The secret key is shown once and stored hashed; copy it before leaving the page. Revoking a key stops its requests immediately.
There is a second kind of key: publishable pk_ keys, made for the browser form widget. They can run the four browser lookups — email, phone, IP and postal address — only from the domains you allow, and stay visible in the dashboard. Their answers carry no meta block, so a page visitor never sees the account's balance. Keep secret keys on servers; ship publishable keys to pages.
Key controls. A secret key can be limited when it is created: scopes (email, IP, phone, address, entity, consistency, account) confine it to the product families an integration needs, a daily credit cap stops a runaway script (single and batch calls count what they bill; bulk runs are not counted but cannot be started once the cap is reached), and an allowlist of addresses or CIDR ranges makes a leaked key useless elsewhere. Refused calls answer KEY_SCOPE_DENIED, KEY_SPEND_CAP_REACHED or KEY_IP_NOT_ALLOWED and bill nothing. A batch that reaches the cap part-way is not refused: it stops there, keeps the items already paid for, and answers 200 with meta.stopped_reason: "key_spend_cap" — so a single large batch can never spend past the cap. All three controls apply to tool calls over the MCP server exactly as they apply here, so a key given to an agent is limited by the same three things; a scope or cap refusal reaches the agent as a tool error naming the same code, because an HTTP status is something an MCP client reports as a broken connection. The controls are fixed at creation: create a new key to change them. To replace a key without downtime — a leak, or a scheduled rotation — use Rotate on the API keys page: the replacement carries the same scopes, cap, address allowlist and default country, and the old key keeps working for 24 hours so you can move each integration over, or you can end it immediately with Revoke now.
Your first request
Send the key as a bearer token, JSON in and out. Verifying an address is one POST — and this one runs as printed, because sk_sandbox_spaw is the published sandbox key rather than a placeholder:
$ curl https://spaw.co/api/v1/email \ -H "Authorization: Bearer sk_sandbox_spaw" \ -H "Content-Type: application/json" \ -d '{"email": "[email protected]"}'
{
"success": true,
"data": {
"email": "[email protected]",
"deliverable": "deliverable",
"reason": null,
"risk_score": 0,
"mx_provider": "other",
"disposable": false,
… 21 more fields, see the API reference
},
"meta": { "credits_used": 0, "credits_remaining": null, "sandbox": true }
} A phone number is the same call on /api/v1/phone, and the sandbox answers the eight test numbers the same way:
$ curl https://spaw.co/api/v1/phone \ -H "Authorization: Bearer sk_sandbox_spaw" \ -H "Content-Type: application/json" \ -d '{"phone": "+12025550100"}'
{
"success": true,
"data": {
"valid": true,
"e164": "+12025550100",
"line_type": "mobile",
"line_type_source": "block_allocation",
"carrier": "Spaw Test Wireless",
"carrier_source": "block_allocation",
"block_status": "allocated",
"risk_score": 0,
"hlr_checked": false,
"ok_to_send": true,
"blocked_by": null,
… more fields, see the API reference
},
"meta": { "credits_used": 0, "credits_remaining": null, "sandbox": true }
} The carrier is the company the number's block was allocated to in the regulator's table, not necessarily the network that serves it today, and ok_to_send is the published default SMS policy applied to the answer. The live carrier-network check (hlr: true) is not enabled on the service: hlr_checked answers false and nothing extra is charged.
What the sandbox key does. It answers five lookups — POST /api/v1/email, /phone, /ip, GET /api/v1/ip/{ip} and POST /api/v1/address — and only for the sample values these docs print: the six test addresses on spaw.test, the eight test numbers +1 202 555 0100 to 0106 and 0113, the sample IP 8.8.8.8, the sample postal address in the reference and the five test postal addresses at 1 to 5 Spaw Test Street, Springfield, IL 99999 (each answers ok_to_ship and blocked_by from the published default shipping policy, and the test numbers answer ok_to_send and blocked_by from the published default SMS policy, so the decision fields can be exercised without an account). The request body has to carry those fields and nothing else, so a body with an extra option is refused rather than quietly ignored. Everything else answers SANDBOX_ENDPOINT_NOT_ALLOWED or SANDBOX_VALUE_NOT_ALLOWED, naming what it would have taken. It is limited to 30 requests a minute from one address.
What it does not do. The key is printed here, so everybody has it: it is attached to no account, spends no credits, writes nothing that lasts, and cannot read a balance, a bulk run, a monitored list, a suppression list or a request log. That is why meta.credits_remaining answers null and meta.sandbox is true. Swap in your own sk_live_ key and the same call answers for any address, bills a credit for a fresh usable answer, and reports the balance it left.
Every endpoint answers the same envelope — { success, data, meta } on success, a typed error.code on failure — and no verification ever sends mail. The dashboard playground runs the exact same lookups if you want to try addresses before writing code, and the six fixed test email addresses ([email protected], [email protected], …), the eight test phone numbers and the five test postal addresses the sandbox answers for are free on your own key too, so integration tests never spend credits; see the API reference.
Credits & billing
You pay for answers, not attempts, and anything you paid to check once is free to check again for a week.
| outcome | credits |
|---|---|
| Fresh lookup with a usable answer | 1 credit |
| Five fresh IP lookups with a usable answer | 1 credit |
| Email answer whose mailbox check was uncertain (unverified) | 0 |
| Undeliverable email verdict within the fair-use allowance | 0 |
| Undeliverable email verdict past the fair-use allowance | 1 credit |
| Invalid input or no match | 0 |
| Repeat of a recent lookup, within 7 days | 0 |
| Rejected or failed request | 0 |
IP lookups are billed in blocks of five: the answered IP lookup that opens a block reports credits_used 1 and the next four report 0. Undeliverable email verdicts that needed a mailbox check are free within a fair-use allowance; the pricing page gives its size.
Two credits arrive without being bought: a free grant at the start of every calendar month, and a one-off signup bonus the first time the account confirms its email address. Both land on the same balance, never expire, and are listed on the Usage page like any other movement. The size of the monthly grant is on the pricing page; the bonus is named in the welcome email.
The dashboard's Usage page lists every credit movement with the key or channel that spent it, and GET /api/v1/account returns the live balance for pre-flight checks.
Need more than the free grant? Buy a credit pack from the Billing page in the dashboard, or start a plan that deposits the same credits every month for less, billed monthly or yearly. Packs start at $9 for 1,000 credits, plans at $7 a month. Pack credits never expire; plan credits roll over up to three months' allowance and stay usable for 60 days after you cancel, and a plan is cancelled at any time.
Next: the email verification guide for verdicts, lists, bulk runs, and the form widget, the phone intelligence guide for every field, the regulators' tables, the SMS policy and the test numbers — or the API reference for every endpoint and field.