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.

Important: The API automatically formats text to fit within 11 characters per line for physical rubber stamps.

Format Guidelines

  • List each item on its own line
  • Use clear, descriptive text
  • Include quantities if applicable
  • Use \n for 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
Python
JavaScript
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);