X provider¶
Configuration¶
- module: cb_social_plugin.x_plugin
config:
access_token: "${X_ACCESS_TOKEN}"
state_db_path: "/srv/data/social-publications.db"
edit_policy: "ignore"
access_token is required. base_url defaults to https://api.x.com. Persistent
state_db_path is strongly recommended for mirroring because it enables durable mappings and the
ambiguous-commit recovery protocol; stateless operation remains accepted for compatibility.
Actions and API contracts¶
| Action | Remote operation |
|---|---|
x.create_post |
POST /2/tweets |
x.update_post |
POST /2/tweets with edit_options.previous_post_id |
x.delete_post |
DELETE /2/tweets/{id} |
x.upload_media |
POST /2/media/upload with tweet_image category |
x.mirror_telegram_message |
mirror Telegram messages using compatibility, native-edit, or replace policy |
Edit policy¶
X's current v2 create endpoint also edits an existing Post when edit_options.previous_post_id is
provided. Each successful edit receives a new Post ID while remaining in the same edit history. X
remains authoritative for edit eligibility, windows, and remaining-edit limits.
edit_policy: ignore(default) leaves the remote post unchanged. The newer Telegram source version is stored locally so the same edit is not retried indefinitely.edit_policy: nativesubmits a native edit against the mapped Post ID and advances the mapping to the newly returned Post ID.edit_policy: replacepreserves the previous delete-then-create behavior for compatibility. It is non-atomic: if deletion succeeds and creation fails, the old Post is already gone.
For new edit-propagation rules, prefer native over replace.
Create idempotency and crash recovery¶
X does not document a create-idempotency key for POST /2/tweets, so the mirror cannot atomically
commit both the remote Post and the local source mapping. When state_db_path is configured, it uses
social_publish_attempts as a write-ahead protocol:
- persist an attempt containing source version, payload fingerprint, expected text, and previous Post ID for an edit;
- issue one X create/edit request;
- persist the returned Post ID as a durable receipt;
- promote the result into the normal publication mapping, which clears the completed attempt.
If the process fails after step 3, the retry recovers the durable receipt without another mutation.
If the request itself ends ambiguously before a Post ID is durable, the retry queries the
authenticated user's recent timeline and adopts the result only when exactly one candidate matches
the expected text; edits additionally require the previous mapped Post ID in edit history. Otherwise
the plugin raises XMirrorRecoveryRequired and refuses another POST.
This recovery path means X mirroring credentials need permission to read the authenticated user and recent posts in addition to publishing. An unresolved attempt should remain in the state DB until it can be reconciled or an operator independently proves X did not commit the mutation.
Without state_db_path, Call Baxter preserves the previous stateless mirror behavior. There is no
durable publication mapping or attempt receipt in that mode, so a process failure after X accepted a
create but before local completion can still lead to a duplicate on a later delivery.
Limitations¶
The shared mirror path handles images only. HTTP/provider errors and rate-limit responses propagate as action failures. Native edit eligibility remains subject to X account/product/API rules and the provider's current edit window/remaining-edit controls.