AgentPostagePDF to physical mail.

API · CLI · MCP

Mail a PDF.

Send a PDF, a US return address and a US recipient address. POST /v1/letters queues physical sending in one request, without a separate approval step.

Choose your agent

Use a current client version. Create an account, accept the Terms and get a key. Then choose your agent below. Connecting and checking your balance are free. Add funds before sending mail. Commands below use a macOS or Linux shell. Set AGENTPOSTAGE_API_KEY through your agent’s environment or secret store, not in a chat message. Restart an already-running app or gateway after changing its environment.

OpenClaw

Install the mail skill on the host where OpenClaw runs:

skill_dir="$HOME/.openclaw/skills/agentpostage"
mkdir -p "$skill_dir" &&
curl -fsS \
  https://agentpostage.com/skills/agentpostage/SKILL.md \
  -o "$skill_dir/SKILL.md"

Make AGENTPOSTAGE_API_KEY available to the agent’s shell and start a new session. The skill uses the CLI with Node.js 22+, or the API with curl. If execution is sandboxed, the key and runtime must be available inside that sandbox. Custom profiles can use a different skills directory.

Prefer native MCP tools? Set the key in the Gateway environment or its private ~/.openclaw/.env, then run:

openclaw mcp set agentpostage \
  '{"url":"https://agentpostage.com/mcp","transport":"streamable-http","headers":{"Authorization":"Bearer ${AGENTPOSTAGE_API_KEY}"}}'
openclaw mcp probe agentpostage

Reload a running Gateway if needed and allow the tools in your agent’s tool policy. The probe checks discovery without sending mail. See OpenClaw’s skills guide and MCP configuration.

Hermes Agent

For Nous Research’s Hermes Agent, install the skill in the default profile:

skill_dir="$HOME/.hermes/skills/agentpostage"
mkdir -p "$skill_dir" &&
curl -fsS \
  https://agentpostage.com/skills/agentpostage/SKILL.md \
  -o "$skill_dir/SKILL.md"

Set the key in Hermes’s environment or private ~/.hermes/.env, then start a new hermes chat session. The skill needs shell access with Node.js 22+ or curl. Custom profiles may use a different home directory.

For native MCP instead, merge this entry into ~/.hermes/config.yaml and start a fresh session:

mcp_servers:
  agentpostage:
    url: "https://agentpostage.com/mcp"
    headers:
      Authorization: "Bearer ${AGENTPOSTAGE_API_KEY}"

See Hermes’s skills guide and MCP configuration.

Codex

Set the key in the environment that runs Codex, then add the remote server:

codex mcp add agentpostage \
  --url https://agentpostage.com/mcp \
  --bearer-token-env-var AGENTPOSTAGE_API_KEY

Start a new session and ask Codex to check your AgentPostage balance. The CLI stores the environment-variable name, not your key. See the Codex MCP commands.

Claude Code

With the key available to Claude Code, run:

claude mcp add --scope user --transport http agentpostage \
  https://agentpostage.com/mcp \
  --header 'Authorization: Bearer ${AGENTPOSTAGE_API_KEY}'

Keep the single quotes so the command saves a variable reference rather than the key. Start a new session and use /mcp to check the connection. User scope makes the server available across projects. See the Claude Code MCP guide.

Cursor

Merge this into .cursor/mcp.json in your project, or ~/.cursor/mcp.json for all projects. Keep your existing servers:

{
  "mcpServers": {
    "agentpostage": {
      "url": "https://agentpostage.com/mcp",
      "headers": {
        "Authorization": "Bearer ${env:AGENTPOSTAGE_API_KEY}"
      }
    }
  }
}

Launch Cursor with the key available in its environment and check that the server is enabled. A shell export does not update an already-running Cursor. Remote HTTP servers do not load an envFile. See the Cursor MCP guide.

ChatGPT

Use a private custom GPT with Actions. This requires a ChatGPT plan and workspace that allow creating GPTs with Actions. The ordinary ChatGPT MCP connector does not accept our API-key header.

  1. Open the GPT editor and create a GPT for your own use.
  2. Add an Action and import the schema below.
  3. Set Authentication to API Key, choose Bearer, and enter a dedicated AgentPostage key in that field. Do not put it in the GPT’s instructions.
  4. Copy the AgentPostage GPT instructions into the GPT’s Instructions field. Set the privacy policy URL to https://agentpostage.com/privacy/ if requested.
  5. Save with visibility Only me. Anyone who can use a GPT carrying your key can spend your mail balance.
  6. Ask it to check your balance first. To send, upload one PDF and provide the recipient and return addresses. To create a PDF in the GPT, enable Code Interpreter & Data Analysis.
https://agentpostage.com/chatgpt-actions.json

The Action sends a temporary attachment reference, not PDF text or guessed base64. AgentPostage fetches that PDF from files.oaiusercontent.com and uses the same mailing API. Links expire quickly. If a link expires, read any known letter ID first, then refresh the same attachment and retry with the original idempotency key.

ChatGPT’s own confirmation rules still apply. This is a connection you configure, not a shared public GPT or an OpenAI endorsement. See OpenAI’s Action authentication and file attachment guide.

Any agent with a shell or HTTP tools

Use the downloadable AgentPostage skill, CLI, MCP server or HTTP API. No agent-specific account is needed. Follow your client’s permissions and tool policy.

Connect and fund your account

Create an account, complete the hosted sign-up and email verification steps, then accept the current Terms and acknowledge the Privacy Policy. Create a named API key and copy it once into your agent’s secret store. Set AGENTPOSTAGE_API_KEY in the agent’s environment. Keep it out of public code and logs.

Add funds from $10 on the billing page. An API key authorizes sending from that balance. You can revoke it at any time. Keys cannot create other keys, buy credit or authorize future card charges. There are no per-key spending allowances.

export AGENTPOSTAGE_URL='https://agentpostage.com'
# Supply AGENTPOSTAGE_API_KEY from your secret store.
curl --fail-with-body "$AGENTPOSTAGE_URL/v1/balance" \
  -H "Authorization: Bearer $AGENTPOSTAGE_API_KEY"

Price before you send

The first black-and-white PDF page costs $4 for First-Class, $12 for Certified, or $15 for Certified with an electronic return receipt. A First-Class flat starts at $6. First-Class with a statement endorsement starts at $4. Each extra PDF page is 30¢. Color adds 30¢ per PDF page, including the first. Duplex uses the same per-PDF-page rate. Printing, an envelope, the address coversheet and US postage are included, without weight surcharges.

Any non-white paper stock adds 10¢ per document sheet, counting two PDF pages per sheet for duplex. A single reply envelope adds 50¢, or $1.50 for a twelve-pack. Reply envelopes are unstamped. The coversheet is not charged as an extra sheet or page.

POST /v1/price takes a page count and options. It returns an exact price without storing a PDF, creating a letter, reserving money or sending mail. No idempotency key is needed. The owner or agent key can use it even before accepting updated terms. A browser owner request requires the same-origin Origin header.

curl --fail-with-body https://agentpostage.com/v1/price \
  -H "Authorization: Bearer $AGENTPOSTAGE_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"page_count":3,"service":"first_class","color":"color","duplex":true}'

This example is $5.50. Count PDF pages, not physical sheets. The current tariff is 2026-09-17-retail. Sending authorizes its price with no separate approval step. A price check does not lock a future rate. The send request fixes the rate when it is queued.

Send a letter

Use multipart form data for a local PDF. The address values are JSON strings. The addresses below are examples. Replace them before sending.

Save each address as a JSON file, sender.json and recipient.json, using the address fields in the JSON example below. Then run:

curl --fail-with-body "$AGENTPOSTAGE_URL/v1/letters" \
  -H "Authorization: Bearer $AGENTPOSTAGE_API_KEY" \
  -H 'Idempotency-Key: letter-2026-001' \
  -F 'pdf=@letter.pdf;type=application/pdf' \
  -F 'sender=<sender.json' \
  -F 'recipient=<recipient.json' \
  -F 'service=first_class'

Alternatively send JSON with pdf_base64, sender, recipient and optional service, color, duplex, paper and return_envelope. Base64 must contain the PDF bytes, without a data-URL prefix. External PDF URLs and plain text letter generation are not accepted.

{
  "pdf_base64": "<base64-encoded PDF bytes>",
  "sender": {
    "name": "Example Sender",
    "address_line1": "100 Example Avenue",
    "city": "Austin", "state": "TX",
    "postal_code": "78701", "country": "US"
  },
  "recipient": {
    "name": "Example Recipient",
    "address_line1": "200 Sample Street",
    "address_line2": "Suite 10",
    "city": "Austin", "state": "TX",
    "postal_code": "78702", "country": "US"
  },
  "service": "first_class"
}

The default service is first_class. Use certified for USPS tracking, or certified_return_receipt for tracking with an electronic return receipt. Both addresses are required. Use a two-letter US state code and ZIP or ZIP+4. Address fields use printable ASCII on single lines. address_line2 is optional. Names allow 80 characters, address lines 100 and cities 60. Provider address restrictions are reported as validation errors.

A new request returns 202 with the letter resource after saving the document and job. Save its id and read the resource to follow progress. Acceptance by the API does not mean the letter is mailed or delivered. Example response, with other fields omitted:

{
  "id": "00000000-0000-4000-8000-000000000001",
  "status": "queued",
  "service": "first_class",
  "cost": {"total_cents": 400, "currency": "USD"},
  "receipt_url": null
}

Use the returned ID, not the example above:

LETTER_ID='<id from your response>'
curl --fail-with-body \
  "$AGENTPOSTAGE_URL/v1/letters/$LETTER_ID" \
  -H "Authorization: Bearer $AGENTPOSTAGE_API_KEY"

For certified_return_receipt, receipt_url is null until evidence.return_receipt_available is true. A return receipt can arrive after the letter reaches delivered. Download it through GET /v1/letters/{id}/receipt with account authentication. It returns 410 after the retention period.

Safe retries

Choose one Idempotency-Key for each intended letter, before the first request. It must be 8–128 ASCII letters, digits, dots, colons, underscores or hyphens. Retry with that same key, PDF, addresses, service and print options if the response is lost. A key reused with different input returns 409. A new key creates a new send request.

Status and cost

Read GET /v1/letters/{id}. The typical sequence is queued → preauthorizing → awaiting_funds → authorizing → accepted. mailed and delivered appear only when supported by provider evidence. First-Class processing ends at confirmed mailing with status mailed. Individual delivery confirmation is generally unavailable. A letter can also be cancelled, rejected or submission_unknown.

New letters return cost:{total_cents,currency:"USD"} immediately. The price is fixed when the letter is queued, using the PDF page count and chosen options. The pricing_version identifies that tariff. Funds are reserved before mailing authorization and settled on definite provider acceptance. A price lookup does not reserve a tariff indefinitely. Previously submitted letters keep their prior pricing, and a legacy unpriced letter can still have cost:null.

awaiting_funds means the account needs enough available credit. If you opted into recharge, it can add funds. Otherwise the owner must fund the account. submission_unknown means a provider operation has an unresolved result. Do not create a replacement request: it could duplicate physical mail.

Cancel with POST /v1/letters/{id}/cancel before authorization starts. A successful response confirms cancellation. A conflict means cancellation did not win. Inspect the resource. Provider acceptance is not proof of physical receipt, and a service name is not a guarantee of legal adequacy.

LETTER_ID='<id returned by the send request>'
curl --fail-with-body "$AGENTPOSTAGE_URL/v1/letters/$LETTER_ID" \
  -H "Authorization: Bearer $AGENTPOSTAGE_API_KEY"

curl --fail-with-body "$AGENTPOSTAGE_URL/v1/letters/$LETTER_ID/pdf" \
  -H "Authorization: Bearer $AGENTPOSTAGE_API_KEY" \
  --output submitted-print.pdf

curl --fail-with-body "$AGENTPOSTAGE_URL/v1/letters/$LETTER_ID/cancel" \
  -X POST -H "Authorization: Bearer $AGENTPOSTAGE_API_KEY"

HTTP resources

These endpoints accept an agent key or owner session. List endpoints accept limit (1–50, default 20) and the opaque cursor returned as next_cursor. A null cursor ends the list.

Method and pathResult
POST /v1/priceExact price for a page count and print options, without sending.
POST /v1/lettersQueue a PDF for physical sending.
GET /v1/letters{letters, next_cursor}
GET /v1/letters/{id}Letter status, cost and available evidence.
POST /v1/letters/{id}/cancelCancel before authorization begins.
GET /v1/letters/{id}/pdfPrivate submitted print PDF.
GET /v1/letters/{id}/receiptPrivate Certified return-receipt PDF, when available.
GET /v1/balance{available_cents,reserved_cents,currency:"USD"}
GET /v1/transactions{transactions,next_cursor} accounting history.
GET /v1/meAccount identity and balance.

Owner session only: GET /v1/keys lists key metadata. POST /v1/keys with {name} returns a new key once. DELETE /v1/keys/{id} revokes it.

Errors

400 means invalid fields or document. 401/403 means missing authentication or insufficient permission. 404 means the resource is unavailable to this account. 409 means a conflicting request or state. 413 means a transport limit. Follow the returned error message. On network errors or 5xx, the request may have succeeded: read known resources and preserve the original idempotency key.

MCP

In a client that supports remote HTTP MCP servers with custom headers, add a server with these connection details. Replace the placeholder with your API key. Do not paste it into a chat message.

Name: AgentPostage
URL: https://agentpostage.com/mcp
Transport: Streamable HTTP
Header: Authorization
Value: Bearer <your API key>

Once connected, ask your agent to check the balance using get_balance. If your client cannot send a custom authorization header, use the CLI or API instead. MCP delegates to the HTTP API and returns the same structured results and errors. The OpenAPI contract covers the request and response fields.

ToolArguments
price_letterpage_count, service?, color?, duplex?, paper?, return_envelope?
send_letterpdf_base64, sender, recipient, service?, color?, duplex?, paper?, return_envelope?, idempotency_key
get_letterid
list_letterslimit?, cursor?
cancel_letterid
get_balanceNo arguments.
list_transactionslimit?, cursor?
get_billing_linkNo arguments. Returns the owner’s billing link.

Sending uses the account’s confirmed funds. When funding or card authentication is needed, give the billing link to the owner. Never request the owner’s session cookie or card details.

Command line

Download the standalone CLI and run it with Node.js 22 or later. It uses AGENTPOSTAGE_API_KEY and defaults to https://agentpostage.com. Set AGENTPOSTAGE_BASE_URL to choose another API origin.

curl -fsS https://agentpostage.com/agentpostage.mjs \
  -o agentpostage.mjs
node agentpostage.mjs --help

Save each address as a JSON file using the fields above, then send the PDF:

node agentpostage.mjs send \
  --pdf letter.pdf \
  --sender sender.json \
  --recipient recipient.json \
  --service first_class \
  --idempotency-key letter-2026-001

node agentpostage.mjs price --pages 3 --service first_class --color color --duplex true
node agentpostage.mjs balance
node agentpostage.mjs list --limit 20
node agentpostage.mjs get "$LETTER_ID"
node agentpostage.mjs cancel "$LETTER_ID"
node agentpostage.mjs transactions --limit 20
node agentpostage.mjs billing

If you omit --idempotency-key, the CLI generates one and prints it to stderr before sending. Reuse that key with identical input after a timeout or uncertain result. JSON results go to stdout. The billing command returns the owner’s billing URL. It does not authorize a payment.

Owner authentication reference

Owner sign-in starts at GET /auth/sign-in and uses a six-digit code sent by email. The callback at /auth/callback establishes the owner session. Add ?next=billing to return to billing after sign-in and terms acceptance. POST /auth/sign-out clears the local session and returns {success:true,url}. Follow the returned URL to finish signing out. Record explicit owner acceptance with POST /v1/terms/accept and {accepted:true,version}, using the current terms_version from GET /v1/me.

All resources belong to the authenticated account. Browser owner actions use a first-party session cookie. Agent API keys remain separate from owner sign-in. JSON responses use integer USD cents. Errors use {"error":{"code":"…","message":"…","next_action":"…"}}. next_action is optional.

Owner billing

Billing shows available and reserved funds, hosted Stripe Checkout and saved-card settings. Enter a top-up amount in USD. Funding and recharge amounts must be at least $10.00 and at most $999,999.99. Saving a card is optional and does not itself enable recharge.

Automatic recharge starts disabled, with initial values of $25 added when available funds fall below $5. Enabling it requires the owner’s explicit authorization for future charges. The owner can disable it at any time. Declined payments or payments needing authentication pause recharge.

GET /v1/billing/settings reads {enabled,threshold_cents,amount_cents,has_payment_method,…}. PUT updates those first three fields and requires consent:true when enabling recharge. POST /v1/billing/checkout takes {amount_cents,save_card}. POST /v1/billing/setup takes {consent:true}. POST /v1/billing/portal takes {}. These POSTs require an Idempotency-Key and return a hosted Stripe url. Funding is credited only after verified payment, never from a return URL.

PDFs, printing and retention

V1 accepts source PDFs up to 10 MiB and normalized print PDFs up to 20 MiB. First-Class and statement-endorsed First-Class support up to 500 document pages. Both Certified services support up to 150. Explicit First-Class flats support 75 single-sided or 150 double-sided pages. A document exceeding its service limit returns HTTP 400 before storage or charging. These are supported printing limits. Ordinary scanned pages, images, embedded fonts and Unicode content are preserved as PDF artwork. Forms and printable annotations work when their appearance is saved in the PDF. A printable annotation without a saved normal appearance returns 400 unsupported_print_annotation. Export or print the document to PDF, then upload that copy. Such annotations are not silently dropped. Encrypted, empty and corrupt PDFs are rejected. Non-US-letter pages are fitted onto US letter paper while preserving appearance.

The default is black-and-white, single-sided on white paper. Choose color, duplex and other paper stocks with the print options above. An address coversheet is added for mailing, separate from the PDF returned by the API. It is included in the price and is not counted as a customer PDF page. The standard upload endpoint does not fetch external PDF URLs. The ChatGPT adapter accepts only the temporary OpenAI attachment links described above. There is no international mailing or plain-text drafting endpoint.

PDFs, stored return receipts and addresses are retained for 30 days after confirmed mailing for First-Class, or after delivery for Certified Mail. Cancellation or rejection starts the 30-day period for either service. First-Class status remains mailed. It does not confirm delivery. Documents for pending mail, including mailed Certified letters, letters awaiting funds and uncertain submissions, remain stored until resolved. Billing and accounting metadata are retained separately as needed. Payment issues do not extend document retention. See Privacy and Terms. Download documents you need to keep. Authenticated PDF links are private and must not be published. The letter’s pdf_url becomes null after deletion. The PDF endpoint then returns 410 document_deleted. The resource includes the original source_sha256 and normalized pdf_sha256 hashes, plus address_coversheet:true.