terminal://docs.api
[ DOCUMENTATION ]

HTTP API

Create, manage, and publish posts via curl or any HTTP client. All responses are JSON.

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