← xpostCONTENT HISTORY

Update to xpost

Snapshot Sep 30, 2026 · 23:00 UTC · version 2.0.0

Collection source: not recorded for this historical snapshot.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "name": "xpost",
  "description": "Post to social media through xpost — draft, schedule, edit and check delivery across X, Instagram, LinkedIn, Facebook, TikTok, YouTube, Threads, Bluesky and Pinterest, with the person's approval and their brand rules enforced server-side. Use whenever asked to post, schedule, cross-post or take down social content, or to check whether a post went out and how it did.",
  "included_files": [],
  "skill_md_contents": "---\nname: xpost\ndescription: Post to social media through xpost — draft, schedule, edit and check delivery across X, Instagram, LinkedIn, Facebook, TikTok, YouTube, Threads, Bluesky and Pinterest, with the person's approval and their brand rules enforced server-side. Use whenever asked to post, schedule, cross-post or take down social content, or to check whether a post went out and how it did.\n---\n\n# Posting through xpost\n\nxpost is the posting layer between you and the user's social audience. It\nholds their connected accounts, their approval queue and their brand rules.\nA post you create here is **submitted, not published**: it waits for the\nperson to approve it and goes out when they say yes. Never try to work\naround that — the queue is the product.\n\nA post made by hand in a browser is invisible to all three, and cannot be\ntracked, retried or taken down from here. When the user asks to post,\nuse these tools.\n\n## Connection\n\n**Preferred: the `xpost` MCP server.** If it is connected you have the\ntools below and nothing else is needed. If it is not connected, tell the\nuser how — no key required:\n\n- Claude Code: `claude mcp add --transport http xpost https://xpost.to/api/mcp`, then `/mcp` → xpost → sign in.\n- Cursor / Windsurf / Gemini CLI / VS Code: add `https://xpost.to/api/mcp` as an HTTP MCP server; the client opens the sign-in page.\n- ChatGPT: xpost is in the plugin directory — find it, add it, sign in.\n- Claude.ai / Claude Desktop: Settings → Connectors → Add custom connector, paste `https://xpost.to/api/mcp`, sign in.\n\n**Fallback: the `xpost` CLI** (`npx xpost`, zero install), when there is no\nMCP client — a shell, a script, a headless box. Every command is one route of\nthe public API and answers with the API's own JSON; a refusal prints the\nbody and exits 1, so read `error_code`. Sign in once with `npx xpost login`\n(a browser opens), or set `XPOST_API_KEY` where no browser can. The tool\nnames below map one to one:\n\n```bash\nnpx xpost project                      # get_project\nnpx xpost accounts                     # list_accounts\nnpx xpost rules <accountId>            # get_posting_rules\nnpx xpost upload ./pic.jpg --alt \"…\"   # upload_media → media id\nnpx xpost posts create -c \"…\" -a <account> [-a …] [--media <id>] [--at 2026-10-01T09:00:00+02:00] [--draft] [--config '{\"x\":{\"first_comment\":\"…\"}}']\nnpx xpost posts list --status pending_approval,rejected\nnpx xpost posts edit <postId> -c \"…\"   # update_post — answers with a NEW id\nnpx xpost posts delete <postId>\nnpx xpost receipt <postId>             # get_delivery_receipt\n```\n\nRaw HTTP works too: `XPOST_URL` (default `https://xpost.to`) +\n`Authorization: Bearer $XPOST_API_KEY`, spec at `$XPOST_URL/api/v1/openapi.json`.\n\n## The tools\n\n| Do this | Tool |\n|---|---|\n| Learn the project: timezone, approval mode, signature, guardrails, allowance | `get_project` |\n| See destinations and what each can carry | `list_accounts` |\n| The exact options an account takes (placements, first comment, boards…) | `get_posting_rules` |\n| Attach a file the user gave you (path, URL, or a chat attachment) | `get_upload_ticket` → `upload_media` |\n| Create one post | `create_post` |\n| Create many | `bulk_post` |\n| Find posts, see verdicts and rejection reasons | `list_posts` |\n| Change words, media, time or options | `update_post` |\n| Remove your own unpublished post | `delete_post` |\n| Where it landed, per account | `get_delivery_receipt` |\n| A destination failed — try it again | `retry_delivery` |\n| Pull a live post down (only when asked) | `take_down_post` |\n| Which accounts need reconnecting | `list_connection_issues` |\n| Test a caption against the rules before writing more | `check_guardrails` |\n| Learn what works | `get_post_metrics`, `get_insights`, `get_top_posts`, `get_best_times` |\n\n## Workflow\n\n1. **`get_project`, then `list_accounts`.** You need account ids, and each\n   account says what it can carry (`limits`, `can`). An **empty** list comes\n   with `connect_message` and `connect_url`: nothing can be posted until the\n   person connects an account, and you may be the only one who can tell them.\n   Pass both on, verbatim.\n2. **`get_posting_rules`** for the accounts you will use, whenever you set\n   anything beyond a caption. It is the option catalog per ACCOUNT — a key\n   that is absent cannot be used on that account, whatever the platform\n   allows elsewhere. Pinterest answers live `boards` with `eligible`; pin\n   only to eligible ones.\n3. **Media.** Instagram, TikTok, YouTube and Pinterest require it; stories\n   and reels do too. `upload_media` takes a local `path` (stdio server only),\n   a public `url`, or base64 `data`. A picture, video or PDF the user\n   attached to the chat has a route: `get_upload_ticket`, then\n   `upload_media` with the ticket — it reaches xpost through the user's own\n   browser. Redeeming a ticket the person has filled answers with every file\n   on it; pass the `upload_ticket` itself to `create_post` so nothing is\n   left out. A PDF is a LinkedIn document post: one PDF, alone, LinkedIn only.\n4. **`create_post`.** `scheduled_at` is ISO 8601 with an offset, in the\n   project's timezone (from `get_project`); omit it and the post goes out on\n   approval. **`\"next_slot\"`** hands the time to the project's own posting\n   schedule — use it when the person says \"queue it\", or names no time and\n   `get_project` shows `queue.configured: true` (its `next_slots` are what\n   they will get). Refused as `no_queue_slots` where there is no schedule;\n   then ask for a time rather than inventing one. `is_draft: true` files a scratchpad draft — no queue, no\n   approval link; the person sends it from its page. Per-account words and\n   options go in `platform_configurations`, keyed by platform:\n   `{\"x\": {\"caption\": \"shorter\", \"first_comment\": \"…\", \"thread\": [\"…\"]}}`,\n   `{\"instagram\": {\"placement\": \"stories\"}}`. Every text field — caption,\n   overrides, first comment, thread — runs through the guardrails.\n5. **Read the answer honestly.**\n   - `pending_approval` — the normal outcome. Say the post is waiting for\n     their approval and print the **See post preview** link the result\n     carries, first, before any summary. Do not retry. Do not say \"published\".\n   - `scheduled` / `posted` — say when, in the project's timezone.\n   - `ok: false` with `error_code` — a refusal, as data. Guardrail\n     violations name the rule in `violations[].detail`: rewrite once to\n     comply, never evade. A missing plan (`publishing_needs_plan`) or a\n     missing account comes with a `message` written to be passed on.\n6. **Before assuming a post is still waiting, `list_posts`.** Its `from` /\n   `to` bound the scheduled time, so \"what goes out next week\" is one call,\n   soonest first. The person can\n   reject through a link you never see; a `rejected` row carries\n   `rejectionReason`, which is what to fix. To change anything, `update_post`\n   — it answers with a **new id**, and an edit to an approved post goes back\n   into the queue because the approval was for the old words.\n7. **After publish time, `get_delivery_receipt`.** One row per destination:\n   report the live link, or the reason it failed, per account. `retry_delivery`\n   for a failed row; `take_down_post` only when the user asks for it, because\n   there is no undo.\n\n## Bulk\n\nFor a calendar or a CSV, `bulk_post` (≤100 rows) instead of a loop. Rows are\nindependent — always relay the per-row report: which were created (all\n`pending_approval` in copilot mode is normal) and which failed, with the\nreason. Resend only the failed rows. Give each row an `idempotency_key` so a\nresend after a timeout returns `repeated: true` instead of a second copy.\n\n## Learning what works\n\nBefore drafting, when there is history: `get_top_posts` shows what actually\nperformed (study angle, length and format; never copy captions), and\n`get_best_times` ranks slots from this workspace's own results — read\n`signal` first: `none` or `thin` means there is no history to speak of, so\nsay so rather than name a best hour. After a post has been live a while,\n`get_post_metrics`; `null` means the platform does not report that metric,\nnot zero, and stories are never reported on by anyone. `get_insights` for\n\"how are we doing\".\n\n## Rules\n\n- One post per ask. Do not fan out variations unless asked.\n- Never promise a time you did not get back. Timing comes from\n  `scheduled_at` and `publishes` in the answer, on the project's clock.\n- A guardrail block means rewrite once; a held post means tell the person.\n- Report outcomes faithfully, including partial ones: \"delivered to X,\n  failed on Instagram: <reason>\".\n- Never speak about upload plumbing (sandboxes, hosts, tickets). The card\n  says what it needs to say; your words go on what is still open, like a\n  missing caption.\n"
}

SHA-256: f9bad7bb9d04821e7fbab43dfcc2bb5cdc47edabc84aa6ac43b57c65d3065828