iContact exposes one JSON REST API at https://app.icontact.com/icp, pinned by an Api-Version: 2.2 header and authenticated with three custom headers rather than OAuth or HTTP Basic. It is a list-and-campaign API, contacts, lists, subscriptions, messages, sends, not a transactional relay, and there is no SMTP service behind it. As of August 2026 no iContact MCP server exists, official or community: agent access runs through Zapier MCP or a thin wrapper you write yourself.
Api-Version header. No XML or SOAP generation.MCP is how an AI agent gets typed, permissioned access to a product’s data and actions, looking up a contact, adding someone to a list, reading campaign stats without glue code. The question buyers ask in 2026 is whether they can point an agent at their ESP and have it work. For iContact the answer is not directly.
Nothing MCP-related sits in iContact’s GitHub organisation, which holds exactly 3 repositories, all PHP REST wrappers, newest commit March 12, 2018. No iContact server appears in the public MCP directories either. Agent access today means Zapier MCP over the official Zapier app, or your own thin server wrapping the REST API, which static three-header auth makes unusually easy to build.
There is no iContact entry to list, so this directory covers the routes that actually work today.
Hosted MCP server exposing the apps in your Zapier account. iContact is an official Zapier app with 8 triggers and 8 actions, so an agent gets Create Contact, Subscribe to List, Update, Unsubscribe and Send HTML Message with no code.
Auth is three static headers and every resource is a flat JSON collection under one scope, so a server covering contacts, lists, subscriptions and sends is a short afternoon. The official PHP wrapper documents the header names and paging parameters.
MCP availability is a proxy for how actively an ESP is engineered: platforms shipping first-party servers in 2026 are the ones shipping modern SDKs and versioned changelogs. iContact has neither an MCP server nor an SDK updated in 8 years. That is not disqualifying, the REST API is stable and usable for list sync and scheduled sends, but if you need agent-driven marketing ops, budget for building that bridge yourself, or compare against Mailchimp, ActiveCampaign and GetResponse.
One API, one version, one base URL. Everything below is verified against iContact’s own PHP client rather than a marketing page, so the header names and paging parameters are literal.
| Base URL | https://app.icontact.com/icp |
| Sandbox base URL | https://app.sandbox.icontact.com/icp TEST FIRST |
| Version | 2.2, sent as Api-Version: 2.2 on every request |
| Protocol | HTTPS REST, JSON bodies. No XML, no SOAP, no GraphQL |
| Resource scope | /a/{accountId}/c/{clientFolderId}/{resource}-both IDs mandatory |
| Pagination | Offset paging: ?limit=100&offset=0, with a total count per collection |
| Sorting | ?orderby=lastName:asc,email:desc-comma-separated pairs |
| Content headers | Accept and Content-Type: application/json |
| Collection writes | POST bodies are JSON arrays, even when creating one record |
Every data call hangs off a two-level scope: an account, then a client folder inside it. Resources fall into three groups. Audience objects, contacts, lists, subscriptions, segments, custom fields, take most integration time. Content and dispatch objects are campaigns, messages and sends: create a message, attach it to a campaign, then create a send that pushes it at lists. Reporting objects (opens, clicks, bounces, unsubscribes, statistics) are read-only. Bulk loading is its own two-step flow, create an upload job, then PUT the CSV, and is the right path above a few thousand rows.
One structural absence matters more than any feature: there is no transactional send path and no SMTP relay, so receipts and password resets need a dedicated relay such as Postmark or Amazon SES alongside iContact. AWeber and Constant Contact sit in the same marketing-only position; Brevo and Mailjet combine both in one account.
iContact uses neither OAuth, bearer tokens nor HTTP Basic. It uses three custom headers, and confusing two of them is the most common cause of a day-one auth failure.
Every request carries Api-AppId, Api-Username and Api-Password alongside Api-Version: 2.2. The AppId identifies an application you register in the developer portal; the username is your iContact account username; the Api-Password is an application-specific password set for that app-not your web UI login password. All three are static strings: no token exchange, no refresh cycle, no expiry to handle.
Accept: application/json Content-Type: application/json Api-Version: 2.2 Api-AppId: your-registered-application-id Api-Username: your-icontact-username Api-Password: your-application-specific-password
Treat them as long-lived secrets: environment variables or a secret manager, never a client-side bundle. Key rotation is covered in Create and Manage API Keys, permissions in Permissions.
If you are building a multi-tenant product on iContact, design around this. No authorisation-code grant, no consent screen, no per-customer token: each customer generates their own application credentials and hands you three strings to store per tenant. Workable, but it turns onboarding from a one-click connect into a documented setup task, and revocation stays on the customer’s side. Mailchimp, HubSpot and Constant Contact offer OAuth instead.
The official PHP client sets CURLOPT_SSL_VERIFYPEER to false, disabling certificate validation outright, and that line has sat there since the last commit in January 2018. Lift the file into production and you inherit a silent transport-security downgrade, with no error and no warning. Read the SDK for header names, then call the API with your own client and normal verification.
iContact publishes no numeric API rate limits in any officially reachable document. What it does document in detail is how plan limits are counted, those are the ceilings that bite.
| Limit | Documented value | What to do |
|---|---|---|
| API requests per minute | Not published | Assume one exists. Back off on errors; batch with array POSTs |
| Emails per hour | Not published | Schedule through /sends rather than pacing yourself |
| Recipients per message | Not published | Target lists and segments, expanded server-side |
| Message size | Not published | Keep HTML lean; size hurts placement before hitting a cap |
| Contact ceiling | Plan tier, two counting models | Check subscriber-based vs contact-based before importing |
| Page size | limit / offset | Read total from page one and consume it |
Treat throughput as something you discover, not assume: run a single writer, keep concurrency at 1 until you have measured real behaviour, and back off exponentially on the codes listed in HTTP Status Codes. For volume loads the /uploads job plus a PUT of the CSV moves tens of thousands of contacts in one request pair. The ceiling that costs money is the contact ceiling: crossing it raises the invoice rather than blocking the send.
iContact’s GitHub organisation holds exactly 3 repositories. All PHP, all Apache-2.0, none touched since 2018, and two of the three target generations that are no longer the product you sign up for.
| Language | Package | Install | Repo |
|---|---|---|---|
| PHP | icontact-api-phpcurrent v2.2 | git clone-not on Packagist | icontact/icontact-api-php |
| PHP | icontact-pro-api-phplegacy, api.icpro.co | git clone | icontact-pro-api-php |
| PHP | icontact-pro-select-api-phpretired, api.omkt.co | git clone | icontact-pro-select-api-php |
The newest commit anywhere in the organisation is March 12, 2018; the current-API wrapper last changed January 2, 2018, the OutMarket wrapper October 28, 2014. None of this breaks the API, a surface pinned to Api-Version: 2.2 stays stable precisely because nothing moves, but plan a direct HTTP integration in your own language and treat these repositories as documentation, not as a dependency.
No widely adopted third-party iContact client exists in any language, itself informative about integration volume. What exists is no-code, the official Zapier app, 8 triggers and 8 actions, the same surface Zapier MCP re-exposes, and platform plugins: iContact Lead Forms for WordPress and the iContact for Salesforce package. Beyond those you write the client, starting from the Code Library article.
Paths are relative to {scope} = https://app.icontact.com/icp/a/{accountId}/c/{clientFolderId}. Those IDs are neither optional nor inferable, fetch them first. Full documentation: the official Resource Call References List.
| Resource | Methods | Description |
|---|---|---|
| Accounts /icp/a/ | GET | Accounts your credentials reach; yields accountId. |
| Client Folders /icp/a/{accountId}/c/ | GET | Folders under an account; yields clientFolderId. |
| Contacts {scope}/contacts | GET, POST | Create and search contacts. Supports limit, offset, orderby, field filters. |
| Contact {scope}/contacts/{contactId} | GET, POST | Read or update one contact. Updates are POST, not PUT. |
| Lists {scope}/lists | GET, POST | Create and enumerate mailing lists, the audience container. |
| List {scope}/lists/{listId} | GET, POST, DELETE | Read, update or delete one list. The only DELETE in the core set. |
| Subscriptions {scope}/subscriptions | GET, POST | Joins a contact to a list with status normal, pending or unsubscribed. |
| Messages {scope}/messages | GET, POST | Create and list messages: subject, HTML and text body, campaign. |
| Message Opens {scope}/messages/{messageId}/opens | GET | Open events for one message; read total for a count. |
| Sends {scope}/sends | GET, POST | Dispatches or schedules a message at lists and segments. |
| Campaigns {scope}/campaigns | GET, POST | Containers grouping messages for reporting and sender identity. |
| Uploads {scope}/uploads | GET, POST | Bulk contact import jobs. Create the job, then stream the file. |
| Upload Data {scope}/uploads/{uploadId}/data | PUT | CSV body for an upload job. The only documented PUT. |
| Web Hooks see official article | GET, POST | Outbound event callbacks. See Web Hooks. |
| Segments see official article | GET, POST | Saved dynamic segments and criteria. See Segments. |
The official article index confirms further resource families whose exact paths were not machine-verifiable: Message Clicks, Message Bounces, Unsubscribes, Custom Fields, Contact History, Automations, Sign-Up Forms, Statistics, Users and a Time endpoint returning iContact’s server clock. Scheduled sends are evaluated against that clock, not your host’s timezone.
Header names and paging parameters below come verbatim from iContact’s own PHP client, so they are literal rather than reconstructed. Point them at the sandbox first.
import os, requests
BASE = "https://app.icontact.com/icp" # sandbox: https://app.sandbox.icontact.com/icp
s = requests.Session()
s.headers.update({
"Accept": "application/json",
"Content-Type": "application/json",
"Api-Version": "2.2",
"Api-AppId": os.environ["ICONTACT_APP_ID"],
"Api-Username": os.environ["ICONTACT_API_USERNAME"],
"Api-Password": os.environ["ICONTACT_API_PASSWORD"],
})
# 1. Resolve the scope every other call needs.
acct = s.get(BASE + "/a/", timeout=30).json()["accounts"][0]["accountId"]
folder = s.get("%s/a/%s/c/" % (BASE, acct), timeout=30).json()["clientfolders"][0]["clientFolderId"]
scope = "%s/a/%s/c/%s" % (BASE, acct, folder)
# 2. Create a contact. The payload is an ARRAY, even for one record.
r = s.post(scope + "/contacts", timeout=30,
json=[{"email": "ada@example.com", "firstName": "Ada", "lastName": "Lovelace"}])
r.raise_for_status()
contact_id = r.json()["contacts"][0]["contactId"]
# 3. Subscribe that contact to a list.
s.post(scope + "/subscriptions", timeout=30,
json=[{"contactId": contact_id,
"listId": os.environ["ICONTACT_LIST_ID"],
"status": "normal"}]).raise_for_status()
# 4. Page with limit/offset, sort with orderby.
page = s.get(scope + "/contacts", timeout=30,
params={"limit": 100, "offset": 0, "orderby": "lastName:asc"}).json()
print(page["total"])const BASE = "https://app.icontact.com/icp";
const headers = {
Accept: "application/json",
"Content-Type": "application/json",
"Api-Version": "2.2",
"Api-AppId": process.env.ICONTACT_APP_ID,
"Api-Username": process.env.ICONTACT_API_USERNAME,
"Api-Password": process.env.ICONTACT_API_PASSWORD,
};
async function call(path, init) {
const res = await fetch(BASE + path, Object.assign({ headers }, init || {}));
if (!res.ok) throw new Error(res.status + " " + (await res.text()));
return res.json();
}
const acct = (await call("/a/")).accounts[0].accountId;
const folder = (await call("/a/" + acct + "/c/")).clientfolders[0].clientFolderId;
const scope = "/a/" + acct + "/c/" + folder;
// Step 1: create the message content.
const created = await call(scope + "/messages", {
method: "POST",
body: JSON.stringify([{
campaignId: process.env.ICONTACT_CAMPAIGN_ID,
subject: "Monthly product update",
htmlBody: "<h1>Hello</h1>",
textBody: "Hello",
}]),
});
// Step 2: dispatch it. The /sends payload fields (message id, target lists,
// schedule) are defined in the official Sends article - confirm the key names
// there before hard-coding them. There is no SMTP path: iContact has no relay.
await call(scope + "/sends", { method: "POST", body: JSON.stringify([sendPayload]) });Search engines surface SMTP relay setup articles on icontact.my.site.com, which look authoritative because the hostname carries the brand. They are not iContact docs. That Experience Cloud site fronts a shared knowledge base whose sitemap index points at knowledge.campaigner.com, and every one of those articles canonicalises there. iContact’s real help centre is help.icontact.com/customers/, and its complete article index has no SMTP relay content at all. Configure a hostname from those pages into an iContact account and nothing happens, there is no relay to connect to.
The URL shape is /icp/a/{accountId}/c/{clientFolderId}/{resource}, so newcomers who POST straight to /contacts get nowhere. GET /a/ for the account ID, then GET /a/{accountId}/c/ for the folder ID, and only then touch contacts, lists, messages or sends. Cache both rather than re-fetching per job, they do not change.
That header takes an application-specific password set when you register an app in the developer portal, not the password you type into the web UI. Feeding it your login password produces a failure that reads exactly like a wrong username, which is why it costs people an afternoon. There is no Authorization header and no OAuth to fall back on.
Creating one contact means posting a JSON array containing one object, and responses arrive as arrays under a plural key such as contacts or clientfolders. Updating a record is also a POST to its singular URL, not a PUT or PATCH. The only PUT in the documented set is the CSV body of a bulk upload job. Code written against a conventional REST API breaks on all three.
iContact runs both subscriber-based and contact-based plans, and which one you are on changes how unsubscribed, bounced and pending records count towards your limit. Crossing the ceiling raises the invoice rather than blocking the send, hence dedicated articles on exceeding the subscriber limit. Audit contact statuses before an API-driven import: a bulk upload is the fastest way to find out which model you are on.
icontact-pro-api-php, closing out the api.icpro.co Pro generation. The newest commit anywhere in the organisation.icontact-api-php, the official SDK for the current app.icontact.com/icp API v2.2. No official SDK release since.icontact-pro-select-api-php. The OutMarket-era api.omkt.co generation is dormant and should be treated as retired.The official version history lives at API Version Changelog; new integrations start at the API Getting Started Guide. Check both before assuming anything here is still current.
The base URL is https://app.icontact.com/icp and the current version is 2.2, sent as an Api-Version header rather than in the path. The sandbox is https://app.sandbox.icontact.com/icp. Two older generations exist-api.icpro.co/icp (Pro) and api.omkt.co/icp (Pro Select), but neither is what a current account uses.
Three custom headers on every request: Api-AppId (the application ID you register in the developer portal), Api-Username (your iContact username) and Api-Password (an application-specific password, not your UI login password), plus Api-Version: 2.2. No OAuth flow, no bearer token, no Authorization header. Credentials are long-lived, so store them like any production secret.
You fetch them, in order. GET /icp/a/ returns the accounts your credentials can reach and yields accountId; GET /icp/a/{accountId}/c/ returns the client folders and yields clientFolderId. Every data resource sits under /a/{accountId}/c/{clientFolderId}/, so nothing works until you have both. Cache them, they are stable for the life of the account.
No. All three official SDKs and every documented resource are JSON REST under Api-Version: 2.2. The SOAP/XML API appearing near iContact in search results belongs to Campaigner, a sibling product on the same shared knowledge platform. It is not a deprecated iContact generation, and its credentials will not work here.
Offset paging via query string: ?limit=100&offset=0. Collection responses include a total, so read it from page one and loop until consumed. Sorting uses ?orderby=field:direction with comma-separated pairs, e.g. orderby=lastName:asc,email:desc. There is no cursor paging and no published page-size ceiling, so measure before assuming a large limit is accepted.
Neither. iContact publishes three PHP wrappers and nothing else, the newest last committed in March 2018, and no iContact MCP server exists in its GitHub organisation or the public MCP ecosystem as of August 2026. For agent access, Zapier MCP over the official Zapier app is the shortest path; otherwise write a direct HTTP client, which static three-header auth makes easy in any language.
This review follows our email infrastructure testing methodology. We disclose affiliate relationships in our editorial independence policy.