PS 01
POST Request verifies that a human collects mail at an address — by mailing them. You send an address; we print a card carrying a single-use QR code and hand it to the United States Postal Service. Days later, a person scans it and confirms. Your webhook fires. Median time to verify: 4.2 days.
All endpoints live under /api/v1, speak JSON, and return errors that say what to do next. Nothing here apologizes for the latency: the latency is the credential.
PS 02
Pass your API key as a Bearer token. Keys come in two modes — pr_test_… and pr_live_… — and the key you use decides which mode the verification is created in. There is no mode parameter; there is only the key.
curl https://postrequest.dev/api/v1/verifications \ -H "Authorization: Bearer pr_live_..."
PS 03
Test mode is the whole product with the clock compressed: mail “prints” in ~10 seconds, “delivers” in ~60, costs nothing, and never touches your credit balance. The QR is shown in the dashboard so you can scan it with your own phone and be your own end user — same verification page, same webhook, same green stamp.
Everything below behaves identically in both modes except the clock and the ledger.
PS 04
POST /api/v1/verifications
Validates the address, debits your balance, prints a card, and mails it. Validation runs before the debit — a bad address is a 422, not a $1.25 lesson. No recipient name is accepted anywhere: mailpieces are addressed to CURRENT RESIDENT, by design.
| Field | Type | Notes |
|---|---|---|
| address | object | line1, line2?, city, state, zip. Required. |
| class | string | bulk ($0.75) · standard ($1.25, default) · certified ($3.00) |
| binding | string | none (default) or email_otp |
| string | Required iff binding=email_otp. Stored encrypted, purged on schedule. | |
| external_id | string | Your reference. The dashboard manifest is searchable by it. |
| return_url | string | https only. Shown to the user after the green stamp. |
| metadata | object | Yours, returned verbatim. |
// 201 Created
{
"id": "01JZ3NVJ4M8Q...",
"class": "standard",
"status": "created",
"price_cents": 125,
"balance_cents": 9875,
"expires_at": "2026-08-18T00:00:00Z",
"signals": {
"address_reuse_count": 0,
"cluster_count": 0,
"po_box": false,
"cmra": null
}
}
// 402 — insufficient credits. Nothing printed. See "Recommended patterns".
{ "error": "insufficient_credits", "balance_cents": 50, "required_cents": 125 }
// 422 — the address failed validation. Nothing debited.
{ "error": "invalid_address", "field": "address.zip", "detail": "..." }
// 429 — this address hit the global limit: 3 mailpieces per 7 days,
// across ALL senders. Harassment protection; not negotiable.
{ "error": "address_rate_limited" }PS 05
GET /api/v1/verifications/:id
Returns the full record, the signals block, and the events timeline — CREATED → PRINTED → IN TRANSIT → DELIVERED → VERIFIED, with timestamps. It is USPS tracking, because that is what it is.
GET /api/v1/verifications?status=&external_id=&limit=
The manifest listing. Filter by status or your external_id.
PS 06
POST /api/v1/verifications/:id/resend
Voids the old QR token, prints a new card, mails it, and debits full price — new mailpiece, new postage. Returns 409 if the verification already succeeded or its address has been purged.
PS 07
DELETE /api/v1/verifications/:id/pii
Erasure passthrough for your users’ deletion requests: the plaintext address and any bound email are destroyed immediately instead of at terminal + 90 days. Keyed fingerprints, the events timeline, and a redacted display form are retained.
PS 08
GET /api/v1/credits
Balance and the append-only ledger. Credits are dollars, held in cents. They never expire.
POST /api/v1/credits/checkout
Returns a Stripe Checkout URL for a top-up. amount_cents minimum is 2500 — we sell books of stamps, not single stamps. Your card is saved in the same Checkout for auto-recharge.
PS 09
Four events: verification.delivered / .verified / .expired / .undeliverable. Delivered is your cue to nudge your user — “your card arrived, go check the mail” — and it is the cheapest conversion lever in the whole loop. Five delivery attempts with exponential backoff; every attempt is logged in the dashboard and can be redelivered.
{
"type": "verification.verified",
"id": "01JZ3NVJ4M8Q...",
"external_id": "user_8123",
"class": "standard",
"mode": "live",
"status": "verified",
"delivered_at": "2026-07-16T19:41:00Z",
"verified_at": "2026-07-17T15:02:11Z",
"binding": "email_otp",
"address_fingerprint": "fp_9c44..."
}Payloads carry the keyed address fingerprint, never the address.
PS 10
Every delivery is signed in X-Post-Request-Signature using the Stripe scheme: t=timestamp,v1=hex, where v1 is HMAC-SHA256 of `${timestamp}.${body}` under your signing secret.
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(header, body, secret) {
const { t, v1 } = Object.fromEntries(
header.split(",").map((p) => p.split("="))
);
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = createHmac("sha256", secret)
.update(`${t}.${body}`)
.digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}PS 11 · Verbatim
What neither mode attests: uniqueness of the human. Sybil resistance comes from address economics and our signals — velocity counts, cluster keys, CMRA detection, scan-pattern anomalies — not from any single verification.
PS 12 · Form PS-110
The Postal Service doesn’t keep a copy of your mail. Neither do we. Detection runs on keyed fingerprints computed at print time; the addresses themselves are destroyed on schedule. Assume our database is stolen someday: the thief gets keyed HMACs they cannot reverse, redacted display strings, token hashes — and plaintext only for mail currently in flight.
| Address, plaintext | Encrypted at rest. Destroyed at terminal + 90 days. |
| Account–address linkage | Destroyed at terminal + 90 days. |
| Address, delinked (fraud R&D) | Retained 18 months. Week-level dates only. |
| Recipient name | Never collected. |
| Email, if bound | Encrypted at rest. Destroyed with the address. |
| QR token | Hash only. Plaintext never stored. |
| Scan signals | Keyed IP hash + zip3 bucket. Nothing reversible. |
PS 13
The 402. Treat verification creation as queueable: on 402, enqueue, alert yourself, retry after top-up. Your user was going to wait five days anyway; thirty minutes of queue delay is invisible. Our latency makes insufficient-balance errors uniquely survivable, and we are telling you so in writing.
User-pays.For human-only communities, pass the fee through: “verification costs $1, paid by the applicant.” A bot farm now has to burn a unique deliverable address and a card payment per fake human; a real human pays a dollar once. Nothing in our API changes — charge your user, then call us.
The delivered nudge. On verification.delivered, email your user. Cards that get an arrival nudge convert dramatically better than cards left to be discovered in a pile of mail.
PS 14 · Effective July 2026
| Bulk · postcard · batched weekly · 2–3 weeks | $0.75 |
| Standard · postcard · mailed next day · 5–7 days | $1.25 |
| Certified · sealed letter · mailed next day | $3.00 |
Pricing is per mailpiece, not per success. Honest math: if your completion rate is 60–75% (typical, with the delivered nudge), your effective cost per verified user on Standard is $1.67–2.08. Postage is non-refundable, with one exception: undeliverable Certified mail refunds 50%. Expired cards refund nothing — delivery happened; the human didn’t act. Credits never expire.