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:
| Endpoint | Per-Minute Limit |
|---|---|
/orders (POST) | 10 requests |
/validate | 100 requests |
/webhooks | 30 requests |
| Other endpoints | 60 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."
}| Code | Description |
|---|---|
| 400 | Bad Request - Invalid parameters |
| 401 | Unauthorized - Invalid or missing API key |
| 402 | Payment Required - Payment failed |
| 404 | Not Found - Resource doesn't exist |
| 422 | Validation Error - Invalid data |
| 429 | Rate Limited - Too many requests |
| 500 | Server Error - Something went wrong |
Create Order
Create a new order for handwritten letters.
/ordersRequest 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
| Field | Type | Description |
|---|---|---|
message | string | Letter content. Use {{field}} for personalization. |
recipients | array | List of recipient objects (max 500). |
options.handwriting_style | string | Style: elegant, casual, formal, friendly |
options.paper_type | string | Paper: cream, white, kraft |
payment.method | string | card_on_file or checkout_session |
skip_invalid | boolean | Skip 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.
/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.
/validateRequest 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.
| Event | Description |
|---|---|
order.created | Order was created (pending payment) |
order.paid | Payment received, order queued |
order.in_production | Letters being written |
order.shipped | Letters mailed |
order.delivered | Delivery confirmed by USPS |
order.cancelled | Order 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
/webhooksCreate Endpoint
/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
/webhooks/{endpoint_id}Delete Endpoint
/webhooks/{endpoint_id}Signature Verification
All webhooks include a signature header for verification. Always verify signatures in production.
Headers
X-Premier-Letters-Signature | HMAC-SHA256 signature |
X-Premier-Letters-Event | Event type |
X-Premier-Letters-Delivery-Id | Unique 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