titen — Javanese: to watch closely, to observe with care.
Self-hosted Threads management platform. Post, schedule, analyze — on your own infrastructure.
Threads has no native post scheduling. Every existing tool is a SaaS you don't control, with your API tokens living on someone else's server. Titen runs locally or on your box, talks directly to the Threads Graph API, and stores everything in a single SQLite file.
That's it. No web dashboard you didn't ask for. No subscription. No vendor lock-in.
| Capability | Details |
|---|---|
| Multi-account | Manage multiple Threads accounts in one instance |
| Post scheduling | Cron-based scheduler — something Threads itself doesn't offer |
| Comment fetching | Pull comments from the Threads API, store locally |
| Sentiment analysis | Pluggable engine trait (stub default; ONNX/LLM/custom API extensible) |
| Analytics | Time-series snapshots per post |
| Media storage | S3-compatible via swappable storage trait |
| MCP server | JSON-RPC 2.0 over stdio — works with Claude Desktop, Cursor, etc. |
| CLI | Full CRUD from the terminal |
| Docker | Single container, minimal footprint |
4 crates, one binary each:
| Crate | Purpose |
|---|---|
titen-core |
Domain logic — models, SQLite, Threads API client, sentiment trait, scheduler, S3 storage |
titen-api |
Axum HTTP server — REST API, API key auth, CORS, rate limiting |
titen-cli |
Clap CLI — all operations via the HTTP API |
titen-mcp |
MCP stdio server — 14 tools for AI agent integration |
7 SQLite tables: accounts, posts, schedules, comments, analytics_snap, media_assets, rate_tracking.
3 migrations: 001_initial (schema), 002_drop_refresh_token, 003_add_app_secret.
curl -sSL https://github.com/codecoradev/titen/releases/latest/download/install.sh | shRequires Rust 1.85+ (edition 2024). SQLite is bundled via sqlx.
git clone https://github.com/codecoradev/titen.git
cd titen
cargo build --release# Start the API server (default: 0.0.0.0:7845)
titen-api
# Or via the CLI with embedded server
titen serve
# Add an account and post
export TITEN_API_KEY=your-key
titen account add myuser --access-token "THREADS_TOKEN" --expires-at "2026-12-01T00:00:00Z"
titen post create myuser --text "Hello from titen!"The server creates a SQLite database at ~/.codecora/titen/titen.db by default (override with TITEN_DB_PATH).
All config via environment variables:
| Variable | Default | Description |
|---|---|---|
TITEN_DB_PATH |
~/.codecora/titen/titen.db |
SQLite database path |
TITEN_API_KEY |
(none) | API key — when unset, all endpoints are open |
TITEN_HOST |
0.0.0.0 |
Bind address |
TITEN_PORT |
7845 |
Bind port |
TITEN_URL |
http://localhost:7845 |
Base URL for CLI |
TITEN_SENTIMENT_ENGINE |
stub |
stub, onnx, llm, or custom_api |
TITEN_SCHEDULER_INTERVAL_SECS |
60 |
Scheduler tick interval |
TITEN_S3_ENDPOINT |
(none) | S3-compatible endpoint |
TITEN_S3_BUCKET |
(none) | S3 bucket name |
TITEN_S3_REGION |
us-east-1 |
S3 region |
TITEN_S3_ACCESS_KEY |
(none) | S3 access key |
TITEN_S3_SECRET_KEY |
(none) | S3 secret key |
TITEN_S3_PUBLIC_URL |
(none) | Public URL for uploaded media |
Base URL: http://localhost:7845
All endpoints except /health require X-API-Key authentication when TITEN_API_KEY is set. Not Bearer — it's a plain header:
X-API-Key: your-key-here
| Method | Path | Auth |
|---|---|---|
| GET | /health |
None |
| Method | Path | Description |
|---|---|---|
| GET | /api/accounts |
List all accounts |
| POST | /api/accounts |
Create an account |
| PUT | /api/accounts/{id} |
Update an account |
| DELETE | /api/accounts/{id} |
Delete an account |
| POST | /api/accounts/{id}/refresh-token |
Refresh OAuth token |
| GET | /api/accounts/{id}/profile |
Fetch Threads profile |
| GET | /api/accounts/{id}/publishing-limit |
Get remaining daily limits |
| GET | /api/accounts/check-tokens |
Batch token expiry check |
| Method | Path | Description |
|---|---|---|
| GET | /api/posts |
List posts (?account_id=&status=&limit=&offset=) |
| POST | /api/posts |
Create and publish a post |
| GET | /api/posts/{id} |
Get a single post |
| DELETE | /api/posts/{id} |
Delete a post |
| GET | /api/posts/{id}/insights |
Fetch and store post insights |
| Method | Path | Description |
|---|---|---|
| GET | /api/schedules |
List schedules (?account_id=&status=) |
| POST | /api/schedules |
Create a schedule |
| PUT | /api/schedules/{id} |
Update a schedule |
| DELETE | /api/schedules/{id} |
Delete a schedule |
| GET | /api/schedules/upcoming |
Next 10 pending schedules |
| Method | Path | Description |
|---|---|---|
| GET | /api/posts/{id}/comments |
List stored comments |
| POST | /api/posts/{id}/comments/fetch |
Fetch comments from Threads API |
| GET | /api/posts/{id}/comments/sentiment |
Analyze comment sentiment |
| Method | Path | Description |
|---|---|---|
| GET | /api/analytics/posts |
Post analytics (?account_id=&from=&to=) |
| GET | /api/analytics/posts/{id}/trend |
Time-series trend for a post |
| Method | Path | Description |
|---|---|---|
| GET | /api/media |
List uploaded media |
| POST | /api/media |
Upload media (multipart) |
| DELETE | /api/media/{id} |
Delete media |
| Method | Path | Description |
|---|---|---|
| POST | /api/threads/container |
Create a Threads media container |
| POST | /api/threads/container/{id}/publish |
Publish a container |
| POST | /api/threads/container/{id}/status |
Check container status |
The CLI talks to the running HTTP server. Set TITEN_URL and TITEN_API_KEY as needed.
titen serve [--host 0.0.0.0] [--port 7845] [--mcp]titen account list
titen account add <username> --access-token <TOKEN> [--user-id <ID>] [--expires-at <ISO8601>]
titen account remove <id>
titen account refresh <id>
titen account status <id>
titen token-checktiten post create <account> --text <TEXT> [--media-type TEXT|IMAGE] [--image-url <URL>]
titen post delete <post_id>
titen post insights <post_id>titen schedule add <account> --text <TEXT> --at <ISO8601> [--media-type TEXT|IMAGE]
titen schedule list [--account <id>] [--status <status>]
titen schedule cancel <id>
titen schedule upcomingtiten comment fetch <post_id>
titen comment list <post_id>
titen comment sentiment <post_id>titen analytics posts <account> [--from <date>] [--to <date>]
titen analytics trend <post_id>
titen analytics sentiment-summary <post_id>titen media list
titen media upload <file_path> [--content-type <mime>]
titen media delete <id>Titen ships an MCP (Model Context Protocol) server for AI agent integration. Communicates over stdio using JSON-RPC 2.0.
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"titen": {
"command": "/path/to/titen-mcp",
"env": {
"TITEN_DB_PATH": "/path/to/titen.db"
}
}
}
}Cursor — add to your MCP settings:
{
"mcp": {
"titen": {
"command": "/path/to/titen-mcp",
"env": {
"TITEN_DB_PATH": "/path/to/titen.db"
}
}
}
}| Tool | Description |
|---|---|
list_accounts |
List all Threads accounts |
get_account |
Get a single account by ID |
create_post |
Create and publish a post |
schedule_post |
Schedule a post for later |
list_schedules |
List scheduled posts |
cancel_schedule |
Cancel a scheduled post |
fetch_comments |
Fetch and store comments from Threads |
get_post_sentiment |
Sentiment analysis on a post's comments |
get_post_analytics |
Analytics for a specific post |
get_account_analytics |
Analytics summary for an account |
upload_media |
Upload media to S3 storage |
refresh_token |
Refresh an account's access token |
check_tokens |
Batch check all accounts' token expiry |
The platform enforces these per-account daily limits:
| Limit | Count |
|---|---|
| Posts | 250/day |
| Replies | 1,000/day |
| Deletes | 100/day |
| Caption length | 500 chars |
text_attachment length |
10,000 chars |
- Engine: tokio-cron with a 60-second tick interval
- Flow: At scheduled time → create media container → wait for ready → publish
- Publish delays: text posts publish immediately (0s), image posts wait 30s, video posts wait 60s for processing
docker build -t titen .
docker run -p 7845:7845 \
-e TITEN_API_KEY=your-key \
-e TITEN_DB_PATH=/data/titen.db \
-e TITEN_S3_ENDPOINT=https://s3.example.com \
-e TITEN_S3_BUCKET=titen-media \
-v titen-data:/data \
titen- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Commit your changes
- Push to the branch (
git push origin feature/my-feature) - Open a Pull Request