
List campaigns in the project, with optional state filter and sort. Use this to find a campaignid before reading metrics, scheduling, triggering or archiving. Page forward with page + 1 while pageinfo.hasnextpage is true.
List the individual sends a recurring campaign has generated. Use listcampaigns with campaignstate ['Recurring'] to find the parent campaignid. Page forward with page + 1 while pageinfo.hasnextpage is true.
Read one campaign by ID, including its state, template, lists and send times. Use list_campaigns to find campaign IDs. Timestamps come back as epoch milliseconds, not ISO 8601.
Get send and engagement metrics for one or more campaigns as CSV. Use list_campaigns to find campaign IDs. Iterable rate-limits this endpoint to 10 requests per minute, so batch the campaign IDs into one call.
Create a blast or triggered campaign from an existing template, in Ready state so it sends nothing until scheduled. Set schedulesend true with sendat to schedule it instead. Use listtemplates for template IDs and listlists for list IDs. Global suppression lists are NOT applied automatically.
Schedule an existing campaign to send at a given time. Use listcampaigns to find campaign IDs. Pass starttimezone and defaulttime_zone together to send in each recipient's own time zone instead.
Fire a triggered campaign at the members of one or more lists. This sends real messages immediately. Use listcampaigns to find a campaign whose type is 'Triggered' and listlists for list IDs.
Abort a campaign that is currently sending. Messages not yet delivered are not sent; those already delivered are unaffected. Use listcampaigns with campaignstate ['Running'] to find campaigns that can be aborted.
Cancel a scheduled or recurring campaign so it does not send. Use listcampaigns with campaignstate ['Scheduled', 'Recurring'] to find campaigns that can be cancelled. Use abort_campaign for one that is already sending.
Activate a triggered campaign so it can fire. Use listcampaigns to find a campaign whose type is 'Triggered'. Use deactivatetriggered_campaign to turn it off again.
Deactivate a triggered campaign so it stops firing. Use listcampaigns to find a campaign whose type is 'Triggered'. Use activatetriggered_campaign to turn it back on.
Archive one or more campaigns, hiding them from the Campaigns page. Scheduled and recurring campaigns are cancelled and running ones aborted as part of this. Iterable defines no un-archive endpoint, so this cannot be undone through the API. Use list_campaigns to find campaign IDs.
List the project's catalog names. Use this to find a catalogname before reading or writing catalog items. Page forward with page + 1 while pageinfo.hasnextpage is true.
Create an empty catalog. The name must be unique in the project, use only alphanumeric characters and dashes, and be at most 255 characters. Declare the field types with setcatalogfieldmappings before loading items. The inverse is deletecatalog.
Delete a catalog and every collection that references it. Use listcatalogs to find catalog names. This is the inverse of createcatalog and cannot be undone.
Read a catalog's declared field types and the fields that have none. Call this before setcatalogfieldmappings — a field's type cannot be changed once set — and before listcatalogitems with orderby, which needs a typed field.
Declare data types for a catalog's fields so they can be sorted and filtered on. Call getcatalogfield_mappings first: a field's type cannot be changed once set, so a wrong type here is permanent for that field.
List a catalog's items with their stored fields, optionally ordered by a typed field. Use listcatalogs for catalog names and getcatalogfieldmappings to check that order_by names a typed field. Page forward with page + 1.
Read one catalog item's stored fields. Use listcatalogitems to find item IDs; they are case-sensitive. Use list_catalogs for catalog names.
Create a catalog item, or REPLACE an existing one entirely — fields left out of value are removed. Use patchcatalogitem to change individual fields instead. Writes are asynchronous, so a following read may not show the change yet.
Set individual fields on a catalog item, leaving the rest as they are, creating the item if it does not exist. Use upsertcatalogitem to replace an item wholesale. Writes are asynchronous, so a following read may not show the change yet.
Delete one item from a catalog. Use listcatalogitems to find item IDs; they are case-sensitive. Deletion is asynchronous, so the item may still read back briefly. This is the inverse of upsertcatalogitem.
Create or overwrite up to 1000 catalog items in one request. Set replaceuploadedfields_only true to change only the fields given and leave the rest of each existing item alone. Writes are asynchronous, so a following read may not show them yet.
Delete several items from a catalog in one request. Use listcatalogitems to find item IDs. Deletion is asynchronous, so the items may still read back briefly. This is the inverse of bulkcreatecatalog_items.
List the project's message channels and whether each is Marketing or Transactional. Call this before upsertemailtemplate: a template on a Marketing channel must carry an unsubscribe link, one on a Transactional channel need not.
List the project's message types with their channel, subscription policy and send caps. Use this to find the messagetypeid that upsertemailtemplate, upsertsmstemplate and updateusersubscriptions require.
Record a completed purchase against a user profile, creating the profile if it does not exist. This also CLEARS the profile's shopping cart. Pass your own id to make repeat calls update one purchase instead of recording duplicates. Use list_campaigns for campaign attribution.
Replace the items in a user's shopping cart, creating the profile if it does not exist. The list given REPLACES the stored cart, so send an empty list to clear it. track_purchase clears the cart on its own.
Create an email template, or update every template sharing the clienttemplateid. Use listmessagetypes for messagetypeid and listchannels to check the channel: HTML on a Marketing channel must contain an unsubscribe link such as {{unsubscribeUrl}}, and fromemail must already be an authorized sender.
Update one existing email template by ID. Read it with getemailtemplate first and send back the fields you want kept — Iterable replaces what it is given. Use listtemplates with messagemedium 'Email' to find template IDs.
Render an email template against sample data and return the HTML, without sending anything. Use this to check merge fields and conditional logic before a send. Use sendemailtemplate_proof when you need a real message delivered.
Send a real test email rendered from a template to one recipient. This delivers an actual message — use previewemailtemplate to check rendering without sending. Identify the recipient by recipientemail or recipientuser_id.
Create an embedded-message template, or update every template sharing the clienttemplateid. Use listmessagetypes for messagetypeid. Read an existing template with getembeddedtemplate first to copy its elements tree, whose shape depends on the placement.
Update one existing embedded-message template by ID. Read it with getembeddedtemplate first and send back the fields you want kept — Iterable replaces what it is given. Use list_templates to find template IDs. Embedded templates have no preview or proof endpoint.
Read one user's recent events, newest first, identified by email or user_id. Use this to see what a user has done before deciding what to send them. Returns 30 events unless limit is set, up to 200.
Record one custom event against a user profile. An event whose name appears in a journey's triggereventnames enrols the user in that journey — use list_journeys to see which names do. Pass your own id to make repeat calls update one event rather than duplicate it.
Record many custom events in one request. Iterable processes bulk and single event calls on separate pipelines, so send a given stream through track_event or this tool, not both, if ordering matters. The result reports per-reason failures.
Record a click on an embedded message, creating an embeddedClick event. Iterable's mobile SDKs send this themselves; call it when integrating without one. Use listembeddedmessages to find message IDs. Proofs raise no events.
Record that a device retrieved an embedded message, creating an embeddedReceived event. This means retrieved, not displayed — use trackembeddedmessage_session for impressions. Iterable's SDKs send this themselves.
Record a viewing session and one impression per embedded message seen during it, creating an embeddedSession event and an embeddedImpression event each. Use trackembeddedmessagereceived for retrieval and trackembeddedmessageclick for clicks.
List the project's experiments, filtered by campaign, state or start date. Use this to find an experiment_id before reading its variants or results. This endpoint pages by offset and limit, not by page number.
Read one experiment: its type, status, variants, traffic allocation and targeting. Use listexperiments to find experiment IDs. Read it before updateexperiment_settings, whose settings blocks replace what they are given.
List an experiment's variants with the template content each sends and its share of the audience. Use this to find the variantid that declareexperimentwinner takes. Use listexperiments for experiment IDs.
Read lifetime send and conversion totals for every variant in an experiment. Use getexperimenttrends for the same numbers over time, and list_experiments to find experiment IDs.
Read an experiment's per-variant metrics as a time series, with the holdout group reported separately. Use getexperimenttotals for lifetime numbers instead, and list_experiments to find experiment IDs.
Get a metrics report across several experiments or campaigns as rows of comma-separated values. Supply experimentids or campaignids. Use getexperimenttotals for one experiment's numbers in structured form.
Attach a new experiment to a campaign, choosing what it varies. Use listcampaigns to find campaign IDs. experimenttype cannot be changed later. Add arms with createexperimentvariant, then start_experiment.
Add a variant to an experiment by copying an existing template into it. Use listtemplates for the template to copy and listexperiments for experiment IDs. Returns the experiment with its updated variant list.
Change how an experiment splits traffic and picks a winner. Read the current settings with getexperiment first — a block you send replaces the stored one, while a block left null is untouched. Use listexperiments for experiment IDs.
Start an experiment so it begins allocating traffic across its variants. Add variants with createexperimentvariant first. Use cancelexperiment to stop it again, or declareexperiment_winner to end it on a chosen arm.
Stop a running experiment without choosing a winner. Use declareexperimentwinner to end it on a chosen arm instead. This is the inverse of startexperiment. Use listexperiments to find experiment IDs.
End an experiment by declaring one variant the winner, after which the campaign sends that variant to everyone. Use listexperimentvariants to find variant IDs; the holdout group cannot be chosen.
Delete an experiment from its campaign. Use cancelexperiment to stop a running experiment while keeping its results. This is the inverse of createexperiment and cannot be undone.
Queue a background export of events or user profiles and return its jobid. Poll listexportfiles with that jobid for state and download links. Use export_data for a small window you want back immediately. Four exports run at once per organization.
List the project's recent export jobs with their state and size. Use this to find a jobid when you no longer have the one startexport returned, then read its files with listexportfiles.
Read an export job's state and the files it has written, each with a signed download URL that expires after 30 minutes. Poll this after startexport until jobstate is Completed. Page with start_after when a job writes many files.
Cancel a queued or running export job. Use listexportjobs to find a jobid, or pass the one startexport returned. This is the inverse of start_export; a job that has already completed cannot be cancelled.
Download campaign analytics immediately as CSV or JSON lines, without a background job. Supply range, or both startdatetime and enddatetime. Use start_export for a large window — this endpoint allows only 4 requests a minute and returns the whole body in one response.
Download one user's complete event history as JSON lines, identified by email or userid. Use listuser_events for a small parsed sample instead — this returns everything in one response.
Create an in-app template, or update every template sharing the clienttemplateid. Use listmessagetypes to find an in-app messagetypeid. Read an existing template with getinapp_template first to copy its display settings.
Update one existing in-app template by ID. Read it with getinapptemplate first and send back the fields you want kept — Iterable replaces what it is given. Use listtemplates with message_medium 'InApp' to find template IDs.
Render an in-app template against sample data and return the HTML, without sending anything. Use this to check merge fields before a send. Use sendinapptemplateproof when you need a real message delivered.
Send a real test in-app message rendered from a template to one recipient. This delivers an actual message — use previewinapptemplate to check rendering without sending. Identify the recipient by recipientemail or recipientuserid.
List the project's journeys with their trigger events and enabled state. Use this to find the journey id that triggerjourney takes as workflowid. Set state ['Archived'] to see archived journeys instead. page_size caps at 50 here.
Enrol one user, or every member of a list, in a journey. This starts real message sends on the journey's schedule. Use listjourneys to find the journey id and pass it as workflowid; use list_lists for list IDs.
List every list in the project with its type and size-relevant metadata. Use this to find a listid before creating a campaign, subscribing users or reading membership. Only a list whose listtype is Standard accepts subscribetolist.
Create a static list. Returns the new listid for use with subscribetolist and createcampaign. Dynamic (segment) lists cannot be created through the API — build those in Iterable.
Delete a list. The users on it are not deleted, only their membership. Use listlists to find list IDs. This is the inverse of createlist and cannot be undone.
Count the users on a list without reading them. Use listlists to find list IDs. Iterable rate-limits this to 5 requests per minute, so prefer it over getlist_users when you only need the number.
Read every user on a list as email addresses, or user IDs when preferuserid is true. Use getlistsize first for the count alone, or previewlistusers for a sample — Iterable rate-limits this to 5 requests per minute and a large list returns a large body.
Read a random sample of up to 5000 users on a list. Use this instead of getlistusers to inspect a large list cheaply. Use list_lists to find list IDs.
Add users to a static list, creating profiles for identifiers that do not exist yet. Use listlists to find a list whose listtype is Standard. The inverse is unsubscribefromlist.
Remove users from a list. Set channelunsubscribe true to also opt them out of the list's whole channel, which is effectively a global opt-out. Use listlists to find list IDs. This is the inverse of subscribetolist.
Cancel a scheduled email before it is delivered. Identify it by scheduledmessageid, or by campaignid plus the recipient. Use listsentmessages and listcampaigns to find those. Already-delivered mail cannot be recalled.
Cancel a scheduled SMS before it is delivered. Identify it by scheduledmessageid, or by campaignid plus the recipient. Use listcampaigns for campaign IDs. An already-sent text cannot be recalled.
Cancel a scheduled mobile push notification before it is delivered. Identify it by scheduledmessageid, or by campaignid plus the recipient. Use cancelweb_push for browser notifications.
Cancel a scheduled browser push notification before it is delivered. Identify it by scheduledmessageid, or by campaignid plus the recipient. Use cancelpush for mobile notifications.
Cancel a scheduled RCS message before it is delivered. Identify it by scheduledmessageid, or by campaignid plus the recipient. Use listcampaigns for campaign IDs.
Cancel a scheduled WhatsApp message before it is delivered. Identify it by scheduledmessageid, or by campaignid plus the recipient. Use listcampaigns for campaign IDs.
Cancel a scheduled in-app message before it is delivered to the user's device. Identify it by scheduledmessageid, or by campaignid plus the recipient. Use listinappmessages to see what is already waiting.
Fetch the rendered HTML of an email already sent to a user, with that recipient's merge data applied. Use listsentmessages to find message IDs. Use previewemailtemplate to render a template that has not been sent.
List the in-app messages waiting for one user on a mobile platform, in display priority order. Use getpriorityinappmessage for just the most relevant one, and listwebinappmessages for the browser.
Read the single most relevant in-app message waiting for one user. Use listinappmessages to see everything queued for them instead. Identify the user by email or userid.
List the in-app messages waiting for one user in the browser. Use listinappmessages for mobile platforms. Identify the user by email or userid.
List the embedded messages waiting for one user, grouped by placement. Read messageId out of each message's metadata to pass to trackembeddedmessageclick. Identify the user by email or userid.
Send a verification code by SMS to a phone number. This delivers a real text message. Check the code the user receives with checksmsverification. The verificationprofileid is configured in Iterable by an administrator.
Check a verification code against the number beginsmsverification sent it to. Use the same phonenumber and verificationprofile_id as that call. A wrong or expired code is reported as a validation error.
List the project's metadata tables. Metadata is Iterable's own JSON key/value store, which templates read with the metadata Handlebars helper. Use this to find a table name before reading or writing keys.
List the keys in one metadata table, without their values. Use listmetadatatables to find table names and getmetadatavalue to read a key. Page forward by passing the next_marker from the previous response.
Read one key's stored JSON value from a metadata table. Use listmetadatakeys to find keys; they are case-sensitive. Use this to confirm an asynchronous setmetadatavalue has landed.
Create or REPLACE one key's JSON value, creating the table if it does not exist. The write is asynchronous: a 200 means it was accepted, not applied, so confirm with getmetadatavalue. Maximum 30 KB per value.
Delete one key and its value from a metadata table. The delete is asynchronous, so the key may still read back briefly — confirm with getmetadatavalue. This is the inverse of setmetadatavalue.
Delete a whole metadata table and every key in it. The delete is asynchronous, so the table may still list briefly — confirm with listmetadatatables. Use deletemetadatakey to remove a single key instead.
Create a push template, or update every template sharing the clienttemplateid. Use listmessagetypes to find a push messagetypeid — the project needs a Push channel for one to exist. Read an existing template with getpushtemplate first to copy its buttons and payload.
Update one existing push template by ID. Read it with getpushtemplate first and send back the fields you want kept — Iterable replaces what it is given. Use listtemplates with messagemedium 'Push' to find template IDs.
Send a real test push notification rendered from a template to one recipient. This delivers an actual notification to the recipient's registered devices. Identify the recipient by recipientemail or recipientuser_id. Push templates have no preview endpoint.
Create an SMS template, or update every template sharing the clienttemplateid. messagetypeid must belong to a channel whose medium is SMS — use listmessagetypes and list_channels to find one, or Iterable rejects the save as an invalid message medium.
Update one existing SMS template by ID. Read it with getsmstemplate first and send back the fields you want kept — Iterable replaces what it is given. Use listtemplates with messagemedium 'SMS' to find template IDs.
Send a real test SMS rendered from a template to one recipient. This delivers an actual text message to the phone number on the recipient's profile. Identify the recipient by recipientemail or recipientuser_id. SMS templates have no preview endpoint.
List the project's snippets with their content and variables. Use this to find a snippet's ID or name before reading, updating or deleting it, or to see what shared blocks a template can reference.
Read one snippet by its numeric ID or its name. Use listsnippets to find both. Read it before upsertsnippet, which replaces the stored content outright.
Create a snippet. The name must be unique in the project. Use upsertsnippet to create-or-update by name instead. Returns the new snippetid. The inverse is delete_snippet.
Create or update a snippet by name, or update one by numeric ID. A numeric identifier only updates an existing snippet; a name creates it when none matches. Content REPLACES what is stored, so read it with get_snippet first.
Delete a snippet by its numeric ID or its name. Use listsnippets to find both. A template still referencing the snippet will fail to render, so check usage first. This is the inverse of createsnippet.
Subscribe one user to a list, message type or channel. Use listlists, listmessagetypes or listchannels for the group ID. The inverse is unsubscribeuserfrom_group. This API is enabled per project by Iterable.
Unsubscribe one user from a list, message type or channel. Use listlists, listmessagetypes or listchannels for the group ID. This is the inverse of subscribeuserto_group. This API is enabled per project by Iterable.
Subscribe or unsubscribe many users to or from one list, message type or channel. Name users by email in users, by user ID in usersbyuserid, or both. Use subscribeusertogroup for a single user. This API is enabled per project by Iterable.
Send a user an SMS confirmation and subscribe them to the named message types once they reply. Only SMS message types configured for double opt-in work here — use listmessagetypes to find them. This sends a real text message.
List the project's templates with their IDs, names and message types, filtered by type, channel or creation date. Use this to find a templateid before reading a template, sending a proof or creating a campaign. Page forward with page + 1 while pageinfo.hasnextpage is true.
Find the templates carrying a given client template ID, the secondary key set when a template is created through upsertemailtemplate and its siblings. Several templates can share one key, so the result is a list. Use list_templates to browse by name instead.
Read one email template's full content: subject, HTML, plain text, sender and link settings. Use listtemplates with messagemedium 'Email' to find template IDs. Read it before updateemailtemplate so unchanged fields can be sent back.
Read one SMS template's full content: message body, image and link settings. Use listtemplates with messagemedium 'SMS' to find template IDs. Read it before updatesmstemplate so unchanged fields can be sent back.
Read one push template's full content: title, message, deep links, buttons and payload. Use listtemplates with messagemedium 'Push' to find template IDs. Read it before updatepushtemplate so unchanged fields can be sent back.
Read one in-app template's full content: HTML, display settings, inbox metadata and expiry. Use listtemplates with messagemedium 'InApp' to find template IDs. Read it before updateinapp_template so the display settings can be sent back unchanged.
Read one embedded-message template's full content: title, body, elements and placement. Use listtemplates to find template IDs. Read it before updateembedded_template so the elements tree can be sent back unchanged.
Delete base templates by ID. A template any campaign references is reported in failed rather than deleted, so check the result. Use listtemplates to find template IDs; a template whose campaignid is null has no campaign using it.
Read one user profile by email address, including every custom data field. Use getuserbyuserid when you hold a user ID instead. Iterable answers an unknown address with found=false rather than an error.
Read one user profile by user ID, including every custom data field. Use getuserby_email when you hold an email address instead. An unknown user ID is an error here, unlike an unknown email address, which comes back as found=false.
List every user-profile field defined in the project and its data type. Call this before update_user to learn which fields exist and what type each expects — Iterable rejects a value whose type differs from the stored one.
List the messages Iterable has sent to one user, with campaign, date and channel filters. Identify the user by email or userid. Use listcampaigns for campaign IDs. Returns 10 messages unless limit is set.
Create or update one user profile, merging the fields given into whatever is already stored. Identify the user by email or userid. Use listuser_fields first: Iterable rejects a field whose type differs from the stored one.
Create or update up to 1000 user profiles in one request. Each user is identified by email or userid. Use listuserfields first to check field types. The result reports per-reason failures in failedupdates.
Move a user profile to a new email address. Identify the profile by currentemail or currentuserid. This is for email-based projects; in a userId-based or hybrid project, change the address with updateuser instead.
Set one user's list, channel and message-type subscriptions. Each list given REPLACES the stored one rather than adding to it, so read the user with getuserbyemail first and send the full intended set. Use listlists, listchannels and listmessage_types for the IDs.
Set list, channel and message-type subscriptions for many users in one request. Each entry REPLACES the stored lists rather than adding to them. Use updateusersubscriptions for a single user.
Validate the Iterable API key with a lightweight project lookup. Returns success plus a message explaining any failure.
List the project's system webhooks with their endpoints, authentication and scope. Use this to find the webhook id that update_webhook takes. Iterable has no endpoint for creating or deleting a webhook — do that in Iterable.
Change an existing webhook's endpoint, authentication, scope or enabled state. Use list_webhooks to find webhook IDs and read the current settings first — headers replace the stored list. Iterable defines no create or delete endpoint for webhooks.

In an mcp.json file, add the configuration below, and restart Cursor.
Learn more in the official documentation ↗
1{
2 "mcpServers": {
3 "agent-handler": {
4 "url": "https://ah-api-develop.merge.dev/api/v1/tool-packs/{TOOL_PACK_ID}/registered-users/{REGISTERED_USER_ID}/mcp",
5 "headers": {
6 "Authorization": "Bearer yMt*****"
7 }
8 }
9 }
10}
11Open your Claude Desktop configuration file and add the server configuration below. You'll also need to restart the application for the changes to take effect.
Make sure Claude is using the Node v20+.
Learn more in the official documentation ↗
1{
2 "mcpServers": {
3 "agent-handler": {
4 "command": "npx",
5 "args": [
6 "-y",
7 "mcp-remote@latest",
8 "https://ah-api-develop.merge.dev/api/v1/tool-packs/{TOOL_PACK_ID}/registered-users/{REGISTERED_USER_ID}/mcp",
9 "--header",
10 "Authorization: Bearer ${AUTH_TOKEN}"
11 ],
12 "env": {
13 "AUTH_TOKEN": "yMt*****"
14 }
15 }
16 }
17}Open your Windsurf MCP configuration file and add the server configuration below.
Click on the refresh button in the top right of the Manage MCP server page or in the top right of the chat box in the box icon.
Learn more in the official documentation ↗
1{
2 "mcpServers": {
3 "agent-handler": {
4 "command": "npx",
5 "args": [
6 "-y",
7 "mcp-remote@latest",
8 "https://ah-api.merge.dev/api/v1/tool-packs/<tool-pack-id>/registered-users/<registered-user-id>/mcp",
9 "--header",
10 "Authorization: Bearer ${AUTH_TOKEN}"
11 ],
12 "env": {
13 "AUTH_TOKEN": "<ah-production-access-key>"
14 }
15 }
16 }
17 }In Command Palette (Cmd+Shift+P on macOS, Ctrl+Shift+P on Windows), run "MCP: Open User Configuration".
You can then add the configuration below and press "start" right under servers. Enter the auth token when prompted.
Learn more in the official documentation ↗
1{
2 "inputs": [
3 {
4 "type": "promptString",
5 "id": "agent-handler-auth",
6 "description": "Agent Handler AUTH_TOKEN", // "yMt*****" when prompt
7 "password": true
8 }
9 ],
10 "servers": {
11 "agent-handler": {
12 "type": "stdio",
13 "command": "npx",
14 "args": [
15 "-y",
16 "mcp-remote@latest",
17 "https://ah-api-develop.merge.dev/api/v1/tool-packs/{TOOL_PACK_ID}/registered-users/{REGISTERED_USER_ID}/mcp",
18 "--header",
19 "Authorization: Bearer ${input:agent-handler-auth}"
20 ]
21 }
22 }
23}Whether you're an engineer experimenting with agents or a product manager looking to add tools, you can get started for free now