
Omnisend runs a date-versioned REST API at https://api.omnisend.com/api, and the version to build against in 2026 is 2026-03-15-not v3 or v5, both still visible in the docs version picker and both superseded. There are no maintained first-party language SDKs: the only Omnisend-branded PHP client on GitHub was archived in 2021. Where Omnisend is ahead of its peer group is MCP, an official hosted Model Context Protocol server at https://mcp.omnisend.com/v2/mcp, already in its second generation.
One structural fact frames everything below: Omnisend has no SMTP relay and no transactional send endpoint. You push contacts, products and commerce events in, and an automation workflow decides what gets sent. That makes POST /api/events the most important endpoint on the platform, and it changes how you architect anything that would be a one-line SMTP submit on Brevo or Mailchimp.
Model Context Protocol is how an AI client. Claude, ChatGPT, Cursor, discovers and calls a vendor’s API without custom glue code. For most email platforms in 2026 the answer is still a community wrapper on GitHub. Omnisend shipped first-party MCP, twice.
Omnisend operates a first-party remote MCP endpoint at https://mcp.omnisend.com/v2/mcp, authenticated by OAuth rather than an API key, you log in and approve permissions during client setup, so there is no token to paste anywhere. It exposes 7 action-specific tools fronting the whole 130+-endpoint API surface, and it is a native connector in Claude and ChatGPT. Legacy v1 at https://mcp.omnisend.com/mcp remains functional.
Two are Omnisend’s own. Note that aggregator directories, mcp.so, Glama, MCPlane, mcpbundles, UBOS, almost all index the community repo rather than the official hosted endpoint, and several mislabel it as official.
Current server at https://mcp.omnisend.com/v2/mcp. Seven action-specific tools: omnisend_search, omnisend_tool_schema, omnisend_reference, omnisend_query, omnisend_create, omnisend_update and omnisend_delete, with reference docs embedded.
Legacy endpoint at https://mcp.omnisend.com/mcp (no trailing slash), also OAuth. Four generic tools: search, tool_schema, tool_documentation, execute. Needs an Owner, Admin, Manager or Partner role.
Self-hosted Node server (Apache-2.0) predating the official one. Covers contacts, products, categories and events. Worth it only if you need a local API-key-based server.
Exposes Zapier’s Omnisend actions. Create Subscriber, campaign send/cancel/copy, Send Customer Event, via Zapier’s MCP endpoint. No developer setup, but triggers poll every 15 minutes.
Why buyers should care. An official MCP server turns “can our team just ask the tool a question?” from a project into a login: a merchandiser can ask Claude for last quarter’s campaign performance, or have ChatGPT draft an on-brand editable email into the Omnisend builder. Two caveats: the meta-tool pattern makes an agent call omnisend_search and omnisend_tool_schema before acting, so first-call latency is higher; and OAuth grants real write access, so set the AI Apps Access tier before approving. Against Klaviyo and ActiveCampaign this is a real differentiator.
A conventional JSON REST surface with one unconventional rule: two headers are mandatory on every request, and omitting the version header silently puts you on a version you did not choose. Errors follow RFC 9457 problem+json.
| Base URL | https://api.omnisend.com/api |
| Version header | Omnisend-Version: 2026-03-15 REQUIRED |
| Auth header | Authorization: Omnisend-API-Key {key} |
| Response format | JSON · errors as RFC 9457 problem+json |
| Pagination | Cursor on contacts, campaigns, segments, automations · offset on products, categories, batches |
| Page size | limit default 100, max 250 |
| Bulk operations | Batch API, max 100 actions per batch |
| Transactional send | None. POST /api/events returns 202; a workflow sends |
The resource model is contact-and-event centric, not message centric. Fourteen groups cover Events, Contacts, Brands, Products, Categories, Batches, Campaigns, Segments, Email Content, Templates, Universal Layouts, Images and Analytics, plus Automations, roughly 130+ endpoints. Notably absent: anything that sends a specific message to a specific address on demand. Campaign statistics also moved into Analytics in 2026-03-15, which matters because those are the most throttled endpoints in the API.
Cursor pagination has one rule that trips people on day one: filters and sort are encoded inside the cursor, so once you have one you pass only the cursor. Changing a filter mid-pagination returns 400. Never send after and before together.
Two paths, depending on whether you are integrating your own store or shipping an app other merchants install.
Generate a key in the Omnisend app and send it as Authorization: Omnisend-API-Key YOUR-API-KEY alongside the mandatory Omnisend-Version: 2026-03-15 header. This replaced the v3 X-API-KEY pattern outright: the old header is rejected on the new base path, and the old base path is retired. Porting pre-2026 code means changing both.
Public and partner apps use the authorization code flow: send the user to https://app.omnisend.com/oauth2/authorize with client_id, redirect_uri, response_type=code, state and a space-separated scope; exchange at https://app.omnisend.com/oauth2/token; then call with Authorization: Bearer {access-token}. Tokens do not expire unless revoked, so there is no refresh loop, and no rotation either, so treat them as long-lived secrets. Connecting a brand via POST /api/brands/current is OAuth-only.
Scopes are declared per endpoint rather than published as one list. Confirmed strings include contacts.write, products.write and events.write; check the reference page for your endpoint before requesting a scope set.
OAuth credentials are not self-serve. There is no developer dashboard where you create a client ID: Omnisend issues them by request through an official form, with a stated turnaround of 1-3 business days. It is a hard dependency you cannot unblock yourself, and the most common reason an Omnisend app integration slips its first sprint.
Limits are enforced per brand on a sliding window over the last minute, or over 24 hours where a daily quota applies. The headline number is generous; the exceptions are what break integrations.
| Scope | Limit | Notes |
|---|---|---|
| Default, all endpoints | 400 req/min | Per brand, sliding one-minute window |
/segments GET, DELETE | 100 req/min | A quarter of the default |
/segments POST, PUT | 15 req/min | Segment writes are expensive server-side |
/contacts/tags POST, DELETE | 60 req/min | Batch tagging across many contacts |
| Template and content render | 40 req/min | /email-templates/{id}/render, /email-content/{id}/render |
| Analytics reports, statistics | 10/min and 55/24h | Both ceilings apply simultaneously |
| Batch payload | 100 actions per batch | Size cap, not a rate cap |
Exceeding a limit returns HTTP 429 with an RFC 9457 body: {"type":"https://problems.omnisend.com/rate-limit-exceeded","title":"Rate limit exceeded","status":429}. Some endpoints add retryAfter in seconds, read it rather than guessing. The analytics workaround is architectural: 55 calls per day is one refresh every 26 minutes for a single report, so a dashboard fanning out one call per widget exhausts its budget before lunch. Consolidate metrics into as few report calls as possible and cache server-side for at least 30 minutes. For contact and product sync, use the Batch API rather than looping single writes.
The weakest part of the developer story: no maintained first-party client library exists for any general-purpose language. Omnisend ships a WordPress plugin family, a Postman workspace and machine-readable docs; everything else in the omnisend GitHub org is archived or not what its name suggests.
| Language | Package | Install | Repo |
|---|---|---|---|
| PHP (WordPress) | wp-omnisend | Install “Omnisend for WooCommerce” from the plugin directory | omnisend/wp-omnisend |
| Postman | Public workspace | Fork the official collection | postman.com/omnisend-api |
| AI agents | llms.txt + OpenAPI | Fetch api-docs.omnisend.com/llms.txt | llms.txt index |
| PHP (generic) | omnisend/php-sdk ARCHIVED | Do not install | omnisend/php-sdk |
| Go | omnisend/mcp-go-sdk | MCP tooling, not an API client | omnisend/mcp-go-sdk |
Two repository traps. omnisend/php-sdk is archived, calls itself a v3 wrapper and was last touched July 1, 2021-it targets the retired /v3/ path and the dead X-API-KEY header, so it cannot authenticate. And omnisend/mcp-go-sdk looks like a Go client but is a fork of the Go SDK for building MCP servers, last updated April 17, 2026, with no REST client in it.
The maintained code is the WordPress family: wp-omnisend (last commit June 2, 2026) plus official bridges for SureCart, Gravity Forms, Contact Form 7, Ninja Forms, Formidable Forms, Paid Memberships Pro and LifterLMS. On WordPress that is your path; elsewhere, plan on a thin HTTP client.
No widely adopted community client exists for Python, Node, Ruby or Go. The nearest thing is plutzilla/omnisend-mcp, whose source doubles as a reference implementation of the HTTP calls, plus Make‘s no-code module set. The honest 2026 recommendation: generate a client from the OpenAPI specs in llms.txt, or write 50 lines of fetch.
Fifteen of the most-used resources across the 130+ documented endpoints, relative to https://api.omnisend.com/api. The full reference is at api-docs.omnisend.com; every page has a Markdown twin reachable by appending .md, the fastest way to feed a spec to an agent.
| Resource | Methods | Description |
|---|---|---|
| Contacts /contacts | GET, POST | List with cursor pagination, or upsert. POST returns 201 on create, 200 on update. |
| Contact by ID /contacts/{contactID} | GET, PATCH | Fetch or update one contact. The response field was renamed contactID to id. |
| Contact by email /contacts/{email} | PATCH | Update a contact addressed by email rather than ID. |
| Contact tags /contacts/tags | POST, DELETE | Batch add or remove tags. Throttled to 60 req/min. |
| Events /events | POST | The transactional entry point. Send contact, eventName, origin; returns 202. Needs events.write. |
| Event metadata /event-metadata | POST, PUT | Declare custom events so they become automation triggers. Keyed on eventName plus origin. |
| Metadata query /event-metadata/query | POST | Discover event definitions. Replaces GET /v3/events; needs a category in the body. |
| Products /products | GET, POST | List or create catalog products. Offset pagination; writes need products.write. |
| Product by ID /products/{productID} | GET, PUT, DELETE | PUT is a full replace, omitted fields are cleared. |
| Categories /categories | GET, POST, PUT, DELETE | Taxonomy behind catalog segmentation and recommendations. Offset paginated. |
| Batches /batches | GET, POST | Async bulk import of products, contacts, events, categories. Max 100 actions. |
| Campaigns /campaigns | GET, POST | Creation now requires content.email.templateID; type changed from standart to regular. |
| Campaign actions /campaigns/{id}/send | POST | Send, cancel, copy or test-send. Siblings cover A/B control and UTM settings. |
| Segments /segments | GET, POST, PUT, DELETE | Segments plus a statistics sub-resource. 100/min reads, 15/min writes. |
| Analytics /analytics/reports | POST | Metrics moved off the campaign object in 2026-03-15. 10/min and 55 per 24h. |
There is no send call: you upsert the contact, emit an event, and a subscribed workflow composes and delivers the email. Money is a float in store currency, never integer cents. A 401 almost always means a legacy X-API-KEY header or a missing version header.
import requests
BASE = "https://api.omnisend.com/api"
HEADERS = {
"Authorization": "Omnisend-API-Key YOUR-API-KEY",
"Omnisend-Version": "2026-03-15",
"Content-Type": "application/json",
}
# Upsert a contact: 201 create, 200 update
r = requests.post(f"{BASE}/contacts", headers=HEADERS, timeout=30, json={
"identifiers": [{
"type": "email",
"id": "jane@example.com",
"channels": {"email": {"status": "subscribed"}},
}],
"firstName": "Jane",
})
r.raise_for_status()
contact_id = r.json()["id"] # renamed from contactID in 2026-03-15
# Trigger the send by emitting an event
ev = requests.post(f"{BASE}/events", headers=HEADERS, timeout=30, json={
"contact": {"email": "jane@example.com"},
"eventName": "placed order",
"origin": "api",
"properties": {"orderID": "1042", "value": 19.99, "currency": "USD"},
})
assert ev.status_code == 202, ev.text # asynchronous; 19.99 NOT 1999const BASE = "https://api.omnisend.com/api";
const headers = {
Authorization: `Omnisend-API-Key ${process.env.OMNISEND_API_KEY}`,
"Omnisend-Version": "2026-03-15",
};
async function allContacts() {
let params = new URLSearchParams({ limit: "250" }); // max 250
const out = [];
for (;;) {
const r = await fetch(`${BASE}/contacts?${params}`, { headers });
if (r.status === 429) {
const problem = await r.json(); // RFC 9457 body
await new Promise((s) => setTimeout(s, (problem.retryAfter ?? 60) * 1000));
continue;
}
if (!r.ok) throw new Error(await r.text());
const body = await r.json();
out.push(...(body.contacts ?? []));
const after = body.paging?.cursors?.after;
if (!body.paging?.hasMore || !after) return out;
params = new URLSearchParams({ after }); // cursor only
}
}No API key is involved, the client opens a browser OAuth flow on first use. Claude: Customize › Connectors › Add custom connector. ChatGPT: Settings › Apps › Developer mode. Cursor takes the raw config.
{
"mcpServers": {
"omnisend": { "url": "https://mcp.omnisend.com/v2/mcp" }
}
}In v3, prices were integers in cents: 1999 meant $19.99. In 2026-03-15 they are floats: 19.99. The API accepts 1999 as a valid float, so nothing errors, you get abandoned-cart emails quoting $1,999.00 and attribution 100x too high. Audit every value, cartSum and total field, including historical imports.
GET /v3/carts, GET /v3/orders and their by-ID and delete siblings were removed in 2026-03-15 with no replacement read endpoint. Cart and order state now exists only as events POSTed to /api/events. If your integration read a cart back to reconcile state, that pattern is dead: Omnisend is write-only for commerce state, and your store must be the system of record.
Omnisend’s Batch API docs warn to ensure no automations could send messages based on imported data. Backfilling historic orders through POST /api/batches fires every listening workflow, because an event is an event regardless of timestamp. Pause those workflows first. Batches cap at 100 actions, so a 50,000-row backfill is 500 requests.
The base path moved to https://api.omnisend.com/api, the header changed to Authorization: Omnisend-API-Key, and Omnisend-Version became mandatory, omit it and you are not on the version you think you are. And omnisend/php-sdk is archived at v3, so it cannot work. Use Postman, plain HTTP or the WordPress plugins.
Legacy v3, v4.0 and v5 remain in the docs version picker, and v2026-preview covers early-access Statistics and Forms endpoints. Omnisend publishes migration guides from v5 and v3, including an AI-agent-assisted path, but no end-of-life dates. Track the official changelog and API reference.
2026-03-15 is current; v3, v4.0 and v5 are legacy even though the version picker still offers them. Send Omnisend-Version: 2026-03-15 on every request against https://api.omnisend.com/api. A v2026-preview covers early-access Statistics and Forms endpoints, and no end-of-life date has been published for the legacy versions.
Generate a key in your account and send it as Authorization: Omnisend-API-Key YOUR-API-KEY with the mandatory Omnisend-Version header; the v3 X-API-KEY header no longer works. Partner apps use OAuth 2.0 instead, and those credentials are not self-serve-request them via an official form and expect 1-3 business days.
The default is 400 requests per minute per brand on a sliding window. Overrides: /segments reads 100/min and writes 15/min, /contacts/tags 60/min, render endpoints 40/min, analytics 10/min and 55 per 24 hours. A 429 returns an RFC 9457 body; some endpoints add retryAfter in seconds. Honour it where present.
Contacts use cursor pagination. Call GET /api/contacts?limit=250 (default 100, max 250), then read paging.hasMore and paging.cursors.after. On follow-up requests pass only the cursor, filters and sort are baked into the token, so changing them mid-pagination returns 400. Products, categories and batches still use offset pagination.
You do not send it directly. Omnisend has no SMTP relay and no transactional send endpoint. POST /api/events with a contact, an eventName and an origin; the call returns 202 Accepted and a workflow subscribed to that event name sends the message. For password resets and other application mail, pair Omnisend with a relay like Postmark.
Yes, two, both first-party and hosted. Current is v2 at https://mcp.omnisend.com/v2/mcp with seven action-specific tools; legacy v1 at https://mcp.omnisend.com/mcp exposes four generic ones. Both are OAuth-only: no key to configure, no npx package. Set the AI Apps Access tier before approving the connection.
This review follows our email infrastructure testing methodology. We disclose affiliate relationships in our editorial independence policy.