AdvisorPPC X Ads MCP Server
Manage X (Twitter) ads from MCP clients (Claude Code, Cursor, Grok, etc.) with a policy-safe connector to Ads API v12.
Read & monitor: list ad accounts, funding instruments, campaigns, ad groups, ads, targeting, custom audiences, DNR lists, pixels, creatives, tweets/cards/media, search targeting values, estimate audience size, find active entities, and pull analytics/reach.
Create/update structure: create and update campaigns and ad groups (budget is set on the ad group), pause/resume, soft-delete entities, and add/replace targeting. Writes default to PAUSED, and spend/status/delete actions require explicit confirm flags.
Audiences & Do Not Reach: create/update/delete custom audiences, add or remove users via SHA-256 hashed emails/phones/handles, see where an audience is targeted, and manage account-level DNR suppression lists.
Pixels: create, list, get, update, and delete X pixels/web event tags.
Creative pipeline: upload images and chunked videos, create cards and promoted-only posts, promote existing posts, and run composite image/video ad creation; it stops if a creative fails and never substitutes a different asset.
Scheduler & agents: set up the AdvisorPPC scheduler, schedule/cancel/pause jobs, and enable agents like publish_queue, analytics_digest, paused_audit, and health.
Transports & UI: works over stdio or Streamable HTTP, with an MCP dashboard/scheduler UI.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@AdvisorPPC X Ads MCP Server show me my active campaigns and their spend"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
AdvisorPPC X Ads
Policy-safe X (Twitter) Ads for MCP clients — Claude Code, Cursor, Grok, VS Code, or any Streamable HTTP host.
This is the operator connector reverse-engineered from Grok’s first-party X Ads tools (27 curated tools) and mapped onto Ads API v12, plus audience/pixel/DNR and chunked video that Grok’s 27 omit. Writes default to PAUSED. Spend, pause, and delete require an explicit confirm flag after a named user ask.
Website · Tasks · Reverse-engineering · MCP v2 notes · Scheduler
What you get
Layer | Detail |
Protocol | MCP 2026-07-28 (SDK v2 |
Transports | stdio (local) and Streamable HTTP |
UI | MCP Apps dashboard + scheduler ( |
API |
|
Safety | Paused-by-default, |
Scheduler | AdvisorPPC queue (X has none). HTTP auto-starts the worker. Import |
Related MCP server: mcp-server-reddit-ads
Tools (51)
Read: x_ads_list_accounts · x_ads_list_funding · x_ads_list_campaigns · x_ads_list_line_items · x_ads_list_ads · x_ads_list_targeting · x_ads_list_audiences · x_ads_audience_targeted · x_ads_list_dnr · x_ads_list_pixels · x_ads_get_pixel · x_ads_list_creatives · x_ads_get_tweets · x_ads_get_cards · x_ads_get_media · x_ads_search_targeting · x_ads_estimate_audience · x_ads_active_entities · x_ads_get_analytics · x_ads_reach
Write structure: x_ads_create_campaign · x_ads_create_ad_group · x_ads_update_campaign · x_ads_update_ad_group · x_ads_set_status · x_ads_add_targeting · x_ads_delete
Audiences & DNR: x_ads_create_audience · x_ads_update_audience · x_ads_delete_audience · x_ads_audience_users · x_ads_create_dnr · x_ads_delete_dnr · x_ads_dnr_users
Pixels (web event tags): x_ads_create_pixel · x_ads_update_pixel · x_ads_delete_pixel
Write creative: x_ads_upload_media (images + chunked video) · x_ads_create_card · x_ads_create_tweet · x_ads_create_ad · x_ads_create_image_ad · x_ads_create_video_ad
Scheduler / agents: x_ads_scheduler_setup · x_ads_scheduler_status · x_ads_scheduler_settings · x_ads_schedule_list · x_ads_schedule_create · x_ads_schedule_cancel · x_ads_agents_list · x_ads_agent_set
Install
git clone https://github.com/atmosphere-ai/advisorppc-x-ads
cd advisorppc-x-ads
npm install
cp .env.example .env # set X_ADS_ACCESS_TOKEN
npm run buildToken: an X developer app with scopes ads.read, ads.write, offline.access (and media.write if you upload). Auth URL https://x.com/i/oauth2/authorize, token URL https://api.x.com/2/oauth2/token. PKCE S256; X rejects client_secret_post.
Claude Code / Cursor (stdio)
{
"mcpServers": {
"advisorppc-x-ads": {
"command": "node",
"args": ["/absolute/path/to/advisorppc-x-ads/dist/index.js"],
"env": { "X_ADS_ACCESS_TOKEN": "…", "ADVISORPPC_SCHEDULER": "1" }
}
}
}Or the plugin path: claude plugin marketplace add atmosphere-ai/advisorppc-x-ads then install advisorppc-x-ads@advisorppc.
Streamable HTTP
npm run start:http
# POST http://127.0.0.1:3333/mcp
# GET http://127.0.0.1:3333/scheduler
# Authorization: Bearer <token> (overrides env)Grok web custom connector: server URL of your hosted /mcp, PKCE, scopes ads.read ads.write offline.access.
Grok Build CLI
[mcp_servers.advisorppc-x-ads]
command = "node"
args = ["/absolute/path/to/dist/index.js"]
[mcp_servers.advisorppc-x-ads.env]
X_ADS_ACCESS_TOKEN = "…"
ADVISORPPC_SCHEDULER = "1"Hierarchy (do not skip)
account → funding instrument → campaign → ad group (line item)
├─ targeting
└─ ad (promoted tweet → tweet → card → media_key)Budget is on the ad group. Campaign create does not take a budget. An ad serves only when campaign and ad group are both ACTIVE.
Safety
Action | Default | To override |
Create campaign / ad group |
|
|
Pause / resume / delete | refused |
|
Failed creative | stop | never promote a substitute |
Invented name or budget | forbidden | user supplies it |
Schedule | AdvisorPPC queue |
|
Skills (bundled)
Skill | When to use |
| Auth, tokens, first |
| Playbooks (audit, analytics, create image ad, schedule pause/resume) |
| How this server is built on MCP v2 |
| Inline dashboard / |
Scheduler (built-in)
X cannot natively schedule. Call x_ads_scheduler_setup once — it starts the worker and returns paste-ready configs for Claude, ChatGPT, Cursor, Grok, and the AdvisorPPC backend (import { createScheduler } from "@advisorppc/x-ads/schedule").
Agents: publish_queue (fires due jobs), analytics_digest / paused_audit (snapshots + optional webhook, never auto-resume spend), health. Allowlisted jobs: set_status, update_campaign, update_ad_group, delete. Details: docs/SCHEDULER.md.
Develop
npm test
npm run typecheck
npm run dev # stdio
npm run dev:http
npm run workerWhat this is not
Not a 1:1 clone of X’s official 74-tool MCP at
https://ads-api.x.com/mcp(app lists, tweet previews, app event tags, and tracking-partner tags still live there).Not a native X scheduler. The built-in queue is AdvisorPPC's.
Not organic posting — that is
atmosphere-ai/advisorppc-x-organic.Not a Google Ads connector — that is
advisorppc-org/advisorppc-plugin→https://mcp.advisorppc.com/claude.
License
MIT for this repository. The AdvisorPPC name and hosted service remain Advisor Media.
Available Tools
51 toolsx_ads_active_entitiesActive entitiesARead-only
Which CAMPAIGN / LINE_ITEM / PROMOTED_TWEET ids had activity in a window. Call before analytics so you don't request empty ids. Times are whole-hour ISO 8601.
Inputs: entity (required), end_time (required), account_id (required), start_time (required).
x_ads_add_targetingAdd targetingA
Add targeting on an ad group. criteria: {targeting_type, targeting_value, operator_type?}. Types: LOCATION, CUSTOM_AUDIENCE, FOLLOWER_LOOK_ALIKE, INTEREST, LANGUAGE, PLATFORM, DEVICE, GENDER, AGE, EVENT, KEYWORD. GENDER=MALE/FEMALE, AGE like AGE_25_TO_34. operator EQ include (default) or NE exclude. replace=true removes existing criteria of the SAME types first. Only set targeting the user asked for.
Inputs: criteria (required), account_id (required), line_item_id (required), replace.
x_ads_agent_setEnable or configure an agentA
Turn an agent on/off and set every_ms / account_id. Live agents (if any) need confirm=true. analytics_digest / paused_audit never change status.
Inputs: agent_id (required), confirm, enabled, every_ms, +1 more.
x_ads_agents_listList scheduler agentsARead-only
Built-in agents: publish_queue, analytics_digest, paused_audit, health. Digests never auto-resume spend; they snapshot and optionally POST webhook_url for Claude/ChatGPT/AdvisorPPC.
Inputs: no named parameters.
x_ads_audience_targetedWhere audience is targetedBRead-only
Campaigns and ad groups currently targeting a custom audience.
Inputs: account_id (required), custom_audience_id (required), with_active.
x_ads_audience_usersAdd/remove audience usersA
Add (Update) or remove (Delete) members of a custom audience. Pass raw emails/phones/handles — this server SHA-256 hashes after X normalization (lowercase email, strip @, digits-only phone). Set hashed=true only if values are already hex SHA-256. Max 2500 users per call. Never log the raw list.
Inputs: users (required), account_id (required), custom_audience_id (required), hashed, +1 more.
x_ads_create_adPromote postA
Promote an EXISTING post under an ad group. Ad serves only if group AND campaign are ACTIVE. For a brand-new image use x_ads_create_image_ad. If the intended creative failed to upload or process, STOP. Do not promote a still, a source image, or a pre-existing library asset.
Inputs: tweet_id (required), account_id (required), line_item_id (required).
x_ads_create_ad_groupCreate ad groupA
Create a line item under a campaign. PAUSED by default. All groups in a campaign must share objective and product_type. Budget is set HERE (daily_budget_amount_local_micro / total_budget_amount_local_micro). Bid omitted = automatic. Amounts are local micro-units (1 unit = 1_000_000). If creating multiple groups from one stated budget, ASK split vs each. confirm_spend required for ACTIVE.
Inputs: objective (required), account_id (required), campaign_id (required), name, +8 more.
x_ads_create_audienceCreate custom audienceA
Create an empty CRM custom audience. Then load members with x_ads_audience_users (SHA-256 hashed). Name must come from the user. targetable stays false until it is large enough.
Inputs: name (required), account_id (required), description.
x_ads_create_campaignCreate campaignA
Create a campaign PAUSED by default (no spend). Pass entity_status=ACTIVE only with confirm_spend=true after an explicit user ask. Needs funding_instrument_id. BUDGETS ARE SET PER AD GROUP — do not pass a campaign budget. Don't invent names.
Inputs: name (required), account_id (required), funding_instrument_id (required), confirm_spend, +1 more.
x_ads_create_cardCreate cardA
Create an ad card from raw components. Returns card_uri. Website: [{"type":"MEDIA","media_key":"…"},{"type":"DETAILS","title":"…","destination":{"type":"WEBSITE","url":"https://…"}}]. App cards use BUTTON not DETAILS. Carousel uses SWIPEABLE_MEDIA. If the intended creative failed to upload or process, STOP. Do not promote a still, a source image, or a pre-existing library asset.
Inputs: account_id (required), components (required), name.
x_ads_create_dnrCreate Do Not Reach listA
Create the account DNR (suppression) list. One per account; API names it 'Do Not Reach List'. Optional description only. Then load emails with x_ads_dnr_users.
Inputs: account_id (required), description.
x_ads_create_image_adCreate image ad (pipeline)A
Composite: upload creative → website card → nullcast post → promote onto an EXISTING ad group. Provide media_url or inline image. Requires text + destination_url. Serves only when campaign and ad group are ACTIVE. If the intended creative failed to upload or process, STOP. Do not promote a still, a source image, or a pre-existing library asset.
Inputs: text (required), account_id (required), line_item_id (required), destination_url (required), +3 more.
x_ads_create_pixelCreate X Pixel (web event tag)A
Create a conversion / site-visit web event tag. type default SITE_VISIT. click_window and view_through_window in days (0,1,7,14,30,60,90). retargeting_enabled default false. Returns embed_code + website_tag_id. Don't invent names.
Inputs: name (required), account_id (required), type, click_window, +2 more.
x_ads_create_tweetCreate ad postA
Create a promoted-only (nullcast) post. media_keys XOR card_uri. Returns tweet id; promote with x_ads_create_ad. nullcast default true. If the intended creative failed to upload or process, STOP. Do not promote a still, a source image, or a pre-existing library asset.
Inputs: text (required), account_id (required), card_uri, nullcast, +2 more.
x_ads_create_video_adCreate video ad (pipeline)A
Composite: chunked video upload (amplify_video) → website card (or media_keys if no destination_url) → nullcast post → promote onto an EXISTING ad group. Provide media_url or inline video (base64). Requires text. If the intended creative failed to upload or process, STOP. Do not promote a still, a source image, or a pre-existing library asset.
Inputs: text (required), account_id (required), line_item_id (required), name, +3 more.
x_ads_deleteDelete entityADestructive
SOFT-delete CAMPAIGN, LINE_ITEM, or PROMOTED_TWEET. Children go with the parent. Cannot be undone here. Deleting an ad does not delete the post. confirm=true after the user named the exact entity. Never inferred.
Inputs: entity (required), entity_id (required), account_id (required), confirm.
x_ads_delete_audienceDelete custom audienceADestructive
Soft-delete a custom audience. Line items targeting it will stop matching. confirm=true after the user named the audience.
Inputs: account_id (required), custom_audience_id (required), confirm.
x_ads_delete_dnrDelete Do Not Reach listADestructive
Delete the account DNR list. confirm=true after the user named it. Suppression stops.
Inputs: account_id (required), do_not_reach_list_id (required), confirm.
x_ads_delete_pixelDelete X PixelADestructive
Delete a web event tag. Conversion tracking for that event stops. confirm=true after the user named it.
Inputs: account_id (required), web_event_tag_id (required), confirm.
x_ads_dnr_usersAdd/remove DNR usersA
Add (Update) or remove (Delete) emails on the Do Not Reach list. Emails only. Raw emails are SHA-256 hashed here. hashed=true if already hex SHA-256. expires_at optional ISO 8601 (must be < 13 months).
Inputs: emails (required), account_id (required), do_not_reach_list_id (required), hashed, +2 more.
x_ads_estimate_audienceEstimate audience sizeARead-only
Estimate reachable unique users for a targeting set BEFORE spending. criteria same shape as x_ads_add_targeting. Does not create anything.
Inputs: criteria (required), account_id (required).
x_ads_get_analyticsGet analyticsARead-only
Sync stats for up to 20 campaigns, ad groups, or ads. Range must be ≤ 7 days. Times whole-hour ISO 8601. granularity TOTAL (default) / DAY / HOUR. metric_groups default ENGAGEMENT,BILLING. Placement ALL_ON_TWITTER. Spend is billed_charge_local_micro / 1_000_000.
Inputs: entity (required), end_time (required), account_id (required), entity_ids (required), +3 more.
x_ads_get_cardsGet cardsARead-only
Expand card_uri values (card://…) into type, components, buttons, media_keys. Images inside a card are media_keys, not URLs — pass those to x_ads_get_media.
Inputs: card_uris (required), account_id (required).
x_ads_get_mediaGet media URLsARead-only
Resolve media_key values (e.g. 3_2069…) to media_url / poster_media_url from the Media Library.
Inputs: account_id (required), media_keys (required).
x_ads_get_pixelGet X PixelBRead-only
Fetch one web event tag: type, windows, retargeting, embed_code, website_tag_id.
Inputs: account_id (required), web_event_tag_id (required).
x_ads_get_tweetsGet ad postsARead-only
Fetch posts behind ads (tweet_id from x_ads_list_ads) WITH card_uri and media. Use this, not a generic post fetch, to tell whether an ad has a card/image. Up to ~200 ids. tweet_type default PUBLISHED (includes promoted-only).
Inputs: tweet_ids (required), account_id (required), tweet_type.
x_ads_list_accountsList ad accountsARead-only
List X ad accounts the connected token can manage. Returns id, name, approval status. Call this first.
Inputs: no named parameters.
x_ads_list_adsList adsARead-only
List promoted posts. Each row has status and tweet_id. Filter with line_item_id. Deleted ads are hidden unless with_deleted=true; the underlying post is not deleted.
Inputs: account_id (required), count, cursor, line_item_id, +1 more.
x_ads_list_audiencesList custom audiencesARead-only
List tailored audiences with size and targetable status. Use ids as CUSTOM_AUDIENCE targeting values.
Inputs: account_id (required), count, cursor.
x_ads_list_campaignsList campaignsBRead-only
List campaigns: status (ACTIVE/PAUSED/DRAFT) and any budgets. Requires account_id.
Inputs: account_id (required), count, cursor, with_deleted.
x_ads_list_creativesList creativesARead-only
Creative inventory: cards (cap 200) and media library (cap 50) in one call. Page with cards_cursor / media_cursor.
Inputs: account_id (required), count, cards_cursor, media_cursor.
x_ads_list_dnrList Do Not Reach listsARead-only
Account-level exclusion list (suppression). An account can have at most one DNR list. Excludes those users from ALL campaigns on the account; it does not strip them from custom audiences.
Inputs: account_id (required), count, cursor.
x_ads_list_fundingList funding instrumentsARead-only
How campaigns are paid. Trust able_to_fund=true and empty reasons_not_able_to_fund — NOT description, which for self-serve cards can read '(no payment method has been set up yet)' even when serving. The API exposes one representative instrument for self-serve accounts.
Inputs: account_id (required), count, cursor.
x_ads_list_line_itemsList ad groupsARead-only
List line items (ad groups): status, objective, bid. Optionally filter by campaign_id. Budget lives HERE, not on the campaign.
Inputs: account_id (required), count, cursor, campaign_id, +1 more.
x_ads_list_pixelsList X Pixels (web event tags)BRead-only
List Universal Website Tags / conversion pixels. id is the web_event_tag_id; website_tag_id is the pixel id used in embed code and Conversion API (tw-{website_tag_id}-…). embed_code is the snippet to put on the site.
Inputs: account_id (required), count, cursor.
x_ads_list_targetingList targetingARead-only
List targeting criteria for an ad group. Always pass line_item_id to see why groups differ.
Inputs: account_id (required), count, cursor, line_item_id.
x_ads_reachCampaign reachARead-only
Unique reach + average frequency for campaigns. Window ≤ 7 days, whole-hour ISO 8601. Impressions cannot tell you this.
Inputs: end_time (required), account_id (required), start_time (required), campaign_ids (required).
x_ads_schedule_cancelCancel, pause, or resume a jobADestructive
Cancel (terminal), pause, or resume a named job id. confirm=true after the user named the job.
Inputs: job_id (required), action, confirm.
x_ads_schedule_createSchedule a jobA
Enqueue a future ads action on AdvisorPPC's queue (X cannot natively schedule). tool must be allowlisted: set_status, update_campaign, update_ad_group, delete. arguments must be user-supplied ids/status — never invent entities. confirm=true after they named the time and the entity. Enables publish_queue. Kinds: once (run_at ISO), every (every_ms), cron (5-field).
Inputs: kind (required), tool (required), arguments (required), cron, +6 more.
x_ads_schedule_listList scheduled jobsARead-only
List AdvisorPPC queue jobs (not X native). Filter by status.
Inputs: status.
x_ads_scheduler_settingsScheduler settingsC
Get or patch timezone, digest webhook_url (Claude/ChatGPT/AdvisorPPC callback), tick_ms, public_url.
Inputs: tick_ms, timezone, public_url, webhook_url.
x_ads_scheduler_setupAuto-setup schedulerA
Self-configure the AdvisorPPC scheduler for Claude, ChatGPT, Cursor, Grok, raw HTTP, or the AdvisorPPC backend. Starts the in-process worker, writes the job store, returns paste-ready MCP snippets. Does not invent campaigns or spend.
Inputs: vendor, timezone, account_id, public_url, +1 more.
x_ads_scheduler_statusScheduler statusARead-only
AdvisorPPC's own job queue (Ads API has no native schedule). Shows worker, agents, upcoming jobs, last runs. HTTP mode starts the worker by itself.
Inputs: no named parameters.
x_ads_search_targetingSearch targeting valuesARead-only
Resolve human terms into targeting_value ids for x_ads_add_targeting. kind ∈ locations, interests, languages, platforms, devices, events, app_store_categories, conversations, network_operators, tv_markets, tv_shows. For locations pass query and optional location_type. Custom audiences: use x_ads_list_audiences.
Inputs: kind (required), count, query, location_type.
x_ads_set_statusPause or resumeA
Set a campaign or ad group to ACTIVE or PAUSED. Pausing a campaign stops its groups and ads. Ads have no on/off switch — pause the group or delete the ad. confirm=true after an explicit named ask. Never inferred.
Inputs: entity (required), status (required), entity_id (required), account_id (required), +1 more.
x_ads_update_ad_groupUpdate ad groupA
Partial update of name, bid, and/or budget micros. Only change what the user named. confirm=true for money fields.
Inputs: account_id (required), line_item_id (required), name, confirm, +3 more.
x_ads_update_audienceUpdate custom audienceA
Rename or change description of a custom audience. Only fields the user named.
Inputs: account_id (required), custom_audience_id (required), name, description.
x_ads_update_campaignUpdate campaignA
Partial update of name and/or budget micros. Only change fields the user named. Daily must be ≤ total. Money-affecting: pass confirm=true.
Inputs: account_id (required), campaign_id (required), name, confirm, +2 more.
x_ads_update_pixelUpdate X PixelA
Partial update of name, windows, or retargeting on a web event tag. Only fields the user named.
Inputs: account_id (required), web_event_tag_id (required), name, click_window, +2 more.
x_ads_upload_mediaUpload mediaA
Upload an image or video via media_url or inline base64 (media.data). Images use simple upload; video and files >5MB use v2 chunked INIT/APPEND/FINALIZE and poll until processed. Ads videos use media_category amplify_video. Returns media_key for cards/tweets. Provide exactly one source. If the intended creative failed to upload or process, STOP. Do not promote a still, a source image, or a pre-existing library asset.
Inputs: account_id (required), name, media, for_ads, +1 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 51 tools
Most tools target distinct resources, but there are overlapping clusters: scheduler_status vs schedule_list both expose job/queue information, create_ad vs create_image_ad vs create_video_ad are all ad-creation entry points, and get_cards/list_creatives/get_tweets/get_media have related creative-resolution duties. Descriptions help, but an agent would need to read carefully to avoid selecting the wrong tool.
The x_ads_ prefix is consistent, and the dominant verb_noun pattern (list_campaigns, create_pixel, update_ad_group) is clear. There are notable exceptions like schedule_list, agents_list, audience_users, audience_targeted, and active_entities that invert or break the pattern, but they are relatively few and do not make the naming chaotic.
51 tools is far beyond the typical well-scoped MCP surface and crosses the 50+ extreme-mismatch threshold. Even though many tools map to real X Ads operations, the scheduler/agents sub-domain and composite ad-creation helpers add significant bulk that could be consolidated or split into a separate server.
The set provides solid lifecycle coverage: campaigns, ad groups, ads, audiences, DNR lists, pixels, targeting, creatives, media, analytics, and reach all have create/read/update/delete or equivalent operations. Minor gaps exist, such as no single-entity get for campaigns/ad groups, no direct remove-targeting tool, and no way to edit a scheduled job after creation, but these are workable.
Maintenance
Related MCP Connectors
Google Ads MCP: reports, search terms, negatives, budgets, campaigns. Approval on every write.
Hosted TikTok ads MCP with OAuth, bounded reads, and prepare/confirm writes.
Hosted Meta ads MCP with OAuth, bounded reads, and prepare/confirm writes.
Ads MCP for Meta, Google Ads, TikTok, Snapchat and LinkedIn: stage launches and manage campaigns.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables management of OpenAI Ads campaigns, ad groups, ads, and insights through MCP, with approval-gated live mutations for safe write operations.127 npmMIT
- AlicenseAqualityCmaintenanceEnables reading and writing Reddit Ads campaigns, ad groups, ads, and performance reports with tiered safety controls, using the Reddit Ads API v3.3084 npm1MIT
- FlicenseNot gradedqualityCmaintenanceEnables management of Google Ads accounts via MCP, providing read and write tools for campaigns, ad groups, keywords, assets, and more, with support for reporting and mutations.-
- FlicenseNot gradedqualityCmaintenanceEnables MCP-based interaction with the X (Twitter) API v2, supporting reads like timeline, mentions, and search, and writes through a two-step draft-and-confirm process that ensures human-visible previews before any public post.-