Skip to main content

Count Promotions (Messaging)

messagingmcp-count_promos

Count distinct promotions matching the filters, deduped by promo_id.

A "distinct promotion" is a campaign: captures of the same offer are linked to one promo_id across time (see explain_promo_data('identity')), and each is counted once regardless of how many captures back it. observed_count is the subset with at least one directly-observed (non-inferred) row. With group_by or interval, returns a buckets array of \{group, count, observed_count}. With interval, a promotion is counted in EVERY bucket it was active in, so bucket counts can sum to more than the unbucketed count. With group_by='promo_type', promos with a null type (rare) fall in no bucket, so buckets sum to slightly less than the total. group_by returns AT MOST 50 buckets — the groups with the most rows behind them — and the response does not say what it dropped: that is every group for 'promo_type', but a fraction of them for 'retailer' (hundreds even inside one region) and 'shoot' (many thousands). To ask about a particular retailer, filter to it rather than expecting it among the buckets. filter_echo reflects what was actually applied.

Parameters​

NameTypeRequiredConstraintsDescription
retailersstring[]no—Retailer slugs to filter by, e.g. ['zara', 'h-m']. Omit for all; matches any listed retailer. Slugs match exactly and are case-sensitive, so take them from list_retailers: match the request against retailer_name, then pass that row's retailer slug and one of the regions it reports.
regionsstring[]no—Regions to filter by, e.g. ['UK', 'US']. Omit for all; matches any listed region. Exact match, so use the values list_retailers reports for the retailer.
start_datestringno—Start of date range (inclusive, YYYY-MM-DD). Filters to promos active at any point in [start_date, end_date].
end_datestringno—End of date range (inclusive, YYYY-MM-DD).
promo_typesstring[]no—Promo types to include: 'percentage_discount', 'flat_discount', 'multi-buy'. Omit for all.
min_depthintegerno—Minimum promo_depth. Depths are unitless — a percentage or a currency amount depending on the promo type — so constrain the type to avoid mixing units.
max_depthintegerno—Maximum promo_depth. Same unit caveat as min_depth.
has_codebooleanno—True = only promos with a code, False = only without; omit for no filter.
include_inferredbooleannodefault: trueInclude inferred promotions (default True). False = observed only.
group_bystringno—Optional categorical bucketing. Mutually exclusive with interval. 'shoot' groups by source image to surface co-promoted bundles. Returns at most 50 buckets — the groups with the most rows behind them — and does not report what it omitted; 'retailer' and 'shoot' have far more groups than that, so treat the result as a top-50, not a complete set.
intervalstringno—Optional temporal bucketing on promotion_date. Exclusive with group_by. Rows are limited to [start_date, end_date], but bucket keys are calendar-aligned (week/month starts), so an edge bucket may be keyed before start_date and cover only the in-window part of its period.

Returns​

  • On success: {"result": {"count", "observed_count", "filter_echo"}} or
  • {"result": {"buckets": [...], "filter_echo"}}.
  • On failure: {"error": {code, message}}
  • code is invalid_input, upstream_unavailable, or
  • not_entitled (authorization refused).

Try it​

Loading interactive widget…

Code examples​

curl -s https://mcp.edited.com/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2025-06-18" \
-H "x-api-key: $MCP_API_KEY" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "messagingmcp-count_promos",
"arguments": {
"start_date": "example",
"end_date": "example",
"min_depth": 1,
"max_depth": 1,
"has_code": false,
"include_inferred": true,
"group_by": "example",
"interval": "example"
}
}
}'

Input schema​

{
"type": "object",
"properties": {
"retailers": {
"description": "Retailer slugs to filter by, e.g. ['zara', 'h-m']. Omit for all; matches any listed retailer. Slugs match exactly and are case-sensitive, so take them from `list_retailers`: match the request against `retailer_name`, then pass that row's `retailer` slug and one of the regions it reports.",
"items": {
"type": "string"
},
"type": "array"
},
"regions": {
"description": "Regions to filter by, e.g. ['UK', 'US']. Omit for all; matches any listed region. Exact match, so use the values `list_retailers` reports for the retailer.",
"items": {
"type": "string"
},
"type": "array"
},
"start_date": {
"description": "Start of date range (inclusive, YYYY-MM-DD). Filters to promos active at any point in [start_date, end_date].",
"format": "date",
"type": "string"
},
"end_date": {
"description": "End of date range (inclusive, YYYY-MM-DD).",
"format": "date",
"type": "string"
},
"promo_types": {
"description": "Promo types to include: 'percentage_discount', 'flat_discount', 'multi-buy'. Omit for all.",
"items": {
"type": "string"
},
"type": "array"
},
"min_depth": {
"description": "Minimum promo_depth. Depths are unitless — a percentage or a currency amount depending on the promo type — so constrain the type to avoid mixing units.",
"type": "integer"
},
"max_depth": {
"description": "Maximum promo_depth. Same unit caveat as min_depth.",
"type": "integer"
},
"has_code": {
"description": "True = only promos with a code, False = only without; omit for no filter.",
"type": "boolean"
},
"include_inferred": {
"default": true,
"description": "Include inferred promotions (default True). False = observed only.",
"type": "boolean"
},
"group_by": {
"description": "Optional categorical bucketing. Mutually exclusive with `interval`. 'shoot' groups by source image to surface co-promoted bundles. Returns at most 50 buckets — the groups with the most rows behind them — and does not report what it omitted; 'retailer' and 'shoot' have far more groups than that, so treat the result as a top-50, not a complete set.",
"enum": [
"retailer",
"promo_type",
"region",
"shoot"
],
"type": "string"
},
"interval": {
"description": "Optional temporal bucketing on promotion_date. Exclusive with group_by. Rows are limited to [start_date, end_date], but bucket keys are calendar-aligned (week/month starts), so an edge bucket may be keyed before start_date and cover only the in-window part of its period.",
"enum": [
"day",
"week",
"month"
],
"type": "string"
}
}
}

Output schema​

Describes both branches of the envelope — result on success, error on a refusal. Validate against this rather than pattern-matching the prose above; see Concepts → Response shape.

{
"type": "object",
"properties": {
"result": {
"type": "object",
"properties": {
"count": {
"description": "Distinct promotions, counted exactly to 40,000 and estimated above that (within roughly half a percent).",
"type": "integer"
},
"observed_count": {
"description": "Distinct promotions, counted exactly to 40,000 and estimated above that (within roughly half a percent). The subset with at least one directly-observed (non-inferred) row.",
"type": "integer"
},
"filter_echo": {
"properties": {
"retailers": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null
},
"regions": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null
},
"start_date": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"end_date": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"include_inferred": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null
},
"min_depth": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null
},
"max_depth": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null
},
"has_code": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null
},
"promo_types": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null
},
"group_by": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"interval": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
}
},
"type": "object"
},
"buckets": {
"description": "Under interval a promotion is counted in every bucket it was active in, so the buckets can sum to more than the unbucketed count. Under group_by this is AT MOST 50 buckets — the groups with the most rows behind them — with no report of what was dropped; 50 buckets is a top-50, not a complete set.",
"items": {
"description": "One bucket of a grouped or intervalled count.",
"properties": {
"group": {
"description": "The term value under group_by, or the bucket's start date (YYYY-MM-DD) under interval.",
"type": "string"
},
"count": {
"description": "Distinct promotions, counted exactly to 40,000 and estimated above that (within roughly half a percent).",
"type": "integer"
},
"observed_count": {
"description": "Distinct promotions, counted exactly to 40,000 and estimated above that (within roughly half a percent). The subset with a directly-observed row.",
"type": "integer"
}
},
"required": [
"group",
"count",
"observed_count"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"filter_echo"
],
"allOf": [
{
"anyOf": [
{
"required": [
"count",
"filter_echo",
"observed_count"
]
},
{
"required": [
"buckets",
"filter_echo"
]
}
]
}
]
},
"error": {
"type": "object",
"description": "Present instead of `result` when the call was rejected. Branch on `code`; never string-match `message`.",
"properties": {
"code": {
"type": "string",
"enum": [
"invalid_input",
"not_entitled",
"upstream_unavailable",
"internal"
]
},
"message": {
"type": "string"
},
"field": {
"type": "string",
"description": "Offending parameter, when the failure is attributable to one."
},
"details": {
"type": "object",
"additionalProperties": true
}
},
"required": [
"code",
"message"
]
}
},
"x-fastmcp-wrap-result": true,
"oneOf": [
{
"required": [
"result"
]
},
{
"required": [
"error"
]
}
]
}