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

# Campaign Draft Steps

> Use the 4 step-scoped PATCH endpoints to build and edit campaign drafts in the wizard

The campaign creation wizard is split into **4 steps**, each backed by its own
PATCH endpoint. You can update any step independently — the draft always saves,
and the response tells you about any validation issues.

| Step | Endpoint                                                | What you edit                                   |
| ---- | ------------------------------------------------------- | ----------------------------------------------- |
| 1    | `PATCH /api/v1/campaigns/drafts/{id}/campaign-settings` | Budget, bidding, targeting, schedule            |
| 2    | `PATCH /api/v1/campaigns/drafts/{id}/ad-groups`         | Ad groups and their keywords                    |
| 3    | `PATCH /api/v1/campaigns/drafts/{id}/ads`               | Responsive Search Ads (headlines, descriptions) |
| 4    | `PATCH /api/v1/campaigns/drafts/{id}/extensions`        | Sitelinks, callouts, phone, snippets            |

<Note>
  All four endpoints share the same response format — the full refreshed draft
  plus a `warnings` array for the current step. Data **always saves** even when
  warnings are present; warnings are informational only.
</Note>

## Authentication

Every request requires a valid Bearer token from an **approved** user who has
access to the draft.

```bash theme={null}
Authorization: Bearer <access_token>
```

***

## Shared response format

All step endpoints return `DraftStepResponseSchema`:

<ResponseField name="draft" type="CampaignDraftSchema" required>
  The full campaign draft after the update, with all related objects (ad groups,
  keywords, RSAs, extensions) prefetched.
</ResponseField>

<ResponseField name="warnings" type="ValidationErrorSchema[]">
  Validation warnings for this step. Each warning contains:

  <Expandable title="Warning object fields">
    <ResponseField name="field" type="string" required>
      JSON path to the problematic field (e.g. `"daily_budget_micros"`).
    </ResponseField>

    <ResponseField name="code" type="string" required>
      Machine-readable error code (e.g. `"REQUIRED"`, `"INVALID_RANGE"`).
    </ResponseField>

    <ResponseField name="message" type="string" required>
      Human-readable description of the issue.
    </ResponseField>
  </Expandable>
</ResponseField>

***

## Step 1 — Campaign Settings

```
PATCH /api/v1/campaigns/drafts/{draft_id}/campaign-settings
```

Update campaign-level settings. Only provided fields are changed — omitted
fields stay untouched.

### Request fields

<ParamField body="name" type="string">
  Internal draft name (1–128 characters).
</ParamField>

<ParamField body="campaign_name" type="string">
  The campaign name visible in Google Ads.
</ParamField>

<ParamField body="daily_budget_micros" type="integer">
  Daily budget in micros (1 000 000 micros = 1 currency unit).
</ParamField>

<ParamField body="bidding_strategy" type="object">
  Bidding strategy configuration.
</ParamField>

<ParamField body="default_match_type" type="string">
  Default keyword match type (`"BROAD"`, `"PHRASE"`, `"EXACT"`).
</ParamField>

<ParamField body="language_ids" type="integer[]">
  Google Ads language criterion IDs.
</ParamField>

<ParamField body="location_targets" type="object[]">
  Geo-targeting locations.
</ParamField>

<ParamField body="network_settings" type="object">
  Search / Display network toggles.
</ParamField>

<ParamField body="ad_schedule" type="object">
  Day-of-week and hour bid adjustments.
</ParamField>

<ParamField body="start_date" type="string (date)">
  Campaign start date (`YYYY-MM-DD`).
</ParamField>

<ParamField body="end_date" type="string (date)">
  Campaign end date (`YYYY-MM-DD`).
</ParamField>

<ParamField body="conversion_goals" type="object[]">
  Conversion goal configurations.
</ParamField>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH https://dev-api.cattix.com/api/v1/campaigns/drafts/42/campaign-settings \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "campaign_name": "Summer Sale 2026",
      "daily_budget_micros": 5000000,
      "start_date": "2026-06-01",
      "end_date": "2026-08-31"
    }'
  ```

  ```python Python theme={null}
  import httpx

  resp = httpx.patch(
      "https://dev-api.cattix.com/api/v1/campaigns/drafts/42/campaign-settings",
      headers={"Authorization": f"Bearer {token}"},
      json={
          "campaign_name": "Summer Sale 2026",
          "daily_budget_micros": 5_000_000,
          "start_date": "2026-06-01",
          "end_date": "2026-08-31",
      },
  )
  ```
</CodeGroup>

```json Response theme={null}
{
  "draft": {
    "id": 42,
    "name": "CATTIX | 2026-02-10 | car",
    "campaign_name": "Summer Sale 2026",
    "daily_budget_micros": 5000000,
    "start_date": "2026-06-01",
    "end_date": "2026-08-31",
    "...": "..."
  },
  "warnings": [
    {
      "field": "bidding_strategy",
      "code": "REQUIRED",
      "message": "Bidding strategy is required"
    }
  ]
}
```

***

## Step 2 — Ad Groups

```
PATCH /api/v1/campaigns/drafts/{draft_id}/ad-groups
```

Bulk-patch ad groups and their nested keywords in one request. Each item
identifies the ad group by `id`.

### Request fields

<ParamField body="ad_groups" type="PatchAdGroupItem[]" required>
  Array of ad group patches.

  <Expandable title="Ad group item fields">
    <ParamField body="id" type="integer" required>
      ID of the ad group to update.
    </ParamField>

    <ParamField body="name" type="string">
      Ad group name.
    </ParamField>

    <ParamField body="cpc_bid_micros" type="integer">
      Default CPC bid in micros.
    </ParamField>

    <ParamField body="keywords" type="PatchKeywordItem[]">
      Nested keyword patches for this ad group.

      <Expandable title="Keyword item fields">
        <ParamField body="id" type="integer" required>
          ID of the keyword to update.
        </ParamField>

        <ParamField body="text" type="string">
          Keyword text.
        </ParamField>

        <ParamField body="match_type" type="string">
          `"BROAD"`, `"PHRASE"`, or `"EXACT"`.
        </ParamField>

        <ParamField body="cpc_bid_micros" type="integer">
          Keyword-level CPC bid in micros (overrides ad group bid).
        </ParamField>

        <ParamField body="final_url" type="string">
          Landing page URL for this keyword.
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH https://dev-api.cattix.com/api/v1/campaigns/drafts/42/ad-groups \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "ad_groups": [
        {
          "id": 988,
          "name": "Brand Terms",
          "cpc_bid_micros": 750000,
          "keywords": [
            { "id": 27467, "text": "cattix crm", "match_type": "PHRASE" },
            { "id": 27468, "cpc_bid_micros": 1000000 }
          ]
        }
      ]
    }'
  ```

  ```python Python theme={null}
  resp = httpx.patch(
      "https://dev-api.cattix.com/api/v1/campaigns/drafts/42/ad-groups",
      headers={"Authorization": f"Bearer {token}"},
      json={
          "ad_groups": [
              {
                  "id": 988,
                  "name": "Brand Terms",
                  "cpc_bid_micros": 750_000,
                  "keywords": [
                      {"id": 27467, "text": "cattix crm", "match_type": "PHRASE"},
                      {"id": 27468, "cpc_bid_micros": 1_000_000},
                  ],
              }
          ]
      },
  )
  ```
</CodeGroup>

```json Response theme={null}
{
  "draft": { "id": 42, "...": "..." },
  "warnings": [
    {
      "field": "ad_groups[fa8820c2-d5fd-4ba6-9f78-0f4804df94a9].keywords",
      "code": "REQUIRED",
      "message": "Ad group 'Brand Terms' must have at least one keyword"
    }
  ]
}
```

<Note>
  If a referenced ad group or keyword ID does not belong to the draft, the
  endpoint returns **404** with details about which IDs are missing.
</Note>

***

## Step 3 — Ads

```
PATCH /api/v1/campaigns/drafts/{draft_id}/ads
```

Bulk-patch Responsive Search Ads (RSAs). Each item identifies the RSA by `id`.

### Request fields

<ParamField body="responsive_search_ads" type="PatchRSAItem[]" required>
  Array of RSA patches.

  <Expandable title="RSA item fields">
    <ParamField body="id" type="integer" required>
      ID of the RSA to update.
    </ParamField>

    <ParamField body="headlines" type="object[]">
      Array of headline objects with `text` and optional `pinned_field`.
    </ParamField>

    <ParamField body="descriptions" type="object[]">
      Array of description objects with `text` and optional `pinned_field`.
    </ParamField>

    <ParamField body="final_url" type="string">
      Landing page URL.
    </ParamField>

    <ParamField body="final_mobile_url" type="string">
      Mobile-specific landing page URL.
    </ParamField>

    <ParamField body="display_path_1" type="string">
      First display path segment (max 15 chars).
    </ParamField>

    <ParamField body="display_path_2" type="string">
      Second display path segment (max 15 chars).
    </ParamField>

    <ParamField body="tracking_url_template" type="string">
      Tracking URL template with `{lpurl}` placeholder.
    </ParamField>
  </Expandable>
</ParamField>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH https://dev-api.cattix.com/api/v1/campaigns/drafts/42/ads \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "responsive_search_ads": [
        {
          "id": 3315,
          "headlines": [
            { "text": "CATTIX" },
            { "text": "Source Inventory Smarter" },
            { "text": "Get Started for Free" }
          ],
          "final_url": "https://cattix.com/research-pro"
        }
      ]
    }'
  ```

  ```python Python theme={null}
  resp = httpx.patch(
      "https://dev-api.cattix.com/api/v1/campaigns/drafts/42/ads",
      headers={"Authorization": f"Bearer {token}"},
      json={
          "responsive_search_ads": [
              {
                  "id": 3315,
                  "headlines": [
                      {"text": "CATTIX"},
                      {"text": "Source Inventory Smarter"},
                      {"text": "Get Started for Free"},
                  ],
                  "final_url": "https://cattix.com/research-pro",
              }
          ]
      },
  )
  ```
</CodeGroup>

```json Response theme={null}
{
  "draft": { "id": 42, "...": "..." },
  "warnings": [
    {
      "field": "rsas.final_url",
      "code": "REQUIRED",
      "message": "2 RSA(s) are missing final_url. Please set landing page URLs."
    }
  ]
}
```

***

## Step 4 — Extensions

```
PATCH /api/v1/campaigns/drafts/{draft_id}/extensions
```

Update the draft's ad extensions (sitelinks, callouts, phone, structured
snippets).

### Request fields

<ParamField body="extensions" type="object">
  Extensions configuration object.

  <Expandable title="Extensions fields">
    <ParamField body="sitelinks" type="object[]">
      2–20 sitelink extensions, each with `link_text`, `final_url`, and optional
      `description_1` / `description_2`.
    </ParamField>

    <ParamField body="callouts" type="object[]">
      Up to 20 callout extensions, each with `callout_text`.
    </ParamField>

    <ParamField body="phone" type="object">
      Phone extension with `country_code` and `phone_number`.
    </ParamField>

    <ParamField body="structured_snippets" type="object[]">
      Structured snippet extensions with `header` and `values`.
    </ParamField>
  </Expandable>
</ParamField>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH https://dev-api.cattix.com/api/v1/campaigns/drafts/42/extensions \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "extensions": {
        "sitelinks": [
          { "link_text": "Pricing", "final_url": "https://example.com/pricing" },
          { "link_text": "Contact Us", "final_url": "https://example.com/contact" }
        ],
        "callouts": [
          { "callout_text": "Free Trial" },
          { "callout_text": "24/7 Support" }
        ]
      }
    }'
  ```

  ```python Python theme={null}
  resp = httpx.patch(
      "https://dev-api.cattix.com/api/v1/campaigns/drafts/42/extensions",
      headers={"Authorization": f"Bearer {token}"},
      json={
          "extensions": {
              "sitelinks": [
                  {"link_text": "Pricing", "final_url": "https://example.com/pricing"},
                  {"link_text": "Contact Us", "final_url": "https://example.com/contact"},
              ],
              "callouts": [
                  {"callout_text": "Free Trial"},
                  {"callout_text": "24/7 Support"},
              ],
          }
      },
  )
  ```
</CodeGroup>

```json Response theme={null}
{
  "draft": { "id": 42, "...": "..." },
  "warnings": []
}
```

***

## How warnings work

Every step endpoint runs its own set of validators **after** saving. This means:

1. Your changes are **always persisted**, even if warnings appear.
2. Warnings highlight issues that would block campaign submission (e.g. missing
   budget, too few keywords).
3. You can safely save incomplete data and come back later — the wizard is
   designed for iterative editing.

<Tabs>
  <Tab title="Typical warnings">
    | Step              | Field                           | Code            | Message                                                          |
    | ----------------- | ------------------------------- | --------------- | ---------------------------------------------------------------- |
    | Campaign Settings | `bidding_strategy`              | `REQUIRED`      | Bidding strategy is required                                     |
    | Campaign Settings | `daily_budget_micros`           | `REQUIRED`      | Daily budget is required                                         |
    | Campaign Settings | `end_date`                      | `INVALID_RANGE` | End date must be after start date                                |
    | Ad Groups         | `ad_groups[{temp_id}].keywords` | `REQUIRED`      | Ad group '{name}' must have at least one keyword                 |
    | Ads               | `rsas`                          | `REQUIRED`      | At least one completed RSA is required                           |
    | Ads               | `rsas.final_url`                | `REQUIRED`      | {n} RSA(s) are missing final\_url. Please set landing page URLs. |
    | Extensions        | `extensions.sitelinks`          | `MIN_COUNT`     | At least 2 sitelinks are required when sitelinks are provided    |
  </Tab>

  <Tab title="Warning object">
    ```json theme={null}
    {
      "field": "bidding_strategy",
      "code": "REQUIRED",
      "message": "Bidding strategy is required"
    }
    ```
  </Tab>
</Tabs>

<Note>
  Warnings do **not** prevent saving. Treat them as a checklist the user should
  resolve before submitting the campaign for review.
</Note>
