Rustiz (Rust, agent-first)
A headless social media scheduler for AI agents: one binary, one SQLite file and no
GUI. It is a Rust port of Postiz v1.47.0
(commit e424fb5), licensed AGPL-3.0 like the original. The original copyright notice
is preserved in LICENSE. If you run a modified version as a network service,
you must offer its source to its users.
Status: no provider has been tested against a real platform. All tests use mock servers, so expect fixes when you connect real accounts. See Porting status.
Measured
Release build on an Apple Silicon Mac, with an empty database. Throughput is 2,976
integrationList calls over MCP at 32 concurrent clients with rate limits turned off, and
the Python test client may be the limit.
| Binary | Ready | RAM idle | RAM after load | Throughput |
|---|---|---|---|---|
| 16.9 MB | 117 ms | 19.0 MB | 24.4 MB | about 18,900 requests/s |
Run
Self-hosted, on your own machine:
rustiz init # creates ~/.rustiz: secret, database and API key
rustiz providers --status # which platforms are ready, and what each one needs
rustiz init prints the MCP config to paste into your agent. After that, rustiz mcp needs
no arguments, and it receives the platform’s login redirect itself. Running init again
never replaces the secret or creates a second organization.
On a server, set your public URL and put TLS in front, because the server speaks plain HTTP:
FRONTEND_URL=https://rustiz.example rustiz serve
Environment variables override ~/.rustiz/config.env; --config or RUSTIZ_CONFIG picks
another file. Back up JWT_SECRET with the database: stored channel credentials can’t be read
without it.
Each OAuth platform needs its own developer app, with credentials such as X_API_KEY or
REDDIT_CLIENT_ID and a redirect URI of FRONTEND_URL/integrations/social/<provider>. They
are the same variables Postiz uses. Some platforms may refuse a plain http://localhost
redirect, in which case that platform needs a public HTTPS FRONTEND_URL.
Email is optional: without EMAIL_PROVIDER, nothing is sent. Set UPLOAD_DIRECTORY for local
media, or STORAGE_PROVIDER=cloudflare|s3 for object storage. Video thumbnails need an
ffmpeg binary, which the Docker image does not include.
| Command | Purpose |
|---|---|
rustiz init | First-time setup |
rustiz serve | HTTP API, MCP and the scheduler (--no-worker for HTTP only) |
rustiz worker | The scheduler alone |
rustiz mcp | MCP over stdio for a local agent, with the scheduler and login callback in-process |
rustiz posts|channels|media|analytics|notifications|find-slot | CLI for agents |
rustiz org create|list|rotate-key | Organizations and API keys; rotating revokes OAuth tokens |
rustiz providers [--status] | List platforms and their readiness |
CLI
Every command prints one JSON document on stdout. Run rustiz --help for the full list.
rustiz channels connect reddit --open
rustiz posts create --channel <id> --content "Hello" --date 2030-01-01T09:00:00Z --type schedule
rustiz posts update <id> --content "Fixed typo"
rustiz posts reschedule <id> --date 2030-01-02T09:00:00Z
rustiz posts delete <id>
Without RUSTIZ_URL the CLI works on the local database and needs no server, but posts only
publish while rustiz serve, worker or mcp is running. With RUSTIZ_URL and
RUSTIZ_API_KEY it talks to a remote server. Exit codes: 1 runtime, 2 bad request, 3 auth,
4 not found, 5 post is publishing.
Connect an agent
Remote: https://your-host/mcp, with either Authorization: Bearer <api key> or OAuth.
Clients that support OAuth discover it from the 401 response. The approval page asks for
the API key, and it is the only form the server renders.
Local: paste the config rustiz init prints.
Set MCP_ALLOWED_HOSTS if clients reach the server on a host other than FRONTEND_URL.
Editing posts
PUT /public/v1/posts/{id}, PUT /public/v1/posts/{id}/date, updatePostTool and the CLI
share one code path. A post keeps its id. While a job is publishing the post, changes are
refused with a 409, so nothing is posted twice. Published posts can’t be edited, because
platforms keep the original; republish with a date publishes one again. An ERROR post may
already be live, so check the platform before retrying with publish. Deleting a post here
does not remove it from a platform.
Rate limits
| Route | Limit per hour | Setting |
|---|---|---|
POST on /public/v1/posts | 90 per organization | API_LIMIT |
/mcp and other /public/v1 routes | 10,000 per organization | REQUEST_LIMIT |
POST /oauth/authorize, /oauth/register | 30 per client address | fixed |
POST /oauth/token | 120 per client address | fixed |
0 turns a setting off. Counts are per process. X-Forwarded-For is ignored unless
TRUSTED_PROXY_HOPS says how many proxies append to it; set that only if the server can’t be
reached except through those proxies.
MCP behaviour
The server uses the official rmcp SDK and implements spec 2026-07-28. Older clients
that use initialize work on the same endpoint.
- Typical flow:
providersStatusTool,connectChannelTool,integrationSchema,validatePostTool,schedulePostTool,postStatusTool. - Safe resends: an identical
schedulePostToolcall within 10 minutes, or one with the sameidempotencyKeywithin 24 hours, returns the first result. - Tasks:
type: "now"returns a durable task handle to clients that declare the Tasks extension. Others get a normal result and pollpostStatusTool. - Connecting accounts: OAuth returns a login link for a human. Credential providers take
customFields. Telegram, Moltbook and Farcaster return what the human must do, then the agent polls withconnectionId. Channels markedinBetweenStepsare finished withtriggerToolandchannelSetupTool. - Live updates:
subscriptions/listenonrustiz://posts/<id>orrustiz://groups/<id>sends a hint when a post’s state changes. It polls the database every 2 seconds (MCP_SUBSCRIPTIONS_POLL_MS), so it also sees a separaterustiz worker. Quick changes between polls collapse into the last one, and deletions are not reported. At most 4 streams per organization (MCP_SUBSCRIPTIONS_MAX_STREAMS).
Not in this project
Generating media is out of scope: agents make it with other tools and upload it. The cloud parts of a hosted product (signup, teams, billing, password emails, a dashboard) belong in a separate layer on top of the REST API.