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

PS 00 · For your coding agent

Coding-agent brief

Copy the brief below into your coding agent. It points to the machine-readable API reference at /llms.txt and lists the constraints the integration must preserve.

Prompt · paste into your agent
You are helping me integrate POST Request (https://postrequest.dev), a mailbox-access verification API. I send an address; a card carrying a single-use QR code gets printed and mailed; when the card is completed, a signed webhook fires. Every attempt needs an address, postage, and time.

Fetch the full machine-readable reference before writing any code: https://postrequest.dev/llms.txt

Facts you must respect:
- Base URL https://postrequest.dev/api/v1. JSON. Bearer auth. Test keys are pr_test_…, live keys pr_live_… — the key chooses the mode; there is NO mode parameter.
- Create: POST /verifications with { address: { line1, line2?, city, state, zip }, class: "bulk"|"standard"|"certified", binding: "none"|"email_otp", email?, external_id?, return_url?, metadata? }. There is NO recipient-name field — mail is addressed to CURRENT RESIDENT.
- Validation runs before the debit: a bad address returns 422 and charges nothing. Insufficient credits returns 402 and mails nothing — treat it as queueable and retry after top-up. A global cap of 3 mailpieces per address per 7 days returns 429.
- Track by polling GET /verifications/:id or by handling webhooks: verification.delivered / .verified / .expired / .undeliverable.
- Verify webhook signatures: header X-Post-Request-Signature = "t=<unix>,v1=<hex>", where v1 = HMAC-SHA256 of `${t}.${rawBody}` under my signing secret. Compute over the RAW body and reject if now - t > 300s.
- On verification.delivered, prompt my user to check the mail while the card is current.

Now help me wire this into my app. Start in test mode. My stack and what I'm gating: <describe here>.

PS 01

Overview

POST Request verifies access to a deliverable mailbox by sending it a single-use QR code. When the card is completed, your signed webhook fires. Standard delivery is scheduled for 5–7 business 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");
  const expectedBytes = Buffer.from(expected, "hex");
  const receivedBytes = Buffer.from(v1, "hex");
  return expectedBytes.length === receivedBytes.length &&
    timingSafeEqual(expectedBytes, receivedBytes);
}

PS 11 · Verbatim

What verification attests

binding: none
a party with physical access to mail at this address completed the loop.
binding: email_otp
the party controlling this email address also completed a credential delivered to this address. A stolen or photographed mailpiece cannot complete the loop without the email code, and the code cannot complete it without the mailpiece.

Neither mode attests legal identity or one-human uniqueness. Abuse resistance comes from address economics, reuse counts, building-level clusters, PO-box detection, and customer policy.

PS 12 · Form PS-110

What we store

The encrypted mailing address remains on the verification through terminal state + 90 days. It then moves to a separate encrypted vault without customer or verification IDs. Redacted address text and keyed fingerprints remain on the verification. Do not place personal data in external_id or metadata; those fields remain on the verification.

Mailing address ciphertextMoves to the delinked vault at terminal + 90 days.
Delinked address vaultEncrypted. Retained 18 months with week-level dates.
Redacted address + keyed fingerprintRetained with the verification.
Recipient nameNever collected.
Email ciphertext, if boundRemoved at terminal + 90 days; hash + mask remain.
External ID + metadataCustomer supplied. Retained with the verification.
QR tokenHash only. Plaintext never stored.
Scan signalsKeyed IP hash + ZIP3 bucket. Retained with the verification.

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. Each additional account now needs another deliverable-address allocation and a card payment. Nothing in our API changes — charge your user, then call us.

The delivered nudge. On verification.delivered, email your user. The delivered event exists so you can prompt them to check the mail while the card is current.

PS 14 · Effective July 2026

Rates & conversion math

Bulk · postcard · batched weekly · 2–3 weeks$0.75
Standard · postcard · mailed next day · 5–7 business days$1.25
Certified · sealed letter · mailed next day$3.00

Pricing is per mailpiece, not per success. Effective cost per verified user equals the mailpiece rate divided by your observed completion rate. We do not yet publish a production completion benchmark; measure your own cohort. 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.