Meta Pixel Spur MCP Tools Reference
Docs / MCP
Tools

Spur MCP tool catalog

Each tool below includes required inputs, operational notes, and JSON-RPC examples.

Meta spec-aligned template guidance

The guidance below aligns with Meta Business Messaging OpenAPI v23.0 template concepts and is adapted for Spur MCP draft payloads.

Reference: business-messaging-api_v23.0.yaml

  • Meta template objects require a name and language. Use deterministic language values such as en or en_US.
  • Meta runtime component types are commonly header, body, and button. Spur draft payloads use uppercase style such as HEADER, BODY, and BUTTONS.
  • For variable placeholders in body text ({{1}}, {{2}}), always include example.body_text sample values.
  • Supported parameter families include text, currency, date/time, and media. For media templates, provide explicit image/video/document structure. For template submission, media headers must include example.header_handle from whatsapp_template_media_upload. For broadcast launches, media headers must use provider-aware output from whatsapp_media_upload: INFOBIP uses Spur S3 links (image.link/video.link/document.link), while other providers use uploaded media IDs (image.id/video.id/document.id). PDF is the safe document default.
  • Button text and payload rules are strict. Keep quick replies and URL suffixes short and final before submission.
  • For MARKETING templates, add opt-out language in body/footer, for example: Send STOP to stop receiving marketing messages.

Recommended MCP workflow for complex templates

  1. Call template_search to find a close existing template by name/category.
  2. Call template_requirements to generate runtime payload requirements and sample contentMessage.
  3. If the template uses IMAGE/VIDEO/DOCUMENT headers, call whatsapp_template_media_upload and place returned headerHandle in example.header_handle.
  4. Build a new payload with template_create_draft using reviewed component structure.
  5. Call template_submit_for_review with the returned draft ID.
  6. If submission fails, read providerError fields (error_user_msg, error_subcode, fbtrace_id), adjust payload, and retry.
  7. Call template_refresh to sync latest review state before sending campaigns.

Broadcast lifecycle flow (recommended)

  1. Call segment_filters_catalog to discover valid filter fields/operators.
  2. Build and check ruleGroup via segment_validate before writing segments.
  3. Create or update audience segments with segment_create / segment_update and confirm validation.validForBroadcast=true.
  4. Use segment_search to select final include/exclude segment ids.
  5. For existing segments, run segment_size and confirm validation.audienceCount > 0 before launch.
  6. Call template_requirements before campaign creation, especially for carousel templates.
  7. For broadcast runtime media headers, call whatsapp_media_upload and place the provider-specific value in contentMessage header parameters: INFOBIP requires Spur S3 link, other providers require uploaded id.
  8. Create draft campaign using broadcast_create and a template reference.
  9. Update variables and audience operations with broadcast_update ( includeSet, includeAdd, excludeAdd, sendAt, deliveryBoost, splitAcrossDays ).
  10. Launch the campaign with broadcast_launch.
  11. Use uploaded media IDs at launch time. header_handle is for template submission examples and should not be used as runtime broadcast media input.
  12. Track performance via broadcast_analytics and broadcast_overview_stats.

CSV import flow (recommended)

  1. Upload CSV to an accessible URL (for example a signed S3 URL).
  2. Call customer_import_csv_validate to confirm mapping and row quality.
  3. Call customer_import_csv_start to queue import of valid rows only.
  4. Poll customer_import_csv_status until completed.
  5. Use customer_import_csv_clear to reset stored status before the next run.

template_create_draft

Creates a WhatsApp template draft inside Spur.

Required fields

  • name
  • channelId
  • category
  • format
  • language
  • components

Optional fields

  • subCategory

Behavior notes

  • category: MARKETING | UTILITY | AUTHENTICATION
  • format: SINGLE | CAROUSEL
  • Creates draft only and does not submit to Meta
  • Template name should be lowercase with underscores for reliable Meta acceptance
  • When BODY has variables ({{1}}, {{2}}), include example.body_text values to avoid review failures
  • For MARKETING templates, include opt-out language such as "Send STOP to stop receiving marketing messages."
  • WhatsApp text formatting is supported in BODY/FOOTER: *bold*, _italic_, ~strikethrough~, `inline code`, ```monospace```, numbered lists (1. item), bullets (- item), and quote lines (> text)
  • BUTTON text must exactly match what you intend to submit. Meta is strict about button text mismatches
  • For IMAGE/VIDEO/DOCUMENT headers, set example.header_handle from whatsapp_template_media_upload before submission

Example request

{
  "jsonrpc": "2.0",
  "id": 11,
  "method": "tools/call",
  "params": {
    "name": "template_create_draft",
    "arguments": {
      "name": "order_update_template",
      "channelId": 101,
      "category": "UTILITY",
      "format": "SINGLE",
      "language": "en",
      "components": {
        "body": "Your order update text"
      }
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "draftTemplateId": 873,
  "templateSummary": {
    "name": "order_update_template",
    "channelId": 101,
    "category": "UTILITY",
    "language": "en",
    "format": "SINGLE"
  }
}

template_create_draft (complex utility example)

Reference payload pattern for variable-rich utility templates with header and quick reply buttons.

Required fields

  • name
  • channelId
  • category
  • format
  • language
  • components

Optional fields

  • subCategory

Behavior notes

  • Use this shape when you need placeholders, deterministic button options, and production-safe examples
  • Quick replies should be concise and final before submitting for review
  • BODY text can include line breaks and WhatsApp formatting markers, but avoid malformed markdown patterns that Meta rejects

Example request

{
  "jsonrpc": "2.0",
  "id": 12,
  "method": "tools/call",
  "params": {
    "name": "template_create_draft",
    "arguments": {
      "name": "address_confirmation_v2",
      "channelId": 3,
      "category": "UTILITY",
      "format": "SINGLE",
      "language": "en",
      "subCategory": "CUSTOM",
      "components": [
        {
          "type": "HEADER",
          "format": "TEXT",
          "text": "Confirm Delivery Address"
        },
        {
          "type": "BODY",
          "text": "Thanks for placing your order with {{1}}. We will deliver to:\n\n{{2}}\n{{3}}\n{{4}}\n\nNeed any corrections before shipping?",
          "example": {
            "body_text": [["Spur Store", "221B Baker Street", "London", "NW1 6XE"]]
          }
        },
        {
          "type": "BUTTONS",
          "buttons": [
            { "type": "QUICK_REPLY", "text": "Address Is Correct" },
            { "type": "QUICK_REPLY", "text": "Edit My Address" }
          ]
        }
      ]
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "draftTemplateId": 910,
  "templateSummary": {
    "name": "address_confirmation_v2",
    "channelId": 3,
    "category": "UTILITY",
    "subCategory": "CUSTOM",
    "language": "en",
    "format": "SINGLE"
  }
}

template_submit_for_review

Submits an existing draft template for Meta review.

Required fields

  • templateId

Behavior notes

  • Returns provider-level error context when Meta rejects submission
  • Use this after template_create_draft
  • Media headers require example.header_handle from whatsapp_template_media_upload (URL/mediaId will fail)
  • warnings is reserved for non-fatal advisories
  • On failure, inspect providerError.error_user_msg, providerError.error_subcode, and traceId

Example request

{
  "jsonrpc": "2.0",
  "id": 12,
  "method": "tools/call",
  "params": {
    "name": "template_submit_for_review",
    "arguments": {
      "templateId": 873
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "submittedTemplate": {
    "id": 873,
    "status": "IN_REVIEW",
    "category": "UTILITY",
    "templateId": "1234567890"
  },
  "status": "IN_REVIEW",
  "category": "UTILITY",
  "warnings": []
	}

whatsapp_template_media_upload

Downloads media from a URL, uploads it through Meta resumable uploads, and returns header_handle for template IMAGE/VIDEO/DOCUMENT headers.

Required fields

  • channelId
  • mediaUrl

Optional fields

  • mediaType
  • filename

Behavior notes

  • Use this for template_create_draft media headers (example.header_handle)
  • Do not use this output in broadcast runtime payloads
  • Use whatsapp_media_upload for runtime broadcast/message media IDs (image.id/video.id/document.id)
  • mediaType: image | video | document (defaults to image)

Example request

{
  "jsonrpc": "2.0",
  "id": 13,
  "method": "tools/call",
  "params": {
    "name": "whatsapp_template_media_upload",
    "arguments": {
      "channelId": 12,
      "mediaUrl": "https://example.com/assets/template-banner.png",
      "mediaType": "image"
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "upload": {
    "channelId": 12,
    "headerHandle": "4::aW1hZ2UvcG5n:ARbx...",
    "mediaType": "image",
    "sourceUrl": "https://example.com/assets/template-banner.png",
    "mimeType": "image/png",
    "sizeBytes": 248321
  },
  "templateUsage": {
    "singleHeaderExample": {
      "type": "HEADER",
      "format": "IMAGE",
      "example": {
        "header_handle": ["4::aW1hZ2UvcG5n:ARbx..."]
      }
    },
    "carouselCardHeaderExample": {
      "card_index": 0,
      "components": [
        {
          "type": "HEADER",
          "format": "IMAGE",
          "example": {
            "header_handle": ["4::aW1hZ2UvcG5n:ARbx..."]
          }
        }
      ]
    }
  }
}

template_refresh

Refreshes template status/components from Meta by internal template id or metaTemplateId.

Required fields

  • one of: id | metaTemplateId

Behavior notes

  • Use id for a specific local template record
  • Use metaTemplateId when your flow stores Meta template IDs
  • Returns updated status and metadata after sync

Example request

{
  "jsonrpc": "2.0",
  "id": 14,
  "method": "tools/call",
  "params": {
    "name": "template_refresh",
    "arguments": {
      "metaTemplateId": "1208259160474051"
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "refreshedBy": "metaTemplateId",
  "template": {
    "id": 601,
    "name": "order_update_v1",
    "status": "APPROVED",
    "category": "UTILITY",
    "format": "SINGLE",
    "language": "en_US",
    "templateId": "1208259160474051"
  }
}

template_delete

Deletes a template by internal template id. MCP validates workspace ownership first, then attempts Meta deletion when a linked templateId exists.

Required fields

  • id

Behavior notes

  • Use template_search first to pick the correct internal id
  • metaDeletion.attempted=true means the deleted template had a linked Meta template id
  • metaDeletion.success can be null when no Meta deletion was attempted or provider response is unavailable

Example request

{
  "jsonrpc": "2.0",
  "id": 15,
  "method": "tools/call",
  "params": {
    "name": "template_delete",
    "arguments": {
      "id": 89
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "deleted": {
    "id": 89,
    "name": "mcp_marketing_simple_1771153651",
    "templateId": "1475797657492255",
    "status": "APPROVED",
    "category": "MARKETING",
    "subCategory": "CUSTOM",
    "format": "SINGLE",
    "language": "en",
    "channelId": 3
  },
  "metaDeletion": {
    "attempted": true,
    "success": true,
    "providerResponse": {
      "success": true
    }
  }
}

template_requirements

Returns runtime send requirements and a sample contentMessage payload for a template. Use this before carousel sends and variable-heavy templates.

Required fields

  • one of: id | name

Optional fields

  • channelId

Behavior notes

  • Use id whenever possible for deterministic lookups
  • When using name in a multi-channel workspace, pass channelId to avoid ambiguity
  • hasCarousel=true means contentMessage.components.carousel.cards is required for broadcast/message sends
  • For media headers, upload first via whatsapp_media_upload and use returned image.id/video.id/document.id
  • For dynamic URL buttons, runtime button text must be the URL suffix only (not a full URL)
  • URL button index must match the approved template button position (check requiredRuntimeComponents notes)
  • For single templates with multiple buttons, include runtime components for each supported button type (for example quick_reply + url)
  • sampleContentMessage is a scaffold, not a final production payload

Example request

{
  "jsonrpc": "2.0",
  "id": 16,
  "method": "tools/call",
  "params": {
    "name": "template_requirements",
    "arguments": {
      "id": 44
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "template": {
    "id": 44,
    "name": "test_template_carousel",
    "format": "CAROUSEL",
    "language": "en",
    "channelId": 3
  },
  "requirements": {
    "hasCarousel": true,
    "carouselCardCount": 2,
    "bodyVariableCount": 0,
    "hasDynamicButtons": true,
    "requiresContentMessage": true
  },
  "sampleContentMessage": {
    "name": "test_template_carousel",
    "language": { "code": "en" },
    "components": [
      {
        "type": "carousel",
        "cards": [
          {
            "card_index": 0,
            "components": [
              {
                "type": "header",
                "parameters": [{ "type": "image", "image": { "id": "<meta-media-id>" } }]
              },
	              {
	                "type": "button",
	                "sub_type": "url",
	                "index": "1",
	                "parameters": [{ "type": "text", "text": "url_suffix_value" }]
	              }
	            ]
	          }
	        ]
	      }
    ]
  }
}

channels_list

Lists connected channels in the workspace. Use this to discover valid sender handles before calling message_send.

Required fields

Optional fields

  • channelType

Behavior notes

  • channelType: whatsapp | instagram | facebook | live_chat
  • For WhatsApp, use channels[].from as options.from in message_send
  • Returns id, type, provider, handle, and displayName

Example request

{
  "jsonrpc": "2.0",
  "id": 13,
  "method": "tools/call",
  "params": {
    "name": "channels_list",
    "arguments": {
      "channelType": "whatsapp"
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "count": 2,
  "channels": [
    {
      "id": 12,
      "type": "whatsapp",
      "provider": "AISENSY",
      "from": "919876543210",
      "handle": "919876543210",
      "displayName": "Spur Support"
    },
    {
      "id": 21,
      "type": "whatsapp",
      "provider": "AISENSY",
      "from": "918888777666",
      "handle": "918888777666",
      "displayName": "Spur Sales"
    }
  ]
}

whatsapp_media_upload

Downloads media from a URL and uploads it using provider-aware routing: INFOBIP channels upload to Spur S3 (link runtime), non-INFOBIP channels upload to Meta (id runtime).

Required fields

  • channelId
  • mediaUrl

Optional fields

  • mediaType
  • filename

Behavior notes

  • mediaType: image | video | document (defaults to image when omitted)
  • Call channels_list first if channel provider is unknown
  • Use this before broadcast_launch for any template with HEADER media
  • Not valid for template submission header_handle. Use whatsapp_template_media_upload for template drafts
  • INFOBIP launch payloads require Spur S3 link in image.link/video.link/document.link
  • Non-INFOBIP launch payloads require uploaded media id in image.id/video.id/document.id
  • Returned runtimeUsage snippets are copy-paste scaffolds for SINGLE and CAROUSEL templates

Example request

{
  "jsonrpc": "2.0",
  "id": 14,
  "method": "tools/call",
  "params": {
    "name": "whatsapp_media_upload",
    "arguments": {
      "channelId": 12,
      "mediaUrl": "https://example.com/assets/sale-banner.png",
      "mediaType": "image"
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "upload": {
    "channelId": 12,
    "provider": "CLOUD",
    "reference": {
      "kind": "meta_media_id",
      "mediaId": "1264783288392198"
    },
    "mediaId": "1264783288392198",
    "mediaType": "image",
    "sourceUrl": "https://example.com/assets/sale-banner.png",
    "mimeType": "image/png",
    "sizeBytes": 248321
  },
  "runtimeUsage": {
    "singleHeaderComponent": {
      "type": "header",
      "parameters": [
        { "type": "image", "image": { "id": "1264783288392198" } }
      ]
    },
    "carouselCardComponent": {
      "card_index": 0,
      "components": [
        {
          "type": "header",
          "parameters": [
            { "type": "image", "image": { "id": "1264783288392198" } }
          ]
        }
      ]
    }
  }
}

message_send

Sends a message to a single WhatsApp, Instagram, or Facebook contact.

Required fields

  • to
  • channel
  • content

Optional fields

  • options.from

Behavior notes

  • channel: whatsapp | instagram | facebook
  • Single-recipient tool only. Do not use this for bulk campaigns
  • content follows Spur send-message payload conventions
  • If multiple WhatsApp channels are connected, options.from is required
  • Call channels_list to discover valid options.from values
  • For bulk sends, use segment_create -> broadcast_create/broadcast_update -> broadcast_launch
  • When WhatsApp 24-hour window is expired, Spur may convert text to a configured template payload
  • WhatsApp sends can auto-create contact/inbox context if the number does not exist
  • warning can be returned for non-blocking conditions

Example request

{
  "jsonrpc": "2.0",
  "id": 16,
  "method": "tools/call",
  "params": {
    "name": "message_send",
    "arguments": {
      "channel": "whatsapp",
      "to": "919876543210",
      "content": {
        "type": "text",
        "text": {
          "body": "Hello from Spur MCP"
        }
      },
      "options": {
        "from": "919999999999"
      }
    }
  }
	}

Example success structuredContent

{
  "ok": true,
  "message": {
    "id": 9981,
    "status": "QUEUED"
  }
}

contact_create

Creates a contact in the workspace.

Required fields

  • firstName
  • lastName
  • number

Optional fields

  • email
  • name

Behavior notes

  • Uses strict REST semantics from POST /contacts
  • Returns conflict-style errors for duplicates
  • Phone format should be digits with country code

Example request

{
  "jsonrpc": "2.0",
  "id": 17,
  "method": "tools/call",
  "params": {
    "name": "contact_create",
    "arguments": {
      "firstName": "Aanya",
      "lastName": "Shah",
      "number": "919876543210",
      "email": "aanya@example.com"
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "contact": {
    "id": 4321,
    "firstName": "Aanya",
    "lastName": "Shah",
    "email": "aanya@example.com",
  "number": "919876543210"
  }
}

contact_update

Updates selected fields on an existing contact.

Required fields

  • id
  • at least one of firstName, lastName, email

Behavior notes

  • Uses strict REST semantics from PATCH /contacts/:id
  • Supported fields: firstName, lastName, email
  • Returns not-found when contact does not exist in workspace

Example request

{
  "jsonrpc": "2.0",
  "id": 19,
  "method": "tools/call",
  "params": {
    "name": "contact_update",
    "arguments": {
      "id": 4321,
      "email": "aanya.updated@example.com"
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "contact": {
    "id": 4321,
    "firstName": "Aanya",
    "lastName": "Shah",
    "email": "aanya.updated@example.com",
  "number": "919876543210"
  }
}

contact_delete

Deletes a contact by explicit contact ID or by WhatsApp number. Use ID when possible for deterministic deletion.

Required fields

  • one of: id | number

Optional fields

  • channel (only whatsapp when deleting by number)

Behavior notes

  • If number is provided, MCP resolves the WhatsApp contact using workspace-scoped number lookup
  • If multiple contacts match a number, MCP returns a conflict with candidate IDs
  • After deletion, sending to the same WhatsApp number can recreate contact context automatically

Example request

{
  "jsonrpc": "2.0",
  "id": 20,
  "method": "tools/call",
  "params": {
    "name": "contact_delete",
    "arguments": {
      "number": "919871739342",
      "channel": "whatsapp"
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "deleted": {
    "id": 10,
    "channel": "whatsapp",
    "number": "919871739342",
    "firstName": "Rohan",
    "lastName": "Rajpal",
    "name": "Rohan Rajpal"
  }
}

customer_import_csv_validate

Validates a contacts CSV with the same backend rules used by Spur UI import before queueing.

Required fields

  • url
  • columnMapping

Optional fields

  • tags

Behavior notes

  • Use this when AI needs a dry run before starting a large import
  • columnMapping entries use [spurField, { type, value }] tuples
  • Returns validRowIndices which are reused during start to skip invalid rows
  • invalidContactsCSV is present when invalid rows exist and includes row-level reasons

Example request

{
  "jsonrpc": "2.0",
  "id": 21,
  "method": "tools/call",
  "params": {
    "name": "customer_import_csv_validate",
    "arguments": {
      "url": "https://spur-uploads.s3.ap-south-1.amazonaws.com/imports/contacts.csv",
      "columnMapping": [
        ["phoneNumber", { "type": "column", "value": "Phone" }],
        ["firstName", { "type": "column", "value": "First Name" }],
        ["lastName", { "type": "column", "value": "Last Name" }],
        ["phoneCountryCode", { "type": "manual", "value": "91" }]
      ],
      "tags": [{ "id": 9, "title": "Imported-Feb-2026" }]
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "validation": {
    "valid": true,
    "totalRows": 1600,
    "invalidRows": 64,
    "validRows": 1536,
    "invalidContactsCSV": "https://spur-uploads.s3.ap-south-1.amazonaws.com/import-errors/invalid.csv",
    "validRowIndices": [0, 1, 2]
  }
}

customer_import_csv_start

Starts background import using the exact UI flow (validate first, then enqueue only valid rows).

Required fields

  • url
  • columnMapping

Optional fields

  • tags

Behavior notes

  • Use this for large imports instead of per-contact creation loops
  • If no valid rows exist, success=false and import is not queued
  • import.jobId maps to the workspace-scoped import queue job id
  • Follow with customer_import_csv_status for progress polling

Example request

{
  "jsonrpc": "2.0",
  "id": 22,
  "method": "tools/call",
  "params": {
    "name": "customer_import_csv_start",
    "arguments": {
      "url": "https://spur-uploads.s3.ap-south-1.amazonaws.com/imports/contacts.csv",
      "columnMapping": [
        ["phoneNumber", { "type": "column", "value": "Phone" }],
        ["firstName", { "type": "column", "value": "First Name" }]
      ],
      "tags": [{ "id": 9, "title": "Imported-Feb-2026" }]
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "success": true,
  "message": "Import started with 1536 valid contacts. 64 invalid contacts skipped.",
  "validation": {
    "valid": true,
    "totalRows": 1600,
    "invalidRows": 64,
    "validRows": 1536,
    "invalidContactsCSV": "https://spur-uploads.s3.ap-south-1.amazonaws.com/import-errors/invalid.csv",
    "validRowIndices": [0, 1, 2]
  },
  "import": {
    "id": "7-import-contacts",
    "name": "IMPORT_CONTACTS",
    "jobId": "7-import-contacts"
  }
}

customer_import_csv_status

Returns current import progress for the workspace import job.

Required fields

Behavior notes

  • Use this after customer_import_csv_start to track progress
  • status is null when no import job exists for the workspace
  • failedContacts can include a CSV URL for rows that failed during processing

Example request

{
  "jsonrpc": "2.0",
  "id": 23,
  "method": "tools/call",
  "params": {
    "name": "customer_import_csv_status",
    "arguments": {}
  }
}

Example success structuredContent

{
  "ok": true,
  "status": {
    "completed": 1200,
    "failed": 13,
    "total": 1500,
    "failedContacts": "https://spur-uploads.s3.ap-south-1.amazonaws.com/import-errors/failed-rows.csv"
  }
}

customer_import_csv_clear

Clears stored import job state when a run is complete or needs reset.

Required fields

Behavior notes

  • Returns cleared=true when a job record existed and was removed
  • Returns cleared=false when no import job exists
  • Use before restarting an import if stale status is blocking your workflow

Example request

{
  "jsonrpc": "2.0",
  "id": 24,
  "method": "tools/call",
  "params": {
    "name": "customer_import_csv_clear",
    "arguments": {}
  }
}

Example success structuredContent

{
  "ok": true,
  "cleared": true
}

segment_filters_catalog

Returns segment categories plus filter/operator metadata so AI can produce valid ruleGroup payloads.

Required fields

Optional fields

  • category

Behavior notes

  • Use this as the first step before segment_create or segment_update
  • category: customerInfo | messageActivity | websiteActivity | shoppingEvents
  • Operators include condition/type combinations required by backend SQL generation

Example request

{
  "jsonrpc": "2.0",
  "id": 30,
  "method": "tools/call",
  "params": {
    "name": "segment_filters_catalog",
    "arguments": {
      "category": "customerInfo"
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "count": 1,
  "categories": [
    {
      "type": "customerInfo",
      "name": "Customer Info",
      "filters": [
        {
          "label": "First Name",
          "value": "contact.firstName",
          "operators": [
            { "name": "Contains", "condition": "contains", "type": "STRING" }
          ]
        }
      ]
    }
  ]
}

segment_validate

Validates a SPUR ruleGroup without writing it and returns audience count + broadcast usability.

Required fields

  • ruleGroup

Optional fields

  • strictNonEmpty

Behavior notes

  • Run this before segment_create/segment_update
  • SPUR_TAGS names are normalized to tag ids in normalizedRuleGroup
  • DATETIME values should be YYYY-MM-DDTHH:mm; if seconds/timezone are provided they are normalized to minute format
  • For rolling windows like "not ordered in last 30 days", prefer RELATIVE_DATETIME values like "30:d" with condition "not_last"
  • Use validation.validForBroadcast=true as launch gate
  • strictNonEmpty defaults to true and treats 0-count audience as invalid for broadcast

Example request

{
  "jsonrpc": "2.0",
  "id": 31,
  "method": "tools/call",
  "params": {
    "name": "segment_validate",
    "arguments": {
      "ruleGroup": {
        "operator": "AND",
        "rules": [
          {
            "ruleType": "rule",
            "categorySelected": "customerInfo",
            "filter": { "field": "customSegment.spurTags" },
            "operator": { "condition": "true", "type": "SPUR_TAGS" },
            "value": "spur partner"
          }
        ]
      }
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "normalizedRuleGroup": {
    "operator": "AND",
    "rules": [
      {
        "ruleType": "rule",
        "categorySelected": "customerInfo",
        "filter": { "field": "customSegment.spurTags" },
        "operator": { "condition": "true", "type": "SPUR_TAGS" },
        "value": "30103"
      }
    ]
  },
  "validation": {
    "valid": true,
    "validForBroadcast": true,
    "isEmpty": false,
    "audienceCount": 124,
    "totalContacts": 3120,
    "audiencePercent": 3.97,
    "guidance": ["Segment currently matches 124 contact(s) and is eligible for broadcast include audiences."]
  }
}

segment_size

Returns audience size and broadcast usability for an existing segment id without modifying anything.

Required fields

  • segmentId

Optional fields

  • strictNonEmpty

Behavior notes

  • Read-only check for existing SPUR/SHOPIFY segments
  • Use this after segment_search when selecting include/exclude segment ids
  • Use validation.validForBroadcast=true as launch gate
  • strictNonEmpty defaults to true and treats 0-count audience as invalid for broadcast

Example request

{
  "jsonrpc": "2.0",
  "id": 32,
  "method": "tools/call",
  "params": {
    "name": "segment_size",
    "arguments": {
      "segmentId": 201
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "segment": {
    "id": 201,
    "name": "Recent WhatsApp Engagers",
    "type": "SPUR"
  },
  "validation": {
    "valid": true,
    "validForBroadcast": true,
    "isEmpty": false,
    "audienceCount": 84,
    "totalContacts": 3120,
    "audiencePercent": 2.69
  }
}

segment_create

Creates a SPUR segment with nested ruleGroup logic.

Required fields

  • name
  • ruleGroup

Optional fields

  • description

Behavior notes

  • Build ruleGroup using operator metadata from segment_filters_catalog
  • Use ruleType=rule for leaf predicates and ruleType=group for nesting
  • For DATETIME rules, send YYYY-MM-DDTHH:mm values (MCP normalizes seconds/timezone to this format)
  • For rolling windows, prefer RELATIVE_DATETIME (example: value="30:d", condition="not_last") instead of absolute dates
  • If you pass a shorthand numeric value for Last Order Date (example: value="30" with missing operator), MCP interprets it as older than 30 days and auto-adds Has Purchased = true
  • For NONE true/false operators, MCP auto-fills empty value as "true"/"false" for UI-safe segments
  • Response contains validation.audienceCount and validation.validForBroadcast
  • Do not use a segment in broadcasts when validation.validForBroadcast is false

Example request

{
  "jsonrpc": "2.0",
  "id": 33,
  "method": "tools/call",
  "params": {
    "name": "segment_create",
    "arguments": {
      "name": "Recent WhatsApp Engagers",
      "description": "Opened at least one message in last 30 days",
      "ruleGroup": {
        "operator": "AND",
        "rules": [
          {
            "ruleType": "rule",
            "categorySelected": "messageActivity",
            "filter": { "field": "customSegment.whatsappRead" },
            "operator": { "condition": "gtz", "type": "NONE" },
            "value": "1"
          }
        ]
      }
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "segment": {
    "id": 201,
    "name": "Recent WhatsApp Engagers",
    "type": "SPUR",
    "updatedAt": "2026-02-16T08:40:00.000Z"
  },
  "validation": {
    "valid": true,
    "validForBroadcast": true,
    "isEmpty": false,
    "audienceCount": 84,
    "totalContacts": 3120,
    "audiencePercent": 2.69
  }
}

segment_update

Updates an existing SPUR segment by replacing name/description/ruleGroup.

Required fields

  • id
  • name
  • ruleGroup

Optional fields

  • description

Behavior notes

  • Shopify segments are read-only and will return an explicit error
  • Send full ruleGroup payload for deterministic updates
  • Response validation tells if the updated segment is currently launch-safe

Example request

{
  "jsonrpc": "2.0",
  "id": 34,
  "method": "tools/call",
  "params": {
    "name": "segment_update",
    "arguments": {
      "id": 201,
      "name": "Recent WA Engagers (30d)",
      "ruleGroup": {
        "operator": "AND",
        "rules": [
          {
            "ruleType": "rule",
            "categorySelected": "messageActivity",
            "filter": { "field": "customSegment.whatsappRead" },
            "operator": { "condition": "gtz", "type": "NONE" },
            "value": "1"
          }
        ]
      }
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "segment": {
    "id": 201,
    "name": "Recent WA Engagers (30d)",
    "type": "SPUR"
  },
  "validation": {
    "valid": true,
    "validForBroadcast": true,
    "isEmpty": false,
    "audienceCount": 92,
    "totalContacts": 3120,
    "audiencePercent": 2.95
  }
}

segment_delete

Deletes a segment when it is not referenced by active broadcasts.

Required fields

  • id

Behavior notes

  • If segment is referenced by DRAFT/SCHEDULED/RUNNING broadcast, deletion fails with structured hints
  • Use broadcast_search + broadcast_update to remove segment references first

Example request

{
  "jsonrpc": "2.0",
  "id": 34,
  "method": "tools/call",
  "params": {
    "name": "segment_delete",
    "arguments": {
      "id": 201
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "deleted": {
    "id": 201,
    "name": "Recent WA Engagers (30d)",
    "type": "SPUR",
    "success": true
  }
}

shopify_segment_sync

Syncs Shopify customer segments from the connected store into Spur local segment records.

Required fields

Behavior notes

  • Use this after Shopify-side writes so segment_search(type=SHOPIFY) reflects latest state
  • Useful as a refresh checkpoint before assigning Shopify audiences to broadcasts

Example request

{
  "jsonrpc": "2.0",
  "id": 34,
  "method": "tools/call",
  "params": {
    "name": "shopify_segment_sync",
    "arguments": {}
  }
}

Example success structuredContent

{
  "ok": true,
  "synced": 13,
  "errors": []
}

shopify_segment_query_reference

Returns decision guidance for Shopify vs SPUR segments and practical Shopify query references/examples.

Required fields

Behavior notes

  • Call this when agent intent is ambiguous and it needs segment-system selection guidance
  • Includes official Shopify docs URLs and curated query examples
  • Pair with shopify_segment_validate before writes

Example request

{
  "jsonrpc": "2.0",
  "id": 35,
  "method": "tools/call",
  "params": {
    "name": "shopify_segment_query_reference",
    "arguments": {}
  }
}

Example success structuredContent

{
  "ok": true,
  "docs": [
    {
      "label": "Shopify Segment Query Language reference",
      "url": "https://shopify.dev/api/shopifyql/segment-query-language-reference"
    }
  ],
  "examples": [
    {
      "name": "Subscribed buyers",
      "query": "email_subscription_status = 'SUBSCRIBED' AND amount_spent > 0"
    }
  ]
}

shopify_segment_validate

Validates Shopify segment query syntax before create or update, without writing segment records.

Required fields

  • query

Behavior notes

  • Use this to fail fast on parse or field errors and reduce write retries
  • Returns valid=false with structured errors for invalid query text
  • Can return estimatedMemberCount when Shopify can evaluate synchronously

Example request

{
  "jsonrpc": "2.0",
  "id": 36,
  "method": "tools/call",
  "params": {
    "name": "shopify_segment_validate",
    "arguments": {
      "query": "email_subscription_status = 'SUBSCRIBED' AND amount_spent > 0"
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "valid": true,
  "query": "email_subscription_status = 'SUBSCRIBED' AND amount_spent > 0",
  "estimatedMemberCount": 12,
  "warnings": [],
  "errors": []
}

shopify_segment_create

Creates a Shopify-native customer segment using Shopify query syntax and optionally syncs it into Spur.

Required fields

  • name
  • query

Optional fields

  • syncAfterWrite

Behavior notes

  • Use this when audience logic must remain reusable in Shopify admin workflows
  • Call shopify_segment_validate first for safer writes
  • syncAfterWrite defaults to true

Example request

{
  "jsonrpc": "2.0",
  "id": 37,
  "method": "tools/call",
  "params": {
    "name": "shopify_segment_create",
    "arguments": {
      "name": "Shopify VIP Subscribers",
      "query": "email_subscription_status = 'SUBSCRIBED' AND amount_spent > 0",
      "syncAfterWrite": true
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "segment": {
    "id": 178,
    "name": "Shopify VIP Subscribers",
    "type": "SHOPIFY",
    "shopifySegmentId": "gid://shopify/Segment/649779282218"
  },
  "shopifySegment": {
    "id": "gid://shopify/Segment/649779282218",
    "name": "Shopify VIP Subscribers"
  },
  "sync": {
    "synced": 13,
    "errors": []
  }
}

shopify_segment_update

Updates a Shopify-native segment name or query by Shopify segment ID (numeric or gid).

Required fields

  • shopifySegmentId
  • at least one of: name | query

Optional fields

  • syncAfterWrite

Behavior notes

  • Use numeric id or gid://shopify/Segment/... format for shopifySegmentId
  • Run shopify_segment_validate before query changes
  • syncAfterWrite defaults to true

Example request

{
  "jsonrpc": "2.0",
  "id": 38,
  "method": "tools/call",
  "params": {
    "name": "shopify_segment_update",
    "arguments": {
      "shopifySegmentId": "649779282218",
      "query": "email_subscription_status = 'SUBSCRIBED' AND amount_spent > 250",
      "syncAfterWrite": true
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "segment": {
    "id": 178,
    "name": "Shopify VIP Subscribers",
    "type": "SHOPIFY"
  },
  "shopifySegment": {
    "id": "gid://shopify/Segment/649779282218",
    "query": "email_subscription_status = 'SUBSCRIBED' AND amount_spent > 250"
  },
  "sync": {
    "synced": 13,
    "errors": []
  }
}

shopify_segment_delete

Deletes a Shopify-native segment by Shopify segment ID and removes local synced records in Spur.

Required fields

  • shopifySegmentId

Optional fields

  • syncAfterWrite

Behavior notes

  • Use with caution in production because this deletes segment definition in Shopify
  • Supports numeric or gid segment IDs
  • syncAfterWrite defaults to true

Example request

{
  "jsonrpc": "2.0",
  "id": 39,
  "method": "tools/call",
  "params": {
    "name": "shopify_segment_delete",
    "arguments": {
      "shopifySegmentId": "gid://shopify/Segment/649779282218",
      "syncAfterWrite": true
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "deleted": {
    "shopifySegmentId": "gid://shopify/Segment/649779282218",
    "localDeletedCount": 1
  },
  "sync": {
    "synced": 12,
    "errors": []
  }
}

broadcast_create

Creates a broadcast draft with template reference, optional variable payload, and initial include/exclude segments. Use this (with segment tools) for bulk sends instead of looping message_send.

Required fields

  • name
  • one of: templateInternalId | template

Optional fields

  • channelId
  • contentMessage
  • includedSegmentIds
  • excludedSegmentIds

Behavior notes

  • When multiple WhatsApp channels exist, pass channelId explicitly
  • templateInternalId maps to internal Spur template id from template_search
  • contentMessage maps to broadcast content.message for template variables/components
  • For carousel templates, contentMessage is required. Call template_requirements first and copy the scaffold
  • For media headers: INFOBIP channels must use image.link/video.link/document.link from whatsapp_media_upload output; other providers must use image.id/video.id/document.id

Example request

{
  "jsonrpc": "2.0",
  "id": 35,
  "method": "tools/call",
  "params": {
    "name": "broadcast_create",
    "arguments": {
      "name": "Weekend Offer Blast",
      "templateInternalId": 873,
      "channelId": 12,
      "includedSegmentIds": [120],
      "excludedSegmentIds": [205]
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "broadcast": {
    "id": 890,
    "name": "Weekend Offer Blast",
    "status": "DRAFT",
    "channelId": 12,
    "includedSegmentIds": [120],
    "excludedSegmentIds": [205]
  }
}

broadcast_update

Updates a DRAFT broadcast with composable include/exclude operations, template/message changes, schedule, and delivery controls.

Required fields

  • id

Optional fields

  • name
  • templateInternalId
  • template
  • contentMessage
  • includeSet
  • includeAdd
  • includeRemove
  • excludeSet
  • excludeAdd
  • excludeRemove
  • sendAt
  • deliveryBoost
  • splitAcrossDays
  • dailyLimit
  • limitAudience
  • limitCount

Behavior notes

  • Only DRAFT broadcasts are editable
  • Delivery Boost and Split Broadcast cannot both be true
  • limitAudience=true requires limitCount
  • excludeAdd entries already present in include list are ignored

Example request

{
  "jsonrpc": "2.0",
  "id": 36,
  "method": "tools/call",
  "params": {
    "name": "broadcast_update",
    "arguments": {
      "id": 890,
      "includeAdd": [121],
      "includeRemove": [120],
      "excludeAdd": [205],
      "contentMessage": {
        "name": "weekend_offer_v1",
        "language": { "code": "en_US" },
        "components": [
          {
            "type": "body",
            "parameters": [{ "type": "text", "text": "20% OFF" }]
          }
        ]
      },
      "sendAt": "2026-02-20T09:00:00.000Z",
      "limitAudience": true,
      "limitCount": 8000
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "broadcast": {
    "id": 890,
    "status": "DRAFT",
    "includedSegmentIds": [121],
    "excludedSegmentIds": [205],
    "sendAt": "2026-02-20T09:00:00.000Z",
    "filterQuery": { "limit": true, "count": 8000 }
  }
}

broadcast_launch

Launches broadcast by calling setReady.

Required fields

  • id

Behavior notes

  • Template media headers must use uploaded media IDs. Direct links are rejected at launch
  • Returns launchedStatus RUNNING for immediate send
  • Returns launchedStatus SCHEDULED when sendAt is in the future

Example request

{
  "jsonrpc": "2.0",
  "id": 37,
  "method": "tools/call",
  "params": {
    "name": "broadcast_launch",
    "arguments": {
      "id": 890
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "launchedStatus": "SCHEDULED",
  "broadcast": {
    "id": 890,
    "name": "Weekend Offer Blast",
    "status": "SCHEDULED"
  }
}

broadcast_analytics

Returns per-broadcast performance metrics and delivery-boost metadata.

Required fields

  • id

Optional fields

  • cached

Behavior notes

  • Includes delivered/opened/clicked/replied counts and percentages
  • Includes revenue, orders, whatsappCost, and template metadata
  • Use cached=true for faster repeated polling

Example request

{
  "jsonrpc": "2.0",
  "id": 39,
  "method": "tools/call",
  "params": {
    "name": "broadcast_analytics",
    "arguments": {
      "id": 890,
      "cached": true
    }
  }
}

Example success structuredContent

{
  "ok": true,
  "analytics": {
    "broadcastId": 890,
    "messages": 10000,
    "delivered": 9300,
    "opened": 6120,
    "clicks": 830,
    "orders": 94,
    "revenue": 274580,
    "whatsappCost": 4810.2,
    "percentDelivered": 93,
    "percentOpened": 65.8,
    "percentClicked": 8.92
  }
}

broadcast_overview_stats

Returns workspace-level aggregate broadcast KPIs.

Required fields

Behavior notes

  • Use alongside broadcast_analytics for campaign-level + workspace-level reporting
  • Totals are currency-normalized to workspace currency

Example request

{
  "jsonrpc": "2.0",
  "id": 40,
  "method": "tools/call",
  "params": {
    "name": "broadcast_overview_stats",
    "arguments": {}
  }
}

Example success structuredContent

{
  "ok": true,
  "overview": {
    "totalCount": 124,
    "totalRevenue": 12495840,
    "totalOrders": 2611
  }
}