API
Build integrations and automate workflows. Pro feature.
Getting started
The Joyful API lets you manage publications, newsletters, subscribers, and more programmatically.
- Go to Settings → API Keys
- Click Create API Key and give it a name
- Copy the key immediately — you won't see it again
Authentication
All requests require a Bearer token in the Authorization header:
curl -H "Authorization: Bearer jf_your_api_key" \
https://joyful.to/api/publications API keys start with jf_ and provide full access to your account.
Rate limits
Each API key is limited to 1,000 requests per hour. Rate limit info is included in response headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1702425600 If you exceed the limit, you'll get a 429 Too Many Requests response.
Headless CMS
Use Joyful as a headless CMS to power your own website or app.
Setup
- Go to Publication Settings → Advanced
- Set Visibility to "API Only" to hide content from joyful.to
- Optionally set a Canonical Domain (e.g.,
myblog.com/posts) for SEO
Fetching content
List newsletters for a publication:
curl -H "Authorization: Bearer jf_your_api_key" \
https://joyful.to/api/publications/your-pub-slug/newsletters Response includes metadata for each newsletter:
{
"newsletters": [
{
"id": "nl_xyz789",
"slug": "my-post",
"title": "My Newsletter Post",
"description": "A brief summary...",
"coverImage": "https://...",
"published": true,
"sentAt": "2024-01-20T..."
}
],
"pagination": { "total": 42, "limit": 50, "offset": 0, "hasMore": false }
} Get full content for a single newsletter:
curl -H "Authorization: Bearer jf_your_api_key" \
https://joyful.to/api/newsletters/nl_xyz789 Response includes rendered HTML:
{
"newsletter": {
"id": "nl_xyz789",
"slug": "my-post",
"title": "My Newsletter Post",
"html": "<p>Your rendered content...</p>",
...
}
} Canonical URLs
When you set a canonical domain, newsletter pages on joyful.to include:
<link rel="canonical" href="https://your-domain.com/your-newsletter-slug" /> This tells search engines your site is the primary source, preventing duplicate content issues.
Endpoints
Publications
| Method | Endpoint | Description |
|---|---|---|
GET | /api/publications | List all publications |
POST | /api/publications | Create a publication |
GET | /api/publications/:id | Get a publication |
PUT | /api/publications/:id | Update a publication |
DELETE | /api/publications/:id | Delete a publication |
GET | /api/publications/:id/newsletters | List newsletters for a publication |
GET | /api/publications/:id/tags | List tags for a publication |
Newsletters
| Method | Endpoint | Description |
|---|---|---|
GET | /api/newsletters | List all newsletters |
POST | /api/newsletters | Create a newsletter |
GET | /api/newsletters/:id | Get a newsletter |
PUT | /api/newsletters/:id | Update a newsletter |
DELETE | /api/newsletters/:id | Delete a newsletter |
Subscribers
| Method | Endpoint | Description |
|---|---|---|
GET | /api/subscribers | List all subscribers |
POST | /api/subscribers | Add a subscriber |
GET | /api/subscribers/:id | Get a subscriber |
PUT | /api/subscribers/:id | Update a subscriber |
DELETE | /api/subscribers/:id | Remove a subscriber |
Templates
| Method | Endpoint | Description |
|---|---|---|
GET | /api/templates | List all templates |
POST | /api/templates | Create a template |
GET | /api/templates/:id | Get a template |
DELETE | /api/templates/:id | Delete a template |
Stats
| Method | Endpoint | Description |
|---|---|---|
GET | /api/stats | Overall statistics |
GET | /api/stats/:newsletterId | Stats for a newsletter |
Examples
JavaScript
const API_KEY = 'jf_your_api_key';
// List all subscribers
const response = await fetch('https://joyful.to/api/subscribers', {
headers: {
'Authorization': `Bearer ${API_KEY}`
}
});
const data = await response.json();
console.log(data.subscribers);
// Add a subscriber
const newSubscriber = await fetch('https://joyful.to/api/subscribers', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: 'reader@example.com',
firstName: 'Jane',
publicationId: 'pub_abc123'
})
}); Python
import requests
API_KEY = 'jf_your_api_key'
BASE_URL = 'https://joyful.to/api'
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json'
}
# List all subscribers
response = requests.get(f'{BASE_URL}/subscribers', headers=headers)
subscribers = response.json()['subscribers']
# Add a subscriber
new_subscriber = requests.post(
f'{BASE_URL}/subscribers',
headers=headers,
json={
'email': 'reader@example.com',
'firstName': 'Jane',
'publicationId': 'pub_abc123'
}
) Responses
All responses are JSON. Success responses contain the requested data:
{
"publications": [
{
"id": "pub_abc123",
"name": "My Newsletter",
"slug": "my-newsletter"
}
]
} Errors include a message:
{
"error": "Rate limit exceeded"
}