Theseus Mail API
A wrapper API for Hack Club's Theseus mail system. Create letters, track costs, and manage events with ease.
Authentication
All API requests require authentication using a Bearer token in the Authorization header.
Event API Keys
Each event has its own API key. Use this key to create letters for that event.
Authorization: Bearer your_event_api_key
Admin API Key
The admin API key is used for administrative operations like marking events as paid.
Authorization: Bearer your_admin_api_key
API Endpoints
POST/api/v1/letters
Create a new letter. Requires an event API key.
Request Body
| Field | Type | Description |
|---|---|---|
| first_name required | string | Recipient's first name |
| last_name required | string | Recipient's last name |
| address_line_1 required | string | Street address |
| address_line_2 optional | string | Apartment, suite, etc. |
| city required | string | City name |
| state required | string | State/Province |
| postal_code required | string | ZIP/Postal code |
| country required | string | Country name (e.g., "Canada", "United States") |
| recipient_email optional | string | Email for tracking notifications |
| mail_type required | string | "lettermail", "bubble_packet", or "parcel" |
| weight_grams conditional | integer | Required for bubble_packet and parcel |
| rubber_stamps required | string | Items to pack (see Rubber Stamps) |
| notes optional | string | Additional metadata |
Response
{
"letter_id": "ltr!32jhyrnk",
"cost_usd": 1.19,
"formatted_rubber_stamps": "1x pack of\nstickers\n1x Postcard",
"status": "queued",
"theseus_url": "https://mail.hackclub.com/back_office/letters/ltr!32jhyrnk"
}
POST/admin/events/{event_id}/mark-paid
Mark an event as paid. Requires admin API key.
Response
{
"event_id": 1,
"event_name": "Haxmas 2024",
"previous_balance_cents": 15430,
"new_balance_cents": 0,
"is_paid": true
}
GET/admin/financial-summary
Get a summary of all unpaid events. Requires admin API key.
Response
{
"unpaid_events": [
{
"event_id": 1,
"event_name": "Haxmas 2024",
"balance_due_usd": 154.30,
"letter_count": 127,
"last_letter_at": "2025-01-09T14:32:00Z"
}
],
"total_due_usd": 154.30
}
POST/admin/check-letter-status
Manually trigger a status check for all pending letters. Requires admin API key.
Response
{
"checked": 47,
"updated": 12,
"mailed": 5
}
Cost Calculator
Use this calculator to estimate shipping costs before creating letters.
Estimate Shipping Cost
Mail Type Limits
| Type | Max Weight | Max Dimensions |
|---|---|---|
| Lettermail | 30g | 245mm × 156mm × 5mm (9.6" × 6.1" × 0.2") |
| Bubble Packet | 500g | 380mm × 270mm × 20mm (14.9" × 10.6" × 0.8") |
| Parcel | Custom quote required - contact @jenin | |
Rubber Stamps Field
The rubber_stamps field specifies what items should be packed in the envelope. This text gets printed on the fulfillment label.
Format Guidelines
- List each item on its own line
- Use clear, descriptive text
- Include quantities if applicable
- Use
\nfor line breaks in JSON
Good Examples
"rubber_stamps": "1x pack of stickers\n1x Postcard of Euan eating a Bread"
"rubber_stamps": "3x Hack Club stickers\n1x Thank you card\n1x Event badge"
"rubber_stamps": "Haxmas 2024 Winner Prize Package"
Bad Examples
"rubber_stamps": "stuff" // Too vague - what should be packed?
"rubber_stamps": "" // Empty - not allowed
Code Examples
curl -X POST https://your-api.com/api/v1/letters \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"first_name": "John",
"last_name": "Doe",
"address_line_1": "123 Main St",
"city": "Burlington",
"state": "VT",
"postal_code": "05401",
"country": "Canada",
"mail_type": "lettermail",
"rubber_stamps": "1x pack of stickers\n1x Postcard"
}'
import requests
response = requests.post(
"https://your-api.com/api/v1/letters",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
json={
"first_name": "John",
"last_name": "Doe",
"address_line_1": "123 Main St",
"city": "Burlington",
"state": "VT",
"postal_code": "05401",
"country": "Canada",
"mail_type": "lettermail",
"rubber_stamps": "1x pack of stickers\n1x Postcard"
}
)
print(response.json())
const response = await fetch('https://your-api.com/api/v1/letters', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
first_name: 'John',
last_name: 'Doe',
address_line_1: '123 Main St',
city: 'Burlington',
state: 'VT',
postal_code: '05401',
country: 'Canada',
mail_type: 'lettermail',
rubber_stamps: '1x pack of stickers\n1x Postcard'
})
});
const data = await response.json();
console.log(data);