Authentication
All endpoints (except /api/health) require a Bearer token matching the BLURT_API_KEY environment variable.
# Generate a key
openssl rand -hex 32
# Set it in your .env
BLURT_API_KEY=your-generated-secret
# Use it in requests
curl http://localhost:3000/api/posts \
-H "Authorization: Bearer your-generated-secret"
Missing or invalid tokens return 401 Unauthorized. If BLURT_API_KEY is not set, the API returns 503 Service Unavailable.
Posts
GET /api/posts
List posts. Defaults to the queue/ directory.
| Param | Default | Description |
|---|---|---|
status |
queue |
queue, sent, failed, or all |
platform |
— | Filter by platform name |
after |
— | ISO 8601 date, filter posts after this date |
before |
— | ISO 8601 date, filter posts before this date |
# List queued posts
curl http://localhost:3000/api/posts \
-H "Authorization: Bearer $BLURT_API_KEY"
# List sent posts for bluesky
curl "http://localhost:3000/api/posts?status=sent&platform=bluesky" \
-H "Authorization: Bearer $BLURT_API_KEY"
GET /api/posts/:filename
Get a single post by filename. Searches across queue/, sent/, and failed/.
curl http://localhost:3000/api/posts/my-post.md \
-H "Authorization: Bearer $BLURT_API_KEY"
POST /api/posts
Create a new post in queue/. The filename is generated from the title (slugified) or you can specify it explicitly.
| Field | Required | Description |
|---|---|---|
title |
For blog platforms | Post title |
platforms |
Yes | Array of platform names |
content |
No | Markdown body |
scheduled_at |
No | ISO 8601 timestamp |
filename |
No | Override auto-generated filename |
curl -X POST http://localhost:3000/api/posts \
-H "Authorization: Bearer $BLURT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "My First Post",
"platforms": ["bluesky", "mastodon"],
"content": "Hello from the API!"
}'
Returns 201 Created with the parsed post. Returns 409 Conflict if the filename already exists.
PUT /api/posts/:filename
Update a queued post. You can change frontmatter fields, content, or both.
curl -X PUT http://localhost:3000/api/posts/my-first-post.md \
-H "Authorization: Bearer $BLURT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Updated Title",
"content": "Updated content here."
}'
DELETE /api/posts/:filename
Remove a post from queue/.
curl -X DELETE http://localhost:3000/api/posts/my-first-post.md \
-H "Authorization: Bearer $BLURT_API_KEY"
POST /api/posts/:filename/publish
Immediately publish a queued post, bypassing the 60-second poll. Publishes to all configured platforms in parallel and returns the results.
curl -X POST http://localhost:3000/api/posts/my-first-post.md/publish \
-H "Authorization: Bearer $BLURT_API_KEY"
Returns 200 OK with per-platform results on success, or 422 if any platform failed (with partial results included).
# Response
{
"post": {
"filename": "my-first-post.md",
"title": "My First Post",
"platforms": ["bluesky", "mastodon"],
"status": "sent",
"results": {
"bluesky": {
"url": "https://bsky.app/profile/.../post/...",
"published_at": "2026-03-28T14:30:45Z"
},
"mastodon": {
"url": "https://mastodon.social/@you/123456",
"published_at": "2026-03-28T14:30:45Z"
}
}
}
}
History
GET /api/history
List published posts from the database log. Paginated and filterable.
| Param | Default | Description |
|---|---|---|
page |
1 |
Page number |
per_page |
25 |
Items per page (max 100) |
platform |
— | Filter by platform name |
after |
— | ISO 8601 date |
before |
— | ISO 8601 date |
curl "http://localhost:3000/api/history?page=1&per_page=10" \
-H "Authorization: Bearer $BLURT_API_KEY"
GET /api/history/:filename
Get a single published post's details, including platform URLs.
curl http://localhost:3000/api/history/my-first-post.md \
-H "Authorization: Bearer $BLURT_API_KEY"
Platforms
GET /api/platforms
List all supported platforms and their configuration status.
curl http://localhost:3000/api/platforms \
-H "Authorization: Bearer $BLURT_API_KEY"
# Response
{
"platforms": [
{ "name": "bluesky", "configured": true, "type": "social" },
{ "name": "mastodon", "configured": true, "type": "social" },
{ "name": "linkedin", "configured": false, "type": "social" },
{ "name": "medium", "configured": false, "type": "blog" },
{ "name": "devto", "configured": true, "type": "blog" },
{ "name": "substack", "configured": false, "type": "blog" }
],
"configured_count": 3
}
Health
GET /api/health
Health check. No authentication required. Useful for monitoring and load balancers.
curl http://localhost:3000/api/health
# Response
{
"status": "ok",
"queue": { "pending": 2 },
"sent": { "total": 47 },
"failed": { "total": 1 },
"platforms": {
"configured": ["bluesky", "mastodon", "devto"],
"count": 3
},
"solid_queue": { "connected": true },
"poll_interval_ms": 60000
}
Export
GET /api/export
Download all published posts as a ZIP archive. Includes directory posts with their images.
curl http://localhost:3000/api/export \
-H "Authorization: Bearer $BLURT_API_KEY" \
-o blurt-export.zip
Returns 404 if there are no sent posts to export.
Error format
All errors return a JSON object with an error key:
{
"error": "Unauthorized"
}
| Status | Meaning |
|---|---|
401 |
Missing or invalid API key |
404 |
Post or resource not found |
409 |
Filename conflict or post already being published |
422 |
Validation error or partial publish failure |
503 |
BLURT_API_KEY not configured |