Skip to content

CIReleaseLicense: Apache-2.0Rust 1.85+

Titen

titen (Javanese): to watch closely, to observe with care.

Self-hosted Threads management platform. Post, schedule, and analyze Threads content from your own infrastructure.

Why Titen?

Threads has no native post scheduling. Existing tools are SaaS products where your API tokens live on someone else's server. Titen runs on your box, talks directly to the Threads Graph API, and stores everything in a single SQLite file.

No subscription. No vendor lock-in. Your tokens stay on your machine, encrypted at rest with AES-256-GCM.

Features

CapabilityDetails
Multi-accountManage multiple Threads accounts in one instance
Post schedulingCron-based scheduler for automated posting that Threads itself does not offer
Comment fetchingPull comments from the Threads API, store locally
Sentiment analysisPluggable engine trait (stub default; ONNX/LLM/custom API extensible)
AnalyticsTime-series snapshots per post
Media storageS3-compatible via swappable storage trait
MCP serverJSON-RPC 2.0 over stdio, compatible with Claude Desktop, Cursor, etc.
CLIFull CRUD from the terminal
DockerSingle container, minimal footprint

Architecture

Detailed docs:Deployment Guide · Usage Guide · Architecture Overview · Auth Flow · Changelog

4 crates, one binary each:

CratePurpose
titen-coreDomain logic: models, SQLite store, Threads API client, sentiment trait, scheduler, S3 storage, AES-256-GCM encryption
titen-apiAxum HTTP server: REST API, API key auth, CORS, rate limiting
titen-cliClap CLI: all operations via the HTTP API
titen-mcpMCP stdio server: 29 tools for AI agent integration

11 SQLite tables: accounts, posts, schedules, comments, analytics_snap, media_assets, rate_tracking, _encryption_meta, mentions, app_settings, sessions.

14 migrations: 001_initial (schema), 002_drop_refresh_token, 003_add_app_secret, 004_encrypt_tokens (encrypts existing plaintext tokens on startup), 005_hitl_scheduling, 006_mentions_table, 007_media_urls_doc, 008_comment_reply_status, 009_app_settings, 010_location_tagging, 011_sessions_table, 012_post_permalink, 013_normalize_timestamps, 014_schedule_reply_to.

Quick Start

Install (pre-built binary)

curl -sSL https://github.com/codecoradev/titen/releases/latest/download/install.sh | sh

Build from source

Requires Rust 1.85+ (edition 2024). SQLite is bundled via sqlx.

git clone https://github.com/codecoradev/titen.git
cd titen
cargo build --release

Run

# 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 postexport 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).

Configuration

All config via environment variables:

VariableDefaultDescription
TITEN_DB_PATH~/.codecora/titen/titen.dbSQLite database path
TITEN_API_KEY(none)API key for endpoint access. When unset, all endpoints are open (dev mode)
TITEN_ENCRYPTION_KEY(none)AES-256-GCM key for token encryption at rest. Generate with openssl rand -hex 32
TITEN_REQUIRE_ENCRYPTIONfalseSet to true in production to fail-fast if encryption key is missing
TITEN_HOST0.0.0.0Bind address
TITEN_PORT7845Bind port
TITEN_URLhttp://localhost:7845Base URL for CLI
TITEN_SENTIMENT_ENGINEstubstub, onnx, llm, or custom_api
TITEN_SCHEDULER_INTERVAL_SECS60Scheduler tick interval
TITEN_S3_ENDPOINT(none)S3-compatible endpoint
TITEN_S3_BUCKET(none)S3 bucket name
TITEN_S3_REGIONus-east-1S3 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
TITEN_CORS_ORIGINS(none)Comma-separated CORS origins for cross-origin API access
TITEN_OAUTH_REDIRECT_URI(none)Explicit OAuth redirect URI override (otherwise derived from APP_URL)
TITEN_ALLOWED_HOSTS(none)Comma-separated trusted hostnames for Host-header redirect derivation
APP_URLhttp://localhost:3000Public URL of the web frontend (used for OAuth redirect URIs)
WEB_PORT3000Port published to the host for the web container (Docker Compose)
RUST_LOGtiten_api=info,tower_http=infoRust log level
TITEN_COOKIE_SECUREfalseAdds Secure flag to the auth cookie (auto-detected behind a reverse proxy)
TITEN_ENV(none)Set to prod/production to enforce strict security guards (API key + encryption required, Swagger disabled)
TITEN_ENABLE_SWAGGER(none)Explicitly enable Swagger UI in production (disabled by default there)

API Reference

Base URL: http://localhost:7845

Interactive docs (Swagger UI):http://localhost:7845/api/docs — explore and test all endpoints directly from the browser.

OpenAPI JSON:http://localhost:7845/api/docs/openapi.json

All endpoints except /health and /api/docs require X-API-Key authentication when TITEN_API_KEY is set. Use a plain header (not Bearer):

X-API-Key: your-key-here

Health

MethodPathAuthDescription
GET/healthNoneServer health check
GET/api/healthNoneServer health check (API-prefixed alias)
GET/readyNoneReadiness check (verifies DB connectivity)
GET/metricsAPI key*Prometheus metrics

*/metrics requires auth unless TITEN_PUBLIC_METRICS=true is set.

Accounts

MethodPathDescriptionQuery Params
GET/api/accountsList all accounts
POST/api/accountsCreate an account
PUT/api/accounts/{id}Update an account
DELETE/api/accounts/{id}Delete an account (requires ?confirm=true; permanently removes the account and ALL its posts, schedules, media, mentions and analytics)?confirm=true
POST/api/accounts/{id}/refresh-tokenRefresh OAuth token
GET/api/accounts/{id}/profileFetch Threads profile (/me)
GET/api/accounts/{id}/publishing-limitGet remaining daily limits
GET/api/accounts/{id}/insightsAccount-level insights (followers, media count)
GET/api/accounts/check-tokensBatch token expiry check + auto-refresh

Posts

MethodPathDescriptionQuery Params
GET/api/postsList posts?account_id=&status=&limit=&offset=
POST/api/postsCreate and publish a post
GET/api/posts/{id}Get a single post
DELETE/api/posts/{id}Delete a post
GET/api/posts/{id}/insightsFetch and store post insights

Schedules

MethodPathDescriptionQuery Params
GET/api/schedulesList schedules?account_id=&status=
POST/api/schedulesCreate a schedule
GET/api/schedules/{id}Get a single schedule
PUT/api/schedules/{id}Full update (all fields)
PATCH/api/schedules/{id}Partial update (specific fields)
DELETE/api/schedules/{id}Delete a schedule
POST/api/schedules/{id}/approveApprove draft → pending (HITL)
POST/api/schedules/{id}/rejectReject draft (HITL)
GET/api/schedules/upcomingNext 10 pending schedules

Comments

MethodPathDescriptionQuery Params
GET/api/posts/{id}/commentsList stored comments
POST/api/posts/{id}/comments/fetchFetch comments from Threads API
GET/api/posts/{id}/comments/sentimentAnalyze comment sentiment

Analytics

MethodPathDescriptionQuery Params
GET/api/analytics/postsPost analytics summary?account_id=&from=&to=
GET/api/analytics/posts/{id}/trendTime-series engagement trend

Insights

MethodPathDescriptionQuery Params
GET/api/insights/trendsAggregate engagement trends?account_id=&horizon_minutes=

Media

MethodPathDescription
GET/api/mediaList uploaded media
POST/api/mediaUpload media (multipart file field)
GET/api/media/{id}Get a single media asset
DELETE/api/media/{id}Delete media

Threads (low-level passthrough)

MethodPathDescription
POST/api/threads/containerCreate a Threads media container
POST/api/threads/container/{id}/publishPublish a container
POST/api/threads/container/{id}/statusCheck container publishing status
POST/api/threads/replyReply to a Threads post or comment
POST/api/threads/reply/{id}/hideHide/unhide a reply
GET/api/threads/profile-lookupLook up any Threads user profile
POST/api/threads/searchSearch Threads by keyword
POST/api/threads/mentionsFetch mentions for an account
POST/api/threads/share-to-instagramCross-post to Instagram

Authentication (web UI session)

MethodPathDescription
POST/api/auth/loginLogin (password → session cookie)
GET/api/auth/sessionCheck current session
POST/api/auth/logoutLogout (clear session)

OAuth

MethodPathDescription
POST/api/oauth/exchangeExchange Threads OAuth code for access token

Request/Response Format

All responses use JSON with a data field for successful requests:

{ "data": { ... } }

Or data array for list endpoints:

{ "data": [ ... ], "count": 42 }

Errors return a JSON body with error and code:

{ "error": "Schedule not found", "code": "NOT_FOUND" }

Key Data Models

Schedule — created as draft, requires approval before auto-publishing:

{
"id": "019fdfae-dcad-7093-95d1-236065ad8aff",
"account_id": "019fdfae-c0b1-7031-afad-e7ae3ed80646",
"caption": "Post text with #hashtags",
"media_type": "CAROUSEL",
"media_urls": ["https://cdn.example.com/slide-01.jpg", "https://cdn.example.com/slide-02.jpg"],
"scheduled_at": "2026-08-09T12:00:00+07:00",
"status": "draft",
"approved_at": null,
"created_at": "2026-08-08T10:00:00Z"
}

Schedule lifecycle (HITL flow):

draft → (approve) → pending → (scheduler at scheduled_at) → published
draft → (reject) → rejected
pending → (publish fails) → failed

Only pending schedules are picked up by the scheduler. A schedule stays as draft until explicitly approved via POST /api/schedules/{id}/approve.

Media types supported:TEXT, IMAGE, CAROUSEL, VIDEO

For IMAGE and CAROUSEL, media_urls must contain publicly accessible URLs to hosted images. Titen does not download or re-host images referenced in schedules (use POST /api/media to upload first if needed).

CLI

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]

Accounts

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-check

Posts

titen post create <account> --text <TEXT> [--media-type TEXT|IMAGE] [--image-url <URL>]
titen post delete <post_id>
titen post insights <post_id>

Schedules

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 upcoming

Comments

titen comment fetch <post_id>
titen comment list <post_id>
titen comment sentiment <post_id>

Analytics

titen analytics posts <account> [--from <date>] [--to <date>]
titen analytics trend <post_id>
titen analytics sentiment-summary <post_id>

Media

titen media list
titen media upload <file_path> [--content-type <mime>]
titen media delete <id>

MCP Server

Titen ships an MCP (Model Context Protocol) server for AI agent integration. It communicates over stdio using JSON-RPC 2.0.

Setup

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"
}
}
}
}

Available Tools (17)

ToolDescriptionKey Parameters
list_accountsList all Threads accounts
get_user_profileFetch a Threads user's profileaccount_id
get_publishing_limitFetch daily publishing quotaaccount_id
create_postCreate and publish a postaccount_id, caption, media_type
schedule_postSchedule a post for future publishingaccount_id, caption, scheduled_at
list_schedulesList scheduled postsaccount_id?, status?
cancel_scheduleCancel a scheduled postid
refresh_tokenRefresh an account's access tokenaccount_id
check_tokensBatch check all token expiry + auto-refresh
fetch_commentsFetch and store comments from Threads APIpost_id
get_post_sentimentSentiment analysis for a post's commentspost_id
get_post_insightsFetch post engagement metricspost_id
get_account_analyticsAnalytics summary for an accountaccount_id
delete_postDelete a post from Threads + DBpost_id
create_containerCreate a Threads media containeraccount_id, media_type
publish_containerPublish a previously created containeraccount_id, container_id

Note: 13 additional API endpoints do not yet have MCP tool wrappers (list_posts, get_schedule, approve_schedule, upload_media, search, mentions, reply, etc.). See issue #84 for the tracking issue.

Threads API Limits

The platform enforces these per-account daily limits:

LimitCount
Posts250/day
Replies1,000/day
Deletes100/day
Caption length500 chars
text_attachment length10,000 chars

Scheduler Behavior

  • 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

Security

  • Token encryption: access_token and app_secret columns encrypted at rest with AES-256-GCM. Each value gets a random 96-bit nonce and a enc:v1: versioned prefix for future migration. Key is zeroized on drop.
  • Fail-fast mode: set TITEN_REQUIRE_ENCRYPTION=true in production to reject startup if the encryption key is missing.
  • API key auth: constant-time comparison against TITEN_API_KEY. Three credential sources: X-API-Key header, api_key query param, titen_session cookie.
  • HTTP client timeouts: all outbound calls to the Threads API and S3 have connect and total timeout limits.

See SECURITY.md for the full policy and vulnerability reporting.

Docker

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

License

Apache-2.0

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/my-feature)
  3. Commit your changes
  4. Push to the branch (git push origin feature/my-feature)
  5. Open a Pull Request

About

Self-hosted Threads management platform — schedule, analyze, monitor

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages