openapi: 3.0.0 info: title: Offer Decisioning Public API - v5 version: 5.0.0 description: | The Offerings API lets you create, update, and list offerings for Offer Decisioning, and list the personalization templates available for offering content. For lifecycle, rate limits, idempotency, and error details, see the [Offerings Overview](/api/offerings/offerings-overview). servers: - url: https://api-{dc}.moengage.com variables: dc: default: "01" description: > MoEngage data center identifier. Replace with your assigned DC number. Full example: https://api-01.moengage.com paths: /v5/offers: get: operationId: listPublicOfferings summary: List Offerings description: | Fetch all offerings in the workspace. Returns a summary of each offering — ID, name, status, tags, and creation metadata. x-mint: content: | #### Rate Limit The rate limit is 150 requests per minute, 500 requests per hour, and 1000 requests per day per consumer. x-moe-mcp: expose: external: true internal: true tool_description: | List all offer campaigns in the workspace. Use this as the first step to discover existing offerings and their IDs before calling update_offering or inspecting details. Returns a paginated summary — ID, name, status, tags, created_at, created_by. Use the fields parameter to request only the fields you need (reduces token usage). Typical agent flow: 1. Call list_offerings (optionally filter by status_in=active or name_contains) 2. Pick the offering ID (e.g. 6a4f69ee490f66322f968b87) from the results 3. Call update_offering with that ID to modify the offering Pagination: pass next_cursor from the pagination object on the next call. Keep paginating until has_more is false. param_overrides: status_in: description: | Filter by lifecycle status. Use 'active' to find offerings currently being served to users. Use 'scheduled' for upcoming ones. Omit to return all statuses including drafts. Valid values: active, scheduled, expired, archived, draft. Example: status_in=active,scheduled cursor: description: | An opaque pagination token used to retrieve the next page of results. * **Initial Request:** Omit this parameter entirely on the first call to fetch the beginning of the list. * **Subsequent Pages:** Pass the exact string received from the `next_cursor` field of the immediate previous response. * **Handling:** Pass this token completely verbatim. Do not decode, alter, or modify the string in any way. fields: description: | Comma-separated list of fields to return. Omit for all six fields. Use to minimise token usage. Valid values: `id`, `name`, `status`, `tags`, `created_at`, `created_by`. Example: fields=id,name,status limit: description: | The number of offerings to return per paginated response. tags: - Public Offerings security: - basicAuth: [] parameters: - name: id in: query required: false schema: type: string description: > Look up a single offering by its exact prefixed ID (e.g. offer_6a4f69ee490f66322f968b87). Use this when you already know the offering ID and want to confirm it exists before updating it. - name: name_contains in: query required: false schema: type: string maxLength: 100 description: > Find offerings whose name contains specific text (case-insensitive). For example, pass "summer" to find all summer-related offerings. - name: status_in in: query required: false schema: type: string description: > Filter by lifecycle status. Valid values: active, scheduled, expired, archived, draft. Example: status_in=active,scheduled - name: tags in: query required: false schema: type: array items: type: string style: form explode: true description: > Filter to offerings that have ALL of the specified tags. Repeat the parameter for multiple tags: tags=summer-sale&tags=vip-users. - name: created_date_gte in: query required: false schema: type: string format: date description: > List offerings created on or after this date. Format: YYYY-MM-DD. - name: created_date_lte in: query required: false schema: type: string format: date description: > List offerings created on or before this date. Format: YYYY-MM-DD. - name: created_by in: query required: false schema: type: array items: type: string style: form explode: true description: > Filter offerings created by specific team members. Pass one or more email addresses. Example: created_by=john.doe@example.com,marketing-team@example.com - name: fields in: query required: false schema: type: string description: > Request only the fields you need. Example: fields=id,name,status returns only those three fields per item. - name: cursor in: query required: false schema: type: string description: > Pagination cursor returned as `next_cursor` in the previous response. Omit on the first request. When `next_cursor` is `null` in the response, you have reached the last page. - name: limit in: query required: false schema: type: integer default: 20 minimum: 1 maximum: 100 description: Items per page. Default 20, max 100. - name: X-MOE-Request-Id in: header required: false schema: type: string format: uuid description: > Client-supplied trace ID (UUID v4). Echoed back in the `X-MOE-Request-Id` response header. responses: '200': description: Paginated list of offerings. headers: X-MOE-Request-Id: schema: type: string description: Trace ID echoed from the gateway. X-RateLimit-Limit: schema: type: integer example: 150 description: Maximum requests allowed in the current window. X-RateLimit-Remaining: schema: type: integer description: Requests remaining in the current window before rate limiting applies. X-RateLimit-Reset: schema: type: integer description: UTC epoch second when the current rate-limit window resets. content: application/json: schema: type: object required: [response_id, type, data, pagination] properties: response_id: type: string description: > Format: `resp_` header value (auto-generated UUID if the `X-MOE-Request-Id` header was not part of the request body). type: type: string enum: [offer] data: type: array description: List of offering summaries matching the applied filters. items: $ref: '#/components/schemas/OfferingListItem' pagination: $ref: '#/components/schemas/CursorPagination' example: response_id: resp_d31d6d61-6f3b-40dc-a459-548f385a60cb type: offer data: - id: offer_6a4f69ee490f66322f968b87 name: Summer_Sale_Promo_2026 status: active tags: [tag_] created_at: "2026-06-23T12:29:41.225270281Z" created_by: marketing-team@example.com - id: offer_6a4f69ee490f66322f968b87 name: Homepage_Banner_AB_Test status: scheduled created_at: "2026-06-23T12:29:41.225270281Z" created_by: growth-team@example.com pagination: next_cursor: b2ZmZXJzOjZhM2E3YzM0NTE4NjdkNGRjZmIxNzQ5Mw== limit: 20 has_more: true total_count: 42 '400': description: | Invalid query parameter. Possible error codes in `error.details[].code`: - `OUT_OF_RANGE` — `limit` > 100, or `name_contains` > 100 chars. - `INVALID_CURSOR` — `cursor` is present but malformed (not valid base64, or corrupted segment/id). - `INVALID_ENUM_VALUE` — unrecognised value in `status_in`. - `INVALID_FIELDS_PARAM` — unrecognised field name in `fields`. - `INVALID_TYPE` — `created_date_gte` or `created_date_lte` not in YYYY-MM-DD format. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: VALIDATION_FAILED message: One or more query parameters are invalid. doc_url: "https://www.moengage.com/docs/api/offerings/offerings-overview" details: - code: INVALID_ENUM_VALUE target: status_in message: "'invalid_status' is not a valid status. Allowed values: active, scheduled, expired, archived, draft." doc_url: "https://www.moengage.com/docs/api/offerings/offerings-overview" response_id: resp_ '401': $ref: '#/components/responses/GatewayAuthError' '403': $ref: '#/components/responses/GatewayAuthError' '429': description: | The rate limit for this endpoint has been exceeded. Retry after the window indicated in the `Retry-After` response header (in seconds). headers: Retry-After: schema: type: integer description: Seconds to wait before retrying. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '503': $ref: '#/components/responses/ServiceUnavailable' post: operationId: createPublicOffering summary: Create Offering description: | Create a new Offering in the workspace. x-mint: content: | #### Rate Limit The rate limit is 100 requests per minute, 300 requests per hour, and 600 requests per day per consumer. x-moe-mcp: expose: external: true internal: true tool_description: | Create a new offering in the workspace. Key constraints: - start_datetime MUST be in the future and use UTC "Z" suffix (e.g. "2026-07-01T00:00:00Z"). A past value returns 400 INVALID_SCHEDULING. Offset strings like "+05:30" are not accepted. - tags must be pre-existing tag IDs — unknown IDs return 400 INVALID_TAG_ID. - variation_meta CAN be updated via PATCH with status-based rules. See update_offering. - name must be alphanumeric + underscores only (no spaces or hyphens). 5–100 chars. Always supply a unique Idempotency-Key (UUID v4) header to prevent duplicate offerings on retries. The same key within 24 hours returns the original 201 response. For a simple single-variant offering with no A/B test: - Set variation_meta.type = SMV, variantsPerLocale = [Variant_A], split = {Variant_A: 100} - Put content in offer_content.locales["0"].variations.Variant_A - Locale key must be "0" for single-locale offerings (not "default" or "en") param_overrides: variation_meta: description: | A/B test configuration. For a simple offering (no A/B test), use: type: SMV, variantsPerLocale: [Variant_A], split: {Variant_A: 100} For A/B test (SMV): split values must sum to exactly 100 inclusive of control group allocation(INVALID_SPLIT if not). For AI optimization (DMV): provide equal initial splits; the server learns over time. Status-based PATCH rules: SCHEDULED=free; ACTIVE+SMV=split% only; ACTIVE+DMV=control_group.percentage only. offer_content: description: | Content payload. Use the locales["0"] nested format for single-locale offerings: {"locales": {"0": {"variations": {"Variant_A": {"content_1": {"type": "json", "value": "..."}}}}}} The locale key "0" is required for single-locale — do not use "default" or "en". Variation keys in offer_content must exactly match variantsPerLocale and split keys (SPLIT_KEY_MISMATCH if not). scheduling: description: | Schedule window. Both start_datetime and expiry_datetime are required and **MUST** use UTC "Z" suffix (e.g. "2026-07-01T00:00:00Z"). Offset strings like "+05:30" return INVALID_TYPE. start_datetime must be in the future. segment_info: description: | Audience targeting. To target all users with no restrictions, send: {"filters": {}} For attribute-based targeting, use filter_type: "user_attributes" with data_type required. tags: - Public Offerings security: - basicAuth: [] parameters: - name: Idempotency-Key in: header required: true schema: type: string format: uuid description: > UUID v4 idempotency token. Submitting the same key within 24 hours returns the original `201 response` without re-creating the Offering. A different body hash on the same key returns `409 IDEMPOTENCY_CONFLICT`. A concurrent in-progress request with the same key returns `409 DUPLICATE_IDEMPOTENCY_KEY`. - name: X-MOE-Request-Id in: header required: false schema: type: string format: uuid description: > Client-supplied trace ID (UUID v4). Echoed back in the X-MOE-Request-Id response header. Use this to correlate client requests with server-side logs. requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ClientOfferCreateRequest' examples: jsonSingleVariantNoCapping: summary: "JSON: 1 variant, no control group, no capping" value: name: Summer_Sale description: Summer sale discount offers priority: 50 delivery: scheduled created_by: john.doe@example.com scheduling: start_datetime: "2026-07-16T00:00:00Z" expiry_datetime: "2026-07-16T23:59:00Z" timezone: Asia/Kolkata segment_info: filters: included_filters: filter_operator: and filters: - filter_type: custom_segments name: level_0_for_automation id: 6a572827fad010a3318a1bc1 offer_content: content_1: type: json value: '{"discount":"10%"}' variation_meta: type: SMV variantsPerLocale: [Variant_A] control_group: is_enabled: false percentage: 0 smv_distribution: split: Variant_A: 100 capping_rules: {} is_global_control_enabled: false imp_track_hours: 36 jsonMultiVariantWithCapping: summary: "JSON: 2 variants, control group enabled, overall + user-level capping (scheduled delivery)" value: name: Summer_Sale description: Summer sale discount offers priority: 50 delivery: scheduled created_by: john.doe@example.com scheduling: start_datetime: "2026-07-16T00:00:00Z" expiry_datetime: "2026-07-16T23:59:00Z" timezone: Asia/Kolkata segment_info: filters: included_filters: filter_operator: and filters: - filter_type: custom_segments name: level_0_for_automation id: 6a572827fad010a3318a1bc1 offer_content: locales: "0": variations: Variant_A: content_1: type: json value: '{"discount":"10%"}' Variant_B: content_1: type: json value: '{"discount":"20%"}' variation_meta: type: SMV variantsPerLocale: [Variant_A, Variant_B] control_group: is_enabled: true percentage: 10 smv_distribution: split: Variant_A: 50 Variant_B: 50 capping_rules: overall: enabled: true limit_value: 1 limit_schedule: DAILY user_level: enabled: true limit_value: 15 limit_schedule: MONTHLY is_global_control_enabled: true imp_track_hours: 36 templateDmvWithCapping: summary: "Template: 2 variants (DMV), control group enabled, user_attributes segment, capping (scheduled delivery)" value: name: Summer_Sale description: Summer sale discount offers priority: 50 delivery: scheduled created_by: john.doe@example.com scheduling: start_datetime: "2026-07-16T11:00:00Z" expiry_datetime: "2026-07-16T23:59:00Z" timezone: Asia/Kolkata segment_info: filters: included_filters: filter_operator: and filters: - filter_type: user_attributes data_type: string name: Name value: [David] case_sensitive: false operator: in negate: false - filter_type: user_attributes data_type: string name: last_purchase_category value: [Electronics] case_sensitive: false operator: in negate: false excluded_filters: filter_operator: and filters: - filter_type: user_attributes data_type: string name: city value: [Detroit] case_sensitive: false operator: in negate: false offer_content: locales: "0": variations: Variant_A: content_1: type: template value: id: card_notification_01 title: Welcome to Your Dashboard description: This is a generic description field supporting standard text notification content. ctaUrl: https://example.com/destination-pathv2 darkModeImageUrl: https://example.com/assets/dark-mode-featurev2.png lightModeImageUrl: https://example.com/assets/light-mode-featurev2.png darkModeBackgroundImageUrl: https://example.com/assets/dark-mode-bgv2.png ariaLabel: Alternative text description for screen readers meta: templateId: feature_hero_slide Variant_B: content_1: type: template value: id: card_notification_01 title: Welcome to Your Dashboard description: This is a generic description field supporting standard text notification content. ctaUrl: https://example.com/destination-path darkModeImageUrl: https://example.com/assets/dark-mode-feature.png lightModeImageUrl: https://example.com/assets/light-mode-feature.png darkModeBackgroundImageUrl: https://example.com/assets/dark-mode-bg.png ariaLabel: Alternative text description for screen readers meta: templateId: feature_hero_slide variation_meta: type: DMV variantsPerLocale: [Variant_A, Variant_B] control_group: is_enabled: true percentage: 5 capping_rules: overall: enabled: true limit_value: 1 limit_schedule: DAILY user_level: enabled: true limit_value: 1 limit_schedule: WEEKLY is_global_control_enabled: true imp_track_hours: 36 responses: '201': description: Offering created successfully. headers: X-MOE-Request-Id: schema: type: string description: Echoed trace ID (from request header or server-generated). X-RateLimit-Limit: schema: type: integer example: 100 description: Maximum requests allowed in the current window. X-RateLimit-Remaining: schema: type: integer description: Requests remaining in the current window before rate limiting applies. X-RateLimit-Reset: schema: type: integer description: UTC epoch second when the current rate-limit window resets. content: application/json: schema: $ref: '#/components/schemas/PublicOfferCreateResponse' '400': description: | Validation errors. All errors are collected before returning, so a single 400 response may contain multiple `details` entries. Possible error codes: - `REQUIRED_FIELD_MISSING` — a required field is absent (e.g. missing `variation_meta`, `name`, `scheduling.start_datetime`). The `target` field identifies the missing field. - `INVALID_TYPE` — a field has the wrong type or format (e.g. `content_1.value` is not a JSON object when `type` is `template`; or `scheduling.start_datetime` is not a valid ISO 8601 datetime with UTC "Z" suffix). - `OUT_OF_RANGE` — numeric field outside its allowed range (e.g. `priority` outside 1–100, `control_group.percentage` outside 1–99). - `INVALID_ENUM_VALUE` — enum field has an unrecognised value. - `INVALID_OFFER_NAME` — `name` contains characters other than alphanumeric characters and underscores. Spaces and hyphens are not allowed. - `INVALID_SCHEDULING` — `expiry_datetime` is not after `start_datetime`; `start_datetime` is in the past; or `expiry_datetime` is less than 10 minutes in the future. - `INVALID_TIMEZONE` — `scheduling.timezone` is present but not a valid IANA timezone name (e.g. `Asia/Kolkata`, `America/New_York`, `UTC`). - `SPLIT_KEY_MISMATCH` — keys in `smv_distribution.split` do not match `variantsPerLocale`. - `INVALID_SPLIT` — values in `smv_distribution.split` do not sum to exactly 100. Adjust the percentages so the total equals 100. - `VARIANT_COUNT_MISMATCH` — count of variants in `offer_content` does not match `variantsPerLocale`. - `INVALID_TEMPLATE_ID` — `meta.templateId` refers to a template ID not registered in the workspace. - `MISSING_TEMPLATE_FIELD` — a required field (per the template's config) is missing or blank in the `value` map. The `target` field will be `offer_content.locales..variations..content_1.value.`. - `DUPLICATE_OFFER_NAME` — `name` matches an existing non-archived offering in this workspace. Offering names must be unique. Choose a different name. - `INVALID_TAG_ID` — one or more tag IDs in `tags` do not exist in this workspace. Create the tags first via the dashboard before referencing them here. - `REVENUE_FIELDS_INCOMPLETE` — `conversion.primary.revenue_amount` or `conversion.primary.revenue_currency` is missing when `is_revenue_tracking` is true. - `INVALID_CURRENCY_CODE` — `conversion.primary.revenue_currency` is not a valid ISO 4217 code (e.g. use "USD", "EUR", "INR"). - `MISSING_IDEMPOTENCY_KEY` — `Idempotency-Key` header is absent. Add the header with a UUID v4 value. - `INVALID_IDEMPOTENCY_KEY` — `Idempotency-Key` is present but not a valid UUID v4. Use a standard UUID v4 format (e.g. ). content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: VALIDATION_FAILED message: Request validation failed. doc_url: "https://www.moengage.com/docs/api/offerings/offerings-overview" details: - code: REQUIRED_FIELD_MISSING target: variation_meta message: variation_meta is required. doc_url: "https://www.moengage.com/docs/api/offerings/offerings-overview" - code: INVALID_SCHEDULING target: scheduling message: expiry_datetime must be after start_datetime. doc_url: "https://www.moengage.com/docs/api/offerings/offerings-overview" - code: INVALID_TEMPLATE_ID target: offer_content.locales.0.variations.Variant_A.content_1.meta.templateId message: "Template 'app_large' is not registered in this workspace." doc_url: "https://www.moengage.com/docs/api/offerings/offerings-overview" - code: MISSING_TEMPLATE_FIELD target: offer_content.locales.0.variations.Variant_A.content_1.value.cta_url message: "Field 'cta_url' is required by the template but is missing or blank." doc_url: "https://www.moengage.com/docs/api/offerings/offerings-overview" response_id: resp_ '401': description: | Authentication failure. Two sources produce 401 on this endpoint: - **Gateway (APISIX)** — credentials missing or invalid. Returns `Content-Type: text/plain` with body `{"code":"ER001","target":"Authentication Invalid","message":"..."}`. - **Service** — credentials passed the gateway but the injected `X-MOE-Tenant-ID` header is missing (request bypassed gateway). Returns `Content-Type: application/json`. content: text/plain: schema: type: string description: Gateway (APISIX) authentication failure. Body is JSON-formatted but sent as text/plain. application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': $ref: '#/components/responses/GatewayAuthError' '409': description: | Idempotency conflict. Two distinct error codes are possible: **DUPLICATE_IDEMPOTENCY_KEY** — a request with the same Idempotency-Key is already in-flight (concurrent duplicate). Wait for the first request to complete before retrying. **IDEMPOTENCY_CONFLICT** — the Idempotency-Key was already used within the 24-hour window but the request body hash differs from the original request. Use a new Idempotency-Key to submit a different payload. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '429': description: | The rate limit for this endpoint has been exceeded. Retry after the window indicated in the `Retry-After` response header (in seconds). headers: Retry-After: schema: type: integer content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '503': $ref: '#/components/responses/ServiceUnavailable' /v5/offers/{offer_id}: patch: operationId: updatePublicOffering summary: Update Offering description: | Modify an existing offering. Only the fields you include in the request body are changed. x-mint: content: | #### Rate Limit The rate limit is 100 requests per minute, 300 requests per hour, and 600 requests per day per consumer. x-moe-mcp: expose: external: true internal: true tool_description: | Partially update an existing offering. Only the fields included in the request body are changed — all other fields retain their current values. Use list_offerings first to discover the offering ID if you don't already have it. Common update patterns: - Extend expiry: include only scheduling.expiry_datetime with a later date (Z suffix required) - Rename: include only name (alphanumeric + underscores only; returns DUPLICATE_OFFER_NAME if taken) - Update content: include only offer_content - Replace tags: include tags as a full list of tag IDs (full replacement, not additive). Tag IDs from list_offerings are returned with "tag_" prefix (e.g. "tag_") and can be passed directly — both "tag_" and raw "" formats are accepted. Status-gated fields — return 400 IMMUTABLE_FIELD if violated: - scheduling.start_datetime: locked once status becomes 'active' - conversion: locked once status becomes 'active' - variation_meta.type: locked once status is 'active' - Adding/removing variants in variantsPerLocale: locked once 'active' - ACTIVE + SMV: only split percentages editable - ACTIVE + DMV: only control_group.percentage editable - SCHEDULED: all variation_meta fields freely editable Returns 403 EXPIRED_OFFERING or 403 ARCHIVED_OFFERING if offering is in terminal state. Returns 404 if the {offer_id} does not exist — verify with list_offerings first. Always supply a unique Idempotency-Key (UUID v4) header. Same key within 24 hours returns the original 200 response without re-applying the update. param_overrides: offer_id: description: | Prefixed offering ID to update (e.g. offer_6a4f69ee490f66322f968b87). Obtain this from list_offerings — it is the 'id' field in the data array. tags: - Public Offerings security: - basicAuth: [] parameters: - name: offer_id in: path required: true schema: type: string description: The unique ID of the offering to be updated. You can fetch the offering ID using the [List Offerings API](/api/public-offerings/list-offerings) by filtering with name. - name: Idempotency-Key in: header required: true schema: type: string format: uuid description: > UUID v4 idempotency token. Submitting the same key within 24 hours returns the original `200 response` without re-creating the Offering. A different body hash on the same key returns `409 IDEMPOTENCY_CONFLICT`. A concurrent in-progress request with the same key returns `409 DUPLICATE_IDEMPOTENCY_KEY`. - name: X-MOE-Request-Id in: header required: false schema: type: string format: uuid description: > Client-supplied trace ID (UUID v4). Echoed back in the `X-MOE-Request-Id` response header. Use this to correlate client requests with server-side logs. requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ClientOfferPatchRequest' examples: extendExpiry: summary: Extend expiry date only value: updated_by: marketing-team@example.com scheduling: expiry_datetime: "2026-12-31T23:59:59Z" updateNameAndTags: summary: Rename offering and replace tags value: updated_by: marketing-team@example.com name: Summer_Sale_Promo_2026_V2 tags: [summer-sale, vip-users] updateContent: summary: Update offer content (single variant, locale "0") value: updated_by: marketing-team@example.com offer_content: locales: "0": variations: Variant_A: content_1: type: json value: '{"headline":"Flash Sale - 30% Off!","cta_text":"Claim Now","cta_url":"https://example.com/flash"}' responses: '200': description: Offering updated successfully. headers: X-MOE-Request-Id: schema: type: string description: Echoed trace ID. X-RateLimit-Limit: schema: type: integer example: 100 description: Maximum requests allowed in the current window. X-RateLimit-Remaining: schema: type: integer description: Requests remaining in the current window before rate limiting applies. X-RateLimit-Reset: schema: type: integer description: UTC epoch second when the current rate-limit window resets. content: application/json: schema: $ref: '#/components/schemas/PublicOfferPatchResponse' '400': description: | Validation errors. All errors collected before returning. Possible error codes: - `REQUIRED_FIELD_MISSING` — a required field is absent (`updated_by`). The `target` field identifies the missing field. - `INVALID_TYPE` — wrong type or format (e.g. `scheduling.start_datetime` is not a valid ISO 8601 datetime with UTC "Z" suffix). - `OUT_OF_RANGE` — numeric field outside its allowed range (e.g. `priority` outside 1–100, `control_group.percentage` outside 1–99). - `INVALID_ENUM_VALUE` — enum field has an unrecognised value (e.g. unrecognised `variation_meta.type`). - `INVALID_OFFER_NAME` — `name` contains characters other than alphanumeric characters and underscores. - `INVALID_SCHEDULING` — `expiry_datetime` is not after `start_datetime`; `start_datetime` is in the past; or `expiry_datetime` is less than 10 minutes in the future. - `INVALID_TIMEZONE` — `scheduling.timezone` is present but not a valid IANA timezone name. - `IMMUTABLE_FIELD` — attempt to change a status-locked field: • `scheduling.start_datetime` when offering is ACTIVE or EXPIRED. • `conversion` when offering is not in `scheduled` status. • `variation_meta.type` when offering is ACTIVE. • Adding or removing keys in `variation_meta.variantsPerLocale` when offering is ACTIVE — only split percentages (SMV) or `control_group.percentage` (DMV) can change on an ACTIVE offering. - `SPLIT_KEY_MISMATCH` — keys in `smv_distribution.split` do not match `variantsPerLocale`. - `INVALID_SPLIT` — values in `smv_distribution.split` do not sum to exactly 100. - `INVALID_TEMPLATE_ID` — `meta.templateId` refers to a template ID not registered in the workspace. - `MISSING_TEMPLATE_FIELD` — a required field (per the template's config) is missing or blank in the `value` map. The `target` field will be `offer_content.locales..variations..content_1.value.`. - `DUPLICATE_OFFER_NAME` — `name` matches an existing non-archived offering in this workspace. - `INVALID_TAG_ID` — one or more tag IDs in `tags` do not exist in this workspace. - `REVENUE_FIELDS_INCOMPLETE` — `conversion.primary.revenue_amount` or `conversion.primary.revenue_currency` is missing when `is_revenue_tracking` is true. - `INVALID_CURRENCY_CODE` — `conversion.primary.revenue_currency` is not a valid ISO 4217 code. - `MISSING_IDEMPOTENCY_KEY` — `Idempotency-Key` header is absent. - `INVALID_IDEMPOTENCY_KEY` — `Idempotency-Key` is present but not a valid UUID v4. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: VALIDATION_FAILED message: Request validation failed. doc_url: "https://www.moengage.com/docs/api/offerings/offerings-overview" details: - code: IMMUTABLE_FIELD target: variation_meta.type message: variation_meta.type cannot be changed on an active offering. doc_url: "https://www.moengage.com/docs/api/offerings/offerings-overview" - code: INVALID_TEMPLATE_ID target: offer_content.locales.0.variations.Variant_A.content_1.meta.templateId message: "Template 'app_large' is not registered in this workspace." doc_url: "https://www.moengage.com/docs/api/offerings/offerings-overview" - code: MISSING_TEMPLATE_FIELD target: offer_content.locales.0.variations.Variant_A.content_1.value.cta_url message: "Field 'cta_url' is required by the template but is missing or blank." doc_url: "https://www.moengage.com/docs/api/offerings/offerings-overview" response_id: resp_ '401': description: | Authentication failure. Two sources produce 401 on this endpoint: - **Gateway (APISIX)** — credentials missing or invalid. Returns `Content-Type: text/plain` with body `{"code":"ER001","target":"Authentication Invalid","message":"..."}`. - **Service** — credentials passed the gateway but the injected `X-MOE-Tenant-ID` header is missing (request bypassed gateway). Returns `Content-Type: application/json`. content: text/plain: schema: type: string description: Gateway (APISIX) authentication failure. Body is JSON-formatted but sent as text/plain. application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: | Forbidden. Two distinct sources produce 403 on this endpoint: - **Gateway (APISIX)** — insufficient scope for this route. Returns `Content-Type: text/plain` with body `{"code":"ER007","target":"Authentication Invalid","message":"..."}`. - **Service** — offering is in a terminal lifecycle state. Returns `Content-Type: application/json`. Error codes: `EXPIRED_OFFERING` (past expiry_datetime) or `ARCHIVED_OFFERING` (manually archived). content: text/plain: schema: type: string description: Gateway (APISIX) scope failure. Body is JSON-formatted but sent as text/plain. application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': description: Offering not found (OFFER_NOT_FOUND). content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '409': description: | Idempotency conflict. Two distinct error codes are possible: **DUPLICATE_IDEMPOTENCY_KEY** — a request with the same Idempotency-Key is already in-flight (concurrent duplicate). Wait for the first request to complete before retrying. **IDEMPOTENCY_CONFLICT** — the Idempotency-Key was already used within the 24-hour window but the request body hash differs from the original request. Use a new Idempotency-Key to submit a different payload. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: duplicateInFlight: summary: Concurrent duplicate (in-flight) value: error: code: DUPLICATE_IDEMPOTENCY_KEY message: A request with this Idempotency-Key is already being processed. doc_url: "https://www.moengage.com/docs/api/offerings/offerings-overview" target: Idempotency-Key response_id: resp_ bodyMismatch: summary: Same key, different body value: error: code: IDEMPOTENCY_CONFLICT message: The Idempotency-Key was already used with a different request body. Use a new key. doc_url: "https://www.moengage.com/docs/api/offerings/offerings-overview" target: Idempotency-Key response_id: resp_ '429': description: | The rate limit for this endpoint has been exceeded. Retry after the window indicated in the `Retry-After` response header (in seconds). headers: Retry-After: schema: type: integer description: Seconds to wait before retrying. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '503': $ref: '#/components/responses/ServiceUnavailable' /v5/offers/templates: get: operationId: listPublicOfferTemplates summary: List Offer Templates description: | Fetch the personalization templates available in the workspace. x-mint: content: | #### Rate Limit The rate limit is 150 requests per minute, 500 requests per hour, and 1000 requests per day per consumer. x-moe-mcp: expose: external: true internal: true tool_description: | List all personalization templates available in the workspace. Use this before creating a template-type content block in an offering, to discover valid template IDs and their labels. Returns a paginated summary — id, value (same as id), label, template_type. Typical agent flow: 1. Call list_offer_templates to find the template you need (match on label). 2. Use its id as meta.templateId in the offer_content content block. 3. Call create_offering or update_offering with that content block. Pagination: pass next_cursor from the pagination object on the next call. Keep paginating until has_more is false. param_overrides: cursor: description: | An opaque pagination token used to retrieve the next page of results. * **Initial Request:** Omit this parameter entirely on the first call to fetch the beginning of the list. * **Subsequent Pages:** Pass the exact string received from the `next_cursor` field of the immediate previous response. * **Handling:** Pass this token completely verbatim. Do not decode, alter, or modify the string in any way. limit: description: | Number of templates per page. Default 20, maximum 100. tags: - Public Offerings security: - basicAuth: [] parameters: - name: cursor in: query required: false schema: type: string description: | An opaque pagination token used to retrieve the next page of results. * **Initial Request:** Omit this parameter entirely on the first call to fetch the beginning of the list. * **Subsequent Pages:** Pass the exact string received from the `next_cursor` field of the immediate previous response. * **Handling:** Pass this token completely verbatim. Do not decode, alter, or modify the string in any way. - name: limit in: query required: false schema: type: integer default: 20 minimum: 1 maximum: 100 description: Number of templates per page. Default 20, maximum 100. - name: X-MOE-Request-Id in: header required: false schema: type: string format: uuid description: > Client-supplied trace ID (UUID v4). Echoed back in the X-MOE-Request-Id response header and in server logs. If omitted, the server generates a trace ID automatically. responses: '200': description: Paginated list of offer templates. headers: X-MOE-Request-Id: schema: type: string description: Trace ID echoed from the gateway. X-RateLimit-Limit: schema: type: integer example: 150 description: Maximum requests allowed in the current window. X-RateLimit-Remaining: schema: type: integer description: Requests remaining in the current window before rate limiting applies. X-RateLimit-Reset: schema: type: integer description: UTC epoch second when the current rate-limit window resets. content: application/json: schema: type: object required: [response_id, type, data, pagination] properties: response_id: type: string description: > Stable trace identifier for this response. Format: "resp_" + X-MOE-Request-Id. Identical on retries that carry the same X-MOE-Request-Id header, enabling client-side deduplication and cross-log correlation. type: type: string enum: [template] data: type: array description: List of templates matching the applied pagination. items: $ref: '#/components/schemas/OfferTemplateListItem' pagination: $ref: '#/components/schemas/CursorPagination' example: response_id: resp_ type: template data: - id: app_small value: app_small label: App small template_type: [] pagination: next_cursor: null limit: 20 has_more: false total_count: 1 '400': description: | Invalid query parameter. Possible error codes in `error.details[].code`: - `OUT_OF_RANGE` — `limit` > 100. - `INVALID_CURSOR` — `cursor` is present but malformed (not valid base64, or corrupted segment/id). content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: code: VALIDATION_FAILED message: One or more fields failed validation. doc_url: "https://www.moengage.com/docs/api/offerings/offerings-overview" details: - code: OUT_OF_RANGE target: limit message: "limit must be between 1 and 100." response_id: resp_ '401': $ref: '#/components/responses/GatewayAuthError' '403': $ref: '#/components/responses/GatewayAuthError' '429': description: | The rate limit for this endpoint has been exceeded. Retry after the window indicated in the `Retry-After` response header (in seconds). headers: Retry-After: schema: type: integer description: Seconds to wait before retrying. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '503': $ref: '#/components/responses/ServiceUnavailable' components: schemas: OfferTemplateListItem: description: > Summary representation of a personalization template returned in GET /v5/offers/templates list responses. type: object properties: id: type: string description: > Caller-assigned natural key for the template (not a prefixed ObjectId — §9.3 deviation, intentional). Copy this value verbatim into offer_content.locales["0"].variations.*.content_1.meta.templateId when creating template-type content. example: app_small value: type: string description: Identical to id. Present for form-select UI compatibility. example: app_small label: type: string description: Human-readable display name of the template. example: App small template_type: type: array description: Tags describing where this template can be used. Empty if unset. items: type: string example: [] example: id: app_small value: app_small label: App small template_type: [] CursorPagination: description: Pagination metadata included in list responses. type: object required: [limit, has_more, total_count] properties: next_cursor: type: string nullable: true description: > Opaque cursor pointing to the start of the next page. Pass this value as the cursor query parameter on the next request. null when there are no more pages. example: "" limit: type: integer description: Number of items returned per page, matching the limit query parameter. example: 20 has_more: type: boolean description: > Whether additional pages exist. When true, pass next_cursor on the next request. When false, the current page is the last page. example: false total_count: type: integer format: int64 description: > Total number of items matching the applied filters — offerings and drafts for the offers list, templates for the templates list. example: 42 ErrorResponse: type: object required: [response_id] properties: error: type: object required: [code, message, doc_url] properties: code: type: string description: Machine-readable ALL_CAPS_SNAKE_CASE error code. example: VALIDATION_FAILED message: type: string description: Human-readable explanation. Specific enough for AI agents to act on. target: type: string description: The field or resource that caused the error (e.g. "scheduling.expiry_datetime"). details: type: array description: > Per-field error entries for VALIDATION_FAILED responses. All field violations are collected and returned in a single response — never truncated. items: type: object properties: code: type: string description: Field-level error code. target: type: string description: Dot-notation path to the failing field. message: type: string description: Human-readable description of the field-level violation. doc_url: type: string description: Link to documentation for this error code. example: "https://www.moengage.com/docs/api/offerings/offerings-overview" response_id: type: string description: > Trace identifier — same format as success responses ("resp_" + X-MOE-Request-Id). Always present even on error responses for log correlation. OfferingListItem: description: > Summary representation of an offering returned in GET /v5/offers list responses. Only these six fields are returned. Use the `fields` query parameter to request a sparse subset of them (e.g. `fields=id,name,status`). type: object properties: id: type: string description: > Stable prefixed offering identifier assigned by the server at creation. Format: the literal prefix "offer_" followed by the hex MongoDB ObjectId. Use this value as the {offer_id} path parameter in PATCH /v5/offers/{offer_id}. example: offer_6a4f69ee490f66322f968b87 name: type: string description: Unique display name of the offering within the workspace. example: Summer_Sale_Promo_2026 status: type: string description: > Current lifecycle status of the offering (always lowercase). "active" — offering is live and eligible to be returned by Decision Policies. "scheduled" — start_datetime is in the future; not yet live. "expired" — expiry_datetime has passed; no longer served. "archived" — manually archived; excluded from decisioning. "draft" — incomplete; not yet published. enum: [active, scheduled, expired, archived, draft] example: active tags: type: array description: > Prefixed tag IDs attached to this offering (format: "tag_" + raw tag ObjectId). Example: "tag_". When passing tag IDs in POST/PATCH requests, both the prefixed format ("tag_") and the raw format ("") are accepted. Tags are used for offer grouping and Decision Policy selection. items: type: string example: [tag_] created_at: type: string format: date-time description: ISO 8601 UTC timestamp when the offering was first created. example: "2026-01-15T09:00:00Z" created_by: type: string description: Email address of the user who created this offering. example: marketing-team@example.com example: id: offer_6a4f69ee490f66322f968b87 name: Summer_Sale_Promo_2026 status: active tags: [summer-sale, returning-users] created_at: "2026-01-15T09:00:00Z" created_by: marketing-team@example.com ClientOfferCreateRequest: description: | Request body for creating an Offering via `POST /v5/offers`. Required fields: `name`, `priority`, `delivery`, `created_by`, `scheduling`, `segment_info`, `offer_content`, `variation_meta`. type: object required: [name, priority, delivery, created_by, scheduling, segment_info, offer_content, variation_meta] properties: name: type: string description: > Unique display name for this offering within the workspace. Names are case-sensitive and must be unique. Allowed characters: alphanumeric characters and underscores (_). minLength: 5 maxLength: 100 example: Summer_Sale_Promo_2026 description: type: string description: > Optional free-text description of the offering's purpose, targeting rationale, or expected user experience. Visible in the MoEngage UI offering list. maxLength: 500 example: 20% discount for returning users who have not purchased in 30 days priority: type: integer description: > Static priority score for offer ranking. Lower number means lower priority (1 is lowest priority, 100 is highest). This score is used when the Decision Policy ranking strategy is set to Offering Priority. minimum: 1 maximum: 100 delivery: type: string description: > Controls when the offering is eligible for delivery. `scheduled` — the offering is served within its scheduling window. enum: [scheduled] created_by: type: string format: email description: > Email address of the user creating this offering. example: marketing-team@example.com tags: type: array description: > Tags that provide context about the Offering's nature or theme. Each tag ID must already exist in the workspace — the service returns `400 INVALID_TAG_ID` for any unknown tag ID. items: type: string maxItems: 10 example: [activation,summer_sale] scheduling: $ref: '#/components/schemas/OfferSchedulingCreate' segment_info: $ref: '#/components/schemas/OfferSegmentInfo' offer_content: $ref: '#/components/schemas/OfferContent' variation_meta: $ref: '#/components/schemas/OfferVariationMeta' capping_rules: $ref: '#/components/schemas/OfferCappingRules' is_global_control_enabled: type: boolean description: > Whether the global control group is enabled. default: false imp_track_hours: type: integer description: > The attribution window in hours. minimum: 1 maximum: 240 example: 36 offering_attribute_configuration: type: array description: > Custom attributes assigned to this offering. These attributes are used in Decision Policy using the Custom formula ranking strategy. items: $ref: '#/components/schemas/OfferAttributeConfigurationRequest' maxItems: 5 conversion: $ref: '#/components/schemas/ConversionDto' description: > Conversion goal configuration for measuring offering effectiveness. This can be set at creation time or later via PATCH while the offering is still in "scheduled" status. Once the offering goes "active", the conversion configuration is locked to preserve Thompson Sampling statistical integrity. ClientOfferPatchRequest: description: | Request body for partially updating an existing offering via the public API (`PATCH /v5/offers/{offer_id}`). Only the fields explicitly provided in the request body will be modified. Some fields are status-gated based on the offering's current lifecycle phase — see the field-level descriptions for `variation_meta`, `conversion`, and `scheduling` for the exact rules. Modifying a locked field returns a `400 IMMUTABLE_FIELD` error. type: object required: [updated_by] properties: updated_by: type: string format: email description: > Email address of the team member making this update. example: marketing-team@example.com name: type: string description: > Updated display name for the offering. Must remain unique within the workspace. **Allowed characters:** alphanumeric characters and underscores (_) only. minLength: 5 maxLength: 100 description: type: string description: Updated free-text description of the offering's purpose. maxLength: 500 priority: type: integer description: Updated static priority score. minimum: 1 maximum: 100 tags: type: array description: > **Full replacement, not additive** — the entire existing tag list is overwritten with this value. To add a single tag, send the complete current tag list plus the new tag. To remove all tags, send an empty array. items: type: string maxLength: 50 maxItems: 10 scheduling: $ref: '#/components/schemas/OfferSchedulingPatch' segment_info: $ref: '#/components/schemas/OfferSegmentInfo' offer_content: $ref: '#/components/schemas/OfferContent' variation_meta: $ref: '#/components/schemas/OfferVariationMeta' capping_rules: $ref: '#/components/schemas/OfferCappingRules' is_global_control_enabled: type: boolean description: > Toggle GCG holdout exclusion. When true, GCG members are excluded from receiving this offering. When false, all eligible users can receive it. imp_track_hours: type: integer description: Updated impression de-duplication window in hours (1 to 240). minimum: 1 maximum: 240 offering_attribute_configuration: type: array description: > Replacement custom attribute configuration. This is a FULL REPLACEMENT — the entire existing attribute list is overwritten. At most 5 attributes. items: $ref: '#/components/schemas/OfferAttributeConfigurationRequest' maxItems: 5 conversion: $ref: '#/components/schemas/ConversionDto' OfferSchedulingCreate: description: | Scheduling window that determines when an offering is eligible for delivery. Both `start_datetime` and `expiry_datetime` are required when creating an offering. type: object required: [start_datetime, expiry_datetime] properties: start_datetime: type: string format: date-time description: > ISO 8601 UTC datetime when the offering becomes eligible for delivery. MUST use the UTC "Z" suffix format. example: "2026-06-15T09:00:00Z" expiry_datetime: type: string format: date-time description: > ISO 8601 UTC datetime when the offering expires and stops being served. MUST use the UTC "Z" suffix format. example: "2026-09-30T23:59:59Z" timezone: type: string description: > IANA timezone name. When omitted, the default timezone set for the workspace will be considered. example: Asia/Kolkata, America/New_York, UTC OfferSchedulingPatch: description: | Partial scheduling update for `PATCH` operations. All fields are optional. `start_datetime` can only be changed when offering status is `scheduled`. `expiry_datetime` can be updated only when offering status is `active` or `scheduled`. type: object properties: start_datetime: type: string format: date-time description: > Updated `start datetime`. MUST use UTC "Z" suffix. example: "2026-06-20T09:00:00Z" expiry_datetime: type: string format: date-time description: > Updated `expiry datetime`. MUST use UTC "Z" suffix. example: "2026-10-31T23:59:59Z" timezone: type: string description: > Updated IANA timezone name. example: Asia/Kolkata OfferSegmentInfo: description: | Segment targeting configuration that controls which users are eligible to receive this offering. `included_filters`: criteria a user MUST match to be eligible. `excluded_filters`: criteria that DISQUALIFY a user even if they match the included filters. To target `all users`, send an empty filters object: { "filters": {} } type: object properties: filters: type: object description: > Container for inclusion and exclusion filter blocks. Pass an empty object to target all users with no segmentation restriction. properties: included_filters: type: object description: > The filtering criteria used for including users. For detailed payload and supported fields, refer to [Create Filter Segment](https://www.moengage.com/docs/api/filter-segments/create-filter-segment). properties: filters: type: array description: > Array of individual filter criteria. Each item is a filter object whose structure varies by filter_type. Common filter_type values: `actions`, `user_attributes`, `custom_segments`. items: type: object description: A single UIS filter criterion. Structure varies by filter_type. filter_operator: type: string description: > The logical operator to combine filters. enum: [and, or] excluded_filters: type: object description: > The filtering criteria used for excluding users. properties: filters: type: array items: type: object filter_operator: type: string enum: [and, or] OfferContent: description: | The core content payload delivered to the end-user. This object supports two mutually exclusive structures depending on whether your offering uses content variations or experimentation. **Mutually Exclusive:** Do not provide both `content_1` and `locales` in the same request payload. Doing so will result in a validation error. ### 1. Flat Format (No Content Variations) Use this structure for simple offerings that do not require A/B testing or localization. * **Payload Structure:** Send a single `content_1` object at the root level. * **Behavior:** The exact same content block is served universally to all eligible users. ### 2. Nested Format (Multiple Content Variations) Use this structure when `variation_meta.type` is configured as either **SMV** (Static Multi-Variant) or **DMV** (Dynamic Multi-Variant). * **Payload Structure:** Send a structured `locales` map object. * **Single-Locale Targeting:** For single-locale offerings, you **must** use `"0"` as the literal locale key string. * **Variations Mapping:** Inside each locale, the nested `variations` map keys must exactly match the variation IDs defined in `variation_meta.variantsPerLocale` and `smv_distribution.split`. type: object properties: content_1: $ref: '#/components/schemas/ContentBlock' description: > Primary content block for single variation offerings. locales: type: object description: > Use "0" as the locale key additionalProperties: type: object description: Content container for a single locale. required: [variations] properties: variations: type: object description: > Each key is a variation of the content payload. The key name **must** match a key in `variation_meta.smv_distribution.split`. additionalProperties: type: object properties: content_1: $ref: '#/components/schemas/ContentBlock' example: content_1: type: json value: '{"headline":"Save 20% today","cta_text":"Claim offer","image_url":"https://cdn.example.com/banner.png","cta_url":"https://example.com/offer/summer"}' ContentBlock: description: | This field indicates the type of the offering content payload. `json` — Serialised JSON string containing the offering content payload. `content_block` — The ID of a content block stored in the MoEngage Content Block library. `template` — value is a JSON object of the personalized template's field values, and meta.templateId carries the template id (from GET /v1/offers/templates). type: object required: [type, value] properties: type: type: string description: > Format of the content payload. Determines how `value` is interpreted. `json` — serialised JSON string. `content_block` — Content Block library reference ID. `template` — object of template field key-value pairs. enum: [json, content_block, template] value: description: | Content payload. Interpretation depends on `type`: - **`json`** — value must be a **serialised JSON string** (not an inline JSON object). The entire payload is encoded as a string. Example: `"{\"discount\": \"10%\"}"`. Passing a raw JSON object instead of a string returns `400 INVALID_TYPE`. - **`content_block`** — value is the reference ID string of a Content Block from the MoEngage Content Block library. - **`template`** — value is a JSON **object** (not a string) whose keys are the field names defined by the template schema. Retrieve available templates and required fields from `GET /v5/offers/templates`. Omitting a required template field returns `400 MISSING_TEMPLATE_FIELD`. oneOf: - type: string description: For `json` and `content_block` types. For `json`, must be a serialised JSON string — not an inline object. - type: object description: For `template` type — a map of template field names to their values. additionalProperties: true meta: type: object description: > Key-value metadata attached to this content block. **Required for template type**: when `type` is `template`, you **must** include `templateId` in this object, set to the template's `id` value returned by GET /v1/offers/templates. additionalProperties: true example: templateId: app_small OfferVariationMeta: description: | Configuration for A/B testing content variations. * `SMV`: Fixed percentage splits across up to 5 variations. * `DMV`: The system automatically learns which variant performs best over time and shifts traffic toward the winner. * `Offer Control Group`: A configurable percentage of eligible users to be held out as a pure control group. #### Lifecycle & Mutation Rules `variation_meta` can be edited later via `PATCH /v5/offers/{offer_id}`, subject to status-based rules. Field mutability depends on both the offering status and the variation type. Attempting to modify a locked field returns a `400 IMMUTABLE_FIELD` error. | Offering Status | Variation Type | Editable Fields | Locked Fields | | :--- | :--- | :--- | :--- | | **`scheduled`** | Any | Entire object (`type`, `variantsPerLocale`, `smv_distribution`, `control_group`) | None | | **`active`** | **SMV** | `smv_distribution.split` percentages, `control_group.percentage` | `type`, Variant key names | | **`active`** | **DMV** | `control_group.percentage` | `type`, `variantsPerLocale`, `smv_distribution` | | **`expired`** | Any | None | All fields are locked | type: object required: [type] properties: type: type: string description: > Variation strategy type. `SMV` — fixed percentage split. `DMV` - adaptive traffic allocation. enum: [SMV, DMV] variantsPerLocale: type: array description: > Ordered list of variations. This list **must** contain exactly the same keys defined in smv_distribution.split and **must** match the variation keys used in `offer_content.locales[0].variations`. items: type: string example: [Variant_A, Variant_B] smv_distribution: type: object description: > Variation percentage allocation. Required when type is `SMV`; optional for `DMV`. properties: split: type: object description: > Map of variations to integer percentage allocation. additionalProperties: type: integer minimum: 1 maximum: 100 example: Variation_1: 70 Variation_2: 30 control_group: type: object description: > When enabled, a percentage of eligible users are held out as a pure control and do not receive the offering. This operates independently of the workspace-level GCG. properties: is_enabled: type: boolean description: Whether the offering-level control group holdout is active. example: false percentage: type: integer description: > Percentage of eligible users to hold out. minimum: 1 maximum: 99 example: 10 example: type: SMV variantsPerLocale: [Variant_A, Variant_B] smv_distribution: split: Variant_A: 70 Variant_B: 30 control_group: is_enabled: true percentage: 10 OfferCappingRules: description: | Frequency capping configuration that limits how often this offering is delivered. Two independent capping rules can be configured and enabled simultaneously. `overall`: Limits the TOTAL number of times the offering is delivered across **ALL users in the target segment** within the capping schedule period. `user_level`: Limits the number of times any SINGLE user can receive the offering within the capping schedule period. type: object properties: overall: $ref: '#/components/schemas/OfferCappingRule' description: Total delivery cap across all users within the target segment. user_level: $ref: '#/components/schemas/OfferCappingRule' description: Per-user delivery cap for individual users. example: overall: enabled: true limit_value: 10000 limit_schedule: DAILY user_level: enabled: true limit_value: 3 limit_schedule: WEEKLY OfferCappingRule: description: A single frequency capping rule (either overall or per-user). type: object required: [enabled] properties: enabled: type: boolean description: Whether this capping rule is enforced. When false, all other fields are ignored. example: true limit_value: type: integer description: > Maximum number of deliveries allowed within one limit_schedule period. Required when enabled is true. Must be at least 1. For overall capping, this is the total across all users. For user_level capping, this is per individual user. minimum: 1 example: 3 limit_schedule: type: string description: > Time window for the capping counter. enum: [DAILY, WEEKLY, MONTHLY] example: WEEKLY ConversionDto: description: | List of conversion goals to track. **Status constraint:** `conversion` can only be set or updated while the offering status is `scheduled`. Attempting to update it on an `active` or `expired` offering returns a `400 IMMUTABLE_FIELD` error. type: object properties: primary: $ref: '#/components/schemas/ConversionGoal' description: > Primary conversion goal. secondary: type: array description: > Additional conversion goals tracked alongside the primary goal. items: $ref: '#/components/schemas/ConversionGoal' ConversionGoal: description: A single conversion goal that tracks a specific user action. type: object required: [goal_name, action] properties: goal_name: type: string description: > Display name for this conversion goal. example: Add to Cart action: type: string description: > MoEngage event name associated with the conversion goal. example: add_to_cart filter_type: type: string description: > How the conversion event is matched. "ACTIONS" matches by event name and optional attribute-level filters. example: ACTIONS attributes: $ref: '#/components/schemas/ConversionAttributesDto' description: > Optional attribute-level filters to narrow which occurrences of the action count as a conversion (e.g. only purchase events where category = "Electronics"). is_revenue_tracking: type: boolean description: > When true, the conversion also records the monetary value from the event. Requires the `revenue_amount` and `revenue_currency` fields to be set. default: false revenue_amount: type: string description: > Name of the event attribute containing the transaction revenue value. Should be used only when `is_revenue_tracking` is true. The attribute must be a numeric field on the tracked event. example: order_value revenue_currency: type: string description: > ISO 4217 three-letter currency code for revenue tracking (e.g. "USD", "EUR", "GBP", "INR"). Should be used only when `is_revenue_tracking` is true. example: USD ConversionAttributesDto: type: object description: > Attribute-level filters applied to the conversion event. Allows narrowing the conversion signal to a specific subset of occurrences of the tracked action (e.g. only purchases where category equals "Electronics"). properties: filters: type: array description: > Array of attribute filter objects. Each object targets a specific event attribute. Follows the same filter structure as `segment_info.filters.included_filters.filters`. items: type: object description: A single attribute filter criterion. Structure varies by filter_type. properties: included_filters: type: object description: > Inclusion criteria for the conversion attribute filter. Contains a "filters" array of UIS filter objects and a "filter_operator" (AND or OR). properties: filters: type: array items: type: object description: A single UIS filter criterion. Structure varies by filter_type. filter_operator: type: string enum: [and, or] example: and excluded_filters: type: object description: Exclusion criteria, same structure as included_filters. properties: filters: type: array items: type: object filter_operator: type: string enum: [and, or] OfferAttributeConfigurationRequest: description: | Assignment of a custom offering attribute and its value(s) to this offering. type: object required: [offering_attribute_id, selected_values] properties: offering_attribute_id: type: string description: > ID of the Offering Attribute. offering_attribute_name: type: string description: > Display name of the referenced Offering Attribute. example: Product Category type: type: string description: > Attribute type of the referenced Offering Attribute. enum: [FIXED, DYNAMIC] example: FIXED selected_values: type: array description: > Selected option value(s) for this attribute. At least one value is required. For `DYNAMIC` attributes, at most one value is allowed per assignment. For `FIXED` attributes, multiple values are allowed up to the attribute definition's `max_inputs_allowed` limit. items: type: object required: [value] properties: id: type: string description: > ID of the option(s) defined as part of the Offering Attribute definition. nullable: true value: type: string description: > Option value string. It must match an existing option name in the Offering Attribute definition. example: Electronics score: type: integer description: > Priority score for this option to be used in a Decision Policy using the Custom Formula ranking strategy. Set to `null` for `DYNAMIC` attributes. Set to a value between 1-100 for `FIXED` attributes. minimum: 1 maximum: 100 nullable: true example: 85 minItems: 1 PublicOfferCreateResponse: description: Response returned by `POST /v5/offers` on successful creation. type: object properties: response_id: type: string description: > Format: `resp_` header value (auto-generated UUID if the `X-MOE-Request-Id` header was not part of the request body). example: resp_c5f83262-3127-4e23-bc1b-9efd4c929e12 type: type: string enum: [offer] description: Resource type discriminator. Always `offer`. data: type: object description: Created offering summary. properties: id: type: string description: > Offering ID assigned by the server. Format: `offer_`. Use this value as the `{offer_id}` path parameter in subsequent PATCH requests. example: offer_6a4f69ee490f66322f968b87 status: type: string enum: [active, scheduled] description: > Lifecycle status of the newly created offering example: scheduled created_at: type: string format: date-time description: ISO 8601 UTC timestamp when the offering was persisted. example: "2026-07-09T09:29:18.526812352Z" example: response_id: resp_c5f83262-3127-4e23-bc1b-9efd4c929e12 type: offer data: id: offer_6a4f69ee490f66322f968b87 status: scheduled created_at: "2026-07-09T09:29:18.526812352Z" PublicOfferPatchResponse: description: Response returned by `PATCH /v5/offers/{offer_id}` on successful update. type: object properties: response_id: type: string description: > Format: `resp_` header value (auto-generated UUID if the `X-MOE-Request-Id` header was not part of the request body). example: resp_c5f83262-3127-4e23-bc1b-9efd4c929e12 type: type: string enum: [offer] data: type: object description: Updated offering summary. properties: id: type: string description: Offering ID example: offer_6a4f69ee490f66322f968b87 status: type: string enum: [active, scheduled] description: Current lifecycle status after the update. example: active updated_at: type: string format: date-time description: ISO 8601 UTC timestamp of the update. example: "2026-07-09T09:29:18.526812352Z" example: response_id: resp_c5f83262-3127-4e23-bb1b-9efd4c929e12 type: offer data: id: offer_6a4f69ee490f66322f968b87 status: active updated_at: "2026-07-09T09:29:18.526812352Z" responses: GatewayAuthError: description: | Authentication or authorisation failure from the API gateway layer. This response is generated by the gateway before the request reaches the service. The body is JSON-formatted but API gateway sends it with `Content-Type: text/plain`. Two gateway error codes are possible: - `ER001` — credentials missing or invalid (401). - `ER007` — credentials valid but insufficient scope for this route (403). content: text/plain: schema: type: string example: '{"code":"ER001","target":"Authentication Invalid","message":"Auth validation failed."}' ServiceUnavailable: description: | Gateway temporarily unable to route the request. Returned when the upstream service is unreachable or the HTTP request cannot be proxied (e.g. malformed headers with control characters). Treat as a transient error and retry with exponential back-off. content: text/html: schema: type: string example: "

503 Service Temporarily Unavailable

" securitySchemes: basicAuth: type: http scheme: basic description: | Authentication is done via Basic Auth. This requires a base64-encoded string of your credentials in the format 'username:password'. - **Username**: Use your MoEngage Workspace ID (also known as the App ID). Find it in the dashboard at **Settings** > **Account** > **APIs** > **Workspace ID (earlier app id)**. - **Password**: On your MoEngage workspace, navigate to **Settings** > **Account** > **API keys**. Use the Key listed within the **Personalize** tile. For more information on authentication and getting your credentials, refer to [Getting your credentials](https://www.moengage.com/docs/api/introduction#getting-your-credentials). Send the value in the `Authorization` header as `Basic` followed by Base64-encoding of `appkey:apisecret` (workspace ID and API key).