---
title: "Developer API, MCP and trigger simulation - Workflow Trigger Extensions"
description: "Inspect and control your triggers from your own code over a REST API or from an AI assistant over MCP, and simulate a trigger to test your Flow workflow without waiting for the real event."
canonical: "https://docs.workflow-trigger-extensions.app/developer-api-and-mcp"
---

# Developer API, MCP and trigger simulation

> [!NOTE]
> **Coming soon**
> The Developer API and MCP server are in active development and not yet available on production stores. This page documents what is coming so you can plan ahead. We will announce it in the changelog the moment it ships.

You can inspect your triggers, switch them on or off, read event history, and **simulate a trigger** from your own code or from an AI assistant. Workflow Trigger Extensions exposes a **REST API** and an **MCP server**, both managed on the **Developer** page.

## Simulating a trigger

Testing a Flow workflow normally means making the real thing happen in your store - editing a product, placing an order, waiting for a poll. Simulation removes that wait: pick a trigger, point it at a resource, and it fires as if the real event had just happened.

Two ways to run one:

- **In the app.** Open any event in **Event History** and choose **Simulate again**. It re-fires that exact event.
- **Over the API or MCP**, with a resource ID or a past event.

Simulation is deliberately faithful. It does not build a shortcut payload - it goes through the same pipeline a real Shopify webhook does, so what your workflow receives is exactly what it would receive in production.

> [!WARNING]
> **A simulated trigger runs your workflows for real**
> If the workflow tags an order, sends an email or updates inventory, that genuinely happens. Use **dry run** first to see what would be sent without firing anything.
>
> Simulated events are recorded as **test** events. They are excluded from your statistics and do **not** count toward your plan's monthly allowance.

```bash
curl -X POST https://your-app-url/api/v1/triggers/product-update-trigger/simulate \
  -H "Authorization: Bearer ftk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"resourceId":"gid://shopify/Product/123456789","dryRun":true}'
```

A dry run validates the trigger, resolves the topic, and shows you the payload - and fires nothing. Remove `dryRun` to fire it for real. You can also pass `{"fromHistoryId":"456"}` to re-fire a past event instead of building a new one.

Triggers driven by polling rather than a webhook cannot be simulated; they report `simulatable: false` in the trigger list.

## API keys

Create a key on the **Developer** page and pick its access level:

- **Read only** - list triggers and their state, read event history, stats and permissions.
- **Read & write** - also switch triggers on and off.
- **Read, write & execute** - also simulate triggers.

The full key is shown **once**, at creation. Keys are stored hashed and can be revoked at any time. Send it as a Bearer token:

```text
Authorization: Bearer ftk_your_key_here
```

Simulation sits behind its own level for the reason above: it runs your automations for real, so a key used for everyday reads cannot fire them.

## REST API

| Method | Path | Level | Purpose |
| --- | --- | --- | --- |
| GET | `/api/v1` | none | API index - confirms the API is up |
| GET | `/api/v1/me` | read | Check auth and see your key's level |
| GET | `/api/v1/triggers` | read | Every trigger, with its state for your store |
| GET | `/api/v1/triggers/:handle` | read | One trigger in detail |
| PUT | `/api/v1/triggers/:handle` | write | Switch one trigger on or off |
| PUT | `/api/v1/triggers` | write | Switch many on or off in one call |
| POST | `/api/v1/triggers/:handle/simulate` | execute | Simulate a trigger |
| GET | `/api/v1/history` | read | List trigger events |
| GET | `/api/v1/history/:id` | read | Get one event, with its payload |
| GET | `/api/v1/stats` | read | Totals, success rate, status breakdown |
| GET | `/api/v1/permissions` | read | Which data permissions you granted, and what each unlocks |

`GET /api/v1/triggers` is the useful one for setup: for every trigger it tells you the permission it needs, whether that permission is granted, whether the trigger is switched on, and where in the app to manage it.

### Switching triggers on and off

`PUT` is used rather than `PATCH` because the call is idempotent - replaying it after a timeout cannot double-apply anything, which matters when an assistant is driving the API.

```bash
curl -X PUT https://your-app-url/api/v1/triggers/order-tags-added-trigger \
  -H "Authorization: Bearer ftk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"enabled":true}'
```

Enabling never grants a permission. If the trigger needs one you have not granted, the call still succeeds and tells you exactly what is missing and where to grant it:

```json
{
  "enabled": true,
  "scopeGranted": false,
  "missingScopeLabels": ["Order Data Access"],
  "permissionsUrl": "https://admin.shopify.com/store/.../app/permissions?permission=read_orders",
  "warning": "Enabled, but this trigger cannot fire until Order Data Access is granted."
}
```

That link opens the Permissions page scrolled straight to the card you need.

Add `"sync": true` to also start a data sync, so the trigger works on records that already exist rather than only on ones created from now on.

To switch several at once, `PUT /api/v1/triggers` accepts an explicit list or a whole category:

```json
{ "enabled": true, "handles": ["order-tags-added-trigger", "order-note-changed-trigger"] }
{ "enabled": true, "category": "orders" }
```

## MCP server

The **MCP** tab on the Developer page shows the server URL and a ready-to-copy connect command for Claude, Cursor, VS Code, Gemini CLI and others. Tools mirror the REST endpoints, and **the tool set reflects your key's level** - a read-only key does not see the write or execute tools at all.

| Tool | Level | What it does |
| --- | --- | --- |
| `list_triggers` | read | Every trigger and its state |
| `list_history` | read | Recent trigger events |
| `get_event` | read | One event including its payload |
| `get_stats` | read | Aggregate statistics |
| `get_permissions` | read | Granted permissions and what they unlock |
| `get_trigger` | write | One trigger in detail |
| `set_trigger` | write | Switch one trigger on or off |
| `set_triggers_bulk` | write | Switch several on or off at once |
| `simulate_trigger` | execute | Fire a trigger on demand |

This is what makes an assistant genuinely useful for setup: it can list what exists, explain what a trigger needs, switch it on, fire a test event, and read back the result - without you leaving the conversation.

## How your data is protected

Event payloads are returned with personal data masked: email addresses, phone numbers, card numbers and person-named fields become `***`. Shopify resource IDs and business fields are left intact so the payload stays useful.

The masking works on field names and value patterns, so it is careful but **not a guarantee** - personal data inside a free-text field can still come through. Error messages are also stripped of internal diagnostic detail before they leave the server.

## Next steps

- [Custom triggers](https://docs.workflow-trigger-extensions.app/custom-triggers.md) - build a trigger of your own with a few lines of JavaScript.
- [Introduction to Workflow Trigger Extensions](https://docs.workflow-trigger-extensions.app/introduction.md) - how triggers work and how to switch them on.
