Email marketing platform
ActiveCampaign logo

ActiveCampaign API + MCP (2026): v3 REST, official MCP, no SDK

ActiveCampaign’s public surface is one REST API — v3, JSON in and out, authenticated with an Api-Token header against an account-specific base URL — plus an eComm GraphQL endpoint sharing the same credential and quota. It is a marketing-automation and CRM API, not a sending API: there is no relay behind it. The 2026 headline is MCP. ActiveCampaign is one of the few ESPs to ship a first-party, hosted, account-scoped MCP server on every plan tier, while the SDK story went the other way — the only official platform SDK is a PHP wrapper frozen in 2017 against the legacy v1 API.

At a glance

v3
Current REST API
v1 still supported but frozen — new functionality ships on v3 only
1
Official platform SDK
PHP only, last released 2017, v1-only — no maintained v3 client
Official
MCP server status
First-party remote MCP, about 50 tools, on all plans

MCP integration in 2026

Model Context Protocol is how an LLM client — Claude, ChatGPT, Cursor — gets structured, permissioned access to a product instead of a raw API key. Most email platforms leave it to community wrappers. ActiveCampaign does not.

First-party hosted MCP server, available on every plan

ActiveCampaign operates an official Remote MCP Server exposing roughly 50 tools over contacts, tags, lists, custom fields, campaigns, automations and deals — on Starter, Plus, Pro and Enterprise alike. The endpoint is unique to your account, copied from Settings › Developer, and connecting is a browser sign-in and approval rather than a token paste. It is a data server, not a sending server: no send-email tool, because there is no relay.

Available MCP servers

Four endpoints matter. Two come from ActiveCampaign itself — the platform server and the Postmark server, on different accounts and different billing — one is Zapier’s wrapper, one a community Python server for teams needing stdio.

Why buyers should care

The split between the two official servers is the decision point. The ActiveCampaign server reads and writes CRM and marketing data — create a contact, apply a tag, enrol someone in an automation — but cannot send email, and its browser login is awkward headless. The Postmark server does the opposite: it sends, inspects bounces and suppressions and reads stats with a static token that works unattended. An agent that must both segment an audience and send to it needs both servers, two accounts, two invoices — unlike Klaviyo or Customer.io, where data and sending sit behind one credential.

ActiveCampaign API essentials

Everything below is the v3 REST API, described in the docs as “structured around REST, HTTP, and JSON”. The two values you need before writing any code — base URL and API key — both live in Settings › Developer.

Base URLhttps://{your-account}.api-us1.com READ FROM ACCOUNT
Account- and region-specific. Never hardcode api-us1.com.
Resource path/api/3/{resource}, e.g. /api/3/contacts
Current versionv3 REST. Legacy v1 remains supported with no announced sunset, but gets no new functionality.
GraphQL endpoint/api/3/ecom/graphql — eComm data only: orders, customers, products, recurring payments
Auth headerApi-Token: {your-key}
FormatJSON over HTTPS — plain HTTP is refused
Paginationlimit (default 20, max 100) and zero-based offset
Result countmeta.total on collection responses — pages = ceil(meta.total / limit)
Sort and filterorders[fieldName]=ASC|DESC (stackable) and filters[fieldName]=value
Bulk writesPOST /api/3/import/bulk_import instead of looping single-contact calls
Rate limit5 requests per second, per account (about 300/minute)

The resource model is flatter than it looks. Contacts, deals, lists, tags and campaigns are top-level collections, and relationships between them are their own collections rather than nested sub-resources: you do not tag a contact by PUTting the contact, you POST a contactTag referencing both IDs. Same for list membership (contactLists), enrolment (contactAutomations) and field data (fieldValues). Once that clicks the API is predictable — create the join record, delete the join record, and remember that removal is a DELETE on the join record’s own ID, so you often GET it just to delete it. The cost is call volume: one logical operation is often four requests against 5 per second for the whole account. Cache tag and list IDs at startup instead of resolving names on every write.

Authentication methods

API key in the Api-Token header

This is the only documented authentication method for v3. The docs are explicit: “The API key should be provided as an HTTP header named Api-Token.” No query-string fallback, no HTTP Basic variant, no bearer form. GET /api/3/users/me is the canonical smoke test — if it returns your user object, key and base URL are both correct.

Keys are per-user, not per-application. Every user has their own key on the Settings › Developer tab, which also shows the account’s base URL. There is no scoping mechanism: a key carries the permissions of its owner, so the only way to constrain an integration is a dedicated user with limited permissions. Rotating a key rotates it for that human too — create a service-account user before five integrations end up sharing one admin’s token.

eComm GraphQL

The GraphQL endpoint uses the same Api-Token header and the same key. No separate credential, no separate quota — it draws on the same 5 requests per second your REST sync is already spending.

MCP and app authorization

OAuth-style authorization exists, but not where API developers look for it. The Remote MCP Server uses an interactive browser login; CX Apps built with App Studio and the Postmark connector have their own authorize flows. None produces a credential usable against the v3 REST API. For server-to-server work there is one path: the Api-Token header.

No OAuth means no delegated access

If you are building a product that connects to your customers’ ActiveCampaign accounts, there is no consent screen to send them through. Each customer pastes an API key and a base URL — two values, because the host differs per account and per region. Validate the pair against /api/3/users/me before storing it, and keep the base URL as configuration rather than deriving it from the account name.

Rate limits

ActiveCampaign publishes one API rate limit and no SMTP-shaped limits, because there is no SMTP surface. Sending volume is metered monthly against contact count rather than throttled per hour.

LimitValueNotes
API requests5 per secondPer account, about 300/minute. Not per key, not per user.
GraphQL requestsSharedeComm GraphQL is bound by “the same rate limit restriction as the existing v3 REST API of 5 requests per second per account”.
Over-limit response429 Too Many RequestsCarries Retry-After, RateLimit-Limit and RateLimit-Remaining — read them, do not guess.
Monthly send allowance10x to 15x contactsStarter and Plus 10x, Professional 12x, Enterprise 15x, for plans bought on or after June 3, 2024.
Overage billing$0.005 per extra sendIf overage reaches 3x your limit, sending is switched off until the next billing cycle.
Trial cap100 sendsTotal, not per day — enough to test an automation, not a campaign.
Per-hour throttle, message size, recipients, concurrencyNot publishedSMTP metrics, and there is no relay — treat any figure found elsewhere as invented.

The workaround is architectural, not clever. Stop looping single writes: POST /api/3/import/bulk_import exists so a 40,000-contact sync is not 40,000 requests. Treat the budget as shared and put every integration behind one queue with a token bucket, because the nightly warehouse sync, the checkout webhook handler and an MCP client someone connected last week all draw from the same five. Honour Retry-After on a 429 — blind retries turn a burst into a lockout. If you need high-frequency writes, this is the wrong shape of product; that is event-pipeline territory, closer to Customer.io.

Official SDKs

This is the weakest part of the ActiveCampaign developer story. The GitHub organisation is busy in 2026, almost entirely with Postmark libraries. There is no maintained client for the platform API in any language.

LanguagePackageInstallRepo
PHP (platform API)activecampaign/api-phpcomposer require activecampaign/api-phpactivecampaign-api-php
Node (Postmark MCP, transactional)@activecampaign/postmark-mcpgit clone then npm installpostmark-mcp

The official PHP wrapper is a v1 relic — do not start new work on it

Its README still calls it “the official PHP wrapper for the ActiveCampaign API”, but composer.json carries "time": "2017-04-26", it requires php >=5.3.0, and its new ActiveCampaign("API_URL", "API_KEY") constructor is the legacy v1 shape. It does not target v3. Use a plain HTTP client and handle pagination and 429 backoff yourself — roughly forty lines, and less risk than a dependency that stopped moving in 2017.

Notable community SDKs

With no first-party v3 client, the community fills the gap and quality varies. Vet any wrapper on three checks: does it read the base URL from configuration rather than hardcoding api-us1.com, does it handle 429 with Retry-After, and does it expose meta.total so pagination is deterministic? The most visible community project in 2026 is the Python MCP server mcp-activecampaign (v0.2.0, MIT), a readable reference implementation of v3 calls even if you never run it as an MCP endpoint. On the Postmark side the picture inverts: postmark-python, postmark-php, postmark.js, postmark-dotnet and postmark-java all shipped releases in 2026.

Endpoints reference

The v3 API covers well over a hundred endpoints. These 15 carry almost every real integration. Full reference at developers.activecampaign.com.

ResourceMethodsDescription
Contacts
/api/3/contacts
GET, POSTList, search and filter contacts, or create one.
Contact (single)
/api/3/contacts/{id}
GET, PUT, DELETERetrieve, update or delete one contact by ID.
Contact sync
/api/3/contact/sync
POSTCreate-or-update keyed on email. Standardise on this.
Bulk import
/api/3/import/bulk_import
POSTMany contacts per call instead of looping against 5 req/sec.
Lists
/api/3/lists
GET, POSTBrowse, search and create contact lists.
List membership
/api/3/contactLists
POSTSubscribe or unsubscribe a contact via status.
Tags
/api/3/tags
GET, POSTList and create tags; filter by name.
Contact tags
/api/3/contactTags
POST, DELETEApply or remove a tag: the main automation trigger.
Field values
/api/3/fieldValues
GET, POST, PUTPer-contact field data; definitions at /api/3/fields.
Automations
/api/3/automations
GETList automation workflows and statuses to resolve IDs.
Contact automations
/api/3/contactAutomations
GET, POST, DELETEEnrol a contact, check progress, eject mid-run.
Deals
/api/3/deals
GET, POSTList and create CRM deals; filter by stage, value, owner.
Campaigns
/api/3/campaigns
GET, POSTRetrieve campaigns by status, type, date; create or duplicate.
Webhooks
/api/3/webhooks
GET, POST, DELETEOutbound subscriptions for contact, deal, campaign events.
Ecommerce orders
/api/3/ecomOrders
GET, POSTOrder data behind Deep Data revenue attribution.

Code examples

curl: smoke test, pagination and tagging

# Credential smoke test. Base URL comes from Settings > Developer.
curl -sS -H "Api-Token: $AC_API_TOKEN" "$AC_BASE_URL/api/3/users/me"

# Page 2 of contacts, newest first, 100 per page (the maximum).
curl -sS -H "Api-Token: $AC_API_TOKEN" "$AC_BASE_URL/api/3/contacts?limit=100&offset=100&orders[cdate]=DESC"

# Tagging is a join record, not a field on the contact object.
curl -sS -X POST -H "Api-Token: $AC_API_TOKEN" -H "Content-Type: application/json" -d '{"contactTag":{"contact":"12345","tag":"42"}}' "$AC_BASE_URL/api/3/contactTags"

Python: idempotent upsert and full pagination

The pattern that matters is /api/3/contact/sync rather than POST /api/3/contacts: sync is keyed on email and safe to replay, which is what you want behind a webhook that can fire twice.

import os, time, requests

# Both values come from Settings > Developer. Never hardcode api-us1.com.
BASE = os.environ["AC_BASE_URL"].rstrip("/")
S = requests.Session()
S.headers.update({"Api-Token": os.environ["AC_API_TOKEN"],
                  "Content-Type": "application/json"})

def call(method, path, **kw):
    while True:
        r = S.request(method, BASE + path, timeout=30, **kw)
        if r.status_code == 429:                 # 5 req/sec, per account
            time.sleep(int(r.headers.get("Retry-After", 1)))
            continue
        r.raise_for_status()
        return r.json()

def upsert(email, first=None):                   # idempotent, keyed on email
    body = {"contact": {"email": email, "firstName": first}}
    return call("POST", "/api/3/contact/sync", json=body)["contact"]

def all_contacts(page=100):
    offset, total = 0, None
    while total is None or offset < total:
        body = call("GET", "/api/3/contacts",
                    params={"limit": page, "offset": offset})
        total = int(body["meta"]["total"])        # authoritative count
        yield from body["contacts"]
        offset += page

Common gotchas

Never hardcode api-us1.com — the base URL is per-account and per-region

Almost every code sample online uses https://youraccount.api-us1.com, and ActiveCampaign’s own guidance pushes back: third-party developers should have users read the base URL from Settings › Developer, because that value is the source of truth for that user, particularly outside the United States. An integration with the US host baked in authenticates fine in testing and silently fails for a non-US customer.

5 requests per second is per account, and everything shares it

The budget is not per API key and not per integration. A nightly warehouse sync, a Shopify webhook handler, a colleague’s Zap and any connected MCP client all draw from the same five per second, and eComm GraphQL shares the ceiling. The failure mode is confusing: it looks like your code broke when in fact somebody else’s job started. Centralise on one queue, use bulk_import for volume writes, and log RateLimit-Remaining so contention is visible rather than inferred.

The MCP server cannot send email, and cannot run headless

Two constraints trip up agent builders. First, the Remote MCP Server has no send-email tool — its roughly 50 tools read and write contacts, tags, lists, fields, deals, campaigns and automations, consistent with a platform that has no relay. Transactional sending is the separate Postmark MCP server, with its own account and token. Second, the server URL is account-scoped and connection is an interactive browser login, so a config cannot be copy-pasted between accounts and does not fit CI. Headless work belongs on the v3 REST API or the Postmark stdio server.

Owning Postmark is not the same as shipping Postmark

ActiveCampaign acquired Postmark and DMARC Digests from Wildbit on May 6, 2022, and its transactional-email page advertises a globally distributed SMTP service under a “Powered by Postmark” banner — which reads like an ActiveCampaign feature. It is not bundled. Postmark’s acquisition FAQ says it “will remain available as a standalone product”, and ActiveCampaign’s setup doc requires “an ActiveCampaign account and an approved Postmark account”. No plan includes Postmark volume, and the in-app “Send a transactional email” action is a connector to your own Postmark account, not a provisioning step.

Deprecations and changelog

  • August 18, 2026 — 2026 investment on the ActiveCampaign GitHub org is concentrated on the Postmark toolchain: postmark-skills updated August 18, postmark-python August 17, postmark-mcp August 12, postmark-php August 11. No platform v3 SDK is maintained there.
  • June 12, 2026postmark-mcp v2.0.0 released under the ActiveCampaign org, expanding the official Postmark MCP server to 24 tools across sending, templates, message search, bounces, suppressions and stats.
  • April 4, 2026 — Community server mcp-activecampaign v0.2.0 published to PyPI under MIT, with 33 tools against the v3 REST API.
  • June 3, 2024 — New send-limit structure for plans purchased on or after this date: Starter 10x contacts, Plus 10x, Professional 12x, Enterprise 15x, with $0.005 per-send overage and sending disabled at 3x the limit.
  • May 6, 2022 — ActiveCampaign announces the acquisition of Postmark and DMARC Digests from Wildbit, while committing to Postmark continuing as a standalone product rather than being absorbed.

ActiveCampaign publishes a developer changelog at developers.activecampaign.com/changelog; its entries carry no machine-readable dates, so pinning an integration against it is manual work. On deprecation risk, v1 is not deprecated — the v3 overview promises “no plans to sunset version 1 of our API at this time” plus advance notice — but it receives no new functionality, so treat it as frozen rather than safe.

Frequently asked questions

What is the ActiveCampaign API base URL and where do I find my API key?

Both live under Settings › Developer. The base URL has the shape https://{your-account}.api-us1.com, resource paths hang off /api/3/, and the key goes in an HTTP header named Api-Token. Do not assume api-us1.com: the host is account-specific and differs by region. Keys are per-user.

What is the difference between the v1 and v3 API, and is v1 deprecated?

v3 is the current REST API and where all new functionality ships. v1 is the legacy query-string API; its own docs say that while it is still fully supported, new functionality goes to v3. v1 is not deprecated — ActiveCampaign has “no plans to sunset version 1 of our API at this time” — but that makes it frozen rather than safe. Build on v3.

What is the ActiveCampaign API rate limit?

5 requests per second, per account. Exceeding it returns 429 Too Many Requests with Retry-After, RateLimit-Limit and RateLimit-Remaining headers. It is not per API key, so every integration competes for the same budget and eComm GraphQL shares the ceiling. For volume writes use POST /api/3/import/bulk_import.

How do I paginate through all contacts with the ActiveCampaign API?

Offset pagination. Pass limit (default 20, maximum 100) and a zero-based offset, then read meta.total from the first response — pages = ceil(meta.total / limit). Sort with orders[fieldName]=ASC|DESC and filter with filters[fieldName]=value. Order by a stable field such as cdate so records do not shuffle between pages.

Does ActiveCampaign have an official MCP server for Claude or ChatGPT?

Yes — genuinely first-party, which is unusual for an ESP in 2026. ActiveCampaign runs a hosted Remote MCP Server with roughly 50 tools across contacts, tags, lists, custom fields, campaigns, automations and deals, on all plan tiers including Starter. The endpoint URL is unique to your account and is copied from Settings › Developer; you connect by signing in through the browser and approving, not by pasting a token. Client guides cover Claude, ChatGPT and Cursor. It has no send-email tool — that is the separate Postmark MCP server.

Is there an official ActiveCampaign SDK for Python, Node or PHP?

Effectively no. The only official platform SDK is activecampaign/api-php, a v1-only wrapper last released 2017-04-26 that requires php >=5.3.0 and does not target v3. There is no official Python or Node client; the maintained libraries on that GitHub org are Postmark SDKs. Call v3 with a plain HTTP client and handle pagination and 429 backoff yourself, or vet a community wrapper for base-URL configurability and retry behaviour first.

Changelog (recent)

  • 2026-08-18 ActiveCampaign's GitHub org shows 2026 activity concentrated on the Postmark toolchain (postmark-skills, postmark-python, postmark-mcp, postmark-php all updated in August) with no ActiveCampaign-platform v3 SDK published or maintained.
  • 2026-06-12 postmark-mcp v2.0.0 released under the ActiveCampaign GitHub org, expanding the official Postmark MCP server to 24 tools across sending, templates, message search, diagnostics, bounces, suppressions, stats and webhooks.
  • 2026-04-04 Community MCP server mcp-activecampaign v0.2.0 published to PyPI under MIT, documenting 33 tools against the v3 REST API.
AAlaa Touil RRabeb How we test →

This review follows our email infrastructure testing methodology. We disclose affiliate relationships in our editorial independence policy.