Skip to content
kazma.
ع Star 7 Get Started

X publisher (official API)

Kazma tweets only through the official X API v2 using OAuth 1.0a user context. There is no scrape path, no Playwright/computer-use poster, and no app-only Bearer posting (Bearer can read; it cannot POST /2/tweets).

You doKazma does
X Developer account + Project + AppNothing until keys exist
App User authentication = Read and writeRefuses 403 with that hint
Generate the four OAuth 1.0a valuesStores them in the vault via Settings → X
Label the account AutomatedReminds you; cannot set the label via API
Approve outbound tweetsChat tools are always HITL. On the Web, your click on X Studio (/x) or /api/scheduled/x is the approval

Do not paste keys in chat. vault_store would put them in history. Settings → X writes connectors.x.* through ConfigStore, which vault-encrypts api_key / *_secret / access_token.

SurfacePathWhat it is
X Studio/xFirst-class composer + X-only planner. Post now, schedule, reschedule, thread hops, delete a live tweet, load a saved draft.
Scheduled/scheduledMixed clock: cron jobs and X posts. X Studio’s All clocks button opens this page on purpose.
Chat toolsTelegram / Discord / Slack / Web chatx_post / x_schedule_post / x_delete_post / x_cancel_scheduled_post — always HITL, even under YOLO.

Settings → X is credentials and caps only. Compose and plan on /x.

  1. developer.x.com → Project + App (Free tier: 1 project / 1 app).
  2. User authentication settings → Read and write.
  3. Keys and tokens → API Key, API Key Secret, Access Token, Access Token Secret.
  4. Kazma Settings → X → paste the four values + your @handle → Save → Test (GET /2/users/me).
  5. Enable posting. Open X Studio (/x) to write and schedule.

Optional env (overrides ConfigStore when set): X_API_KEY, X_API_KEY_SECRET, X_ACCESS_TOKEN, X_ACCESS_TOKEN_SECRET. Kill-switches: KAZMA_X_POST=0 (all posting), KAZMA_X_SCHEDULE=0 (scheduling only).

The Web operator surface for posting. Alpine page xStudioPage() (static/js/x_studio.js); EN/AR strings in i18n/catalog/x_studio.py. Mutating calls send X-Requested-With and go through CSRF Origin checks.

Composer

  • Live ToU preview from evaluate_post (280 chars, @ / # / $ caps) — no network, no ledger write (POST /api/x/preview).
  • Post nowpublish_x_post (POST /api/x/post). Your click is the approval.
  • Schedulebook_x_post (POST /api/scheduled/x). Caps and the 30-day duplicate hash are reserved at booking.
  • Reply-to accepts a tweet id or an x.com/…/status/… URL. After a successful Post now, the composer hops onto the new tweet id so the next send is a thread hop. Clear thread drops it.
  • Use on a saved save_proposal draft stamps proposal_id. On post or schedule the stored text wins over whatever is in the box; the proposal is then marked posted. Editing the box after Use clears the id on the client so a later type-in is not rewritten. An unknown id returns 400.

Planner (X-only)

  • Upcoming X posts with a datetime-local field + Reschedule (PUT /api/scheduled/x/{id}) and Cancel.
  • Posted rows: open on X, Reply (loads the id into the composer), Delete (POST /api/x/delete — confirm first). Chat x_delete_post is still always-HITL.
  • Drafts from GET /api/x/drafts (artifact store list_proposals).
  • Audit from GET /api/x/audit (x_audit.db).
  • All clocks/scheduled (cron + X together). The Studio queue stays X-only so a reminder job does not sit next to a tweet.

Not in this version: media, polls, quote tweets, likes, follows, DMs, scrape, recurring identical tweets, native X schedule API.

These are Kazma caps on top of X’s own quota. They fail closed.

GuardDefaultWhy
Always-HITL (ALWAYS_HITL_TOOLS)x_post, x_delete_post, x_schedule_post, x_cancel_scheduled_postChat/agent tools: human sees the exact text. YOLO / grants / HITL-off cannot skip. Web Studio does not call these tools — the operator click is the approval.
Official API onlyPOST /2/tweetsAutomation must use the API, not the website.
No extra verbsno like / follow / DM / searchStays inside posting.
No write retrynetwork drop ≠ second POSTAvoids double-posts.
Length280 charsFree-tier text tweets.
@mentionsmax 2 (excluding your handle)Unsolicited mention spam.
$cashtagsmax 1Ticker spam.
Hashtagsmax 4Hashtag stuffing.
Duplicate hash30 daysIdentical automated copies.
Volume8/day, 80/30dWell under Free-tier ~500/month. Raise only if your dashboard quota allows.
User-AgentKazma/<ver> (self-hosted; official X API v2)X requires a UA.

Media, polls, quote tweets, and v1.1 upload are not in this version (Free-tier media is often blocked; adding them later still goes through the same HITL + ledger).

ToolHITLWhat
x_statusnoConfigured?, handle, remaining caps. Never returns secrets.
x_postalwaystext, optional reply_to_id for a thread hop. Chat path requires a resolvable proposal_id (stored draft wins).
x_delete_postalwaystweet_id from a previous post.
x_schedule_postalwaystext + when (‘5m’, ‘1h’, ‘daily at 9am’, ISO) + optional reply_to_id. Approve once at booking.
x_list_schedulednoList scheduled X posts and their status.
x_cancel_scheduled_postalwaysCancel a scheduled post before it fires (releases reserved quota).

These tools are the chat path. X Studio posts through publish_x_post / book_x_post / delete_x_post directly. Swarm workers stay capped by the commitment swarm-scope (outbound CRITICAL denied by default). A proposal_id is required on chat x_post / x_schedule_post — the commitment resolver rewrites text to the stored draft.

X has no native scheduled-post API — the /2/broadcasts/scheduled endpoint schedules live video streams (it needs an RTMP source_id), and POST /2/tweets has no scheduling field. So Kazma owns the clock: a scheduled post is stored in kazma-data/x_scheduled.db and the scheduler fires POST /2/tweets directly at the appointed time (the same client-side pattern X’s own docs describe).

  • Approve once at booking. x_schedule_post is always-HITL; you approve the exact draft + time when you book it. The fire is deterministic (no LLM re-reading the post).
  • Caps + dedupe are reserved at booking. Pending scheduled posts count toward the daily/monthly caps and the 30-day duplicate rule, so the schedule cannot be used to exceed them.
  • Double-post guard. A failed fire is never auto-retried on an ambiguous error (we can’t know whether it reached X). A 429 is deferred by the Retry-After window (bounded); anything else is marked failed and you’re notified.
  • Kill-switch. KAZMA_X_SCHEDULE=0 disables scheduling (and KAZMA_X_POST=0 disables posting entirely).

Honest limitation: a scheduled post fires only while the Kazma server is running. If the server is down at fire time, the post is caught up on the next boot. X cannot hold the schedule for you.

Manage the X content calendar in X Studio (/x). Open All clocks (or /scheduled) when you also need cron jobs. Chat tools stay in sync with both stores.

Every X API call is recorded in an append-only audit trail — kazma-data/x_audit.db (SQLite WAL): local date-and-time with timezone, action (post / reply / delete / verify_credentials), endpoint, HTTP status, tweet id, the full request payload and full response body (success, HTTP error, and network failure alike), and duration. The hook lives at XClient._request — the single choke point covering the native skill, /api/x/*, and scheduled tweets. Recording is best-effort and never blocks the call. Inspect with any SQLite browser or query_x_audit(action="post", limit=50); nothing is auto-pruned (purge_x_audit(older_than_days=…) exists if you ever want retention).

  • Free tier is tight. Your X developer dashboard is the source of truth for quota.
  • An Automated label is your action in X settings; Kazma cannot flip it.
  • If keys leak, regenerate them in the dashboard, then Settings → X → Disconnect and save the new four.