Skip to content

X plugin

The x entry point publishes through the current X API v2 post and media endpoints. It exposes native create/edit/delete/image-upload actions and the shared Telegram mirror workflow.

Configuration

Use a user-context access token authorized to create and manage posts for the target account. base_url defaults to https://api.x.com.

plugins:
  - module: cb_social_plugin.x_plugin
    config:
      access_token: "${X_ACCESS_TOKEN}"
      state_db_path: "/srv/data/social-publications.db"
      edit_policy: "native"
      media_group_wait_seconds: 2

Keep OAuth credentials outside committed configuration. App-only bearer tokens are not a substitute for the user authorization required by post mutations.

Actions

Action Purpose
x.create_post Create a post with POST /2/tweets.
x.update_post Create the next eligible edit version using edit_options.previous_post_id.
x.delete_post Delete an authored post with DELETE /2/tweets/:id.
x.upload_media Upload an image with POST /2/media/upload.
x.mirror_telegram_message Mirror one Telegram message/update.
x.flush_due_media_groups Publish staged Telegram albums that are due.

x.update_post requires previous_post_id and replacement text, with optional media_ids. X returns a new post ID for a successful edit; Call Baxter stores that ID as the new current remote mapping.

Telegram edit policy

edit_policy controls what happens when a Telegram edit reaches an already-mapped X post:

  • native (default) sends edit_options.previous_post_id and advances the local mapping to the new post ID returned by X.
  • ignore leaves the remote post unchanged but records the newer Telegram source version, so the same edit is not retried forever.
  • replace deletes the mapped post and creates a new one. This is non-atomic and is intended only as an explicit fallback strategy.

X decides whether a post remains edit-eligible. The platform documents bounded edit eligibility and post types that cannot be edited, so a native edit can be rejected even though the endpoint itself is supported. Call Baxter surfaces that HTTP failure rather than silently switching to a destructive replacement policy.

actions:
  - kind: x.mirror_telegram_message
    args:
      update: "{{ event.attrs.raw_update }}"
      required_hashtag: "#social"

Images

The alpha mirror uploads at most four Telegram photos/image documents, using the tweet_image media category, and references their media IDs in the post request. Video and animated-GIF media need the chunked upload/processing flow and are deliberately not emulated in this first slice.

Idempotency and failure boundary

Unlike the Bluesky deterministic-record target and Mastodon's create idempotency header, this X plugin has no durable remote create-idempotency key. A crash after X accepts a create but before the local mapping commits can therefore duplicate a source on retry. Use state_db_path and monitor failed publications closely.

replace is also non-atomic: if deletion succeeds and the subsequent create fails, Call Baxter cannot restore the old post.