Build and test your integration against the live service without spending money or mailing anything. A pony_test_ key runs every request in a separate test environment with free test credit and simulated order progress.
Authentication, idempotency, errors and scopes work exactly as they do with a live key.
Get a test key#
Create a test key the same way as a live key, while signed in to the website, with "test": true:
{"label": "Billing integration (test)", "scopes": ["letters:read", "letters:write", "orders:read", "orders:send", "orders:cancel", "wallet:read", "grants:read"], "expires_at": "$expires_at", "test": true}POST /api/account/api-keys returns the secret once. Test secrets start with pony_test_ and live secrets with pony_live_. GET /api/account/api-keys lists both, each with its environment, and you revoke them the same way.
What a test key does#
A request made with a pony_test_ key runs entirely in the isolated test environment:
GET /identityandGET /walletreport"environment": "test".- Test records are separate: wallet, credit history, quotes, send intents, sending permissions, orders, drafts, proofs, templates and batches. Live keys, OAuth connections and the website never see them, and test keys never see live records.
- Saved address references from the website are live records, so test letters use inline
recipientandsenderaddresses. - No permission setup is needed. A test key sends through an open test permission with no limits or address restrictions. Live keys still need a sending permission.
- Final quotes use the configured price with
tax_minor: 0andtax_provenance.type: "test_mode". No tax or payment provider is called. - Orders are marked
test_mode: trueandcredit_environment: "test". Nothing is printed or mailed, and test orders never reach the print queue, notifications or analytics. POST /wallet/topupsis refused, because test keys can't create real funding links. Use test credit instead.
You don't need real credits to use a test key, so you can finish your integration before you fund your account.
Add test credit#
POST /test/credits adds up to $1,000 (100000 cents) of test credit per call. Call it again for more. It needs a test key with wallet:read and an Idempotency-Key header. Live keys and OAuth tokens get 403.
{"amount_minor": 50000, "currency": "USD"}The response contains the ledger transaction (type test_credit) and the test wallet.
Send a test letter#
Use exactly the live flow from the quickstart: create a draft (or render a template), render a proof, get a final quote and send. grant_id can be left out.
POST /api/v1/drafts -> draft_id, version
POST /api/v1/drafts/{id}/proofs -> proof_id
POST /api/v1/send-quotes -> send_intent_id, quote_id, total_minor
POST /api/v1/orders -> order_id, status "queued", environment "test"Simulate order progress#
POST /test/orders/{order_id}/advance moves a test order forward so you can exercise your status handling and webhooks. It needs a test key with orders:read. With no body it advances one step. With a status, it advances through every step up to that status:
queued -> preparing -> printed -> mailed -> returned{"status": "mailed"}The response has the same shape as GET /orders/{order_id}, including history and tracker. Each simulated step is recorded with the source test_mode_simulation.
Orders only move forward. returned requires mailed first, and cancelled orders can't advance. You can cancel a queued test order with POST /orders/{order_id}/cancel, which returns its test credit.
Going live#
Create a live key on Account → Credits → API keys, fund your balance and swap the key. Nothing else in your code changes. Test records stay in the test environment and aren't copied over.