> ## Documentation Index
> Fetch the complete documentation index at: https://moengage.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Offerings Overview

> Create, update, and list offerings for Offer Decisioning in MoEngage.

The MoEngage Offerings API allows you to manage offerings. You can create time-bound promotions and personalized content, update scheduling or targeting on existing offerings, list offerings for reporting or orchestration workflows, and list the personalization templates available for offering content.

<Info>
  If this API is not enabled for your account, contact your MoEngage Customer Success Manager (CSM) or the Support team to request enablement.
</Info>

## Offering lifecycle

An offering moves through different states automatically based on its scheduling window. There is no separate publish step — the offering goes live as soon as it is created, if the scheduling window is current.

| State       | Meaning                                                                                                                                           |
| :---------- | :------------------------------------------------------------------------------------------------------------------------------------------------ |
| `scheduled` | Indicates that the `start_datetime` is in the future. The offering is not yet eligible for decisioning.                                           |
| `active`    | Indicates that the current time is between `start_datetime` and `expiry_datetime`. The offering is eligible for decisioning.                      |
| `expired`   | Indicates that the `expiry_datetime` has passed. The offering is no longer served and cannot be updated via the API.                              |
| `archived`  | Indicates that the offering is manually archived from the dashboard. The offering is excluded from decisioning and cannot be updated via the API. |
| `draft`     | Indicates that the offering is not yet published. Offerings created via the API are never in `draft` state.                                       |

## Endpoints

The Offerings API is a collection of the following endpoints:

* [List Offerings](/docs/api/public-offerings/list-offerings): Returns a paginated list of offerings, with filtering by ID, name, status, tags, date range, and creator.
* [Create Offering](/docs/api/public-offerings/create-offering): Creates an offering in your workspace, including content, scheduling, segmentation, and variation configuration.
* [Update Offering](/docs/api/public-offerings/update-offering): Partially updates an existing offering. Only the fields in the request body are changed.
* [List Offer Templates](/docs/api/public-offerings/list-offer-templates): Returns a paginated list of the personalization templates available in your workspace. Use a template `id` as `meta.templateId` in offering content.

<Note>
  There is no endpoint to fetch a single offering by ID.
</Note>

## Rate Limits

Rate limits apply per consumer. Each endpoint has its own limit:

| Endpoint                | Method | Rate limit                           |
| :---------------------- | :----- | :----------------------------------- |
| `/v5/offers`            | GET    | 150 requests/min, 500/hour, 1000/day |
| `/v5/offers/templates`  | GET    | 150 requests/min, 500/hour, 1000/day |
| `/v5/offers`            | POST   | 100 requests/min, 300/hour, 600/day  |
| `/v5/offers/{offer_id}` | PATCH  | 100 requests/min, 300/hour, 600/day  |

Exceeding a limit returns a `429` response with a `Retry-After` header indicating how many seconds to wait before retrying.

## Idempotency

The Create and Update endpoints require an `Idempotency-Key` header (UUID v4). Reusing the same key within 24 hours returns the original response without re-running the operation, so retries on network failures are safe.

Two conflict cases return `409`:

* **`DUPLICATE_IDEMPOTENCY_KEY`** - a request with the same key is already in progress. Wait for it to complete before retrying.
* **`IDEMPOTENCY_CONFLICT`** - the key was already used within 24 hours with a different request body. Use a new UUID v4 key to submit the changed payload.

Use the **same key** only when retrying an identical request after a network failure or timeout. Use a **new key** for every logically distinct operation, including any request where the payload has changed.

## Errors

The Offerings API uses standard HTTP status codes and returns all errors in a consistent JSON envelope.

### HTTP status codes

| Code  | Meaning                                                                                 |
| :---- | :-------------------------------------------------------------------------------------- |
| `200` | Success (GET, PATCH).                                                                   |
| `201` | Offering created (POST).                                                                |
| `400` | Validation failed. One or more fields are invalid, missing, or violate a business rule. |
| `401` | Authentication failed. Credentials are missing or invalid.                              |
| `403` | Forbidden. The feature is not enabled, or the offering is expired or archived.          |
| `404` | Offering not found. The `offer_id` in the path does not exist in this workspace.        |
| `409` | Idempotency conflict. See [Idempotency](#idempotency).                                  |
| `429` | Rate limit exceeded. Retry after the number of seconds in the `Retry-After` header.     |
| `503` | Gateway unavailable. Treat as transient and retry with exponential backoff.             |

### Error response shape

All `4xx` and `5xx` responses return a JSON body in the following format:

```json theme={null}
{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "One or more fields failed validation.",
    "target": "scheduling.expiry_datetime",
    "details": [
      {
        "code": "REQUIRED_FIELD_MISSING",
        "target": "variation_meta",
        "message": "variation_meta is required."
      },
      {
        "code": "INVALID_SCHEDULING",
        "target": "scheduling",
        "message": "expiry_datetime must be after start_datetime."
      }
    ],
    "doc_url": "https://www.moengage.com/docs/api/offerings/offerings-overview"
  },
  "response_id": "resp_c5f83262-3127-4e23-bc1b-9efd4c929e12"
}
```

| Field             | Description                                                                                                                                           |
| :---------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `error.code`      | Machine-readable error code in `ALL_CAPS_SNAKE_CASE`. Use this to branch on specific errors in your code.                                             |
| `error.message`   | Human-readable explanation of the error.                                                                                                              |
| `error.target`    | The field or resource that caused the error, in dot-notation (e.g. `scheduling.expiry_datetime`).                                                     |
| `error.details[]` | Per-field violations for `400 VALIDATION_FAILED` responses. The API collects all errors before responding — a single `400` may list multiple entries. |
| `error.doc_url`   | Link to documentation for this error code.                                                                                                            |
| `response_id`     | Trace identifier for this response. Always present, even on errors. Provide this to MoEngage support when reporting an issue.                         |

<Note>
  The `details` array is only present on `400 VALIDATION_FAILED` responses. All other error codes return only the top-level `error` object.
</Note>

### Common error codes

For the full list of error codes per endpoint, see the individual endpoint pages. The most commonly encountered codes across all Offerings API operations are:

| Code                        | HTTP | Meaning                                                                                |
| :-------------------------- | :--- | :------------------------------------------------------------------------------------- |
| `VALIDATION_FAILED`         | 400  | One or more request fields failed validation. Check `error.details` for the full list. |
| `REQUIRED_FIELD_MISSING`    | 400  | A required field is absent. `error.target` identifies which field.                     |
| `INVALID_SCHEDULING`        | 400  | `expiry_datetime` is not after `start_datetime`, or a datetime is in the past.         |
| `INVALID_OFFER_NAME`        | 400  | `name` contains characters other than letters, numbers, and underscores.               |
| `DUPLICATE_OFFER_NAME`      | 400  | `name` matches an existing non-archived offering in the workspace.                     |
| `INVALID_TAG_ID`            | 400  | One or more tag IDs in `tags` do not exist in the workspace.                           |
| `IMMUTABLE_FIELD`           | 400  | An attempt was made to change a status-locked field.                                   |
| `MISSING_IDEMPOTENCY_KEY`   | 400  | The `Idempotency-Key` header is absent.                                                |
| `INVALID_IDEMPOTENCY_KEY`   | 400  | The `Idempotency-Key` is not a valid UUID v4.                                          |
| `DUPLICATE_IDEMPOTENCY_KEY` | 409  | A request with the same key is in-flight.                                              |
| `IDEMPOTENCY_CONFLICT`      | 409  | The key was reused with a different request body.                                      |
| `EXPIRED_OFFERING`          | 403  | The offering is past its `expiry_datetime` and cannot be modified.                     |
| `ARCHIVED_OFFERING`         | 403  | The offering is archived and cannot be modified.                                       |
| `OFFER_NOT_FOUND`           | 404  | No offering with the given ID exists in this workspace.                                |

## FAQs

### Getting Started

<AccordionGroup>
  <Accordion title="Do I need to do anything before using the API?">
    Yes. Make sure the Offer Decisioning feature is enabled for your workspace — if it isn't, all API calls return `403 FORBIDDEN`. Contact your MoEngage CSM or the Support team to request enablement.
    Before calling the Create Offering API, ensure the following workspace resources already exist:

    * **Tags** — create and manage them in the MoEngage dashboard under **Settings → Advanced Settings → Tags**. The tag IDs you pass in the `tags` array must already exist.
    * **Offering attributes** — configured under **Offer Decisioning → Attributes** in the dashboard. Pass their IDs in `offering_attribute_configuration`.
    * **Personalization templates** — list available templates using `GET /v5/offers/templates` and use the returned `id` as `meta.templateId` in your content.
  </Accordion>

  <Accordion title="Where do I find my API key and Workspace ID?">
    In the MoEngage dashboard, go to **Settings → Account → APIs**. Your Workspace ID (App ID) is listed there. Your API key is in the **Personalize** tile on the same page. Use the Workspace ID as the Basic Auth username and the API key as the password.
  </Accordion>
</AccordionGroup>

### Offering Lifecycle

<AccordionGroup>
  <Accordion title="What states can an offering be in?">
    An offering is always in one of five states: `scheduled`, `active`, `expired`, `archived`, or `draft`.
    See the [Offering lifecycle](#offering-lifecycle) section above for the full state table.
  </Accordion>

  <Accordion title="Can I fetch a single offering by ID?">
    Currently, there is no dedicated `GET /v5/offers/{offer_id}` endpoint. To retrieve a specific offering, call [List Offerings](/docs/api/public-offerings/list-offerings) with the `id` query parameter set to the offering's ID (including the `offer_` prefix). This returns a single-item list for that offering.
  </Accordion>
</AccordionGroup>

### Create Offering

<AccordionGroup>
  <Accordion title="What is the minimum required to create an offering?">
    The required top-level fields are: `name`, `priority`, `delivery`, `created_by`, `scheduling` (with both `start_datetime` and `expiry_datetime`), `segment_info`, `offer_content`, and `variation_meta`. All other fields — `tags`, `capping_rules`, `conversion`, `imp_track_hours`, `offering_attribute_configuration` — are optional and can be omitted.
  </Accordion>

  <Accordion title="Can I change variation_meta after creation?">
    It depends on the offering's status and variation `type`:

    * While the offering is `scheduled`, the entire `variation_meta` object is editable.
    * Once the offering is `active`, the `type` and the `variantsPerLocale` key set are locked. For `SMV`, you can still update the `smv_distribution` split percentages and `control_group.percentage`. For `DMV`, only `control_group.percentage` is editable.

    Editing a locked field returns `IMMUTABLE_FIELD`. The variation types are `SMV` (static allocation you control) and `DMV` (dynamic allocation MoEngage optimizes).
  </Accordion>

  <Accordion title="Can I include all fields in a single Create request, or do I build it incrementally?">
    You include all fields in a single Create request — there is no incremental or draft-building flow. The offering's initial status is set from the scheduling dates: `scheduled` when `start_datetime` is in the future, `active` when the current time is within the scheduling window, or `expired` when `start_datetime` is already in the past.
  </Accordion>

  <Accordion title="What does priority control?">
    Priority is an integer from 1 to 100 used by Decision Policies to rank eligible offerings for a user. Higher values rank the offering higher relative to others with lower priority. The exact ranking behavior also depends on any scoring formulas configured in the associated Decision Policy and any `offering_attribute_configuration` scores.
  </Accordion>

  <Accordion title="What characters are allowed in an offering name?">
    Offering names must be 5–100 characters and contain only letters, numbers, and underscores. Spaces, hyphens, and special characters are not allowed. Names must be unique within the workspace. Example of a valid name: `Summer_Sale_Promo_2026`.
  </Accordion>
</AccordionGroup>

### Update Offering

<AccordionGroup>
  <Accordion title="Which fields can I update after creation?">
    You can update `name`, `description`, `priority`, `tags`, `scheduling` (within state-based rules below), `segment_info`, `offer_content`, `capping_rules`, `is_global_control_enabled`, `imp_track_hours`, `offering_attribute_configuration`, and `conversion`. You can also update `variation_meta`, but only within the status-based and type-based rules described below.
  </Accordion>

  <Accordion title="What can I change when an offering is active vs. scheduled?">
    | Field                                      | `scheduled` | `active`   |
    | :----------------------------------------- | :---------- | :--------- |
    | `scheduling.start_datetime`                | ✅ Editable  | ❌ Locked   |
    | `scheduling.expiry_datetime`               | ✅ Editable  | ✅ Editable |
    | `conversion`                               | ✅ Editable  | ❌ Locked   |
    | `offer_content`                            | ✅ Editable  | ✅ Editable |
    | `name`, `tags`, `priority`, `segment_info` | ✅ Editable  | ✅ Editable |
    | `variation_meta.type`, `variantsPerLocale` | ✅ Editable  | ❌ Locked   |
    | `variation_meta.smv_distribution` (`SMV`)  | ✅ Editable  | ✅ Editable |
    | `variation_meta.smv_distribution` (`DMV`)  | ✅ Editable  | ❌ Locked   |
    | `variation_meta.control_group.percentage`  | ✅ Editable  | ✅ Editable |
  </Accordion>

  <Accordion title="Do I need to send the full offering payload when updating?">
    No. The Update endpoint is a PATCH — only the fields you include in the request body are changed. Omitted fields retain their current values. However, `tags` and `offering_attribute_configuration` are **replaced entirely** when included, not merged. To add a single tag, send the current tag list plus the new one. To remove all tags, send an empty array.
  </Accordion>

  <Accordion title="Can I update an expired or archived offering?">
    No. The API returns `403` with `EXPIRED_OFFERING` or `ARCHIVED_OFFERING`. Only offerings in `active` and `scheduled` states can be updated.
  </Accordion>

  <Accordion title="How do I find the offering ID to use in the Update request?">
    The `offer_id` (including the `offer_` prefix) is returned in the `data.id` field of the Create Offering response. You can also retrieve it by calling [List Offerings](/docs/api/public-offerings/list-offerings) and filtering by name or status.
  </Accordion>
</AccordionGroup>

### Idempotency

<AccordionGroup>
  <Accordion title="When should I use the same Idempotency-Key vs. a new one?">
    Use the **same key** only when retrying a request that failed due to a network error or timeout — where you are not sure whether the server processed the original request. Reusing the same key within 24 hours returns the original response without re-running the operation.
    Use a **new key** for every logically distinct operation, and for any retry where the request body has changed from the previous attempt.
  </Accordion>

  <Accordion title="What happens if I reuse an Idempotency-Key with a different request body?">
    The API returns `409 IDEMPOTENCY_CONFLICT`. The original operation is not replayed and the new payload is not processed. Generate a new UUID v4 key and resubmit.
  </Accordion>

  <Accordion title="What format is required for the Idempotency-Key?">
    UUID v4 (for example, `550e8400-e29b-41d4-a716-446655440000`). For automated workflows, generate a fresh UUID v4 per distinct operation. Do not derive the key from the request payload — if the payload is identical to a previous request, an intentional replay would return the cached response.
  </Accordion>
</AccordionGroup>
