Copy as markdown[View .md](https://docs.funnelfizz.com/ai-agents/mutations "View the raw markdown for this page")[Open in Claude](https://claude.ai/new?q=Read%20https%3A%2F%2Fdocs.funnelfizz.com%2Fai-agents%2Fmutations.md%20and%20help%20me%20with%20this%20FunnelFizz%20topic%3A%20Mutations%20%26%20writes "Open this page in Claude with context")[Open in ChatGPT](https://chat.openai.com/?q=Read%20https%3A%2F%2Fdocs.funnelfizz.com%2Fai-agents%2Fmutations.md%20and%20help%20me%20with%20this%20FunnelFizz%20topic%3A%20Mutations%20%26%20writes "Open this page in ChatGPT with context")

# Mutations & writes

The MCP server can do everything you can do in the dashboard — set up workspaces, build funnels, send emails, activate automations, even manage teams and billing. Every write is gated by one of three safety tiers (see [Safety tiers](https://docs.funnelfizz.com/ai-agents/safety-tiers.md) for the full model):

* **T0 reads** — free use, plan + role filtered at `tools/list`
* **T1 mutations** — require a `target_token` minted after explicit user confirmation
* **T2 admin** — require a 6-digit code emailed to the API-key-holder

This page focuses on T1 — the bulk of write activity.

## The T1 flow (funnel-scoped)[​](#the-t1-flow-funnel-scoped "Direct link to The T1 flow (funnel-scoped)")

```
User: "Draft an email to US trial users in my Widgets Pro funnel."

       │

       ▼

Agent: funnel.resolve_by_name({ query: "Widgets Pro" })

       │  → matches[]

       ▼

Agent: shows matches to user, asks them to confirm

       │

       ▼

User: "Yes, Widgets Pro Main."

       │

       ▼

Agent: funnel.confirm_target({ funnelId, action: "email.draft.create" })

       │  → targetToken (10-min single-use)

       ▼

Agent: email.draft.create({ targetToken, audience: { stage: "TRIAL", conditions: [...] }, ... })

       │  → { campaignId, autoCreated[] }

       ▼

Agent: tells user the draft is ready, links to dashboard for review
```

The agent **must** ask the user to confirm — even when there's a single exact match. There is no auto-confirm code path.

## The T1 flow (entity-scoped)[​](#the-t1-flow-entity-scoped "Direct link to The T1 flow (entity-scoped)")

For high-impact non-funnel writes — deleting tracking sites, templates, senders, domains, disconnecting integrations:

```
1. Agent enumerates entities:

     tracking.site.list()

     → user picks which to delete



2. Agent: confirm_target({

            targetType: "tracking_site",

            targetId: "site-id",

            action: "tracking.site.delete"

          })

     → targetToken



3. Agent: tracking.site.delete({ id, targetToken })

     → site deleted; token consumed
```

## Tool surface[​](#tool-surface "Direct link to Tool surface")

Each tool's required scope and tier are documented in [Tool reference](https://docs.funnelfizz.com/ai-agents/tool-reference.md). The categories below cover the writable surface.

### Resolution + targeting[​](#resolution--targeting "Direct link to Resolution + targeting")

| Tool                     | What it does                                                                                                                          |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| `funnel.resolve_by_name` | Find funnels by free-text query, ranked by match + activity                                                                           |
| `funnel.confirm_target`  | Mint a single-use 10-min target token for a funnel mutation                                                                           |
| `confirm_target`         | Generalized: mint a token for any entity type (tracking\_site, email\_template, email\_domain, email\_sender, integration, workspace) |

### Funnel structure (`scope: write`, tier: T1 funnel-token)[​](#funnel-structure-scope-write-tier-t1-funnel-token "Direct link to funnel-structure-scope-write-tier-t1-funnel-token")

| Tool                                               | Notes                                                                  |
| -------------------------------------------------- | ---------------------------------------------------------------------- |
| `funnel.create`                                    | Optionally returns a `chainTargetToken` for an immediate follow-up     |
| `funnel.rename`                                    | Bound to action `funnel.rename`                                        |
| `funnel.archive`                                   | Soft-delete; revertible                                                |
| `funnel.split.create`                              | Main, nested, extension, nested-extension via flags                    |
| `funnel.split.archive`                             | Revertible                                                             |
| `funnel.extension.add`                             | Builds the full Cons + Trial + Customer extension pipeline in one call |
| `funnel.stage.link_provider` · `.unlink_provider`  | Attach OAuth providers to stages                                       |
| `funnel.link_tracking_site` · `funnel.link_stripe` | Wire workspace resources to a funnel                                   |
| `funnel.replay_history`                            | Backfill historical Stripe customers onto stages + tracks (idempotent) |
| `funnel.feature_flags.update`                      | Toggle per-funnel feature flags                                        |

### Email content[​](#email-content "Direct link to Email content")

| Tool                                                                                                    | Notes                                                                                                   |
| ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `email.draft.create`                                                                                    | Auto-creates missing splits, attaches DRAFT campaign                                                    |
| `email.draft.update`                                                                                    | Edit subject/blocks/etc. on a DRAFT                                                                     |
| `email.draft.archive`                                                                                   | Delete a DRAFT                                                                                          |
| `email.draft.send_test`                                                                                 | Send a test email to a verified address                                                                 |
| `email.draft.schedule`                                                                                  | Schedule the DRAFT to send at a future time                                                             |
| `email.draft.send_now`                                                                                  | **Promote DRAFT to live send.** T1 funnel-token. Once sent, can't be unsent (tombstone in change feed). |
| `email.draft.create_drip`                                                                               | Multi-step drip in one call                                                                             |
| `email.template.list` · `.create` · `.update` · `.preview`                                              | Template CRUD (no token for create/update — `write` scope is enough)                                    |
| `email.template.delete`                                                                                 | T1 entity-token (only required if linked to active sends)                                               |
| `email.sender.add` · `.list` · `.verify_with_code` · `.update` · `.resend_confirmation` · `.get_status` | Sender lifecycle                                                                                        |
| `email.sender.delete` · `email.domain.delete`                                                           | T1 entity-token                                                                                         |
| `email.asset.request_upload_url` · `.list` · `.delete`                                                  | Brand image library                                                                                     |
| `email.campaign.list` · `.get` · `.get_analytics` · `.list_recipients`                                  | Read-only                                                                                               |

### Automations[​](#automations "Direct link to Automations")

| Tool                                                                            | Notes                                                                                      |
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `automation.draft.create` · `.list` · `.archive` · `.add_step` · `.remove_step` | Draft authoring                                                                            |
| `automation.activate`                                                           | **Goes live.** T1 funnel-token. Tombstone (can pause but already-triggered runs continue). |
| `automation.pause` · `.resume`                                                  | State transitions on a live automation                                                     |
| `automation.canvas.get` · `automation.canvas.update`                            | Read or replace the canvas state (snapshot saved on update)                                |
| `automation.list_runs` · `automation.get_stats`                                 | Read-only                                                                                  |

### Tracking & integrations[​](#tracking--integrations "Direct link to Tracking & integrations")

| Tool                                                                                                                         | Notes                                                                                               |
| ---------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `tracking.site.add` · `.check` · `.list` · `.get_metrics`                                                                    | Setup + read                                                                                        |
| `tracking.site.update`                                                                                                       | `write` scope, no token                                                                             |
| `tracking.site.delete`                                                                                                       | **T1 entity-token.** Tombstone — events history orphaned.                                           |
| `integration.list` · `integration.check_connection`                                                                          | Read                                                                                                |
| `integration.start_oauth` · `integration.poll_oauth`                                                                         | Browser handoff (see [Browser handoffs](https://docs.funnelfizz.com/ai-agents/browser-handoffs.md)) |
| `integration.configure`                                                                                                      | Provider-specific config (e.g. GSC site URL, brand mentions keywords)                               |
| `integration.disconnect`                                                                                                     | T1 entity-token                                                                                     |
| `integration.stripe.connect` · `.get_setup_guide` · `.get_connection` · `.list_products` · `.sync_catalog` · `.list_catalog` | Stripe-specific                                                                                     |

### Workspace settings[​](#workspace-settings "Direct link to Workspace settings")

| Tool                                                       | Notes                                                             |
| ---------------------------------------------------------- | ----------------------------------------------------------------- |
| `workspace.update_brand` · `workspace.update_profile`      | `write` scope, no token (snapshot in `mcp_changes.previousState`) |
| `workspace.complete_onboarding`                            | Idempotent                                                        |
| `workspace.custom_event.register` · `.update` · `.archive` | Workspace event registry                                          |

### Admin (T2 code-paste — see [Admin actions](https://docs.funnelfizz.com/ai-agents/admin-actions.md))[​](#admin-t2-code-paste--see-admin-actions "Direct link to admin-t2-code-paste--see-admin-actions")

| Tool                                                                                  |
| ------------------------------------------------------------------------------------- |
| `team.invite_member` · `team.remove_member` · `team.change_role`                      |
| `billing.cancel_subscription` · `billing.resume_subscription` · `billing.change_plan` |
| `api_key.create` · `api_key.revoke`                                                   |
| `workspace.transfer_ownership` · `workspace.delete`                                   |

## Plan tiers + quotas[​](#plan-tiers--quotas "Direct link to Plan tiers + quotas")

|                       | FREE            | HOBBY           | HOBBY-trial | PRO             | PRO-trial |
| --------------------- | --------------- | --------------- | ----------- | --------------- | --------- |
| Read scope grantable  | ❌              | ✅              | ✅          | ✅              | ✅        |
| Write scope grantable | ❌              | ✅              | ✅          | ✅              | ✅        |
| Admin scope grantable | ✅ (role-gated) | ✅ (role-gated) | ✅          | ✅ (role-gated) | ✅        |
| Mutations / day       | 0               | 50              | 25          | 500             | 100       |
| Mutations / month     | 0               | 500             | 150         | 5,000           | 500       |
| Mutations / minute    | 0               | 15              | 10          | 60              | 30        |

A "mutation" = one top-level tool call with `write` scope, regardless of fan-out. `email.draft.create` that auto-creates 2 splits + 1 draft = 1 mutation. Charge for the **intent**, not the implementation.

Admin (T2) actions don't count against mutation quotas — the code-paste step is sufficient throttling on its own.

## Safety model[​](#safety-model "Direct link to Safety model")

1. **Target-bound.** Every T1 mutation requires a `target_token` validated against (api key, target, action). Wrong key, wrong action, wrong target, expired, or already-consumed → `400 invalid_request` with a structured `tokenStatus` reason. **Failed token validation does not consume mutation quota.**
2. **Code-paste for admin.** T2 actions (team, billing, keys, workspace transfer/delete) require a 6-digit code emailed to the API-key-holder. See [Admin actions](https://docs.funnelfizz.com/ai-agents/admin-actions.md).
3. **24-hour revert.** Every successful revertible mutation creates an `mcp_changes` row. The agent can call `mcp.revert_change` within 24h. Irreversible actions write tombstone rows with `revertible: false`. See [Revert changes](https://docs.funnelfizz.com/ai-agents/revert-changes.md).
4. **Audit log.** Every `/api/public/*` call writes an `ApiAuditLog` row with PII-redacted args, scope, tool, status, and duration. Surfaced in **Settings → Developer → API keys → Activity**.
5. **Per-workspace advisory lock.** Concurrent writes within a workspace serialize via Postgres advisory lock — UI and agent edits can't race.
6. **Plan/role downgrade.** A plan downgrade or role demotion silently revokes scopes mid-key — the next call returns `403 forbidden_scope` without burning quota.

## Walkthrough: drafting a US-desktop trial campaign[​](#walkthrough-drafting-a-us-desktop-trial-campaign "Direct link to Walkthrough: drafting a US-desktop trial campaign")

User prompt:

> *"Send an email to everyone who signed up for a free trial from the US who entered through desktop in my Widgets Pro funnel. Use template 1, image1.png as the banner. Don't send — draft it for me."*

```
// 1. Find the funnel.

funnel.resolve_by_name({ query: "Widgets Pro", intent: "mutate" })

// → { matches: [...], suggestion: "disambiguate" }



// 2. Show matches to the user. Wait for explicit confirmation.



// 3. Mint a token.

funnel.confirm_target({ funnelId: "fn_abc", action: "email.draft.create" })

// → { targetToken: "fft_…", expiresAt: "..." }



// 4. (Optional) Upload the banner image.

email.asset.request_upload_url({ mimeType: "image/png", sizeBytes: 84210, slot: "banner" })

// → { uploadUrl, r2Key, publicUrl }



// 5. Find a verified sender + the template id.

email.sender.list()       // → senderId

email.template.list()     // → template id for "Template 1"



// 6. Draft.

email.draft.create({

  targetToken: "fft_…",

  audience: {

    stage: "TRIAL",

    conditions: [

      { condition: "geography", value: "US" },

      { condition: "device_type", value: "Desktop" },

    ],

  },

  senderId,

  subject: "Quick tip for your US desktop trial",

  bodyMode: "TEMPLATE",

  templateId,

  blocks: { /* template variables incl. banner image url */ },

})

// → { campaignId, autoCreated: [...], funnelId, trackId }
```

If `Trial / geography=US / device_type=Desktop` doesn't yet exist as a track combination on the funnel, the server creates the missing tracks inside the same transaction as the draft and returns them in `autoCreated`. The agent surfaces this to the user.

## Walkthrough: 3-step drip on Hobby→Pro upgrade[​](#walkthrough-3-step-drip-on-hobbypro-upgrade "Direct link to Walkthrough: 3-step drip on Hobby→Pro upgrade")

```
funnel.resolve_by_name({ query: "Widgets Pro" })

funnel.confirm_target({ funnelId, action: "email.draft.create_drip" })



email.sender.list()

email.template.list()



email.draft.create_drip({

  targetToken,

  triggerStage: "CUSTOMER",

  triggerType: "subscription_paid",

  name: "Hobby → Pro upgrade nurture",

  emails: [

    { subject: "Welcome to Pro!", templateId, bodyMode: "TEMPLATE", delayDays: 0 },

    { subject: "Top 3 Pro features", templateId, bodyMode: "TEMPLATE", delayDays: 3 },

    { subject: "Ready to share with your team?", templateId, bodyMode: "TEMPLATE", delayDays: 3 },

  ],

})

// Status: DRAFT. To go live: automation.activate({ id, targetToken }).
```

## Walkthrough: sending a draft live[​](#walkthrough-sending-a-draft-live "Direct link to Walkthrough: sending a draft live")

```
// Drafted earlier; now ready to send.

funnel.resolve_by_name({ query: "Widgets Pro" })

funnel.confirm_target({ funnelId, action: "email.draft.send_now" })



email.draft.send_now({ draftId, targetToken })

// → { campaignId, recipientsCount, sentAt }

// Tombstone row added to mcp_changes (revertible: false).
```

## Content engine[​](#content-engine "Direct link to Content engine")

FunnelFizz does not become a CMS. The model is MCP-to-MCP federation: your agent has FunnelFizz MCP installed alongside Notion / Drive / GitHub MCPs. Brand voice + copy lives wherever you already manage it; the agent reads it via those other MCPs and hands the rendered content to FunnelFizz MCP via `email.draft.create`.

The only "content" stored in FunnelFizz is the email image library (uploaded via `email.asset.request_upload_url`).

## Errors you'll see[​](#errors-youll-see "Direct link to Errors you'll see")

| Code                                              | Meaning                                                                                                             |
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `forbidden_scope`                                 | Plan/role doesn't grant this scope (filtered from `tools/list` if no scope path applies)                            |
| `plan_required`                                   | Plan-tier feature gate (e.g. profile search on HOBBY)                                                               |
| `invalid_request` + `tokenStatus: "missing"`      | Pass `X-MCP-Target-Token` header on the mutation request                                                            |
| `invalid_request` + `tokenStatus: "expired"`      | Token older than 10 min — mint a new one                                                                            |
| `invalid_request` + `tokenStatus: "consumed"`     | Token already used — mint a fresh one for the next mutation                                                         |
| `invalid_request` + `tokenStatus: "wrong_action"` | Token's `action` doesn't match this tool                                                                            |
| `invalid_request` + `tokenStatus: "wrong_key"`    | Token belongs to a different API key                                                                                |
| `invalid_request` + `tokenStatus: "wrong_target"` | Entity-token bound to a different target type/id                                                                    |
| `missing_admin_token`                             | T2 tool called without an admin token — see [Admin actions](https://docs.funnelfizz.com/ai-agents/admin-actions.md) |
| `monthly_quota_exceeded`                          | Hit a daily/monthly mutation cap — `Retry-After` header indicates reset                                             |

All errors are non-destructive — the underlying state is unchanged, the audit log captures the attempt, and failed token-validation calls do **not** consume mutation quota.
