Post RequestForm PR-01Publication 1 · API Reference · July 2026

PS 01

Overview

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

Authentication

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

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

Create a verification

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.

FieldTypeNotes
addressobjectline1, line2?, city, state, zip. Required.
classstringbulk ($0.75) · standard ($1.25, default) · certified ($3.00)
bindingstringnone (default) or email_otp
emailstringRequired iff binding=email_otp. Stored encrypted, purged on schedule.
external_idstringYour reference. The dashboard manifest is searchable by it.
return_urlstringhttps only. Shown to the user after the green stamp.
metadataobjectYours, 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

Retrieve & list

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

Resend

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

Erase PII

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

Credits

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

Webhooks

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

Verifying signatures

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 verification attests

binding: none
a person with physical access to mail at this address completed the loop.
binding: email_otp
the party controlling this email address ALSO receives mail at this address. The conjunction is the product’s strongest claim: a stolen or photographed mailpiece cannot complete it, and an intercepted OTP cannot complete it without the mailpiece.

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

What we store

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, plaintextEncrypted at rest. Destroyed at terminal + 90 days.
Account–address linkageDestroyed at terminal + 90 days.
Address, delinked (fraud R&D)Retained 18 months. Week-level dates only.
Recipient nameNever collected.
Email, if boundEncrypted at rest. Destroyed with the address.
QR tokenHash only. Plaintext never stored.
Scan signalsKeyed IP hash + zip3 bucket. Nothing reversible.

PS 13

Recommended patterns

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

Rates & conversion math

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.