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 chargesEvery 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 backoffA 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 onA 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 dependenciesThe 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.