Base URL: https://www.mailbypony.com/api/v1. The machine-readable contract is /api/v1/openapi.json.
Each endpoint lists its MCP tool name and the scope it needs. HTTP and MCP accept the same fields. Over HTTP, send the idempotency key in the Idempotency-Key header; over MCP, send it as the idempotency_key argument.
Errors use one format for every endpoint. See Errors.
| Group | Endpoints |
|---|---|
| Account | GET /catalog, GET /identity, GET /address-references |
| Drafts | GET /drafts, POST /drafts, GET /drafts/{id}, PATCH /drafts/{id}, attachments, artwork, archive |
| Proofs | POST /drafts/{id}/proofs, GET /proofs/{id} |
| Credits | GET /wallet, GET /wallet/transactions, POST /wallet/topups, GET /wallet/topups/{id} |
| Sending | GET /agent-grants, POST /send-quotes, POST /orders |
| Orders | GET /orders, GET /orders/{id}, POST /orders/{id}/cancel |
| Templates | GET /templates, POST /templates, GET /templates/{id}, DELETE /templates/{id}, POST /templates/{id}/drafts |
| Batches | POST /batches, GET /batches/{id} |
| Webhooks | GET /webhooks, POST /webhooks, DELETE /webhooks/{id}, deliveries, test |
| Requests | GET /requests/{operation}/{key} |
| Test mode | POST /test/credits, POST /test/orders/{id}/advance |
Account#
GET /catalog#
Returns the options and limits you can use: papers, envelopes, styles, pricing, limits and prepaid. Use the id values it returns when creating drafts.
prepaid includes minimum_topup_minor, maximum_topup_minor, quote_lifetime_seconds and environment.
GET /identity#
Returns the credential's own client_id, credential_type (oauth or api_key), scopes and environment (live or test). An assistant's client_id is what you enter in Let my assistant send letters.
GET /address-references#
Lists addresses you've confirmed on the website in the last 30 days.
Each item has id, role (sender or recipient), masked (city, state and country), draft_id and expires_at. Use id as a draft's sender_id or recipient_id.
Drafts#
A draft response includes draft_id, version, title, content_type, style, paper, envelope, masked addresses, address_validation, attachments and edit_url.
GET /drafts#
Lists your saved drafts as items.
POST /drafts#
Creates a draft. Nothing is ordered or charged.
| Field | Type | Notes |
|---|---|---|
content_policy_acknowledged |
boolean, required | Must be true: confirms the letter is ordinary correspondence |
title |
string | Up to 150 characters |
content_type |
string | text (default), markdown or pdf |
text |
string | Up to 30,000 characters |
style |
string | A styles ID from the catalog. Default serif. |
paper |
string | A papers ID. Default classic. |
envelope |
string | An envelopes ID. Default white. |
recipient, sender |
object | A US address: name, line1, line2, city, state, postal_code, country |
recipient_id, sender_id |
string | A confirmed address reference, instead of a full address |
For a PDF letter, create the draft with content_type: "pdf", then upload the file.
GET /drafts/{draft_id}#
Returns one draft, including its text and pages.
PATCH /drafts/{draft_id}#
Edits a draft. Send the current version and only the fields you're changing; it accepts the same fields as POST /drafts. Returns the draft with a new version. If the draft changed since you read it, you get VERSION_CONFLICT.
Any edit makes earlier proofs and quotes stale.
POST /drafts/{draft_id}/attachments#
Uploads a print-ready PDF, a photo or PNG artwork. Returns the draft with a new version.
| Field | Type | Notes |
|---|---|---|
version |
integer, required | The draft's current version |
kind |
string, required | pdf, photo or decoration |
name |
string, required | The file name |
content_base64 |
string, required | The file, base64-encoded. Up to the catalog's max_upload_bytes (10 MB). |
content_policy_acknowledged |
boolean, required | Must be true |
PDFs print exactly as uploaded. They must be US Letter, 1 to 10 pages and flattened: no forms, scripts, attachments or links. Upload the original file rather than extracting its text.
DELETE /drafts/{draft_id}/attachments/{file_id}#
Removes an attachment and any artwork placed from it. Body: {"version": 3}.
POST /drafts/{draft_id}/artwork#
Adds decorative artwork from Pony's library. Body: version and artwork_id.
POST /drafts/{draft_id}/archive#
Archives a draft. You can restore it on the website. Body: {"version": 3}.
Proofs#
POST /drafts/{draft_id}/proofs#
Renders the exact pages and envelope for a draft version. Body: {"version": 1}.
Returns proof_id, status (ready), version, artifact_hash, errors and review_url, a link to view the proof on the website. Rendering finishes before the response returns. If errors isn't empty (for example, a missing ZIP code), fix the draft and render again.
GET /proofs/{proof_id}#
Returns a proof you've rendered. Proofs never change.
Credits#
GET /wallet#
Returns balance_minor, available_minor, currency, status, as_of and environment. Spend only what available_minor shows.
GET /wallet/transactions#
Lists credit history, newest first.
| Query | Notes |
|---|---|
limit |
1 to 100. Default 50. |
cursor |
The next_cursor from the previous page |
Each item has id, amount_minor (negative for spending), currency, type, created_at and related order or top-up IDs. next_cursor is null on the last page.
POST /wallet/topups#
Creates a Stripe Checkout link to add credits. It never charges a card on its own; a person has to pay at the link. Not available to test keys.
{"amount_minor":2500,"currency":"USD"}Returns topup_id, status, checkout_url, expires_at, amount_minor and expected_credit_minor.
GET /wallet/topups/{topup_id}#
Returns a top-up's status: pending, credited, failed or expired. If you lost the response to POST /wallet/topups, this finds the original Checkout session rather than creating a new one.
Sending#
GET /agent-grants#
Lists the sending permissions that apply to this credential. Each includes its limits, active, remaining_daily_minor, remaining_lifetime_minor, remaining_orders and next_reset_at.
POST /send-quotes#
Gets the final price for one proof.
| Field | Type | Notes |
|---|---|---|
draft_id |
string, required | |
version |
integer, required | The proof's draft version |
proof_id |
string, required | |
client_reference |
string, required | Your identifier for this mailing |
send_intent_id |
string | Include when re-quoting a mailing you haven't sent |
Returns quote_id, send_intent_id, proof_id, proof_hash, subtotal_minor, tax_minor, total_minor, currency and expires_at. The quote expires after catalog.prepaid.quote_lifetime_seconds.
POST /orders#
Sends a letter. Pony checks the permission and your balance, deducts the total and queues the letter, all in one step.
| Field | Type | Notes |
|---|---|---|
send_intent_id |
string, required | From the quote |
quote_id |
string, required | From the quote |
proof_id |
string, required | From the quote |
expected_total_minor |
integer, required | Must equal the quote's total_minor |
currency |
string, required | USD |
grant_id |
string | The sending permission to use. Leave it out to use your key's or assistant's newest active permission. |
Returns order_id, send_intent_id, status (queued), payment_method (credits), debited_minor, currency, ledger_transaction_id and status_url.
Save the request body and idempotency key before sending. If the response is lost, look it up instead of sending again.
Orders#
An order includes order_id, status, client_reference, send_intent_id, created_at, mailed_at, the masked draft and a history of status changes. See order statuses.
GET /orders#
Lists your orders as items. Filter with ?client_reference=….
GET /orders/{order_id}#
Returns one order and its current status.
POST /orders/{order_id}/cancel#
Cancels an order before preparation starts. Body: {"approved":true} after explicit cancellation approval. The order's sending permission must allow cancellation.
Returns order_id, status (cancelled), refund_status, credited_minor and currency. The credits return to your balance once, even if you repeat the request. After preparation starts, you get CANCELLATION_TOO_LATE.
Templates#
A template is a reusable letter with {{placeholders}}. See Templates for the placeholder rules.
| Request | MCP | Scope | Notes |
|---|---|---|---|
POST /templates |
create_template |
letters:write |
Idempotency key required. Takes name, title, content_type (text or markdown), text or pages, style, paper, envelope, an optional default sender and content_policy_acknowledged. Returns template_id and the variables it uses. |
GET /templates |
list_templates |
letters:read |
|
GET /templates/{template_id} |
get_template |
letters:read |
|
DELETE /templates/{template_id} |
delete_template |
letters:write |
Idempotency key required. Drafts and orders made from it don't change. |
POST /templates/{template_id}/drafts |
create_template_draft |
letters:write |
Idempotency key required. Takes variables, a recipient or recipient_id, and optionally sender, sender_id or title. Returns an ordinary draft. |
Batches#
POST /batches#
Sends up to 100 letters from your balance in one request. Each item has a unique client_reference and either a template_id with variables, or a draft_id. Each item is drafted, proofed, quoted and sent on its own, and one item's failure doesn't stop the others.
Returns batch_id, status, a summary (requested, queued, failed, pending, debited_minor) and a result for each item. Allow about a second per item. See Batch sends for the full request and retry rules.
GET /batches/{batch_id}#
Returns a batch's stored result.
Webhooks#
HTTP only. Scope webhooks:write. These routes don't take an idempotency key. See Webhooks.
| Request | Notes |
|---|---|
POST /webhooks |
Takes url, and optionally events and description. Returns the endpoint with its secret, shown once. |
GET /webhooks |
Lists endpoints, without secrets |
DELETE /webhooks/{endpoint_id} |
Removes an endpoint and cancels its pending deliveries |
GET /webhooks/{endpoint_id}/deliveries |
The 50 most recent deliveries |
POST /webhooks/{endpoint_id}/test |
Sends a signed ping |
Requests#
GET /requests/{operation}/{key}#
Looks up the result of an earlier write. operation is the original request's MCP tool name, such as send_with_credits, and key is its idempotency key.
Returns state (committed, rejected or processing) with the original result or error. Returns 404 REQUEST_NOT_FOUND if Pony never recorded the request. See Recovering from a lost response.
Test mode#
These endpoints work only with a pony_test_ key. See Test mode.
| Request | Scope | Notes |
|---|---|---|
POST /test/credits |
wallet:read |
Idempotency key required. Adds up to $1,000 of test credit per call: {"amount_minor": 50000, "currency": "USD"} |
POST /test/orders/{order_id}/advance |
orders:read |
Moves a test order forward one step, or up to the status you send |