The API is for sending people and events to PennyPost from your server. Most apps only need one call: results_ready.
Authentication
Create a key on API keys and send it as a bearer token:
Authorization: Bearer mc_...
Keys start with mc_. PennyPost shows a key once and stores only a hash of it. A wrong or revoked key gets 401 Invalid API key. Revoke a key on the same page.
Every request and response body is JSON.
Send an event
POST https://usepennypost.com/api/v1/events
| Field | Type | |
|---|---|---|
app |
string | Your app's ID, shown on Activation (older code may send it as newsletter; both work) |
email |
string | The user's email |
event |
string | 1 to 64 letters, digits, _, ., : or -. Use results_ready for results |
properties |
object | Optional, under 2 KB. For results_ready: url and headline |
createContact |
boolean | Add the person if PennyPost doesn't know them yet |
firstName, lastName |
string | Optional |
curl -X POST https://usepennypost.com/api/v1/events \
-H "Authorization: Bearer $PENNYPOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{"app": "your-app", "email": "jane@example.com", "event": "results_ready", "createContact": true,
"properties": {"url": "https://yourapp.com/projects/42", "headline": "Your 12 headshots are ready."}}'
Returns 202 with {"ok": true, "contactId": "...", "created": true}.
| Status | Meaning |
|---|---|
400 |
A field is missing or invalid; the body says which |
404 |
App not found, or contact not found (send "createContact": true) |
403 |
Your plan blocks new people this month |
429 |
More than 6,000 events an hour on this key |
Add or update a person
POST https://usepennypost.com/api/v1/contacts adds a person. Fields: newsletter (your app's ID), email, and optionally firstName, lastName, tags, fields (an object of your own values) and source. Returns 201 for a new person, 200 for one PennyPost already had. Limits: 600 an hour per key, and 5 every 10 minutes per visitor IP if you pass ip.
PATCH https://usepennypost.com/api/v1/contacts updates a person who already exists, with the same fields plus removeTags. It never creates anyone. 6,000 an hour per key.
curl -X PATCH https://usepennypost.com/api/v1/contacts \
-H "Authorization: Bearer $PENNYPOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{"newsletter": "your-app", "email": "jane@example.com", "tags": ["activated"], "removeTags": ["trial"]}'
MCP
The same key works with the MCP server at https://usepennypost.com/mcp. See AI assistants (MCP).