Statistics

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 grouped

Results 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

MetricDescription
sentTotal messages sent
failedMessages that failed to deliver (bounces, invalid addresses, etc.)
sentCostCost incurred for sending messages. SMS only.
markedAsSpamUniqueUnique messages marked as spam by recipients
unsubscribedUniqueUnique messages that resulted in an unsubscribe

Engagement

MetricDescription
openedUniqueUnique messages opened at least once
openedTotal opens, including repeat opens of the same message
clickedUniqueUnique messages clicked at least once
clickedTotal clicks, including repeat clicks on the same message

Revenue attribution

MetricDescription
attributedOrdersUniqueUnique messages with at least one attributed order
attributedOrdersTotal placed orders attributed to messages
attributedRevenueTotal revenue from orders attributed to messages

Sales

MetricDescription
totalOrdersTotal number of placed orders. Counts every placed order in the date range, regardless of attribution.
totalRevenueTotal revenue from all placed orders. Sums order value for every placed order in the date range, regardless of attribution.
totalOrderedProductUnitsTotal 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.
attributedOrderedProductUnitsTotal 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: totalOrders and totalRevenue count all placed orders in your store for the given date range — regardless of whether a message triggered them. attributedOrders and attributedRevenue count only orders attributed to a specific marketing message. Use sales metrics to understand overall store performance; use attributed metrics to measure marketing impact.

totalOrderedProductUnits and attributedOrderedProductUnits are 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

MetricDescription
subscribedEmailContacts who joined the email list. Includes repeated subscriptions. Data from 2024-08-01 only.
subscribedSmsContacts who joined the SMS list. Includes repeated subscriptions. Data from 2024-08-01 only.
subscribedPushContacts who joined the push list. Includes repeated subscriptions. Data from 2024-08-01 only.
unsubscribedEmailContacts who left the email list. Includes repeated unsubscribes. Data from 2024-08-01 only.
unsubscribedSmsContacts who left the SMS list. Includes repeated unsubscribes. Data from 2024-08-01 only.
unsubscribedPushContacts 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 (subscriptionMethod or unsubscribeReason), 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.

CategoryDimensionsUse case
Timetimestamp (with granularity: hour, day, week, month)Time-series analysis
Marketing activitymarketingActivityID, marketingActivityType, marketingActivityNamePerformance by campaign or automation workflow
MessagemessageID, messageTitle, messageChannel, messageFormatPer-message breakdown
DeliverysenderDomain, emailDomain, phoneNumberCountryDeliverability analysis
FailurebounceType, deliveryFailureReasonBounce and failure analysis (failed metric only)
ClientclientDeviceType, clientAppName, clientAppLanguage, clientOperatingSystemDevice and app analytics (engagement and revenue metrics only)
ClickurlClickedClick-through analysis (click and revenue metrics only)
ProductproductID, productTitle, productSku, productVariantID, productVariantTitleProduct and variant breakdown (product unit metrics only)
SubscriptionsubscriptionMethod, unsubscribeReasonAudience 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 groupMetricsAvailable from
Audience growthsubscribedEmail, subscribedSms, subscribedPush, unsubscribedEmail, unsubscribedSms, unsubscribedPush2024-08-01
Sales (product units)totalOrderedProductUnits, attributedOrderedProductUnits2025-11-01
All other metricssent, 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 subscribedEmail before 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}
      ]
    }
  ]
}

hour granularity 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 windowLimit
Per minute10 requests
Per day55 requests

When exceeded, the API returns HTTP 429 Too Many Requests.

Request limits

ConstraintLimit
Queries per request4
Non-timestamp dimensions per query2
Filters per query100
Date span per query1 calendar year
Lookback period5 years from today
Cross-query date rangeAll queries within same 1 calendar year

Date range limits by granularity

GranularityMax date range
hour7 days
day60 days
week52 weeks
month12 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"]
        }
      ]
    }
  ]
}
FieldTypeRequiredDescription
queriesarrayYes1–4 queries per request
queries[].aliasstringYesUnique identifier for the query. Pattern: ^[a-zA-Z0-9 |_.-]+$
queries[].metricsarrayYesAt least 1 metric. No duplicates within a query.
queries[].dimensionsarrayYesMust include timestamp. Up to 2 additional non-timestamp dimensions.
queries[].dimensions[].granularitystringRequired for timestampOne of: hour, day, week, month
queries[].dateRange.fromdatetimeYesRFC3339/ISO8601. Inclusive. Must not be earlier than 5 years from today.
queries[].dateRange.todatetimeYesRFC3339/ISO8601. Exclusive. Must be on or after from.
queries[].filtersarrayNoUp to 100 filters per query. Filter dimension must be supported by all metrics in the query.
queries[].filters[].operatorstringYesin or notIn
queries[].filters[].valuesarrayYesNon-empty array of string values

Response schema

{
  "statistics": [
    {
      "alias": "string",
      "dimensions": [
        {"name": "string", "granularity": "string"}
      ],
      "metrics": [
        {"name": "string"}
      ],
      "rows": [
        {
          "dimensionName": "dimensionValue",
          "metricName": 0
        }
      ]
    }
  ]
}
FieldTypeDescription
statisticsarrayOne entry per query alias
statistics[].aliasstringMatches the alias from the request
statistics[].dimensionsarrayEchoes requested dimensions
statistics[].metricsarrayEchoes requested metrics
statistics[].rowsarrayResult 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

DimensionDescriptionAvailable values
timestampEvent date/time. Always required. Requires granularity.
marketingActivityIDUnique ID of the campaign or automation workflow
marketingActivityTypeCategory of marketing activityCampaign, Automation
marketingActivityNameName of the campaign or automation workflow
messageIDIn an automation workflow, this refers to a specific message block (also known as block statistics ID)
messageTitleTitle of the message
messageChannelChannel used to send the messageEmail, SMS, Push
messageFormatFormat of the messageSMS, MMS
senderDomainDomain of the sendere.g. example.com
emailDomainRecipient email domaine.g. gmail.com
phoneNumberCountryCountry of the recipient phone numbere.g. United States of America
bounceTypeType of bounceSoft, Hard, Not delivered
deliveryFailureReasonReason for delivery failureSpam 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
clientDeviceTypeRecipient's device typeDesktop, Mobile, Tablet, Unknown, Unidentified
clientAppNameApplication used to open the message
clientAppLanguageLanguage of the recipient's application
clientOperatingSystemRecipient's operating systemWindows, MacOSX, Linux, iOS, Android, FireOS, Other
urlClickedURL clicked in the message
productIDProduct identifier from the e-commerce platform
productTitleName/title of the product
productSkuProduct SKU
productVariantIDProduct variant identifier
productVariantTitleName/title of the product variant
subscriptionMethodMethod used to subscribeSignup form, Text to join, Browser notification, Preference page, Checkout, Imported as subscribed, Was manually subscribed, Subscribed externally, Unknown
unsubscribeReasonReason for unsubscribingNo 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.

MetricSupported dimensions
sentmarketingActivityID, marketingActivityType, marketingActivityName, messageID, messageTitle, messageChannel, messageFormat, senderDomain, emailDomain, phoneNumberCountry
sentCostmarketingActivityID, marketingActivityType, marketingActivityName, messageID, messageTitle, messageChannel, messageFormat, senderDomain, emailDomain, phoneNumberCountry
openedUnique, openedAll of sent dimensions + clientDeviceType, clientAppName, clientAppLanguage, clientOperatingSystem
clickedUnique, clickedAll of opened dimensions + urlClicked
attributedOrdersUnique, attributedOrders, attributedRevenueSame as clicked dimensions
failedAll of sent dimensions + bounceType, deliveryFailureReason
markedAsSpamUniqueSame as sent dimensions
unsubscribedUniqueSame as sent dimensions
subscribedEmail, subscribedSms, subscribedPushsubscriptionMethod
unsubscribedEmail, unsubscribedSms, unsubscribedPushunsubscribeReason
totalOrders, totalRevenuetimestamp only
totalOrderedProductUnitsproductID, productTitle, productSku, productVariantID, productVariantTitle
attributedOrderedProductUnitsproductID, 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).

PropertyRequiredDescription
nameYesDimension name to filter on
operatorYesin — include matching values; notIn — exclude matching values
valuesYesNon-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.

StatusMeaningRetry
429Too Many Requests — rate limit exceededBack off, then retry
500Internal Server ErrorRetry with exponential backoff
503Service UnavailableRetry with exponential backoff
524Timeout — request timed outRetry 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.

StatusMeaning
403Forbidden — invalid brand or insufficient permissions
415Unsupported 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.