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

# Create a Formula

> Complete guide to creating a formula with labeled alternates using the Composer API.

## Overview

A **formula** is a reusable message template that drives a campaign. It defines the channel (push, SMS, email, etc.), message slots, and the labeled alternates Aampe uses to personalize each send.

Formulas start in **Draft** status and must go through a review/approval workflow before going live.

***

## Core Concepts

### Slots vs. Alternates

The structure defines **slots** — one position per section (header, body). It does **not** list all alternates inline. Alternates for a slot are linked by a shared `component_id`.

```
Formula structure
└── section (header)
│     └── ONE variant node  ← slot; node id = component_id for this slot
└── section (body)
      └── ONE variant node  ← slot; node id = component_id for this slot

Variants (separate)
├── component_id = header_slot_id  label = Calm       ← alternate 1
├── component_id = header_slot_id  label = Breathe    ← alternate 2
├── component_id = header_slot_id  label = Stillness  ← alternate 3
├── component_id = body_slot_id    label = Science    ← alternate 1
└── component_id = body_slot_id    label = Sleep      ← alternate 2
```

### Component IDs — two distinct concepts

There are two different IDs called "component ID" and confusing them is the most common source of errors:

| Name                      | Type                  | Source                            | Used for                                                                                                          |
| ------------------------- | --------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| **Component type ID**     | UUID                  | `GET /component-types` → `id`     | `componentId` in structure nodes; identifies what *kind* of slot this is                                          |
| **Component instance ID** | UUID (self-generated) | You create it with `uuid.uuid4()` | Variant node `id` in the structure; `component_id` when creating variants — links all alternates to the same slot |

### Seed variant

When you put text in a variant node's `children[0].text`, that text is automatically created as the **first alternate** for that slot. Do not re-submit it via the bulk endpoint — it will create a duplicate.

### componentId — component type UUIDs

The `componentId` in a structure variant node is the component type UUID from `GET /component-types`:

| Component type    | `data_key`         | UUID                                   |
| ----------------- | ------------------ | -------------------------------------- |
| Offering          | `Offering`         | `8e3f0698-7564-4522-9c07-49686667c0b4` |
| Value Proposition | `ValueProposition` | `72150aa9-6b20-4d95-9659-0c05701ef7de` |
| Call to Action    | `CallToAction`     | `8c0b9b73-b305-4f93-b41f-bde62cebac98` |
| Greeting          | `Greeting`         | `43cb8b03-4503-48ed-a09a-9110fd260e3d` |
| Incentive         | `Incentive`        | `27eb65fc-75ec-4a38-8576-03672ee6c562` |
| Tone              | `Tone`             | `0fb13b80-d999-48c3-9ca9-3975c0af0bf7` |

Fetch the live list at any time:

```python theme={null}
response = requests.get(
    "https://composer.api.aampe.com/api/component-types",
    headers={"X-API-KEY": "YOUR_API_KEY"},
)
CT = {ct["data_key"]: ct["id"] for ct in response.json()}
```

### Labels and `variant_type`

When associating a label with a variant, the label's `variant_type` must exactly match the `data_key` of the component type.

<Warning>
  A label created with `variant_type: "Offering"` **cannot** be used on a `ValueProposition` variant, and vice versa. Create separate labels for each component type you need.
</Warning>

***

## Creation Flow (4 Steps)

### Step 1 — Create the formula

Create the formula with `structure: []`. The message structure is set separately on the message-content record in Step 3.

```python theme={null}
import uuid, requests

BASE_URL = "https://composer.api.aampe.com/api"
headers  = {"X-API-KEY": "YOUR_API_KEY", "Content-Type": "application/json"}

CT_OFFERING          = "8e3f0698-7564-4522-9c07-49686667c0b4"
CT_VALUE_PROPOSITION = "72150aa9-6b20-4d95-9659-0c05701ef7de"

response = requests.post(f"{BASE_URL}/formulas", headers=headers, json={
    "name": "My Formula",
    "message_type": "push",
    "structure": [],
    "labels": [
        {"label_id": 101, "variant_type": "Offering",          "component_type_id": CT_OFFERING},
        {"label_id": 102, "variant_type": "Offering",          "component_type_id": CT_OFFERING},
        {"label_id": 103, "variant_type": "ValueProposition",  "component_type_id": CT_VALUE_PROPOSITION},
    ],
})
response.raise_for_status()
formula_id = response.json()["id"]
```

### Step 2 — Fetch the message-content ID

When a formula is created, the API automatically creates a message-content record for it. Fetch its ID:

```python theme={null}
mc_resp = requests.get(
    f"{BASE_URL}/formulas/{formula_id}/message-contents",
    headers=headers,
)
mc_resp.raise_for_status()
message_content_id = mc_resp.json()[0]["id"]
```

### Step 3 — Set the structure

Pre-generate a UUID for each slot. That UUID is the **component instance ID** — it links all of that slot's alternates together and must be referenced when creating variants in Step 4.

The text in `children[0].text` is the **seed variant** — it is automatically created as the first alternate for that slot.

```python theme={null}
# Pre-generate component instance IDs (one per slot)
header_component_id = str(uuid.uuid4())
body_component_id   = str(uuid.uuid4())

put_resp = requests.put(
    f"{BASE_URL}/message-contents/{message_content_id}",
    headers=headers,
    json={
        "id": message_content_id,
        "structure": [
            {
                "kind": "section",
                "sectionType": "header",
                "children": [
                    {
                        "id":            header_component_id,  # ← you generated this
                        "kind":          "variant",
                        "variantType":   "Offering",
                        "componentId":   CT_OFFERING,          # ← component type UUID
                        "labelId":       101,                  # ← label for the seed variant
                        "children":      [{"text": "Your first header alternate"}],
                    }
                ],
            },
            {
                "kind": "section",
                "sectionType": "body",
                "children": [
                    {
                        "id":            body_component_id,
                        "kind":          "variant",
                        "variantType":   "ValueProposition",
                        "componentId":   CT_VALUE_PROPOSITION,
                        "labelId":       103,
                        "children":      [{"text": "Your first body alternate"}],
                    }
                ],
            },
        ],
        "deeplink": None,
        "deeplink_dataset_field_id": None,
    },
)
put_resp.raise_for_status()
```

<Warning>
  Each slot must have exactly **one** variant node in the structure. Do not add multiple sibling variant nodes — they render as separate inline components, not as alternatives for the same slot.
</Warning>

### Step 4 — Add the remaining alternates

Add all remaining alternates using either `POST /variants/create/bulk` (multiple at once) or `POST /variants` (one at a time). Use the component instance IDs you generated in Step 3. Do **not** include the seed variants — they were already created in Step 3.

```python theme={null}
bulk_resp = requests.post(
    f"{BASE_URL}/variants/create/bulk",
    headers=headers,
    json=[
        # Additional header alternates (seed "Your first header alternate" was already created)
        {"component_id": header_component_id, "content": "Your second header alternate", "label_id": 102},
        {"component_id": header_component_id, "content": "Your third header alternate",  "label_id": 101},
        # Additional body alternates
        {"component_id": body_component_id,   "content": "Your second body alternate",   "label_id": 103},
    ],
)
bulk_resp.raise_for_status()
```

After this call the formula has 3 header alternates × 2 body alternates = **6 unique message combinations**.

***

## Endpoint Reference

### `POST /formulas`

Creates a new formula.

| Field                   | Type     | Required | Description                                                                                                    |
| ----------------------- | -------- | -------- | -------------------------------------------------------------------------------------------------------------- |
| `message_type`          | string   | Yes      | `push`, `sms`, `whatsapp`, `web_push`, `in_app_banner`, `in_app_fullpage`, `email`, `content_cards`, `surface` |
| `name`                  | string   | No       | Display name (must be unique within `message_type`)                                                            |
| `structure`             | array    | Yes      | Always pass `[]` — structure is set on the message-content in a separate call                                  |
| `labels`                | array    | No       | Formula-level label associations                                                                               |
| `topic_id`              | uuid     | No       | Topic this formula belongs to                                                                                  |
| `audience_id`           | uuid     | No       | Restrict delivery to a segment                                                                                 |
| `send_start`            | datetime | No       | ISO 8601 start of delivery window                                                                              |
| `is_controlled_message` | boolean  | No       | Controlled A/B test (default: `false`)                                                                         |

**`labels` item:**

```python theme={null}
{"label_id": 101, "variant_type": "Offering", "component_type_id": CT_OFFERING}
```

***

### `PUT /message-contents/{id}`

Sets the message structure and seed variants on the message-content record.

**Variant node fields:**

| Field         | Type    | Required | Description                                                                                |
| ------------- | ------- | -------- | ------------------------------------------------------------------------------------------ |
| `id`          | uuid    | Yes      | Component instance ID — a UUID you generate; used as `component_id` when creating variants |
| `kind`        | string  | Yes      | Always `"variant"`                                                                         |
| `variantType` | string  | Yes      | Component type `data_key` (e.g. `"Offering"`)                                              |
| `componentId` | uuid    | Yes      | Component type UUID from `GET /component-types`                                            |
| `labelId`     | integer | No       | Label for the seed variant                                                                 |
| `children`    | array   | Yes      | `[{"text": "seed text"}]` — becomes the first alternate for this slot                      |

***

### `POST /variants/create/bulk`

Creates multiple variants in a single request. Use this when you have several alternates to add at once.

| Field          | Type    | Required | Description                                                           |
| -------------- | ------- | -------- | --------------------------------------------------------------------- |
| `component_id` | uuid    | Yes      | The component instance ID from the structure (the `id` you generated) |
| `content`      | string  | Yes      | The alternate's message copy                                          |
| `label_id`     | integer | No       | Label for this alternate                                              |

### `POST /variants`

Creates a single variant. Use this for one-off additions. Accepts the same fields as a bulk item, plus `formula_id` and `message_content_id` for context.

| Field                | Type    | Required | Description                                  |
| -------------------- | ------- | -------- | -------------------------------------------- |
| `component_id`       | uuid    | Yes      | The component instance ID from the structure |
| `content`            | string  | Yes      | The alternate's message copy                 |
| `label_id`           | integer | No       | Label for this alternate                     |
| `formula_id`         | integer | No       | The formula this variant belongs to          |
| `message_content_id` | uuid    | No       | The message-content this variant belongs to  |

***

## Formula Lifecycle

```
Draft → Review → Approved → Live / Scheduled / Expired
                     ↓
                 Archived
```

Use the state-transition endpoints (`/review`, `/approve`, `/archive`) to progress a formula.
