# Direct messages actions

> Direct messages: 44 Xholic actions (16 read, 28 change), each with its MCP tool, REST endpoint and CLI command.

Source: https://docs.xholic.ai/actions/dm/

44 actions. Every field marked *Required* must be sent; `account`, `dryRun`, `idempotencyKey`, `confirmCredits` and `quoteId` are the [control fields](/api/responses/#control-fields) every surface adds.

## DM overview

`dm.dashboard` · read

Direct messages at a glance: sender state (paused, stopped, needs reconnect), today's usage and limits, active campaigns and unread replies.

- **MCP tool:** `dm_dashboard`. Catalog: reach it with `find_actions` and `run_read_action`, or turn on the full toolset.
- **REST:** `GET /v1/dm/dashboard`
- **CLI:** `xholic dm dashboard`
- **Scope:** `read`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `read`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |

## Find people to DM

`dm.preview_recipients` · write

Find people to message from a post's repliers, a search, handles, an X list, recent interactions or a contact list, with whether each can be messaged. Returns the recipients (with preview tokens) that send_dms takes.

- **MCP tool:** `dm_preview_recipients`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/preview-recipients`
- **CLI:** `xholic dm preview-recipients`
- **Scope:** `write`
- **Plan:** Pro plan or higher
- **Cost:** Live X lookup: no credits, counts toward the plan's X data limits.
- **Rate limit bucket:** `live_x`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `type` | "FROM_TWEET" \\| "SEARCH" \\| "HANDLES" \\| "X_LIST" \\| "INTERACTIONS" \\| "CONTACT_LIST" | Yes | Where to find people: FROM_TWEET (repliers/reposters of tweetUrl), SEARCH (query), HANDLES (handles), X_LIST (listUrl or numeric listId), INTERACTIONS (people who replied to or reposted the account in the last days), CONTACT_LIST (listId of a contact list). |
| `tweetUrl` | string (uri) ≤ 2048 chars | No |  |
| `includeReplies` | boolean | No |  |
| `includeReposts` | boolean | No |  |
| `keyword` | string ≤ 120 chars or null | No | FROM_TWEET: only replies matching one of these comma-separated terms. |
| `excludeMessagedForPost` | boolean | No |  |
| `continuation` | object or null | No | FROM_TWEET: the continuation from a previous preview, to scan deeper. |
| `query` | string ≤ 80 chars | No |  |
| `handles` | array of string ≤ 120 chars (min 1, max 500) | No |  |
| `listId` | string ≤ 64 chars or null | No | X_LIST: numeric X list id. CONTACT_LIST: the contact list id. |
| `listUrl` | string (uri) ≤ 2048 chars or null | No |  |
| `days` | integer 1–7 | No |  |
| `skipQueued` | boolean | No |  |
| `skipCooldown` | boolean | No |  |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## Send DMs now

`dm.quick_send` · publish

Send one message to up to 500 people found with dm.preview_recipients, paced safely from the user's X account. Write the message yourself; variables like [first] personalise it. Only pass confirmedConsent true after the user confirmed these people agreed to or expect the message. Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `send_dms`. Loaded by default in MCP clients.
- **REST:** `POST /v1/dm/quick-send`
- **CLI:** `xholic dm quick-send`
- **Scope:** `publish`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string ≤ 80 chars or null | No |  |
| `messageTemplate` | string ≤ 5000 chars | Yes | The message. Variables: [first], [name], [handle], [location], [followers], list fields such as [first_line], with optional fallbacks like [first\|there]; spintax like {Hi\|Hey}. Write it yourself in the user's voice. |
| `confirmedConsent` | true | Yes |  |
| `recipients` | array of object (min 1, max 500) | Yes | Recipients exactly as dm.preview_recipients returned them. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## Pause or resume all DMs

`dm.control_queue` · publish

PAUSE_ALL holds every queued DM, RESUME_ALL continues, CANCEL_ALL_PENDING cancels everything not sent yet, ENABLE_SENDING turns DMs back on after a stop. Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `dm_control_queue`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/control-queue`
- **CLI:** `xholic dm control-queue`
- **Scope:** `publish`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `action` | "PAUSE_ALL" \\| "RESUME_ALL" \\| "CANCEL_ALL_PENDING" \\| "ENABLE_SENDING" | Yes |  |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## Stop all DMs

`dm.stop_all` · write

Emergency stop: cancel every pending DM and stop sending until turned back on with dm.control_queue ENABLE_SENDING. Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `dm_stop_all`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/stop-all`
- **CLI:** `xholic dm stop-all`
- **Scope:** `write`. Destructive: it can't be taken back
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## DM campaigns

`dm.campaigns` · read

The account's DM campaigns with status and totals, newest first.

- **MCP tool:** `dm_campaigns`. Catalog: reach it with `find_actions` and `run_read_action`, or turn on the full toolset.
- **REST:** `GET /v1/dm/campaigns`
- **CLI:** `xholic dm campaigns`
- **Scope:** `read`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `read`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `status` | "DRAFT" \\| "QUEUED" \\| "RUNNING" \\| "PAUSED" \\| "COMPLETED" \\| "CANCELLED" | No |  |
| `limit` | integer 1–100 | No |  |
| `offset` | integer 0–9007199254740991 | No |  |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |

## Create a DM campaign

`dm.create_campaign` · write

Create a draft DM campaign for a contact list. Then write its steps with dm.update_campaign, check it with dm.review_campaign and launch it with launch_dm_campaign. Write the messages and any per-person first lines with your own model: import the lines as a CSV column (dm.import_csv, e.g. first_line) and use [first_line]. The [ai_line] variable makes Xholic's AI write a line per recipient at launch and spends AI credits; use it only when the user asks for Xholic to write them. Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `create_dm_campaign`. Loaded by default in MCP clients.
- **REST:** `POST /v1/dm/create-campaign`
- **CLI:** `xholic dm create-campaign`
- **Scope:** `write`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string ≤ 80 chars | No |  |
| `contactListId` | string (uuid) or null | No | Contact list to message. |
| `deliveryMode` | "AUTOMATIC" \\| "ASSISTED" | No | AUTOMATIC sends from the X account; ASSISTED queues them for the user to send by hand. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## DM campaign details

`dm.campaign` · read

One DM campaign: steps, variants, schedule, rules and totals.

- **MCP tool:** `dm_campaign`. Catalog: reach it with `find_actions` and `run_read_action`, or turn on the full toolset.
- **REST:** `GET /v1/dm/campaign`
- **CLI:** `xholic dm campaign`
- **Scope:** `read`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `read`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | Yes | DM campaign id. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |

## Edit a DM campaign

`dm.update_campaign` · write

Replace a draft campaign's settings: send the whole draft (read it with dm.campaign first). Steps are the first message plus up to 2 follow-ups, each with up to three A/B variants. Write the messages and any per-person first lines with your own model: import the lines as a CSV column (dm.import_csv, e.g. first_line) and use [first_line]. The [ai_line] variable makes Xholic's AI write a line per recipient at launch and spends AI credits; use it only when the user asks for Xholic to write them. Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `dm_update_campaign`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/update-campaign`
- **CLI:** `xholic dm update-campaign`
- **Scope:** `write`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | Yes | DM campaign id. |
| `name` | string ≤ 80 chars | Yes |  |
| `deliveryMode` | "AUTOMATIC" \\| "ASSISTED" | Yes |  |
| `contactListId` | string (uuid) or null | Yes |  |
| `steps` | array of object (min 1, max 3) | Yes | In order from position 1. Step 1 has delayDays 0; follow-ups wait at least a day. Each variant's template is the message: [first], [name], list fields like [first_line], {Hi\|Hey} spintax. |
| `schedule` | object | Yes |  |
| `schedule.timezone` | string ≤ 64 chars | Yes |  |
| `schedule.sendDays` | integer 1–127 | Yes |  |
| `schedule.windowStartMinutes` | integer 0–1439 | Yes |  |
| `schedule.windowEndMinutes` | integer 1–1440 | Yes |  |
| `schedule.dailyLimit` | integer 1–100 | Yes |  |
| `schedule.rampStart` | integer 1–100 or null | Yes |  |
| `schedule.rampStep` | integer 1–50 or null | Yes |  |
| `schedule.minGapSeconds` | integer 75–3600 | Yes |  |
| `schedule.maxGapSeconds` | integer 75–3600 | Yes |  |
| `schedule.startsAt` | string (date-time) or null | Yes |  |
| `rules` | object | Yes |  |
| `rules.recontactAfterDays` | 30 or 60 or 90 or null | No | Default: `null`. |
| `rules.excludeCampaignIds` | array of string (uuid) (max 20) | No | Default: `[]`. |
| `rules.excludeListIds` | array of string (uuid) (max 20) | No | Default: `[]`. |
| `rules.skipFollowers` | boolean | No | Default: `false`. |
| `rules.skipFollowing` | boolean | No | Default: `false`. |
| `rules.skipExistingConversation` | boolean | No | Default: `true`. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## Pause or resume a campaign

`dm.control_campaign` · publish

PAUSE or RESUME a running campaign, or CANCEL_PENDING to cancel its unsent messages. Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `dm_control_campaign`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/control-campaign`
- **CLI:** `xholic dm control-campaign`
- **Scope:** `publish`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | Yes | DM campaign id. |
| `action` | "PAUSE" \\| "RESUME" \\| "CANCEL_PENDING" | Yes |  |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## Delete a draft campaign

`dm.delete_campaign` · write

Delete a DM campaign that hasn't launched. Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `dm_delete_campaign`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/delete-campaign`
- **CLI:** `xholic dm delete-campaign`
- **Scope:** `write`. Destructive: it can't be taken back
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | Yes | DM campaign id. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## Check a campaign before launch

`dm.review_campaign` · read

Check a draft campaign before launch: who can be messaged, who is skipped and why, missing variables, and the outreach guidelines the user must accept. Show the guidelines to the user before launching.

- **MCP tool:** `dm_review_campaign`. Catalog: reach it with `find_actions` and `run_read_action`, or turn on the full toolset.
- **REST:** `GET /v1/dm/review-campaign`
- **CLI:** `xholic dm review-campaign`
- **Scope:** `read`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `read`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | Yes | DM campaign id. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |

## Launch a DM campaign

`dm.launch_campaign` · publish

Launch a reviewed campaign: its messages start sending on its schedule. Run dm.review_campaign first, show the user the outreach guidelines, and pass confirmedConsent true only after they accept. If a template uses [ai_line], launching reserves AI credits for each recipient and needs confirmCredits. Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `launch_dm_campaign`. Loaded by default in MCP clients.
- **REST:** `POST /v1/dm/launch-campaign`
- **CLI:** `xholic dm launch-campaign`
- **Scope:** `publish`
- **Plan:** Pro plan or higher
- **Cost:** Xholic AI: up to about 0 credits (Xholic AI first lines ([ai_line]) for each eligible recipient; nothing when no template uses them). Returns a quote first; pass `confirmCredits` after the user agrees.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | Yes | DM campaign id. |
| `confirmedConsent` | boolean | Yes | The user accepted the cold-outreach guidelines shown by dm.review_campaign. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |
| `confirmCredits` | number 0–100000 | No | Xholic AI credits the user agreed to spend. Call first without it to get the quote, show the quote to the user, and pass the amount only after they say yes. |
| `quoteId` | string ≤ 100 chars | No | The quoteId from the quote the user agreed to. Send it with confirmCredits. |

## Preview campaign messages

`dm.preview_campaign` · write

Render a campaign step's message for a few recipients exactly as it will be sent. With generateAiLine true, Xholic's AI writes the missing [ai_line] first lines and charges per line: quote first, show it to the user, and pass confirmCredits after they agree. Without it, previews are free.

- **MCP tool:** `dm_preview_campaign`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/preview-campaign`
- **CLI:** `xholic dm preview-campaign`
- **Scope:** `write`
- **Plan:** Pro plan or higher
- **Cost:** Xholic AI: up to about 10 credits (Xholic AI first lines ([ai_line]) for up to 5 previewed recipients). Returns a quote first; pass `confirmCredits` after the user agrees.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | Yes | DM campaign id. |
| `offset` | integer 0–9007199254740991 | No |  |
| `limit` | integer 1–5 | No |  |
| `stepPosition` | integer 1–3 | No |  |
| `variantLabel` | "A" \\| "B" \\| "C" or null | No |  |
| `generateAiLine` | boolean | No | Write missing [ai_line] first lines with Xholic's AI (spends credits). |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |
| `confirmCredits` | number 0–100000 | No | Xholic AI credits the user agreed to spend. Call first without it to get the quote, show the quote to the user, and pass the amount only after they say yes. |
| `quoteId` | string ≤ 100 chars | No | The quoteId from the quote the user agreed to. Send it with confirmCredits. |

## Duplicate a campaign

`dm.duplicate_campaign` · write

Copy a campaign's steps and settings into a new draft. Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `dm_duplicate_campaign`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/duplicate-campaign`
- **CLI:** `xholic dm duplicate-campaign`
- **Scope:** `write`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | Yes | DM campaign id. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## Campaign recipients

`dm.campaign_recipients` · read

A campaign's recipients with each one's status, step and reply.

- **MCP tool:** `dm_campaign_recipients`. Catalog: reach it with `find_actions` and `run_read_action`, or turn on the full toolset.
- **REST:** `GET /v1/dm/campaign-recipients`
- **CLI:** `xholic dm campaign-recipients`
- **Scope:** `read`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `read`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | Yes | DM campaign id. |
| `status` | "PENDING" \\| "SENDING" \\| "SENT" \\| "SKIPPED" \\| "FAILED" \\| "DELIVERY_UNKNOWN" \\| "CANCELLED" | No |  |
| `step` | integer 1–3 | No |  |
| `q` | string ≤ 50 chars | No | Search by name or handle. |
| `limit` | integer 1–100 | No |  |
| `offset` | integer 0–9007199254740991 | No |  |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |

## DM campaign results

`dm.campaign_stats` · read

How a campaign is doing: sent, replied, interested and booked, by step and A/B variant.

- **MCP tool:** `get_dm_campaign_stats`. Loaded by default in MCP clients.
- **REST:** `GET /v1/dm/campaign-stats`
- **CLI:** `xholic dm campaign-stats`
- **Scope:** `read`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `read`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | Yes | DM campaign id. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |

## Export a campaign

`dm.export_campaign` · read

A campaign's recipients and results as CSV.

- **MCP tool:** `dm_export_campaign`. Catalog: reach it with `find_actions` and `run_read_action`, or turn on the full toolset.
- **REST:** `GET /v1/dm/export-campaign`
- **CLI:** `xholic dm export-campaign`
- **Scope:** `read`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `read`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | Yes | DM campaign id. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |

## Assisted DMs for today

`dm.today` · read

Messages from ASSISTED campaigns ready for the user to send by hand today, with the rendered text and a link to each person's DMs.

- **MCP tool:** `dm_today`. Catalog: reach it with `find_actions` and `run_read_action`, or turn on the full toolset.
- **REST:** `GET /v1/dm/today`
- **CLI:** `xholic dm today`
- **Scope:** `read`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `read`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |

## Mark an assisted DM sent

`dm.mark_assisted_sent` · write

Record that the user sent an assisted DM by hand. Undo works for a minute. Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `dm_mark_assisted_sent`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/mark-assisted-sent`
- **CLI:** `xholic dm mark-assisted-sent`
- **Scope:** `write`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`
- **Undo:** from the activity log, which runs `dm.undo_assisted_sent`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | Yes | Campaign recipient id from dm.today or dm.campaign_recipients. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## Undo marking an assisted DM sent

`dm.undo_assisted_sent` · write

Put an assisted DM back in today's queue within a minute of marking it sent.

- **MCP tool:** `dm_undo_assisted_sent`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/undo-assisted-sent`
- **CLI:** `xholic dm undo-assisted-sent`
- **Scope:** `write`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | Yes | Campaign recipient id from dm.today or dm.campaign_recipients. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## Skip a recipient

`dm.skip_recipient` · write

Skip one person in a campaign so they aren't messaged. Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `dm_skip_recipient`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/skip-recipient`
- **CLI:** `xholic dm skip-recipient`
- **Scope:** `write`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | Yes | Campaign recipient id from dm.today or dm.campaign_recipients. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## Cancel a pending DM

`dm.cancel_recipient` · write

Cancel a DM that is queued but not sent yet. Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `dm_cancel_recipient`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/cancel-recipient`
- **CLI:** `xholic dm cancel-recipient`
- **Scope:** `write`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | Yes | Campaign recipient id from dm.today or dm.campaign_recipients. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## DM inbox

`dm.inbox` · read

DM conversations with replies, newest first, with each person's lead status. Filter by lead status, campaign or unread.

- **MCP tool:** `get_dm_inbox`. Loaded by default in MCP clients.
- **REST:** `GET /v1/dm/inbox`
- **CLI:** `xholic dm inbox`
- **Scope:** `read`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `read`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `leadStatus` | "NEW" \\| "REPLIED" \\| "INTERESTED" \\| "NOT_INTERESTED" \\| "MEETING_BOOKED" \\| "DO_NOT_CONTACT" | No |  |
| `campaignId` | string (uuid) | No |  |
| `unread` | "true" \\| "false" | No | "true" for unread conversations only. |
| `limit` | integer 1–50 | No |  |
| `offset` | integer 0–9007199254740991 | No |  |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |

## DM conversation

`dm.thread` · read

The full DM conversation with one person.

- **MCP tool:** `dm_thread`. Catalog: reach it with `find_actions` and `run_read_action`, or turn on the full toolset.
- **REST:** `GET /v1/dm/thread`
- **CLI:** `xholic dm thread`
- **Scope:** `read`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `read`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `creatorId` | string | Yes | The person's numeric X user id. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |

## Update a DM conversation

`dm.update_conversation` · write

Set a conversation's lead status (e.g. INTERESTED, MEETING_BOOKED, DO_NOT_CONTACT) or mark it read or unread. Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `dm_update_conversation`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/update-conversation`
- **CLI:** `xholic dm update-conversation`
- **Scope:** `write`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `creatorId` | string | Yes | The person's numeric X user id. |
| `leadStatus` | "NEW" \\| "REPLIED" \\| "INTERESTED" \\| "NOT_INTERESTED" \\| "MEETING_BOOKED" \\| "DO_NOT_CONTACT" | No |  |
| `unread` | boolean | No |  |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## Reply to a DM

`dm.reply` · publish

Send a DM reply in a conversation from the user's X account. Write the reply yourself from the thread (dm.thread) in the user's voice. Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `reply_to_dm`. Loaded by default in MCP clients.
- **REST:** `POST /v1/dm/reply`
- **CLI:** `xholic dm reply`
- **Scope:** `publish`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `creatorId` | string | Yes | The person's numeric X user id. |
| `text` | string ≤ 5000 chars | Yes |  |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## Suggest a DM reply with Xholic

`dm.suggest_reply` · write

Have Xholic's AI suggest a reply in a DM conversation. Use only when the user asks for Xholic to write it; otherwise write the reply yourself. Spends Xholic AI credits: quote first, show it to the user, and pass confirmCredits after they agree. Nothing is sent.

- **MCP tool:** `dm_suggest_reply`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/suggest-reply`
- **CLI:** `xholic dm suggest-reply`
- **Scope:** `write`
- **Plan:** Pro plan or higher
- **Cost:** Xholic AI: up to about 8 credits (Xholic AI reply suggestion for this DM conversation). Returns a quote first; pass `confirmCredits` after the user agrees.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `creatorId` | string | Yes | The person's numeric X user id. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |
| `confirmCredits` | number 0–100000 | No | Xholic AI credits the user agreed to spend. Call first without it to get the quote, show the quote to the user, and pass the amount only after they say yes. |
| `quoteId` | string ≤ 100 chars | No | The quoteId from the quote the user agreed to. Send it with confirmCredits. |

## DM lists

`dm.lists` · read

Contact lists available for DM campaigns, with sizes, custom fields and how many can be messaged.

- **MCP tool:** `dm_lists`. Catalog: reach it with `find_actions` and `run_read_action`, or turn on the full toolset.
- **REST:** `GET /v1/dm/lists`
- **CLI:** `xholic dm lists`
- **Scope:** `read`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `read`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |

## Estimate a lead import

`dm.estimate_import` · read

Estimate how many profiles an import (followers, following, keyword posters, X list, post engagers or handles) would bring in and what it uses of the plan's import pool. Imports nothing.

- **MCP tool:** `dm_estimate_import`. Catalog: reach it with `find_actions` and `run_read_action`, or turn on the full toolset.
- **REST:** `GET /v1/dm/estimate-import`
- **CLI:** `xholic dm estimate-import`
- **Scope:** `read`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `read`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `source` | object | Yes | Who to import. FOLLOWERS/FOLLOWING {handle}; KEYWORD_POSTERS {query, days?, language?, excludeHandles?}; X_LIST/LIST_FOLLOWERS {listUrl}; POST_ENGAGERS {tweetUrl, includeReplies?, includeReposts?, keyword?}; HANDLES {handles}. |
| `source.kind` | "FOLLOWERS" \\| "FOLLOWING" \\| "KEYWORD_POSTERS" \\| "X_LIST" \\| "LIST_FOLLOWERS" \\| "POST_ENGAGERS" \\| "HANDLES" | Yes |  |
| `filters` | object | No | Optional filters: followersMin, followersMax, bioIncludes, bioExcludes, locationIncludes, verified (any\|only\|exclude), dmsOpenOnly, excludeProtected, excludeAutomated, minAccountAgeDays. |
| `maxProfiles` | integer 1–15000 | Yes |  |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |

## Import leads for DMs

`dm.queue_import` · write

Queue an import of X profiles into a contact list for DM campaigns. Runs in the background and uses the plan's import pool; estimate it first with dm.estimate_import. Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `dm_queue_import`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/queue-import`
- **CLI:** `xholic dm queue-import`
- **Scope:** `write`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `source` | object | Yes | Who to import. FOLLOWERS/FOLLOWING {handle}; KEYWORD_POSTERS {query, days?, language?, excludeHandles?}; X_LIST/LIST_FOLLOWERS {listUrl}; POST_ENGAGERS {tweetUrl, includeReplies?, includeReposts?, keyword?}; HANDLES {handles}. |
| `source.kind` | "FOLLOWERS" \\| "FOLLOWING" \\| "KEYWORD_POSTERS" \\| "X_LIST" \\| "LIST_FOLLOWERS" \\| "POST_ENGAGERS" \\| "HANDLES" | Yes |  |
| `filters` | object | No | Optional filters: followersMin, followersMax, bioIncludes, bioExcludes, locationIncludes, verified (any\|only\|exclude), dmsOpenOnly, excludeProtected, excludeAutomated, minAccountAgeDays. |
| `maxProfiles` | integer 1–15000 | Yes |  |
| `listId` | string (uuid) or null | No | Contact list to import into. |
| `newListName` | string ≤ 80 chars or null | No | Name for a new list instead. Pass exactly one of listId or newListName. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## Import a CSV of leads

`dm.import_csv` · write

Import handles and custom fields from CSV text (up to 200 KB) into a contact list. This is how you hand Xholic first lines you wrote: add a column such as first_line and use [first_line] in the campaign template. Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `dm_import_csv`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/import-csv`
- **CLI:** `xholic dm import-csv`
- **Scope:** `write`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `csv` | string | Yes | The CSV text, with a header row. |
| `handleColumn` | string ≤ 80 chars or null | No | Header of the column holding X handles; detected when left out. |
| `fieldMap` | object | No | CSV header to field key (lowercase, e.g. first_line), or null to ignore a column. |
| `listId` | string (uuid) or null | No | Contact list to import into. |
| `newListName` | string ≤ 80 chars or null | No | Name for a new list instead. Pass exactly one of listId or newListName. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## DM lead imports

`dm.imports` · read

Lead imports with their progress, optionally for one list.

- **MCP tool:** `dm_imports`. Catalog: reach it with `find_actions` and `run_read_action`, or turn on the full toolset.
- **REST:** `GET /v1/dm/imports`
- **CLI:** `xholic dm imports`
- **Scope:** `read`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `read`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `listId` | string (uuid) | No |  |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |

## Stop a lead import

`dm.cancel_import` · write

Stop a running lead import. Profiles already imported stay.

- **MCP tool:** `dm_cancel_import`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/cancel-import`
- **CLI:** `xholic dm cancel-import`
- **Scope:** `write`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | Yes | Import id from dm.imports. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## DM settings

`dm.settings` · read

Default delivery mode, schedule and rules for new campaigns, the assisted daily limit and opt-out phrases.

- **MCP tool:** `dm_settings`. Catalog: reach it with `find_actions` and `run_read_action`, or turn on the full toolset.
- **REST:** `GET /v1/dm/settings`
- **CLI:** `xholic dm settings`
- **Scope:** `read`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `read`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |

## Change DM settings

`dm.update_settings` · write

Replace the DM settings: send all of them (read them with dm.settings first). Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `dm_update_settings`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/update-settings`
- **CLI:** `xholic dm update-settings`
- **Scope:** `write`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `defaults` | object | Yes |  |
| `defaults.deliveryMode` | "AUTOMATIC" \\| "ASSISTED" | No | Default: `"AUTOMATIC"`. |
| `defaults.schedule` | object | No |  |
| `defaults.schedule.timezone` | string ≤ 64 chars | Yes |  |
| `defaults.schedule.sendDays` | integer 1–127 | Yes |  |
| `defaults.schedule.windowStartMinutes` | integer 0–1439 | Yes |  |
| `defaults.schedule.windowEndMinutes` | integer 1–1440 | Yes |  |
| `defaults.schedule.dailyLimit` | integer 1–100 | Yes |  |
| `defaults.schedule.rampStart` | integer 1–100 or null | Yes |  |
| `defaults.schedule.rampStep` | integer 1–50 or null | Yes |  |
| `defaults.schedule.minGapSeconds` | integer 75–3600 | Yes |  |
| `defaults.schedule.maxGapSeconds` | integer 75–3600 | Yes |  |
| `defaults.schedule.startsAt` | string (date-time) or null | Yes |  |
| `defaults.rules` | object | No | Default: `{"recontactAfterDays":null,"excludeCampaignIds":[],"excludeListIds":[],"skipFollowers":false,"skipFollowing":false,"skipExistingConversation":true}`. |
| `defaults.rules.recontactAfterDays` | 30 or 60 or 90 or null | No | Default: `null`. |
| `defaults.rules.excludeCampaignIds` | array of string (uuid) (max 20) | No | Default: `[]`. |
| `defaults.rules.excludeListIds` | array of string (uuid) (max 20) | No | Default: `[]`. |
| `defaults.rules.skipFollowers` | boolean | No | Default: `false`. |
| `defaults.rules.skipFollowing` | boolean | No | Default: `false`. |
| `defaults.rules.skipExistingConversation` | boolean | No | Default: `true`. |
| `assistedDailyLimit` | integer 1–100 | Yes |  |
| `optOutPhrases` | array of string ≤ 40 chars (max 20) | Yes | Replies containing one of these stop all messages to that person. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## Do-not-message list

`dm.suppressions` · read

People the account will never DM.

- **MCP tool:** `dm_suppressions`. Catalog: reach it with `find_actions` and `run_read_action`, or turn on the full toolset.
- **REST:** `GET /v1/dm/suppressions`
- **CLI:** `xholic dm suppressions`
- **Scope:** `read`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `read`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `q` | string ≤ 50 chars | No | Search by name or handle. |
| `limit` | integer 1–100 | No |  |
| `offset` | integer 0–9007199254740991 | No |  |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |

## Never DM someone

`dm.suppress` · write

Add a person to the do-not-message list and cancel their pending DMs. Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `dm_suppress`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/suppress`
- **CLI:** `xholic dm suppress`
- **Scope:** `write`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`
- **Undo:** from the activity log, which runs `dm.unsuppress`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `creatorId` | string | Yes | The person's numeric X user id. |
| `reason` | string ≤ 1000 chars or null | No |  |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## Never DM these people

`dm.bulk_suppress` · write

Add many handles to the do-not-message list at once and cancel their pending DMs. Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `dm_bulk_suppress`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/bulk-suppress`
- **CLI:** `xholic dm bulk-suppress`
- **Scope:** `write`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `handles` | array of string ≤ 120 chars (min 1, max 5000) | Yes |  |
| `reason` | string ≤ 200 chars or null | No |  |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## Allow DMs to someone again

`dm.unsuppress` · write

Remove a person from the do-not-message list. Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `dm_unsuppress`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/unsuppress`
- **CLI:** `xholic dm unsuppress`
- **Scope:** `write`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `creatorId` | string | Yes | The person's numeric X user id. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## Keyword auto-replies

`dm.inbound_rules` · read

Keyword rules that DM people automatically when they message or reply with a word like GUIDE.

- **MCP tool:** `dm_inbound_rules`. Catalog: reach it with `find_actions` and `run_read_action`, or turn on the full toolset.
- **REST:** `GET /v1/dm/inbound-rules`
- **CLI:** `xholic dm inbound-rules`
- **Scope:** `read`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `read`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |

## Add a keyword auto-reply

`dm.create_inbound_rule` · publish

Add a rule that automatically DMs replyTemplate to anyone who sends the keyword. Write the reply yourself. Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `dm_create_inbound_rule`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/create-inbound-rule`
- **CLI:** `xholic dm create-inbound-rule`
- **Scope:** `publish`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `keyword` | string ≤ 40 chars | Yes | A single word, like GUIDE. |
| `replyTemplate` | string ≤ 5000 chars | Yes | The message. Variables: [first], [name], [handle], [location], [followers], list fields such as [first_line], with optional fallbacks like [first\|there]; spintax like {Hi\|Hey}. Write it yourself in the user's voice. |
| `active` | boolean | No |  |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## Edit a keyword auto-reply

`dm.update_inbound_rule` · publish

Replace a keyword rule's keyword, reply or active state. Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `dm_update_inbound_rule`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/update-inbound-rule`
- **CLI:** `xholic dm update-inbound-rule`
- **Scope:** `publish`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | Yes | Rule id from dm.inbound_rules. |
| `keyword` | string ≤ 40 chars | Yes | A single word, like GUIDE. |
| `replyTemplate` | string ≤ 5000 chars | Yes | The message. Variables: [first], [name], [handle], [location], [followers], list fields such as [first_line], with optional fallbacks like [first\|there]; spintax like {Hi\|Hey}. Write it yourself in the user's voice. |
| `active` | boolean | No |  |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |

## Delete a keyword auto-reply

`dm.delete_inbound_rule` · write

Delete a keyword rule. Call it only when the user asked for this in the conversation; never on your own initiative.

- **MCP tool:** `dm_delete_inbound_rule`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/delete-inbound-rule`
- **CLI:** `xholic dm delete-inbound-rule`
- **Scope:** `write`. Destructive: it can't be taken back
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | Yes | Rule id from dm.inbound_rules. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |
