Intro
Endpoint: https://api.omnisend.com/api/analytics/statistics
What the Statistics API is for
The Statistics API provides aggregated, time-series analytics for your marketing data. It lets you answer questions like:
- Campaign and automation workflow performance — How many messages were sent, opened, or clicked over a given period?
- Subscriber growth — How is my email, SMS, or push audience growing or shrinking?
- Sales attribution — How much revenue and how many orders can be attributed to specific campaigns or automation workflows?
- Deliverability insights — What are my bounce, spam complaint, and unsubscribe rates?
How results are groupedResults are grouped by the date each event actually happened — when an email was opened, a link was clicked, or an order was placed. This gives you an accurate picture of activity over time.
What this API is not for
- Replicating Omnisend in-app reports — The Omnisend app groups data by send date; this API groups by event date. The numbers will differ.
- Individual contact or event-level data — This API returns aggregated metrics, not per-recipient activity logs.
- Real-time monitoring — Data is based on completed hours. The current hour may be incomplete (see Data freshness).
Available data at a glance
Metrics
Metrics are the values you want to measure. At least one metric is required per query. Duplicate metrics within a query are not allowed.
Delivery
| Metric | Description |
|---|---|
sent | Total messages sent |
failed | Messages that failed to deliver (bounces, invalid addresses, etc.) |
sentCost | Cost incurred for sending messages. SMS only. |
markedAsSpamUnique | Unique messages marked as spam by recipients |
unsubscribedUnique | Unique messages that resulted in an unsubscribe |
Engagement
| Metric | Description |
|---|---|
openedUnique | Unique messages opened at least once |
opened | Total opens, including repeat opens of the same message |
clickedUnique | Unique messages clicked at least once |
clicked | Total clicks, including repeat clicks on the same message |
Revenue attribution
| Metric | Description |
|---|---|
attributedOrdersUnique | Unique messages with at least one attributed order |
attributedOrders | Total placed orders attributed to messages |
attributedRevenue | Total revenue from orders attributed to messages |
Sales
| Metric | Description |
|---|---|
totalOrders | Total number of placed orders. Counts every placed order in the date range, regardless of attribution. |
totalRevenue | Total revenue from all placed orders. Sums order value for every placed order in the date range, regardless of attribution. |
totalOrderedProductUnits | Total product units ordered (sum of quantities across all order lines). Counts every ordered product event in the date range, regardless of attribution. Data from 2025-11-01. |
attributedOrderedProductUnits | Total product units from orders attributed to messages (sum of quantities). Only ordered product events attributed to a marketing message. Data from 2025-11-01. |
Sales vs. attributed metrics:
totalOrdersandtotalRevenuecount all placed orders in your store for the given date range — regardless of whether a message triggered them.attributedOrdersandattributedRevenuecount only orders attributed to a specific marketing message. Use sales metrics to understand overall store performance; use attributed metrics to measure marketing impact.
totalOrderedProductUnitsandattributedOrderedProductUnitsare the product-line-level equivalents: they sum ordered quantities across individual product lines rather than counting orders as a whole. Use them when you need product- or variant-level breakdowns.
Audience growth
| Metric | Description |
|---|---|
subscribedEmail | Contacts who joined the email list. Includes repeated subscriptions. Data from 2024-08-01 only. |
subscribedSms | Contacts who joined the SMS list. Includes repeated subscriptions. Data from 2024-08-01 only. |
subscribedPush | Contacts who joined the push list. Includes repeated subscriptions. Data from 2024-08-01 only. |
unsubscribedEmail | Contacts who left the email list. Includes repeated unsubscribes. Data from 2024-08-01 only. |
unsubscribedSms | Contacts who left the SMS list. Includes repeated unsubscribes. Data from 2024-08-01 only. |
unsubscribedPush | Contacts who left the push list. Includes repeated unsubscribes. Data from 2024-08-01 only. |
Channel differences: Most metrics apply across all channels (Email, SMS, Push). Exceptions:
sentCost— currently available for SMS only.- Audience growth metrics (
subscribedEmail,unsubscribedSms, etc.) are channel-specific by definition.- Audience growth metrics support only one dimension each (
subscriptionMethodorunsubscribeReason), while other metrics support a wider set of dimensions. See Dimension/Metric compatibility.
Dimensions
Dimensions let you break down metrics into groups. The timestamp dimension is always required. You can add up to 2 additional dimensions per query.
| Category | Dimensions | Use case |
|---|---|---|
| Time | timestamp (with granularity: hour, day, week, month) | Time-series analysis |
| Marketing activity | marketingActivityID, marketingActivityType, marketingActivityName | Performance by campaign or automation workflow |
| Message | messageID, messageTitle, messageChannel, messageFormat | Per-message breakdown |
| Delivery | senderDomain, emailDomain, phoneNumberCountry | Deliverability analysis |
| Failure | bounceType, deliveryFailureReason | Bounce and failure analysis (failed metric only) |
| Client | clientDeviceType, clientAppName, clientAppLanguage, clientOperatingSystem | Device and app analytics (engagement and revenue metrics only) |
| Click | urlClicked | Click-through analysis (click and revenue metrics only) |
| Product | productID, productTitle, productSku, productVariantID, productVariantTitle | Product and variant breakdown (product unit metrics only) |
| Subscription | subscriptionMethod, unsubscribeReason | Audience growth breakdown (audience growth metrics only) |
Not every dimension works with every metric. For example, you cannot use clientDeviceType with sent because device information is only captured on opens. See the full compatibility matrix below.
Data freshness and availability
Data freshness
Data for the last completed hour is available. The current hour may be incomplete as events are still being processed. Do not rely on partial-hour data for accurate reporting.
Earliest available dates
| Metric group | Metrics | Available from |
|---|---|---|
| Audience growth | subscribedEmail, subscribedSms, subscribedPush, unsubscribedEmail, unsubscribedSms, unsubscribedPush | 2024-08-01 |
| Sales (product units) | totalOrderedProductUnits, attributedOrderedProductUnits | 2025-11-01 |
| All other metrics | sent, opened, clicked, attributedRevenue, etc. | Up to 5 years from today |
Why a query might return empty results
Empty results are not errors. Common reasons:
- Date range is before the metric's availability date — querying
subscribedEmailbefore 2024-08-01 returns no rows. - Current hour is incomplete — data for the ongoing hour is not yet finalized.
- No matching data — no events occurred that match your query's dimensions and filters.
Step-by-step query guide
All examples use POST https://api.omnisend.com/api/analytics/statistics with Content-Type: application/json.
Example 1: Total sends per day
The simplest query — one metric, one dimension (timestamp), no filters.
Why: You want to know how many messages your brand sent each day over a few days.
{
"queries": [
{
"alias": "daily-sends",
"metrics": [{"name": "sent"}],
"dimensions": [{"name": "timestamp", "granularity": "day"}],
"dateRange": {
"from": "2026-02-01T00:00:00Z",
"to": "2026-02-04T00:00:00Z"
}
}
]
}Response — one row per day:
{
"statistics": [
{
"alias": "daily-sends",
"dimensions": [{"name": "timestamp", "granularity": "day"}],
"metrics": [{"name": "sent"}],
"rows": [
{"timestamp": "2026-02-01", "sent": 1200},
{"timestamp": "2026-02-02", "sent": 980},
{"timestamp": "2026-02-03", "sent": 1540}
]
}
]
}Each row contains one key per dimension and one key per metric. The timestamp format depends on granularity — 2026-02-01 for day, 2026-02-01T15:00:00Z for hour.
Example 2: Sends broken down by channel
Add a dimension to split results by email, SMS, and push.
Why: You want to see daily send volume per channel over a couple of days.
{
"queries": [
{
"alias": "sends-by-channel",
"metrics": [{"name": "sent"}],
"dimensions": [
{"name": "timestamp", "granularity": "day"},
{"name": "messageChannel"}
],
"dateRange": {
"from": "2026-02-01T00:00:00Z",
"to": "2026-02-03T00:00:00Z"
}
}
]
}Response — one row per day per channel:
{
"statistics": [
{
"alias": "sends-by-channel",
"dimensions": [
{"name": "timestamp", "granularity": "day"},
{"name": "messageChannel"}
],
"metrics": [{"name": "sent"}],
"rows": [
{"timestamp": "2026-02-01", "messageChannel": "Email", "sent": 1000},
{"timestamp": "2026-02-01", "messageChannel": "SMS", "sent": 150},
{"timestamp": "2026-02-01", "messageChannel": "Push", "sent": 50},
{"timestamp": "2026-02-02", "messageChannel": "Email", "sent": 1100},
{"timestamp": "2026-02-02", "messageChannel": "SMS", "sent": 130},
{"timestamp": "2026-02-02", "messageChannel": "Push", "sent": 45}
]
}
]
}Example 3: Hourly engagement
Use hour granularity for a short window.
Why: You want to see open and click activity hour by hour for a short window.
{
"queries": [
{
"alias": "hourly-engagement",
"metrics": [
{"name": "openedUnique"},
{"name": "clickedUnique"}
],
"dimensions": [{"name": "timestamp", "granularity": "hour"}],
"dateRange": {
"from": "2026-02-10T00:00:00Z",
"to": "2026-02-10T02:00:00Z"
}
}
]
}Response — one row per hour with both metrics:
{
"statistics": [
{
"alias": "hourly-engagement",
"dimensions": [{"name": "timestamp", "granularity": "hour"}],
"metrics": [{"name": "openedUnique"}, {"name": "clickedUnique"}],
"rows": [
{"timestamp": "2026-02-10T00:00:00Z", "openedUnique": 42, "clickedUnique": 8},
{"timestamp": "2026-02-10T01:00:00Z", "openedUnique": 15, "clickedUnique": 3}
]
}
]
}
hourgranularity allows a maximum date range of 7 days.
Example 4: Filter to campaigns only
Use a filter to include only campaign data (excluding automation workflows).
Why: You want campaign-level performance with revenue attribution.
{
"queries": [
{
"alias": "campaign-performance",
"metrics": [
{"name": "sent"},
{"name": "openedUnique"},
{"name": "clickedUnique"},
{"name": "attributedRevenue"}
],
"dimensions": [
{"name": "timestamp", "granularity": "day"},
{"name": "marketingActivityID"}
],
"dateRange": {
"from": "2026-01-15T00:00:00Z",
"to": "2026-01-17T00:00:00Z"
},
"filters": [
{
"name": "marketingActivityType",
"operator": "in",
"values": ["Campaign"]
}
]
}
]
}Response — one row per day per campaign:
{
"statistics": [
{
"alias": "campaign-performance",
"dimensions": [
{"name": "timestamp", "granularity": "day"},
{"name": "marketingActivityID"}
],
"metrics": [
{"name": "sent"},
{"name": "openedUnique"},
{"name": "clickedUnique"},
{"name": "attributedRevenue"}
],
"rows": [
{
"timestamp": "2026-01-15",
"marketingActivityID": "c123",
"sent": 120,
"openedUnique": 35,
"clickedUnique": 10,
"attributedRevenue": 245.50
},
{
"timestamp": "2026-01-16",
"marketingActivityID": "c123",
"sent": 95,
"openedUnique": 28,
"clickedUnique": 7,
"attributedRevenue": 182.00
}
]
}
]
}Filters use in (include) or notIn (exclude) operators. Multiple filters are combined with AND logic. A filter dimension must be supported by all metrics in the query — same rules as dimensions.
Example 5: Multiple queries in one request
You can send up to 4 queries in a single request. Each query has its own alias, metrics, dimensions, and filters.
Why: You want campaign metrics and automation workflow metrics in one call, avoiding two separate requests.
{
"queries": [
{
"alias": "campaign_metrics",
"metrics": [
{"name": "sent"},
{"name": "openedUnique"},
{"name": "clickedUnique"},
{"name": "attributedRevenue"}
],
"dimensions": [
{"name": "timestamp", "granularity": "day"},
{"name": "marketingActivityID"}
],
"dateRange": {
"from": "2026-01-15T00:00:00Z",
"to": "2026-01-16T00:00:00Z"
},
"filters": [
{"name": "marketingActivityType", "operator": "in", "values": ["Campaign"]}
]
},
{
"alias": "automation_metrics",
"metrics": [
{"name": "sent"},
{"name": "openedUnique"},
{"name": "clickedUnique"},
{"name": "attributedRevenue"}
],
"dimensions": [
{"name": "timestamp", "granularity": "day"},
{"name": "marketingActivityID"}
],
"dateRange": {
"from": "2026-01-15T00:00:00Z",
"to": "2026-01-16T00:00:00Z"
},
"filters": [
{"name": "marketingActivityType", "operator": "in", "values": ["Automation"]}
]
}
]
}Response — one entry in statistics per alias. Use the alias to match results to your queries:
{
"statistics": [
{
"alias": "campaign_metrics",
"dimensions": [
{"name": "timestamp", "granularity": "day"},
{"name": "marketingActivityID"}
],
"metrics": [
{"name": "sent"},
{"name": "openedUnique"},
{"name": "clickedUnique"},
{"name": "attributedRevenue"}
],
"rows": [
{
"timestamp": "2026-01-15",
"marketingActivityID": "c123",
"sent": 120,
"openedUnique": 35,
"clickedUnique": 10,
"attributedRevenue": 245.50
}
]
},
{
"alias": "automation_metrics",
"dimensions": [
{"name": "timestamp", "granularity": "day"},
{"name": "marketingActivityID"}
],
"metrics": [
{"name": "sent"},
{"name": "openedUnique"},
{"name": "clickedUnique"},
{"name": "attributedRevenue"}
],
"rows": [
{
"timestamp": "2026-01-15",
"marketingActivityID": "a789",
"sent": 45,
"openedUnique": 22,
"clickedUnique": 9,
"attributedRevenue": 180.00
}
]
}
]
}Cross-query constraint: All queries in a request must have date ranges that fall within the same 1 calendar year.
Constraints and limits
All limits are enforced per request and per brand.
Rate limits
| Time window | Limit |
|---|---|
| Per minute | 10 requests |
| Per day | 55 requests |
When exceeded, the API returns HTTP 429 Too Many Requests.
Request limits
| Constraint | Limit |
|---|---|
| Queries per request | 4 |
| Non-timestamp dimensions per query | 2 |
| Filters per query | 100 |
| Date span per query | 1 calendar year |
| Lookback period | 5 years from today |
| Cross-query date range | All queries within same 1 calendar year |
Date range limits by granularity
| Granularity | Max date range |
|---|---|
hour | 7 days |
day | 60 days |
week | 52 weeks |
month | 12 months |
If you need more than 60 days of daily data, split into multiple requests.
Example: invalid request and error
Requesting hourly data for 30 days (exceeds the 7-day limit):
{
"queries": [
{
"alias": "too-wide",
"metrics": [{"name": "sent"}],
"dimensions": [{"name": "timestamp", "granularity": "hour"}],
"dateRange": {
"from": "2026-01-01T00:00:00Z",
"to": "2026-01-31T00:00:00Z"
}
}
]
}Response (HTTP 400):
{
"error": "1 error(s) found. Check 'fields' array for details.",
"fields": {
"queries[0].dimensions": "hourly granularity cannot be used with date range greater than 7 days"
}
}Agency guidance
If you manage multiple brands (e.g., as an agency), keep the following in mind:
- One brand per request — each API call is scoped to a single brand. To report across brands, make separate requests per brand.
- Rate limits are per brand — each brand has its own rate limit counters (10/min, 55/day). Querying one brand does not consume another brand's quota.
- Authentication — use OAuth or API key authentication.
Technical reference
Endpoint
POST https://api.omnisend.com/api/analytics/statistics
Content type: application/json
Request schema
{
"queries": [
{
"alias": "string",
"metrics": [
{"name": "string"}
],
"dimensions": [
{"name": "string", "granularity": "string"}
],
"dateRange": {
"from": "RFC3339 datetime",
"to": "RFC3339 datetime"
},
"filters": [
{
"name": "string",
"operator": "in | notIn",
"values": ["string"]
}
]
}
]
}| Field | Type | Required | Description |
|---|---|---|---|
queries | array | Yes | 1–4 queries per request |
queries[].alias | string | Yes | Unique identifier for the query. Pattern: ^[a-zA-Z0-9 |_.-]+$ |
queries[].metrics | array | Yes | At least 1 metric. No duplicates within a query. |
queries[].dimensions | array | Yes | Must include timestamp. Up to 2 additional non-timestamp dimensions. |
queries[].dimensions[].granularity | string | Required for timestamp | One of: hour, day, week, month |
queries[].dateRange.from | datetime | Yes | RFC3339/ISO8601. Inclusive. Must not be earlier than 5 years from today. |
queries[].dateRange.to | datetime | Yes | RFC3339/ISO8601. Exclusive. Must be on or after from. |
queries[].filters | array | No | Up to 100 filters per query. Filter dimension must be supported by all metrics in the query. |
queries[].filters[].operator | string | Yes | in or notIn |
queries[].filters[].values | array | Yes | Non-empty array of string values |
Response schema
{
"statistics": [
{
"alias": "string",
"dimensions": [
{"name": "string", "granularity": "string"}
],
"metrics": [
{"name": "string"}
],
"rows": [
{
"dimensionName": "dimensionValue",
"metricName": 0
}
]
}
]
}| Field | Type | Description |
|---|---|---|
statistics | array | One entry per query alias |
statistics[].alias | string | Matches the alias from the request |
statistics[].dimensions | array | Echoes requested dimensions |
statistics[].metrics | array | Echoes requested metrics |
statistics[].rows | array | Result rows. Each row contains one key per dimension and one key per metric. |
Metric values are numbers. Dimension values are strings (except timestamp, whose format depends on granularity).
Dimension reference
| Dimension | Description | Available values |
|---|---|---|
timestamp | Event date/time. Always required. Requires granularity. | — |
marketingActivityID | Unique ID of the campaign or automation workflow | — |
marketingActivityType | Category of marketing activity | Campaign, Automation |
marketingActivityName | Name of the campaign or automation workflow | — |
messageID | In an automation workflow, this refers to a specific message block (also known as block statistics ID) | — |
messageTitle | Title of the message | — |
messageChannel | Channel used to send the message | Email, SMS, Push |
messageFormat | Format of the message | SMS, MMS |
senderDomain | Domain of the sender | e.g. example.com |
emailDomain | Recipient email domain | e.g. gmail.com |
phoneNumberCountry | Country of the recipient phone number | e.g. United States of America |
bounceType | Type of bounce | Soft, Hard, Not delivered |
deliveryFailureReason | Reason for delivery failure | Spam content, Wrong media format, Phone number is inaccessible or incorrect, Not delivering to previously unsubscribed contact, Other, Sender domain misfunction, Soft bounce, Email is inaccessible or incorrect, Service misfunction |
clientDeviceType | Recipient's device type | Desktop, Mobile, Tablet, Unknown, Unidentified |
clientAppName | Application used to open the message | — |
clientAppLanguage | Language of the recipient's application | — |
clientOperatingSystem | Recipient's operating system | Windows, MacOSX, Linux, iOS, Android, FireOS, Other |
urlClicked | URL clicked in the message | — |
productID | Product identifier from the e-commerce platform | — |
productTitle | Name/title of the product | — |
productSku | Product SKU | — |
productVariantID | Product variant identifier | — |
productVariantTitle | Name/title of the product variant | — |
subscriptionMethod | Method used to subscribe | Signup form, Text to join, Browser notification, Preference page, Checkout, Imported as subscribed, Was manually subscribed, Subscribed externally, Unknown |
unsubscribeReason | Reason for unsubscribing | No longer wants to receive messages, Hard bounced, Marked as spam, Was manually unsubscribed, Imported as unsubscribed, Unsubscribed externally, Contact deleted, Was automatically unsubscribed |
Dimension/Metric compatibility
For a dimension to be valid in a query, all metrics in that query must support it. The timestamp dimension is always supported. Duplicate dimensions within a query are not allowed.
| Metric | Supported dimensions |
|---|---|
sent | marketingActivityID, marketingActivityType, marketingActivityName, messageID, messageTitle, messageChannel, messageFormat, senderDomain, emailDomain, phoneNumberCountry |
sentCost | marketingActivityID, marketingActivityType, marketingActivityName, messageID, messageTitle, messageChannel, messageFormat, senderDomain, emailDomain, phoneNumberCountry |
openedUnique, opened | All of sent dimensions + clientDeviceType, clientAppName, clientAppLanguage, clientOperatingSystem |
clickedUnique, clicked | All of opened dimensions + urlClicked |
attributedOrdersUnique, attributedOrders, attributedRevenue | Same as clicked dimensions |
failed | All of sent dimensions + bounceType, deliveryFailureReason |
markedAsSpamUnique | Same as sent dimensions |
unsubscribedUnique | Same as sent dimensions |
subscribedEmail, subscribedSms, subscribedPush | subscriptionMethod |
unsubscribedEmail, unsubscribedSms, unsubscribedPush | unsubscribeReason |
totalOrders, totalRevenue | timestamp only |
totalOrderedProductUnits | productID, productTitle, productSku, productVariantID, productVariantTitle |
attributedOrderedProductUnits | productID, productTitle, productSku, productVariantID, productVariantTitle, marketingActivityID, marketingActivityName, marketingActivityType, messageID, messageTitle, messageChannel, phoneNumberCountry |
Filters
Filters restrict results based on dimension values. A filter dimension must be supported by all metrics in the query (same rules as dimensions).
| Property | Required | Description |
|---|---|---|
name | Yes | Dimension name to filter on |
operator | Yes | in — include matching values; notIn — exclude matching values |
values | Yes | Non-empty array of string values to match |
- Maximum 100 filters per query.
- Multiple filters use AND logic — all conditions must match.
Date range and timezone
Both from and to are required in RFC3339/ISO8601 format. The from date must be before or equal to to. The from date is inclusive and to is exclusive.
- With timezone offset:
2026-02-01T00:00:00+02:00 - Explicit UTC:
2026-02-01T00:00:00Z
The timezone offset determines how data is bucketed into time periods. If using timezone offsets, both from and to should use the same offset — mixing offsets may produce unexpected results. Brand timezone can be retrieved from Brands API.
2026-02-01T00:00:00+02:00 represents midnight in UTC+2, which is 10:00 PM the previous day in UTC. These mark different points in time and return different data.
Error responses
All non-validation errors return {"error": "<message>"}.
Retryable errors
Transient failures are expected. Your integration must retry requests that fail with these status codes.
| Status | Meaning | Retry |
|---|---|---|
| 429 | Too Many Requests — rate limit exceeded | Back off, then retry |
| 500 | Internal Server Error | Retry with exponential backoff |
| 503 | Service Unavailable | Retry with exponential backoff |
| 524 | Timeout — request timed out | Retry with exponential backoff |
Socket timeouts and TCP disconnects should also be retried.
Use exponential backoff with large intervals (e.g. 30 s, 120 s, 480 s) between retries.
Non-retryable errors
These indicate a problem with your request. Fix the request before sending again.
| Status | Meaning |
|---|---|
| 403 | Forbidden — invalid brand or insufficient permissions |
| 415 | Unsupported Media Type — missing or wrong Content-Type |
Request validation errors (400)
Returned when the request body fails validation. The fields object maps each invalid field path to an error message.
{
"error": "2 error(s) found. Check 'fields' array for details.",
"fields": {
"queries[0].metrics": "must provide at least one metric",
"queries[0].dimensions": "timestamp dimension is required"
}
}Non-validation 400 errors (e.g. "invalid request body") return {"error": "<message>"} without the fields object.