SDKs
Official clients for TypeScript and Python, built around one refusal: neither of them will let you turn three verdicts into two.
TypeScript
npm install @proofwire/sdk
import { Proofwire } from '@proofwire/sdk';
const proofwire = new Proofwire(); // reads PROOFWIRE_API_KEY
const result = await proofwire.email('someone@example.com');
const action = result.match({
valid: () => 'send',
invalid: () => 'drop',
unknown: () => 'ask them to confirm the address',
});Python
pip install proofwire
from proofwire import Proofwire
proofwire = Proofwire() # reads PROOFWIRE_API_KEY
result = proofwire.email("someone@example.com")
action = result.match(
valid=lambda r: "send",
invalid=lambda r: "drop",
unknown=lambda r: "ask them to confirm the address",
)There is no result.valid
It would be the most convenient thing either package could offer and the most damaging. if (result.valid) files every inconclusive answer under "not valid" — and inconclusive is the case this product exists to surface.
A large minority of business mail servers accept every address you ask about, so nothing observable distinguishes a real mailbox from a fictional one. Most validators resolve that into "valid" and invoice you. You find out when it bounces. Both clients make you handle the third case, and neither charges you for it.
What happens if you forget
TypeScript — it does not compile.
Property 'unknown' is missing in type
'{ valid: () => string; invalid: () => string; }'
but required in type 'VerdictHandlers<string>'.Python — it fails at the call site.
TypeError: match() missing 1 required keyword-only argument: 'unknown'
Python cannot check exhaustiveness the way a compiler can, so the handlers are required keyword arguments. That is the closest thing that fails immediately rather than three weeks later in a bounce report.
What else they do
| No double charges | Every call carries an idempotency key, generated for you. Retries after a timeout replay the original response instead of spending again, which is what makes retrying on 5xx safe enough to do by default. |
| Honest backoff | A 429 is respected by the header it came with, not by a guess. Guessing over the top of a stated limit is how a client turns a brief throttle into a long one. |
| Errors you can act on | A malformed key, an empty balance and a 502 are three different problems with three different fixes. An inconclusive verdict is not among them: it is a successful response. |
| No dependencies | The Python client is standard library only. Neither should ever break a build for reasons unrelated to validation. |
Kept in step with the API
Both clients are hand-written rather than generated. The OpenAPI document leaves per-subject attributes free-form, so a generator produces vague types and a great deal of code nobody wants to read.
What generation would have bought — a client that cannot silently fall behind — is bought separately, by a check that reads the live OpenAPI document and fails if an endpoint exists that neither client covers and nobody has said why, or if the set of verdicts ever changes. That second one matters most: a fourth verdict would make every exhaustive handler in the wild wrong, and nothing else would notice.
Also available as an MCP server for AI agents. The API docs cover the raw HTTP interface. Both clients are MIT licensed.