The short answer

To prepare one custom US postcard with the IRIS REST API, upload and register the already-rendered PDF face or faces, quote the complete order, and let a person inspect the actual artwork, addresses, and price before a separate idempotent submission. Small uses one PDF with art and message; 4 × 6, 6 × 9, and Pro require different front and back PDFs.

Choose the postcard size before exporting PDFs

Unlike a letter PDF, a postcard is not a multipage document sent to a printer to arrange. IRIS accepts already-rendered print faces. Each registered postcard PDF must have exactly one landscape page, match the document kind and dimensions for its selected size within 1 pt, and open without a password. The API does not turn text or an image URL into print artwork.

These are the IRIS provider print-file dimensions, including bleed for the Lob sizes—not a claim that every size qualifies for a retail USPS postcard stamp. USPS distinguishes retail and commercial First-Class postcard dimensions; use the server-calculated IRIS quote rather than guessing postage from a stamp price.

One-page PDF files by postcard size
options.sizeUpload kind and page sizeFaces and availability
4x6postcard_4x6 · 450 × 306 pt (6.25 × 4.25 in)Different front and back files; Lob provider.
6x9postcard_6x9 · 666 × 450 pt (9.25 × 6.25 in)Different front and back files; Lob provider.
5.5x4.25postcard_small · 396 × 306 pt (5.5 × 4.25 in)Small: one custom side with art and message; omit secondaryDocumentId. LetterStream staging, held Test Mode job.
8.5x5.5postcard_pro · 612 × 396 pt (8.5 × 5.5 in)Pro: different main-art and message/address-side files; LetterStream staging, held Test Mode job.

Place the message and address space on the correct side

For Lob 4 × 6 or 6 × 9, prepare two different one-page PDFs of the same postcard kind: primaryDocumentId identifies the front; secondaryDocumentId identifies the back. Do not repeat one ID for both faces. Small is the exception: put the art and message on its single custom side inside a quarter-inch white border; the provider generates the address side, so do not upload a second file.

For Pro, the primary file is the main artwork and the secondary file is the message/address side. Reserve the rightmost 3.75 inches (270 pt) of that side for the provider’s address area. Its two files must both use postcard_pro dimensions. Small and Pro depend on the LetterStream staging provider; current LetterStream submissions stay held in Test Mode rather than being printed and mailed. A size for the other active provider is rejected before charging.

Inspect a rendering of every PDF, especially edges, bleed, reading direction, message placement, and address clearance. The options.layout (banner, poster, minimal), options.font (editorial, modern, handwritten), and options.artwork describe your finished PDFs; artwork: { type: "upload" } does not upload or render them.

Presign, PUT, and register each face

Keep an account-bound bearer key on your server, never in browser JavaScript. For each face, POST /v1/uploads/presign with fileName, the exact sizeBytes, and the selected kind. PUT the bytes to uploadUrl before its ten-minute expiry with every returned uploadHeaders value, including If-None-Match when supplied. Do not forward the bearer key to object storage. A presigned URL alone is not a completed upload.

After a successful PUT, POST /v1/documents with the returned storage key, the same fileName, and kind. Save data.id from the registered immutable document; the storage key is not a document ID. A changed face needs a new upload and registration. Each PDF is limited to 26 MiB. Use only artwork and recipient data your organization is authorized to process; do not route sensitive official forms, identity documents, or medical data through a consumer upload without an applicable written agreement.

Prepare a 4 × 6 postcard and request a quote, not a send

This Node.js 22+ server-side example expects front.pdf and back.pdf as separate 450 × 306 pt, one-page PDFs. Save the JSON below as addresses.json with your real reviewed US sender and recipient addresses; the script refuses the sample placeholders. Both sender.recipient and recipient.recipient are required names or companies, not optional labels. Each address also needs primaryLine, city, two-letter state, and zipCode.

Provide IRIS_API_KEY in the server environment with documents:write and mailings:read scopes. This example never calls POST /v1/mailings. It saves the exact quote payload and price for human review, but neither checks credit balance nor creates a mailing. The active provider must support 4x6; if it supports LetterStream staging instead, use the Small or Pro PDF kinds and face count above rather than merely changing the size string.

addresses.json — replace all placeholders before running
{
  "sender": {
    "recipient": "REPLACE WITH SENDER NAME",
    "primaryLine": "REPLACE WITH SENDER STREET",
    "city": "Boston", "state": "MA", "zipCode": "02108"
  },
  "recipient": {
    "recipient": "REPLACE WITH RECIPIENT NAME",
    "primaryLine": "REPLACE WITH RECIPIENT STREET",
    "city": "Boston", "state": "MA", "zipCode": "02109"
  }
}

Run the private preparation script

Save this as prepare-postcard.mjs beside your two PDFs and addresses.json. Set IRIS_API_KEY only in your trusted server environment, then run node prepare-postcard.mjs. Keep postcard-review.json private: it includes addresses and the registered document IDs. If preparation fails, stop and inspect the error; a quote is not an approval or an order.

prepare-postcard.mjs — two PDF uploads and a quote; no submission
import { readFile, writeFile } from 'node:fs/promises';

const base = 'https://api.irispostalforms.com';
const key = process.env.IRIS_API_KEY;
if (!key || !key.startsWith('irk_')) throw new Error('Set a server-side IRIS_API_KEY.');

async function api(path, body) {
  const response = await fetch(base + path, {
    method: 'POST',
    headers: { Authorization: 'Bearer ' + key, 'Content-Type': 'application/json' },
    body: JSON.stringify(body)
  });
  const result = await response.json();
  if (!response.ok) throw new Error(response.status + ' ' + JSON.stringify(result.error));
  return result.data;
}

async function register(fileName) {
  const pdf = await readFile(fileName);
  const kind = 'postcard_4x6';
  const upload = await api('/v1/uploads/presign', {
    fileName, sizeBytes: pdf.byteLength, kind
  });
  const put = await fetch(upload.uploadUrl, {
    method: 'PUT', headers: upload.uploadHeaders, body: pdf
  });
  if (!put.ok) throw new Error(fileName + ' upload failed: HTTP ' + put.status);
  const document = await api('/v1/documents', {
    key: upload.key, fileName, kind
  });
  return document.id;
}

const addresses = JSON.parse(await readFile('addresses.json', 'utf8'));
if (JSON.stringify(addresses).includes('REPLACE')) {
  throw new Error('Replace every sample address placeholder first.');
}
const primaryDocumentId = await register('front.pdf');
const secondaryDocumentId = await register('back.pdf');
const mailing = {
  product: 'postcard', primaryDocumentId, secondaryDocumentId,
  sender: addresses.sender, recipient: addresses.recipient,
  options: { size: '4x6', layout: 'minimal', font: 'modern',
             artwork: { type: 'upload' } }
};
const quote = await api('/v1/mailings/quote', mailing);
await writeFile('postcard-review.json',
  JSON.stringify({ mailing, quote }, null, 2), { flag: 'wx' });
console.log('Quote in US cents:', quote.priceCents);
console.log('STOP: inspect both PDFs, addresses, options, and price. No order was created.');

Review, approve, submit once, then read status

POST /v1/mailings/quote returns priceCents and a breakdown. It does not charge credits, reserve a price, or submit the postcard. IRIS creates individual US orders, not campaigns, mailing-list imports, EDDM, scheduled jobs, or international mail. If the PDFs or mailing details change, register new files as needed, quote again, and seek fresh approval.

Only after a person sees both actual faces (or Small’s one custom face), complete sender and recipient addresses, current quote, purpose, and authority to send should trusted server code POST the reviewed mailing object—not the entire review wrapper—to /v1/mailings with a persistent Idempotency-Key. This call recalculates price, charges prepaid credits, and releases fulfillment. Save one unique key with that approved payload; on an uncertain response, retry the unchanged payload with the same key. A new key can create another paid order.

Save data.orderId from the 201 creation result (or 200 completed replay). Its paid status is an order acknowledgement, not proof of printing, USPS acceptance, or delivery. GET /v1/mailings/{id} with that order ID for current status; trackingNumber and expectedDeliveryDate can be null, and stages may be skipped. Ordinary First-Class postcards do not include USPS Tracking, and an estimated arrival date is not a delivery guarantee.

Sources & further reading

Product details checked against the IRIS API contract when this article was published. Platform references can change; consult the linked documentation when implementing.

Published by IRIS Postal Forms, operated by Great Lakes Computing Office LLC. Send a correction.

Put it into practice

Make your next postcard

Create a postcard in the website editor, or use the documented print-face payload for your custom integration.