> ## Documentation Index
> Fetch the complete documentation index at: https://docs.consile.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Write tools & guardrails

> How write-capable connectors behave: DRAFT/PAUSED defaults, the confirm argument, the dry-run preview, tool annotations, per-user access levels, and what the guardrails do and do not guarantee.

Six Consile connectors are read-only: Google Search Console, CVR, Dinero, Billy,
Ordrestyring and Business Central (early access) expose only read tools, so the AI
can read your data but cannot create, change or delete anything at the provider.
Seven connectors combine read tools with guarded write tools: e-conomic, Uniconta,
Shopify, HubSpot CMS, HubSpot Marketing Email, LinkedIn Ads and Meta Ads.

This page documents the developer-facing mechanics of those write tools: default
statuses, the `confirm` argument, the dry-run preview, tool annotations and per-user
access levels. For the plain-language safety model, see
[Handlinger & guardrails](/essentials/handlinger-og-guardrails).

<Info>
  Each connector is its own MCP server at `https://mcp.consile.ai/<id>/mcp` with
  only its own tools. Write tools are called on the connector endpoint. The
  aggregate `https://mcp.consile.ai/mcp` is the Consile Concierge, which has no
  connector tools. Tool names carry the endpoint's id as a prefix,
  `<id>__<tool>`, for example `economic__create_draft_invoice`,
  `shopify__set_product_status` or `linkedin-ads__set_campaign_status`. An extra
  portal has its own endpoint and prefix, for example
  `https://mcp.consile.ai/economic-2/mcp` with `economic-2__create_draft_invoice`.
  Only the `connect_account` system tool has no prefix. The AI calls these names on
  your behalf.
</Info>

## Which connectors have write tools

| Connector | Endpoint | Capability | Write tools |
| - | - | - | - |
| Google Search Console | `mcp.consile.ai/gsc/mcp` | read-only | 0 |
| CVR | `mcp.consile.ai/cvr/mcp` | read-only | 0 |
| Dinero | `mcp.consile.ai/dinero/mcp` | read-only | 0 |
| Billy | `mcp.consile.ai/billy/mcp` | read-only | 0 |
| Ordrestyring | `mcp.consile.ai/ordrestyring/mcp` | read-only | 0 |
| Business Central (early access) | `mcp.consile.ai/business-central/mcp` | read-only | 0 |
| e-conomic | `mcp.consile.ai/economic/mcp` | read + guarded write | 12 of 40 |
| Uniconta | `mcp.consile.ai/uniconta/mcp` | read + guarded write | 4 of 25 |
| Shopify | `mcp.consile.ai/shopify/mcp` | read + guarded write | 10 of 50 |
| HubSpot CMS | `mcp.consile.ai/hubspot/mcp` | read + guarded write | 9 of 23 |
| HubSpot Marketing Email | `mcp.consile.ai/hubspot-email/mcp` | read + guarded write | 11 of 19 |
| LinkedIn Ads | `mcp.consile.ai/linkedin-ads/mcp` | read + guarded write | 19 of 64 |
| Meta Ads | `mcp.consile.ai/meta-ads/mcp` | read + guarded write | 5 of 39 |

Every tool on a read-only endpoint is a read. Everything below applies to the seven
write-capable connectors.

***

## The three guardrail rules

The write-capable connectors follow three rules. Whether a particular call is
guarded depends on the tool and its arguments, as listed per connector below.

1. New objects are created paused or as drafts by default. Meta Ads objects
   are created `PAUSED`. New LinkedIn Ads objects and posts, Shopify products and
   HubSpot pages and blog posts are drafts. e-conomic invoices, credit notes and
   journal entries are unbooked drafts, Uniconta journal lines are unposted, and a
   new HubSpot marketing email cannot send. A create call that asks for a live status (`ACTIVE`, or `PUBLISHED` for a
   LinkedIn post) needs `confirm: true`.
2. Spending, destructive and public actions need `confirm: true`. That covers
   setting a campaign or ad `ACTIVE`, changing a budget or bid, changing a price or
   inventory, publishing to a live website, sending a marketing email, publishing
   organic social content, booking to the ledger, and archiving or deleting.
3. Without `confirm: true`, a guarded call is a dry-run. It returns a preview
   of exactly what would happen and writes nothing at the provider. Nothing changes
   until a call carries `confirm: true`. The AI normally shows you the preview and
   then repeats the same call with `confirm: true`.

Some writes apply directly without a preview, for example HubSpot draft edits and
pausing a Meta Ads campaign. They are listed under
[Writes that apply without confirm](#writes-that-apply-without-confirm). On
e-conomic and Uniconta, every write needs `confirm: true`.

<Warning>
  The server does not require a separate preview call: a call that carries
  `confirm: true` is applied. The client or AI should show the preview and send
  `confirm: true` only after the user has approved it. A tool's `readOnlyHint`
  annotation describes the tool and is not an access control by itself. Two limits
  hold whatever the AI does: a read-only connector cannot write, because it has no
  write tools, and for a user whose e-conomic or Uniconta access level is Read only,
  Consile blocks all write operations on the server (see
  [Per-user access levels](#per-user-access-levels)).
</Warning>

***

## Default status: DRAFT / PAUSED

By default, creating an object does not make it live:

* Meta Ads: `create_campaign`, `create_ad_set` and `create_ad` create the
  object as `PAUSED`. It exists, but does not deliver and spends nothing. Passing
  `status: "ACTIVE"` (or `"ARCHIVED"`) at creation needs `confirm: true`; with it,
  the object is created in that status.
* LinkedIn Ads: `create_campaign_group` and `create_campaign` create the object
  as `DRAFT`. `PAUSED` is also applied directly, while `ACTIVE` or `ARCHIVED` needs
  `confirm: true`. `create_organization_post` and `create_member_post` create a
  `DRAFT` post, and `lifecycleState: "PUBLISHED"` needs `confirm: true`.
* Shopify: `create_product` creates the product as `DRAFT`, not published to
  any sales channel. `ACTIVE` or `ARCHIVED` at creation needs `confirm: true`.
* HubSpot CMS: new pages and blog posts are forced to draft. HubDB row writes
  land in the table's draft.
* HubSpot Marketing Email: new emails are forced to state `DRAFT`, and the
  create and update tools never accept `state`, `publishDate` or `sendOnPublish`,
  so they cannot send or schedule an email.
* e-conomic: `create_draft_invoice`, `duplicate_invoice` and
  `create_credit_note` create unbooked drafts, and `create_journal_entries` creates
  unbooked journal entries. Every e-conomic write needs `confirm: true`, including
  these.
* Uniconta: `create_journal_lines` creates unposted lines in the daily journal
  (kassekladde). Every Uniconta write needs `confirm: true`, including this one.

Exception: on Shopify, `create_product_variant` applies directly, including the new
variant's price; there is no confirmation step. On a product that is already
`ACTIVE` and published, the new variant is available in the store right away.

Taking an object live (setting it `ACTIVE`, publishing or sending) needs the
relevant tool and `confirm: true`, because those steps spend money or change what
the public sees (rule 2).

***

## The `confirm` argument

Guarded tools accept an optional boolean `confirm` argument.

<ParamField path="confirm" type="boolean">
  Optional. Set `true` to apply the change. Omit it (or send `false`) and a guarded
  call returns a dry-run preview and writes nothing at the provider. Leaving it out
  is not a validation error. The HubSpot CMS and HubSpot Marketing Email draft tools
  (`create_page_draft`, `update_page_draft`, `create_blog_post_draft`,
  `update_blog_post_draft`, `upsert_hubdb_row_draft`, `create_email`,
  `update_email_draft`, `clone_email`, `create_email_ab_variation`) have no
  `confirm` argument; they always apply directly.
</ParamField>

A call is guarded (needs `confirm: true` to apply) when it would:

* spend money: set a campaign, campaign group, ad set, ad or creative to `ACTIVE`,
  at creation or later (Meta Ads, LinkedIn Ads);
* change a budget or bid: Meta Ads `update_budget`, LinkedIn Ads `update_campaign`
  with a budget or `unitCostAmount`, and `update_campaign_group` with
  `totalBudgetAmount`;
* change a price or inventory: a Shopify variant's `price` or `compareAtPrice`,
  `set_inventory_quantity` and `adjust_inventory_quantity`;
* publish to or withdraw from a live surface: the HubSpot CMS publish tools, HubSpot
  Marketing Email `publish_email` and `unpublish_email`, a LinkedIn post set to
  `PUBLISHED` or with changed text, LinkedIn comments and reactions, and Shopify
  `publish_product`, `unpublish_product` or status `ACTIVE`;
* change or discard saved HubSpot work: HubSpot Marketing Email `update_email`,
  `reset_email_draft` and the two revision restores, and HubSpot CMS
  `reset_hubdb_draft`;
* archive or delete an object (LinkedIn Ads `delete_reaction` is the exception);
* book to the ledger: e-conomic `book_journal_entries` and Uniconta
  `post_journal_lines`.

For e-conomic and Uniconta, every write tool, including the create tools, needs
`confirm: true`, with no exceptions.

### Writes that apply without confirm

These writes are applied on the first call, without a preview. Some of them change
something that is already live:

* HubSpot CMS: `create_page_draft`, `update_page_draft`,
  `create_blog_post_draft`, `update_blog_post_draft` and `upsert_hubdb_row_draft`.
  Nothing reaches the live website before a publish.
* HubSpot Marketing Email: `create_email`, `update_email_draft`, `clone_email`
  and `create_email_ab_variation`. A draft cannot send.
* Meta Ads: creating campaigns, ad sets and ads as `PAUSED`, and `update_status`
  to `PAUSED`.
* LinkedIn Ads: creating campaign groups and campaigns as `DRAFT` or `PAUSED`,
  including a budget set at creation; `update_campaign_group` that changes only the
  name or end date; `update_campaign` that changes only the name or
  `targetingCriteria`, also on an active campaign; `set_campaign_group_status`,
  `set_campaign_status` and `set_creative_status` to `DRAFT` or `PAUSED`;
  `create_conversion_rule`, `update_conversion_rule` and
  `associate_campaign_conversion`; posts created as `DRAFT`; `update_post` that
  only sets `lifecycleState: "DRAFT"`; and `delete_reaction`.
* Shopify: `create_product` as `DRAFT`; `update_product` without a status change
  to `ACTIVE` or `ARCHIVED` (on a live product, the new title, description, vendor,
  type or tags show in the store right away); `set_product_status` or
  `update_product` to `DRAFT`, which takes a live product off sale;
  `create_product_variant`, including its price and also on a live product; and
  `update_product_variant` without a `price` or `compareAtPrice` change.
* e-conomic: none.
* Uniconta: none.

***

## The dry-run preview

A guarded call without `confirm: true` returns a normal tool result, not an error.
The preview object is in `structuredContent` and, serialized as JSON, in the text
content block. Every write connector uses the same envelope; what goes inside
`request` differs per connector:

```json Dry-run preview (no confirm) theme={null}
{
  "preview": {
    "action": "update_status",
    "summary": "Set object 1203456789012345 to ACTIVE.",
    "request": {
      "method": "POST",
      "path": "1203456789012345",
      "body": { "status": "ACTIVE" }
    },
    "confirmHint": "Re-run the same call with confirm: true to apply this change."
  }
}
```

Key fields:

<ResponseField name="preview" type="object">
  Present only on a dry-run. Nothing was written.
</ResponseField>

<ResponseField name="preview.action" type="string">
  The tool that produced the preview, e.g. `update_status` or `publish_email`.
</ResponseField>

<ResponseField name="preview.summary" type="string">
  A readable description of the intended change.
</ResponseField>

<ResponseField name="preview.request" type="object">
  The exact write that would be sent. e-conomic, Uniconta, Meta Ads, LinkedIn Ads,
  HubSpot CMS and HubSpot Marketing Email give `method`, `path` and `body`
  (e-conomic leaves out `body` on a `DELETE`; Uniconta's `DELETE` carries the line
  to delete). Shopify gives `method: "GRAPHQL"`, `mutation` and
  `variables`. An e-conomic write that takes several steps (for example
  `create_delivery_location` that also sets the customer's default) also returns
  `preview.plan` with every step in order; `request` is the first step.
</ResponseField>

<ResponseField name="preview.state" type="object">
  Only on e-conomic `book_journal_entries` and Uniconta `post_journal_lines`:
  `{ "expectedState": "..." }`, which the confirming call must send back as the
  `expectedState` argument.
</ResponseField>

<ResponseField name="preview.confirmHint" type="string">
  How to apply the change.
</ResponseField>

A preview never writes. Some tools read from the provider before they return it,
for example to show the record as it would look after an update, or exactly which
entries would be deleted or booked. A dry-run can therefore still fail with a read
error from the provider.

To apply the change, the AI makes the same call again with `confirm: true`:

```json Confirmed write (executes upstream) theme={null}
{
  "name": "meta-ads__update_status",
  "arguments": {
    "objectId": "1203456789012345",
    "status": "ACTIVE",
    "confirm": true
  }
}
```

<Note>
  The AI runs these calls for you. It is expected to show you the preview and get
  your go-ahead before it repeats the call with `confirm: true`. Treat the preview
  as the moment to review before any spend, publish, send or booking.
</Note>

***

## Tool annotations

Every tool in `tools/list` carries MCP annotations, so a client can tell reads from
writes without calling anything:

| Hint | Read tools | Write tools |
| - | - | - |
| `readOnlyHint` | `true` | `false` |
| `destructiveHint` | `false` | `true` on every write tool, including draft-only writes |
| `openWorldHint` | `false` | `true` on every Meta Ads, LinkedIn Ads and Shopify write, on HubSpot CMS `publish_page`, `publish_blog_post` and `publish_hubdb_table`, and on HubSpot Marketing Email `publish_email`, `unpublish_email`, `delete_email` and `restore_email_revision`. `false` on all e-conomic and Uniconta writes and on the other HubSpot CMS and HubSpot Marketing Email writes (the draft tools, `reset_hubdb_draft`, `update_email`, `reset_email_draft` and `restore_email_revision_to_draft`). |
| `idempotentHint` | not set | e-conomic and Uniconta only. e-conomic: `true` for `update_customer`, `update_draft_invoice`, `update_journal_entries` and `delete_journal_entry`, `false` for the other eight. Uniconta: `true` for `update_journal_lines` and `delete_journal_line`, `false` for `create_journal_lines` and `post_journal_lines`. |

The tool's `title` is also repeated in `annotations.title`. Some clients use these
hints; Claude, for example, may ask you to approve each call to a tool marked
`destructiveHint`. The hints describe the tool. Whether a particular call also needs
`confirm: true` depends on its arguments, as described above.

***

## Per-user access levels

Some connectors have access levels. An administrator of the Consile account chooses
one per user on the **Team** page of the portal (see [Team](/portal/team)). Today
e-conomic and Uniconta have them:

| Level (portal label) | e-conomic tools | Uniconta tools | What it allows |
| - | - | - | - |
| Read only (Kun læsning) | 28 | 21 | The tools whose `readOnlyHint` is `true`. No write tools. |
| Full access (Fuld adgang) | 40 | 25 | All tools, including the guarded write tools (12 on e-conomic, 4 on Uniconta). This is the default, also for a user for whom no level has been chosen. |
| Custom (Tilpasset) | chosen | chosen | Exactly the tools the administrator picks for this user. Available on every connector, also those without the two levels above. The list must hold at least one tool; the user gets no tool that is not picked. |

The counts are the connector's full set. Every level only gives tools that the
organization has switched on: a tool released after the organization bought the
connector (on Uniconta, for example, the six daily-journal tools for an organization
that bought Uniconta before they were released) stays off until an administrator
activates it on the connector's page. See [Tools and new tools](/portal/vaerktoejer).

Both levels cost the same. For a user on Read only, Consile blocks all write
operations on the server, on top of the confirm guardrail:

* `tools/list` on the user's endpoints for that connector lists only the read
  tools (28 on e-conomic, 21 on Uniconta), plus the `connect_account` system tool.
* A `tools/call` to a write tool is refused before anything is sent to the
  provider.
  The result has `isError: true` and starts with
  `This tool is not enabled for your user on <id>, so it cannot be used in this session. Nothing was changed.`
  The rest of the text says that an administrator decides which tools are
  switched on for the organization and which each user may use, under Team in
  the portal, and that every level costs the same.
* The `initialize` result carries `instructions` telling the model that the session
  can only read, so it does not offer changes it cannot make. A session that sees
  only some of the tools but still has a write tool (for example a **Tilpasset**
  list) gets instructions to treat the listed tools as the complete surface
  instead.
* The level is set per connector and applies to all of the account's portals of
  that connector. It is set per user in Consile and does not change the provider
  connection, which the whole organization shares.
* A change takes effect within seconds, without a new sign-in. A client that cached
  `tools/list` in an open conversation can still show the write tools, but calls to
  them are refused. Start a new conversation, or call `tools/list` again, to refresh
  the list. In the [Microsoft 365 Copilot](/connect/forbind-microsoft-copilot) agent
  (pilot, e-conomic only), a Read only user also still sees the write tools, and calls to them are
  refused in the same way.

A user who holds no seat on the connector still sees its tools, but every call is
refused with a seat message. See [Authentication](/developers/authentication) for
the full access model.

***

## e-conomic write tools

e-conomic exposes 12 write tools (of 40). Every one needs `confirm: true`, with no
exceptions: a call without it never writes and returns a preview.
`book_journal_entries` always takes two calls, because the confirming call must send
back the `expectedState` from a preview.

| Tool | What it does | Notes |
| - | - | - |
| `create_customer` | Create a customer (debtor) | Needs name, currency, customer group, payment terms, VAT zone and layout numbers. |
| `update_customer` | Update a customer | Only the fields you pass change; the rest of the customer is kept. |
| `create_customer_contact` | Add a contact person to a customer | Can also set the contact as the customer's 'Deres reference' and/or 'Attention' under the same confirmation. |
| `create_delivery_location` | Add a delivery address to a customer | Can also make it the customer's default delivery location under the same confirmation. |
| `create_draft_invoice` | Create a draft invoice | At least one line. Never booked. |
| `update_draft_invoice` | Update a draft invoice | Only the fields you pass change. `replaceLines` swaps all lines; leave it out to keep them. Booked invoices cannot be edited. |
| `duplicate_invoice` | Copy a booked or draft invoice to a new draft | Optional new customer, dates, notes and `replaceLines`. |
| `create_credit_note` | Create a draft credit note | `source: "booked"` or `"draft"` reverses that invoice: every line is negated, or pass `lines` to credit only those lines. A document that already has negative lines is refused. `source: "scratch"` builds a credit note from your `lines` and needs customer, currency, payment terms, layout and recipient. Quantities are given as positive numbers and negated by the tool; unit prices cannot be negative. Without `date` the credit note is dated today. Never booked. |
| `create_journal_entries` | Create 1-50 unbooked entries in a journal (kassekladde) | Types `financeVoucher`, `customerPayment`, `supplierInvoice`, `supplierPayment` and `manualCustomerInvoice`. `text` is at most 250 characters. Nothing is booked. |
| `update_journal_entries` | Update 1-50 unbooked entries by `entryNumber` | Only the fields you pass change. |
| `delete_journal_entry` | Delete one unbooked entry | The preview names the entry (type, amount, currency, date and text). Booked entries cannot be deleted. |
| `book_journal_entries` | Book specific unbooked entries to the ledger | Irreversible. See below. |

For `update_customer`, `update_draft_invoice` and `update_journal_entries`, the
confirming call applies your changes to the record as it is at that moment.

Invoices and credit notes are only created and edited as drafts; no tool books or
sends them. A draft does not affect the accounts until someone books it in
e-conomic. Booked data cannot be edited or deleted; corrections are new entries.

### Booking journal entries

1. Call `book_journal_entries` with `journalNumber` and the exact `entryNumbers`
   (1-50). Only those entries are booked, never the whole journal.
2. The preview lists every entry (number, type, amount, currency, date and text)
   with a total per currency, and returns `preview.state.expectedState`.
3. To book, repeat the call with `confirm: true` and `expectedState` set to that
   value. A confirming call without `expectedState` is refused.
4. If any entry was changed, deleted or booked after the preview, the call is
   refused and nothing is booked. Run it again without `confirm` to see the current
   state. A number that is not an unbooked entry in that journal is named in the
   error, and nothing is booked.

If a booking call times out, the result is `Tool invocation failed.`; check
`list_account_entries` before you retry, because the entries may already be booked.

A user on Read only does not get these tools (see
[Per-user access levels](#per-user-access-levels)). Parameters and examples are in
the [e-conomic reference](/developers/tools/economic).

***

## Uniconta write tools

Uniconta exposes 4 write tools (of 25), all in the daily journal (kassekladde).
Every one needs `confirm: true`, with no exceptions: a call without it never writes
and returns a preview. `post_journal_lines` always takes two calls, because the
confirming call must send back the `expectedState` from a preview.

| Tool | What it does | Notes |
| - | - | - |
| `create_journal_lines` | Create 1-50 unposted lines in a journal | Ledger, debtor or creditor lines with optional offset account, VAT codes, voucher, invoice, due date, project and dimensions. `text` is at most 250 characters. Nothing is posted. Not idempotent. |
| `update_journal_lines` | Update 1-50 unposted lines by `lineId` | Only the fields you pass change. The confirming call applies your changes to the lines as they are at that moment. Posted lines cannot be changed. |
| `delete_journal_line` | Delete one unposted line | The preview names the line (id, date, accounts, amount and text). Posted ledger rows cannot be deleted. |
| `post_journal_lines` | Post specific unposted lines to the ledger | Irreversible. See below. |

A `lineId` is the line's `RowId` from `list_journal_lines`, always within the one
journal named in `journal`. A journal that is blocked in Uniconta is refused. No
tool changes master data (debtors, creditors, items), invoices, orders or posted
ledger rows, and the read tools, including `query` and `aggregate`, cannot write.

The Server-Login User's rights in Uniconta also apply. Read access is enough for
the read tools; to create, change and post lines the user needs write access to the
daily journal and the right to post. When Uniconta refuses a write, the result has
`isError: true` and the text
`Uniconta refused <METHOD> <path>: <reason>. Nothing was changed.`

### Posting journal lines

1. Call `post_journal_lines` with `journal` and the exact `lineIds` (1-50). Only
   those lines are posted, never the whole journal.
2. The preview lists every line (id, date, accounts, amount and text) with debit,
   credit and net totals, says how many other unposted lines in the journal are not
   included, and returns `preview.state.expectedState`.
3. To post, repeat the call with `confirm: true` and `expectedState` set to that
   value. A confirming call without `expectedState` is refused.
4. If any line was changed, deleted or posted after the preview, the call is
   refused, the changed line ids are named, and nothing is posted. Run it again
   without `confirm` to see the current state.
5. After posting, the connector reads the journal again and returns `posted`,
   `stillUnposted`, `remainingUnpostedLinesInJournal` and, when a selected line is
   still unposted, a `warning`.

If a posting call times out, the result is `Tool invocation failed.`; check
`list_journal_lines` before you retry, because the lines may already be posted.

A user on Read only does not get these tools (see
[Per-user access levels](#per-user-access-levels)). Parameters and examples are in
the [Uniconta reference](/developers/tools/uniconta).

***

## Shopify write tools

Shopify exposes 10 write tools (of 50), limited to the three write permissions
Consile requests: products, publication to sales channels, and inventory.

* Orders, draft orders, customers and discounts are read-only. `graphql_query`
  accepts a single read-only `query` operation and refuses mutations and
  subscriptions.
* New products are created `DRAFT`. Publishing, unpublishing, setting status
  `ACTIVE` or `ARCHIVED`, changing a variant's `price` or `compareAtPrice`, setting
  or adjusting inventory and deleting variants need `confirm: true`; without it the
  call returns a dry-run preview.
* These apply directly, with no confirmation step: `create_product_variant`
  (including the price of new variants), `update_product` without a status change to
  `ACTIVE` or `ARCHIVED`, `update_product_variant` without a price field, and setting
  status `DRAFT`. On a product that is already live, these changes show in the store
  right away, and status `DRAFT` takes the product off sale.
* There is no tool that deletes a product.
* A write Shopify rejects (with `userErrors`) never looks like a success. The result
  is `Tool invocation failed.`, without Shopify's message.

***

## HubSpot CMS write tools

HubSpot CMS exposes 9 write tools (of 23).

* Draft writes apply directly and have no `confirm` argument: `create_page_draft`,
  `update_page_draft`, `create_blog_post_draft`, `update_blog_post_draft` and
  `upsert_hubdb_row_draft`. A draft never touches the live website.
* Actions that change the live site or discard work need `confirm: true`:
  `publish_page`, `publish_blog_post`, `publish_hubdb_table` and
  `reset_hubdb_draft`. Without it the call returns a preview and writes nothing to
  HubSpot.
* HubDB row writes land in the table's draft and stay invisible on live pages until
  `publish_hubdb_table` runs. There is no delete tool.

***

## HubSpot Marketing Email write tools

HubSpot Marketing Email exposes 11 write tools (of 19).

* Draft writes (4): `create_email`, `update_email_draft`, `clone_email` and
  `create_email_ab_variation`. They apply directly, have no `confirm` argument and
  cannot send. New emails are forced to state `DRAFT`.
* Confirm-gated writes (7): `update_email` (changes the live version),
  `publish_email` (sends a real email), `unpublish_email`, `delete_email` (archives
  the email), `reset_email_draft`, `restore_email_revision` and
  `restore_email_revision_to_draft`. Without `confirm: true` they return a dry-run
  preview. Email reaches recipients only through a confirmed `publish_email`.

***

## Meta Ads write tools

Meta Ads exposes 5 write tools (of 39). Amounts are in minor units of the ad
account's currency.

| Tool | Needs `confirm: true` when | Notes |
| - | - | - |
| `create_campaign` | `status` is `ACTIVE` or `ARCHIVED` | The default, `PAUSED`, is created directly. |
| `create_ad_set` | `status` is `ACTIVE` or `ARCHIVED` | Exactly one of `dailyBudgetMinor` or `lifetimeBudgetMinor`; optional `bidAmountMinor`. |
| `create_ad` | `status` is `ACTIVE` or `ARCHIVED` | Built only from an existing `creativeId`. |
| `update_status` | `status` is `ACTIVE` or `ARCHIVED` | `objectId` is a campaign, ad set or ad id. `PAUSED` applies directly. |
| `update_budget` | Always | Daily or lifetime budget. There is no tool that changes a bid; a bid can only be set when an ad set is created. |

<Warning>
  Meta Ads has no delete tool. Objects can only be archived with `update_status`,
  which needs `confirm: true` for `ARCHIVED`.
</Warning>

***

## LinkedIn Ads write tools

LinkedIn Ads exposes 19 write tools (of 64): 11 for ad management and 8 for organic
social.

| Tool | Needs `confirm: true` when |
| - | - |
| `create_campaign_group`, `create_campaign` | `status` is `ACTIVE` or `ARCHIVED`. The default, `DRAFT`, and `PAUSED` apply directly. |
| `update_campaign_group` | `totalBudgetAmount` is changed |
| `update_campaign` | a daily or total budget, or `unitCostAmount` (the bid), is changed |
| `set_campaign_group_status`, `set_campaign_status`, `set_creative_status` | `status` is `ACTIVE` or `ARCHIVED` |
| `remove_campaign_conversion` | Always |
| `create_conversion_rule`, `update_conversion_rule`, `associate_campaign_conversion` | Never |
| `create_organization_post`, `create_member_post` | `lifecycleState` is `PUBLISHED`. The default is `DRAFT`. |
| `update_post` | `commentary` is changed, or `lifecycleState` is `PUBLISHED` |
| `create_comment`, `delete_comment`, `create_reaction`, `delete_post` | Always |
| `delete_reaction` | Never |

<Warning>
  22 of the 64 LinkedIn Ads tools are listed but not available yet; calls to them
  fail, and the tool result has `isError: true` with the text
  `Tool invocation failed.`. These are the 8 organic-social write tools
  (`create_organization_post`, `create_member_post`, `update_post`, `delete_post`,
  `create_comment`, `delete_comment`, `create_reaction` and `delete_reaction`) and the
  14 read tools for Company Pages, organic posts and the member profile:
  `list_administered_organizations`, `get_organization`,
  `find_organization_by_vanity_name`, `get_organization_follower_count`,
  `get_organization_page_statistics`, `get_organization_follower_statistics`,
  `get_organization_share_statistics`, `list_organization_posts`, `get_post`,
  `get_post_social_metadata`, `list_post_comments`, `list_post_reactions`,
  `get_my_profile` and `get_my_connections_count`. The other 31 read tools and the
  11 ad-management write tools work today. See the
  [LinkedIn Ads reference](/developers/tools/linkedin-ads).
</Warning>

***

## Why read-only connectors are different

Google Search Console, CVR, Dinero, Billy, Ordrestyring and Business Central ship
no write tools at all. Their safety is structural: there is no write tool to gate.
For example, the Billy tool `billy__list_daybook_transactions` only reads daybook
entries, and no Billy tool posts an entry or edits a record.

The generic query tools on read-only connectors can only read as well, and so can
Uniconta's `query` and `aggregate`. Two more examples:

* Ordrestyring's `graphql_query` takes a GraphQL document, but it accepts only a
  single read-only `query` operation; a mutation or subscription is refused.
  Shopify's `graphql_query` works the same way.
* Business Central: Microsoft offers no read-only permission for Business Central,
  so the consent also covers writing, but the Consile connector only reads, and
  `query_entity` accepts only an allow-listed set of standard entities. See the
  [Business Central reference](/developers/tools/business-central).

For the plain-language safety model, see
[Handlinger & guardrails](/essentials/handlinger-og-guardrails).

<Note>
  A write the provider rejects comes back as a tool result with `isError: true`.
  e-conomic includes a readable validation message (for example the rejected
  field), and Uniconta returns
  `Uniconta refused <METHOD> <path>: <reason>. Nothing was changed.`, so the AI can
  correct the call and try again. For Meta Ads, LinkedIn Ads,
  Shopify, HubSpot CMS and HubSpot Marketing Email the result text is
  `Tool invocation failed.` and does not include the provider's status code or
  message. An expired or rejected connection usually returns a message asking for a
  reconnect instead. See [Errors & limits](/developers/errors-and-limits).
</Note>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.