Hermes

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",
  "email_sent": true
}

A confirmation email will be sent to the recipient if an email address was provided.

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
}

POST/api/v1/order

Create a new order request. Hermes will order from a local carrier and ship to the recipient. Charged to your event's HCB. $1 fee per item.

Request Body

Field Type Description
order_text required string Description of what to order (max 5000 chars). Do NOT include names/addresses here - use the dedicated fields below.
first_name required string Recipient's first name (not stored)
last_name required string Recipient's last name (not stored)
email optional string Recipient's email (not stored)
address_line_1 required string Street address (not stored)
address_line_2 optional string Apartment, suite, etc. (not stored)
city required string City name (not stored)
state required string State/Province (not stored)
postal_code required string ZIP/Postal code (not stored)
country required string Country name (not stored)

Response

{
  "order_id": "aB3xK9m",
  "status": "pending",
  "status_url": "https://fulfillment.hackclub.com/odr!aB3xK9m",
  "created_at": "2025-01-16T10:30:00Z",
  "email_sent": true
}

A confirmation email will be sent to the recipient if an email address was provided.

Example: GitHub Notebook

curl -X POST https://fulfillment.hackclub.com/api/v1/order \
  -H "Authorization: Bearer YOUR_EVENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "order_text": "GitHub Notebook - 1x black hardcover, 100 pages",
    "first_name": "Orpheus",
    "last_name": "Hacksworth",
    "email": "orpheus@hackclub.com",
    "address_line_1": "123 Hacker Way",
    "city": "Shelburne",
    "state": "VT",
    "postal_code": "05482",
    "country": "United States"
  }'

GET/odr!{order_id}

Public order status page. Shows pending/fulfilled status with tracking code (no personal information displayed).

Status Display

  • Pending: Shows "Your order is being processed"
  • Fulfilled: Shows fulfillment note and tracking code if provided

GET/api/v1/order/{order_id}/status

Get order status via API (public endpoint, no auth required).

Response

{
  "order_id": "aB3xK9m",
  "status": "fulfilled",
  "tracking_code": "1Z999AA10123456784",
  "fulfillment_note": "Shipped via UPS Ground",
  "created_at": "2025-01-16T10:30:00Z",
  "fulfilled_at": "2025-01-17T14:00:00Z"
}

Orders

The Orders API allows you to request items that Hermes will order from a local carrier and ship to the recipient. Orders are charged to your event's HCB account.

💵 Pricing: There is a $1 fee per item ordered, plus actual shipping costs. All charges go to your event's HCB.
How it works: Submit an order with shipping address → Jenin orders from local carrier → Ships to recipient → Charged to your HCB → Status page updates with tracking.

Order Flow

  1. Send a POST request to /api/v1/order with order details and shipping address
  2. Receive an order ID and status URL (e.g., fulfillment.hackclub.com/odr!aB3xK9m)
  3. Share the status URL with the recipient
  4. Hermes orders items from local carrier and ships to the provided address
  5. Costs are charged to your event's HCB account
  6. Once fulfilled, status page shows tracking code and notes

Example: Ordering a GitHub Notebook

cURL
Python
curl -X POST https://fulfillment.hackclub.com/api/v1/order \
  -H "Authorization: Bearer YOUR_EVENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "order_text": "GitHub Notebook - 1x black hardcover, 100 pages",
    "first_name": "Orpheus",
    "last_name": "Hacksworth",
    "email": "orpheus@hackclub.com",
    "address_line_1": "123 Hacker Way",
    "city": "Shelburne",
    "state": "VT",
    "postal_code": "05482",
    "country": "United States"
  }'
import requests

response = requests.post(
    "https://fulfillment.hackclub.com/api/v1/order",
    headers={
        "Authorization": "Bearer YOUR_EVENT_API_KEY",
        "Content-Type": "application/json"
    },
    json={
        "order_text": "GitHub Notebook - 1x black hardcover, 100 pages",
        "first_name": "Orpheus",
        "last_name": "Hacksworth",
        "email": "orpheus@hackclub.com",
        "address_line_1": "123 Hacker Way",
        "city": "Shelburne",
        "state": "VT",
        "postal_code": "05482",
        "country": "United States"
    }
)

print(response.json())

Privacy & Security

Shipping addresses are NEVER stored in the database. PII is:

  • Sent directly to the Slack #mail channel when the order is created
  • Visible only in that one Slack message
  • Never stored, cached, or logged anywhere
  • Cannot be retrieved from the API or database

The public status page (/odr!{id}) only shows:

  • Order ID
  • Status (Pending or Fulfilled)
  • Tracking code (if provided)
  • Fulfillment note (if provided)

No personal information (names, addresses, etc.) is ever displayed on the public status page.

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);

API Playground

Test the API directly from your browser. Enter your API key and send requests — everything runs locally, nothing is stored.

Privacy: Your API key and request data are only sent directly to the API. Nothing is stored or logged by this page.

Request Body