API Documentation

The Premier Letters API allows you to programmatically create and manage handwritten letter orders. All API access is over HTTPS, and all request and response bodies are JSON.

Base URL: https://premierletters.com/api/v1

Authentication

All API requests require authentication using an API key. Include your API key in theAuthorization header:

Authorization: Bearer pl_live_xxxxxxxxxxxxx

Generate API keys in your Settings.

Rate Limits

API requests are rate limited per API key. Limits vary by endpoint:

EndpointPer-Minute Limit
/orders (POST)10 requests
/validate100 requests
/webhooks30 requests
Other endpoints60 requests

Daily limits: 500 orders per day, 5,000 total API calls per day.

Rate limit information is included in response headers:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
X-RateLimit-Reset: 1703203200
X-RateLimit-Daily-Limit: 5000
X-RateLimit-Daily-Remaining: 4987
X-RateLimit-Daily-Reset: 1703289600

Errors

The API uses conventional HTTP response codes. Errors include a JSON body with details:

{
  "error": "invalid_api_key",
  "message": "Invalid or inactive API key",
  "suggestion": "Check that your API key is correct and active."
}
CodeDescription
400Bad Request - Invalid parameters
401Unauthorized - Invalid or missing API key
402Payment Required - Payment failed
404Not Found - Resource doesn't exist
422Validation Error - Invalid data
429Rate Limited - Too many requests
500Server Error - Something went wrong

Create Order

Create a new order for handwritten letters.

POST/orders

Request Body

{
  "message": "Dear {{first_name}},\n\nThank you for your business...",
  "recipients": [
    {
      "first_name": "John",
      "last_name": "Doe",
      "address_line1": "123 Main St",
      "city": "New York",
      "state": "NY",
      "zip": "10001"
    }
  ],
  "options": {
    "handwriting_style": "elegant",
    "paper_type": "cream",
    "product_type": "letter"
  },
  "payment": {
    "method": "card_on_file"
  }
}

Parameters

FieldTypeDescription
messagestringLetter content. Use {{field}} for personalization.
recipientsarrayList of recipient objects (max 500).
options.handwriting_stylestringStyle: elegant, casual, formal, friendly
options.paper_typestringPaper: cream, white, kraft
payment.methodstringcard_on_file or checkout_session
skip_invalidbooleanSkip invalid recipients instead of failing

Response

{
  "order_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "paid",
  "letter_count": 1,
  "total_cents": 499,
  "tracking_url": "https://premierletters.com/orders/550e8400..."
}

Get Order

Retrieve details of a specific order including recipient status and tracking.

GET/orders/{order_id}

Response

{
  "order": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "mailed",
    "letter_count": 1,
    "total_cents": 499,
    "shipped_date": "2024-01-15T10:30:00Z"
  },
  "recipients": [
    {
      "id": "rec_123",
      "name": "John Doe",
      "status": "mailed",
      "tracking_number": "9400111899223..."
    }
  ],
  "summary": {
    "total": 1,
    "pending": 0,
    "mailed": 1
  }
}

Validate Order

Validate an order without creating it. Useful for testing integrations and getting pricing.

POST/validate

Request body is the same as Create Order. Response includes validation status and pricing:

{
  "valid": true,
  "letter_count": 10,
  "total_cents": 4990,
  "total_formatted": "$49.90",
  "message": "Order is valid. 10 letters at $4.99 each = $49.90 total."
}

Webhook Events

Receive real-time notifications when order status changes.

EventDescription
order.createdOrder was created (pending payment)
order.paidPayment received, order queued
order.in_productionLetters being written
order.shippedLetters mailed
order.deliveredDelivery confirmed by USPS
order.cancelledOrder cancelled or refunded

Webhook Payload

{
  "event": "order.shipped",
  "created_at": "2024-01-15T10:30:00Z",
  "data": {
    "order_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "mailed",
    "letter_preview": "Dear John,\n\nThank you for your business...",
    "recipient_count": 10,
    "total": 34.90,
    "tracking_numbers": ["9400111899223..."],
    "source": "api"
  }
}

Manage Webhook Endpoints

List Endpoints

GET/webhooks

Create Endpoint

POST/webhooks
{
  "url": "https://your-app.com/webhooks/premier-letters",
  "events": ["order.paid", "order.shipped"]
}

Response includes a signing secret - store it securely, it's only shown once.

Update Endpoint

PUT/webhooks/{endpoint_id}

Delete Endpoint

DELETE/webhooks/{endpoint_id}

Signature Verification

All webhooks include a signature header for verification. Always verify signatures in production.

Headers

X-Premier-Letters-SignatureHMAC-SHA256 signature
X-Premier-Letters-EventEvent type
X-Premier-Letters-Delivery-IdUnique delivery ID

Verification Example (Node.js)

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// In your webhook handler:
app.post('/webhooks/premier-letters', (req, res) => {
  const signature = req.headers['x-premier-letters-signature'];
  const payload = JSON.stringify(req.body);

  if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process webhook...
  res.status(200).json({ received: true });
});

Important: Always return a 2xx response within 10 seconds. Failed deliveries are logged but not retried automatically.

Need Help?

Have questions or need assistance with the API?

Contact Support