{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/growomat-com/json-schema/growomat-com-campaign-update-schema.json",
  "title": "CampaignUpdate",
  "description": "Partial update of a campaign: any subset of `CampaignCreate` fields. Omitted fields keep their values.",
  "x-generated": "2026-11-10",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/_original/growomat-com-api-openapi.json#/components/schemas/CampaignUpdate",
  "type": "object",
  "properties": {
    "_native": {
      "$ref": "#/$defs/NativeExtensions"
    },
    "_platformBudgets": {
      "$ref": "#/$defs/PlatformBudgets"
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "description": "Campaign name, shown in the app and on the platforms."
    },
    "budget": {
      "type": "number",
      "exclusiveMinimum": 0,
      "description": "Daily budget, in `currency`."
    },
    "currency": {
      "type": "string",
      "minLength": 1,
      "maxLength": 3,
      "description": "ISO 4217 currency code of `budget`, for example `EUR` or `USD`."
    },
    "status": {
      "type": "string",
      "enum": [
        "DRAFT",
        "ENABLED",
        "PAUSED",
        "REMOVED"
      ],
      "default": "PAUSED",
      "description": "`PAUSED` (the default) is safe: the campaign exists on the platforms after a sync but does not serve. `ENABLED` serves after the next sync. `DRAFT` is an unconfirmed build that is never synced until it is confirmed (*Confirm a draft campaign*); no other status change can take a campaign out of `DRAFT`."
    },
    "type": {
      "type": "string",
      "enum": [
        "SEARCH",
        "DISPLAY",
        "VIDEO",
        "SHOPPING",
        "PERFORMANCE_MAX"
      ],
      "default": "SEARCH",
      "description": "The campaign type. It decides which ad formats and which asset roles apply."
    },
    "networks": {
      "type": "string",
      "description": "Platform network setting, as the platform names it."
    },
    "languages": {
      "type": "string",
      "default": "en",
      "description": "Language of the audience, as a language code."
    },
    "businessId": {
      "type": "string",
      "description": "The business this campaign advertises. Generated copy and the brand voice come from it."
    },
    "goal": {
      "type": "string",
      "description": "What the campaign should achieve, in your own words. Used when Growomat drafts ads and keywords for it."
    },
    "geoTargets": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/GeoTarget"
      },
      "description": "Where the ads may show: named areas and radius circles in one include/exclude list. Empty means no location restriction; the ads can serve anywhere."
    },
    "locationIntent": {
      "type": "string",
      "enum": [
        "PRESENCE",
        "PRESENCE_OR_INTEREST"
      ],
      "default": "PRESENCE",
      "description": "Who counts as being in the targeted places. `PRESENCE` (default): people who are in, or regularly in, those places. `PRESENCE_OR_INTEREST`: also people elsewhere who search about them. The second is wider and much less qualified; it is the platforms' own default, which this API deliberately does not inherit."
    },
    "customLabels": {
      "type": "array",
      "maxItems": 10,
      "items": {
        "type": "string",
        "minLength": 1,
        "maxLength": 50
      },
      "description": "Up to ten free-form labels for your own grouping and reporting."
    },
    "adSchedule": {
      "$ref": "#/$defs/CampaignAdScheduleField"
    },
    "adScheduleUseSearcherTimeZone": {
      "type": "boolean",
      "description": "Interpret the ad schedule in the searcher's local time zone instead of the account time zone, on platforms that support it (Microsoft Advertising). Ignored elsewhere."
    },
    "biddingStrategy": {
      "$ref": "#/$defs/BiddingStrategy"
    },
    "targeting": {
      "$ref": "#/$defs/CampaignTargeting"
    },
    "trackingUrlTemplate": {
      "type": "string",
      "description": "Tracking template applied to every ad in the campaign, for example `{lpurl}?src=ads`."
    },
    "finalUrlSuffix": {
      "type": "string",
      "description": "Query string appended to every final URL in the campaign."
    },
    "urlCustomParameters": {
      "type": "object",
      "additionalProperties": {
        "type": "string"
      },
      "description": "Custom `{_name}` parameters available to tracking templates."
    },
    "negativeKeywords": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/NegativeKeywordEntry"
      },
      "description": "Search terms the whole campaign must not show for."
    }
  },
  "$defs": {
    "BiddingStrategy": {
      "type": "object",
      "description": "How the platform bids for this campaign.",
      "properties": {
        "type": {
          "type": "string",
          "enum": [
            "MANUAL_CPC",
            "ENHANCED_CPC",
            "MAXIMIZE_CLICKS",
            "MAXIMIZE_CONVERSIONS",
            "MAXIMIZE_CONVERSION_VALUE",
            "TARGET_CPA",
            "TARGET_ROAS",
            "TARGET_IMPRESSION_SHARE"
          ],
          "default": "MAXIMIZE_CLICKS",
          "description": "The bidding strategy. Target strategies read their target from the matching field below."
        },
        "targetCpa": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Target cost per acquisition, for `TARGET_CPA`."
        },
        "targetRoas": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Target return on ad spend as a ratio (2.5 means 250%), for `TARGET_ROAS`."
        },
        "targetImpressionShare": {
          "type": "number",
          "minimum": 0,
          "maximum": 100,
          "description": "Target impression share in percent, for `TARGET_IMPRESSION_SHARE`."
        },
        "maxCpcBidCeiling": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Never bid more than this per click."
        },
        "cpcBid": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "The click bid for `MANUAL_CPC`, in the account currency. Every ad group without its own `cpcBid` bids this; an ad group's own `cpcBid` wins."
        }
      }
    },
    "CampaignAdScheduleField": {
      "description": "The campaign's ad schedule, as a preset name (`ALL_HOURS_ALL_DAYS`, `BUSINESS_HOURS`, `CUSTOM`) or as a `StructuredSchedule`. On read, `adSchedule` returns what you last wrote and `adScheduleStructured` always holds the expanded weekly schedule. `CUSTOM` is treated as `ALL_HOURS_ALL_DAYS`.",
      "oneOf": [
        {
          "type": "string",
          "enum": [
            "ALL_HOURS_ALL_DAYS",
            "BUSINESS_HOURS",
            "CUSTOM"
          ]
        },
        {
          "$ref": "#/$defs/StructuredSchedule"
        }
      ]
    },
    "CampaignTargeting": {
      "type": "object",
      "description": "Additional targeting: schedule, devices and platform-specific audience settings. Locations live in the campaign's `geoTargets` list, not here.",
      "properties": {
        "schedule": {
          "type": "array",
          "items": {
            "type": "object"
          }
        },
        "devices": {
          "type": "object"
        },
        "linkedin": {
          "type": "object"
        }
      }
    },
    "GeoTarget": {
      "type": "object",
      "required": [
        "kind",
        "name"
      ],
      "description": "One row of a campaign's location targeting: a named area or a radius circle, included or excluded.",
      "properties": {
        "id": {
          "type": "string",
          "description": "Stable row id. Generated when omitted."
        },
        "kind": {
          "type": "string",
          "enum": [
            "AREA",
            "RADIUS"
          ],
          "description": "`AREA` is a named place, from a country down to a postcode. `RADIUS` is a circle around a point."
        },
        "negative": {
          "type": "boolean",
          "default": false,
          "description": "`true` excludes this row from the included set. Not allowed on `RADIUS` rows (`400 RADIUS_EXCLUSION_UNSUPPORTED`), and a set where every row is negative is rejected (`400 ONLY_EXCLUSIONS`)."
        },
        "bidAdjustment": {
          "type": "number",
          "description": "Percent bid adjustment for this row. Included rows only."
        },
        "reach": {
          "type": "number",
          "description": "Audience estimate as reported by the platform, when it gave one. Read-only; never estimated by Growomat."
        },
        "criterionId": {
          "type": [
            "string",
            "null"
          ],
          "description": "Areas: the Google Ads geo target id."
        },
        "msLocationId": {
          "type": [
            "string",
            "null"
          ],
          "description": "Areas: the Microsoft Advertising location id."
        },
        "canonicalId": {
          "type": [
            "string",
            "null"
          ],
          "description": "Areas: Growomat's platform-independent id for the place."
        },
        "name": {
          "type": "string",
          "description": "Display name of the place or circle."
        },
        "type": {
          "type": "string",
          "enum": [
            "COUNTRY",
            "REGION",
            "METRO",
            "CITY",
            "DISTRICT",
            "POSTAL_CODE",
            "AIRPORT",
            "UNIVERSITY",
            "GLOBAL"
          ],
          "description": "Areas only: what kind of place this is."
        },
        "countryCode": {
          "type": [
            "string",
            "null"
          ],
          "description": "Areas: ISO 3166-1 country code."
        },
        "lat": {
          "type": "number",
          "minimum": -90,
          "maximum": 90,
          "description": "Circles only: latitude of the centre."
        },
        "lng": {
          "type": "number",
          "minimum": -180,
          "maximum": 180,
          "description": "Circles only: longitude of the centre."
        },
        "radius": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Circles only. Clamped to the platform's range (1 to 500) when pushed; a clamp is reported as a `RADIUS_CLAMPED_TO_MIN` warning."
        },
        "unit": {
          "type": "string",
          "enum": [
            "km",
            "mi"
          ],
          "default": "km",
          "description": "Circles only: the unit of `radius`."
        }
      }
    },
    "NativeExtensions": {
      "type": "object",
      "description": "Settings that exist on one platform only, keyed by platform id (for example `google-ads`). Each value is a free-form object that is stored and returned exactly as sent; keys Growomat does not know are kept. Absent by default.\n\nKeys read by platforms whose ad is a post (on the ad, with the campaign as fallback): `postType` (`TEXT` or `IMAGE`), `postTitle`, `postBody`, `callToAction`, `imageUrl`, `videoUrl`, `videoThumbnailUrl`, `carouselCards` (`[{ imageUrl, destinationUrl }]`), `communities`, `interests`.\n\nDelivery keys read on the campaign: `objective`, `goalType`, `startTime`, `endTime`, `optimizationGoal`, `conversionPixelId`.",
      "additionalProperties": {
        "type": "object",
        "additionalProperties": true
      },
      "properties": {
        "openai-ads": {
          "type": "object",
          "description": "ChatGPT Ads settings. Read on the campaign: `budgetKind`, `biddingType`, `startTime`, `endTime`, `platforms`, `customAudienceIds`, `excludedCustomAudienceIds`, `conversionEventSettingId`, `queryStringTemplate`, `description`, `imageUrl`, `postTitle`, `postBody`; on the ad group: `strategy`, `maxBid`, `cpmBid`, `description`; on the ad: `imageUrl`, `postTitle`, `postBody`, `imageCrop`, `price`. Unknown keys are kept.",
          "properties": {
            "budgetKind": {
              "type": "string",
              "enum": [
                "DAILY",
                "LIFETIME"
              ],
              "default": "DAILY",
              "description": "On the campaign: `LIFETIME` makes `budget` the total for the campaign's whole run instead of a day's. The lifetime minimum is 1 unit of the account currency; the daily minimum is per currency (EUR 15). One-way: once pushed, a lifetime budget can never become daily. Maximize-results strategies need `DAILY`."
            },
            "biddingType": {
              "type": "string",
              "enum": [
                "CLICKS",
                "IMPRESSIONS",
                "CONVERSIONS"
              ],
              "description": "On the campaign: What the campaign buys. Set once, at the first push. Defaults from `biddingStrategy` (clicks for a CPC strategy, conversions for a CPA strategy); `IMPRESSIONS` is reachable only here."
            },
            "startTime": {
              "type": "string",
              "format": "date-time",
              "description": "On the campaign: When delivery starts. Falls back to `startDate`; absent means as soon as it is enabled."
            },
            "endTime": {
              "type": "string",
              "format": "date-time",
              "description": "On the campaign: When delivery ends. Falls back to `endDate` (end of that day, UTC)."
            },
            "platforms": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "ios_app",
                  "android_app",
                  "web",
                  "desktop_web",
                  "ios_web",
                  "android_web"
                ]
              },
              "description": "On the campaign: The ChatGPT surfaces to deliver on. Absent means all. `web` covers every web surface, so it is never combined with the granular web values."
            },
            "customAudienceIds": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "On the campaign: Custom audiences to reach."
            },
            "excludedCustomAudienceIds": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "On the campaign: Custom audiences to exclude."
            },
            "conversionEventSettingId": {
              "type": "string",
              "description": "On the campaign: The conversion event setting a conversions campaign optimises for (required when `biddingType` is `CONVERSIONS`; fixed after the first push). Defaults to the connection's."
            },
            "queryStringTemplate": {
              "type": "string",
              "description": "On the campaign: Query parameters appended to every destination URL, for example `utm_source=chatgpt&utm_medium=cpc`. The platform's own click id is added by the platform and is dropped here."
            },
            "description": {
              "type": "string",
              "description": "On the campaign: An internal note on the campaign. On the ad group: An internal note on the ad group."
            },
            "imageUrl": {
              "type": "string",
              "format": "uri",
              "description": "On the campaign: One image for every chat card in the campaign that has none of its own. On the ad: The chat card's image (at least 640 × 640). Wins over an image bound through *Ad assets*; `creative.imageUrl` wins over this."
            },
            "postTitle": {
              "type": "string",
              "maxLength": 50,
              "description": "On the campaign: One title for every chat card that sets none of its own. On the ad: The card's title as it will read (3–50 characters). Defaults to the first headline."
            },
            "postBody": {
              "type": "string",
              "maxLength": 100,
              "description": "On the campaign: One body for every chat card that sets none of its own. On the ad: The card's body as it will read (up to 100 characters). Defaults to the first description."
            },
            "strategy": {
              "type": "string",
              "enum": [
                "fixed_bid",
                "maximize_clicks",
                "maximize_conversions"
              ],
              "description": "On the ad group: How the ad group bids. `fixed_bid` bids `cpcBid` (or `maxBid` / `cpmBid`); a `maximize_*` strategy needs a daily budget. Must suit the campaign's `biddingType`."
            },
            "maxBid": {
              "type": "number",
              "minimum": 0,
              "description": "On the ad group: The fixed bid in the account currency, when `cpcBid` does not apply (a CPA bid on a conversions campaign)."
            },
            "cpmBid": {
              "type": "number",
              "minimum": 0,
              "description": "On the ad group: For an `IMPRESSIONS` campaign: the bid per thousand impressions, in the account currency."
            },
            "imageCrop": {
              "type": "object",
              "required": [
                "x",
                "y",
                "width",
                "height"
              ],
              "properties": {
                "x": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1
                },
                "y": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1
                },
                "width": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1
                },
                "height": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1
                }
              },
              "description": "On the ad: A square crop of the image, each value a fraction (0–1) of the source."
            },
            "price": {
              "type": "string",
              "maxLength": 100,
              "description": "On the ad: A price shown on the card, as written (for example `From €49`)."
            }
          },
          "additionalProperties": true
        }
      }
    },
    "NegativeKeywordEntry": {
      "type": "object",
      "required": [
        "text",
        "matchType"
      ],
      "description": "A search term the campaign must not show for.",
      "properties": {
        "text": {
          "type": "string",
          "description": "The term."
        },
        "matchType": {
          "type": "string",
          "enum": [
            "EXACT",
            "PHRASE",
            "BROAD"
          ],
          "description": "How closely a search must match the term."
        }
      }
    },
    "PlatformBudgets": {
      "type": "object",
      "description": "Where this campaign runs, and how much of the budget each platform gets, keyed by platform id (`reddit-ads`, `microsoft-ads`, and so on).\n\n**A platform not listed here is not part of the campaign.** It is never exported to and never spends, whatever is connected. At least one entry must be switched on.\n\nAn entry without `amount` gets an even share of the budget the listed amounts leave, so `{ \"reddit-ads\": {} }` is the whole budget on Reddit Ads. `percentage` is computed from `amount` on every write; sending it is ignored. The split is checked against the campaign budget and rejected when it exceeds it.",
      "additionalProperties": {
        "type": "object",
        "properties": {
          "amount": {
            "type": "number",
            "minimum": 0,
            "description": "This platform's daily share of the budget. Omit it for an even share of what is left. `0` is valid but the platform then has nothing to spend."
          },
          "enabled": {
            "type": "boolean",
            "description": "Whether the campaign runs on this platform. Absent means on; `false` keeps the amount but takes the platform out (a live one is paused at the next sync)."
          },
          "percentage": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "readOnly": true,
            "description": "Share of the campaign budget, computed from `amount`."
          }
        }
      }
    },
    "ScheduleWindow": {
      "type": "object",
      "required": [
        "day",
        "startHour",
        "endHour"
      ],
      "description": "One block of time on one day of the week during which ads may show.",
      "properties": {
        "day": {
          "type": "string",
          "enum": [
            "MONDAY",
            "TUESDAY",
            "WEDNESDAY",
            "THURSDAY",
            "FRIDAY",
            "SATURDAY",
            "SUNDAY"
          ],
          "description": "The day of the week."
        },
        "startHour": {
          "type": "integer",
          "minimum": 0,
          "maximum": 23,
          "description": "Hour the window opens (0 to 23), in the schedule's time zone."
        },
        "startMinute": {
          "type": "string",
          "enum": [
            "ZERO",
            "FIFTEEN",
            "THIRTY",
            "FORTY_FIVE"
          ],
          "default": "ZERO",
          "description": "Minute the window opens, in quarter hours."
        },
        "endHour": {
          "type": "integer",
          "minimum": 0,
          "maximum": 24,
          "description": "Hour the window closes (0 to 24). `24` means end of day."
        },
        "endMinute": {
          "type": "string",
          "enum": [
            "ZERO",
            "FIFTEEN",
            "THIRTY",
            "FORTY_FIVE"
          ],
          "default": "ZERO",
          "description": "Minute the window closes, in quarter hours."
        },
        "bidModifier": {
          "type": "number",
          "minimum": 0.1,
          "maximum": 10,
          "description": "Bid multiplier for this window; `1.0` means no change. Platforms that express this as a percentage receive the converted value, clamped to their range; a clamp is reported as a `BID_ADJUSTMENT_CLAMPED` warning."
        }
      }
    },
    "StructuredSchedule": {
      "type": "object",
      "required": [
        "windows"
      ],
      "description": "A weekly ad schedule: up to 84 windows and the time zone they are in.",
      "properties": {
        "windows": {
          "type": "array",
          "maxItems": 84,
          "items": {
            "$ref": "#/$defs/ScheduleWindow"
          },
          "description": "The windows during which ads may show."
        },
        "timezone": {
          "type": "string",
          "description": "IANA time zone the hours are in, for example `Europe/Berlin`. Defaults to the account time zone."
        }
      }
    }
  }
}