Base URL: /api
All endpoints except POST /api/auth/login and GET /api/system/health require Authorization: Bearer <jwt_token>.
Login with owner password.
Body: { "password": "string" }
Response: { "access_token": "string", "token_type": "bearer", "expires_in": 86400 }
Public health check.
Response: { "status": "healthy", "version": "1.1.0", "timestamp": "ISO8601" }
Manually trigger the full research → draft pipeline.
Response: Pipeline result object with batch_id and post count.
List posts with filtering and pagination.
Query params: status, batch_id, search, page (default 1), page_size (default 20)
Response: { "items": [Post], "total": int, "page": int, "per_page": int }
Dashboard overview statistics.
Response: { "posts_today", "posts_this_month", "pending_review", "published_total", "sources_healthy", "sources_total" }
Recent post activity.
Query: limit (default 10)
Get single post by ID.
Update post — approve, reject, edit content.
Body: { "status?": "approved|rejected", "content?": "string", "feedback?": "string", "scheduled_for?": "ISO8601" }
Upload image attachment (multipart/form-data).
Response: { "message": "string", "image_url": "string" }
Submit manual engagement metrics.
Body: { "impressions": int, "likes": int, "retweets": int, "replies": int }
Get metrics for a specific post.
Fetch metrics from X API for recently published posts.
List all content sources.
Create new source.
Body: { "name": "string", "url": "string", "source_type": "hackernews|rss", "category": "string", "is_active": true }
Update source.
Delete source.
List all categories.
Create category.
Body: { "name": "string", "is_active": true }
Update category.
Get active style configuration (tone, topics, hashtags, thread preference).
Response: { "id", "tone", "topics": [], "hashtag_policy": "always|never", "thread_preference": bool, "updated_at" }
Note: Stored as JSONB in DB, flattened by the API. use_hashtags → hashtag_policy, thread_style → thread_preference.
Update style configuration. Creates a new version.
Aggregate engagement metrics.
Query: period (7d, 30d, all)
Response: { "post_count", "total_impressions", "total_likes", "total_reposts", "total_replies", "engagement_rate", "period" }
Per-category analytics breakdown.
Query: period (7d, 30d, all)
List AI-generated improvement proposals (newest first).
Get single proposal.
Approve or reject proposal.
Body: { "status": "approved|rejected" }
Get current LLM model configuration.
Response: { "model_id": "gemini-2.0-flash", "source": "database|env" }
Set LLM model dynamically (persisted to database).
Body: { "model_id": "gemini-2.0-flash" }
Response: { "model_id": "gemini-2.0-flash", "message": "LLM model updated" }