{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/growsurf-com/json-schema/growsurf-com-campaign-design-schema.json",
  "title": "CampaignDesign",
  "description": "A program's design configuration. It includes the dashboard Program Editor's Design tab and the payout-destination confirmation page copy configured from payout integration cards. The exact fields available depend on the program type (for example, `referralSummary` is referral-only, while `affiliateSummary`, `commissions`, and `payouts` are affiliate-only). `participantSettings` is available to both program types; its manual payout and Wise fields are affiliate-only. `GET` returns the fields configured for the program; `payoutDestinationConfirmation` is omitted when no confirmation fields are stored. Stored `null` fields are returned as `null`; omitted and `null` fields use localized defaults. `PATCH` back only the sections or fields you want to change (arrays such as `signup.fields` replace wholesale). Text fields use the same character limits as the dashboard Program Editor. Text the Program Editor requires, such as Window labels, button text, and status names, cannot be set to `null`, empty, or only spaces. A value that is already saved is accepted unchanged, even when it breaks a newer limit.",
  "x-generated": "2026-11-10",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/growsurf-com-openapi.json#/components/schemas/CampaignDesign",
  "type": "object",
  "properties": {
    "participantAvatarStyle": {
      "type": "string",
      "enum": [
        "CHARACTERS",
        "INITIALS",
        "ANIMALS",
        "GRADIENT"
      ],
      "default": "INITIALS",
      "description": "The participant avatar style. New programs start with `INITIALS`. Programs with a missing or unknown stored value return `INITIALS` for backward compatibility."
    },
    "visualStyle": {
      "type": "string",
      "enum": [
        "GLOW",
        "CLEAN",
        "FLAT",
        "ROUND",
        "TINT",
        "GLASS"
      ],
      "description": "The program Style. It sets corners, shadows, button shape and background on your program pages and GrowSurf Window. New referral programs start with `GLOW`, and new affiliate programs start with `CLEAN`. Omitted when the program has no saved Style; those programs keep their original look."
    },
    "window": {
      "type": "object",
      "description": "How the sections inside the GrowSurf window are laid out. `navigationMode` chooses between a tabbed layout (`TABS`) and a single scrolling list (`LIST`)."
    },
    "header": {
      "type": "object",
      "description": "Header content for participants (`postText`) and non-participants (`preText`), and the progress labels beside a participant's numbers: `referralsMadeText` (up to 100 characters, shown for single-sided, double-sided, and milestone rewards) and `yourRankText` (up to 100 characters, shown for leaderboard rewards). Both labels are required."
    },
    "stats": {
      "type": "object",
      "description": "The stats panel a participant sees at the top of the GrowSurf window, summarizing their own referral progress. Only its heading text (`title`) is editable here."
    },
    "share": {
      "type": "object",
      "description": "Share channels (`type.<channel>.{isVisible,buttonText,message,order}`), invite settings, and share-button styling."
    },
    "signup": {
      "type": "object",
      "description": "Signup form (`fields[]`, GDPR consent, button and login text)."
    },
    "login": {
      "description": "Customizable text shown during participant sign-in.",
      "$ref": "#/$defs/ParticipantLoginDesign"
    },
    "payoutDestinationConfirmation": {
      "description": "Customizable text for the participant-facing payout-destination confirmation page.",
      "$ref": "#/$defs/PayoutDestinationConfirmationDesign"
    },
    "countryLabels": {
      "type": "object",
      "description": "Participant-facing country-name overrides, keyed by ISO 3166-1 alpha-2 code (for example `GB`). Each label replaces the default country name wherever participants pick a country, such as payout and tax forms. Overrides merge per code, so send only the codes you want to change; send `null` (or the default name) for a code to restore its default. Unknown codes are rejected.",
      "additionalProperties": {
        "type": [
          "string",
          "null"
        ],
        "maxLength": 500
      }
    },
    "referralStatus": {
      "type": "object",
      "description": "The Referral Status section, which lists the people a participant has invited and each one's progress (invite sent, pending, awarded, or expired). Controls its title, icon, column and status labels, empty-state text, and email masking. `revealInviteEmails` lets a signed-in participant see original invitation addresses when disclosure permission was recorded. Private invites and full email masking keep addresses hidden. Other referral addresses stay masked. `revealInviteEmailText` supplies the sign-in prompt; `loadErrorMessage` is shown when the list cannot load; `followUp` supplies the invitation follow-up labels and messages. `emptyStateGraphic` sets the graphic shown when the section has no entries, in the `CampaignDesignEmptyStateGraphic` shape: `type` (`DEFAULT`, `IMAGE`, or `NONE`), `key` (the starter graphic used when `type` is `DEFAULT`), and `imageUrl` (the HTTP or HTTPS image used when `type` is `IMAGE`; send `null` to clear it). `statusTooltip` sets the tooltip text for each referral status. `sortFilter` controls the sort and filter menu: `isEnabled` shows or hides it, and the other fields set its labels and options."
    },
    "leaderboard": {
      "type": "object",
      "description": "The Leaderboard section that ranks participants against each other. Controls its title, icon, column labels, which time-period and ranking selectors appear, empty-state text, and how other participants' names are masked. `emptyStateGraphic` sets the graphic shown when the section has no entries, in the `CampaignDesignEmptyStateGraphic` shape: `type` (`DEFAULT`, `IMAGE`, or `NONE`), `key` (the starter graphic used when `type` is `DEFAULT`), and `imageUrl` (the HTTP or HTTPS image used when `type` is `IMAGE`; send `null` to clear it)."
    },
    "referredExperience": {
      "type": "object",
      "description": "The banner, headline, and Claim Offer Popup shown to someone who arrives on your site through a referral link. Controls their copy and placement, whether the popup is enabled, and how the referrer's name is formatted. The popup is available for both referral and affiliate programs. The `offerPage*` fields set the side content of the GrowSurf page that shows the popup when `installation.offerPageRedirectSeconds` is set.",
      "properties": {
        "isOfferPopupEnabled": {
          "type": "boolean",
          "description": "Whether referred visitors see the Claim Offer Popup. Affiliate programs start with it disabled until you opt in."
        },
        "offerPopupTitle": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "Popup heading. Supports the same dynamic-text variables as the dashboard editor."
        },
        "offerPopupDescription": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "Text shown below the popup heading."
        },
        "offerPopupButtonText": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 100,
          "description": "Text on the button that saves the visitor's offer."
        },
        "offerPopupImageUrl": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 500,
          "description": "Optional image shown in the popup."
        },
        "isOfferPopupReferrerImageShown": {
          "type": "boolean",
          "description": "Whether the popup shows the referrer's profile image when one is available."
        },
        "offerPopupPlacement": {
          "type": "string",
          "enum": [
            "CENTER",
            "BOTTOM",
            "BOTTOM_RIGHT",
            "BOTTOM_LEFT",
            "TOP"
          ],
          "description": "Where the popup appears. Small screens may use a bottom sheet."
        },
        "offerPopupDelaySeconds": {
          "type": "integer",
          "enum": [
            0,
            3,
            5,
            10
          ],
          "description": "Seconds to wait before showing the popup."
        },
        "offerPopupThankYouText": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "Message shown after the visitor saves the offer."
        },
        "offerPopupThankYouButtonText": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 100,
          "description": "Text on the post-claim button that opens the configured signup URL."
        },
        "isOfferPopupConfettiEnabled": {
          "type": "boolean",
          "description": "Whether to show confetti after the visitor saves the offer."
        },
        "isOfferPopupShownOnAllPages": {
          "type": "boolean",
          "description": "Whether the popup can appear on every page where GrowSurf is installed instead of only the Share URL."
        },
        "offerPopupSecondaryLinkText": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 100,
          "description": "Optional secondary-link text shown after the offer is saved."
        },
        "offerPopupSecondaryLinkUrl": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "Destination for the optional secondary link. Must start with `http://` or `https://`. Send `null` or an empty string to clear it."
        },
        "isOfferPopupOverlayDimmed": {
          "type": "boolean",
          "description": "Whether the centered popup dims the page behind it."
        },
        "offerPopupEmailPlaceholder": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 100,
          "description": "Hint text inside the popup's email field. Leave it empty to reuse the email field label from your signup form."
        },
        "offerPopupPromoCodeCopyLabel": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 100,
          "description": "Text on the copy action beside the promo code, shown after the offer is claimed when an upfront-discount coupon is enabled."
        },
        "offerPopupSubmitError": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "Message shown inside the popup when the offer cannot be saved, such as during a connection problem."
        },
        "offerPageDescription": {
          "type": "string",
          "maxLength": 150000,
          "description": "Text shown beside the popup on the GrowSurf page that `installation.offerPageRedirectSeconds` turns on. Accepts HTML and the same dynamic-text variables as the dashboard editor; unsafe HTML is removed when saved. Send an empty string to clear it. Leave the description and steps empty to show the popup alone."
        },
        "offerPageSteps": {
          "type": "array",
          "maxItems": 5,
          "description": "The \"How it works\" steps shown beside the popup on the GrowSurf page. Sending this array replaces the entire list; send `[]` to remove every step.",
          "items": {
            "type": "object",
            "required": [
              "iconKey"
            ],
            "properties": {
              "text": {
                "type": "string",
                "maxLength": 255,
                "description": "Step text. Accepts the same dynamic-text variables as the dashboard editor."
              },
              "iconKey": {
                "type": "string",
                "minLength": 1,
                "maxLength": 100,
                "description": "The step's icon, using the keys from the Program Editor's icon picker, such as `SIGNUP`, `APPROVED`, or `REWARD`. The icons use your button color."
              }
            }
          }
        },
        "offerPageLinks": {
          "type": "array",
          "maxItems": 3,
          "description": "Links shown beside the popup on the GrowSurf page. They open in a new tab, so the friend can still save their offer. Sending this array replaces the entire list; send `[]` to remove every link.",
          "items": {
            "type": "object",
            "properties": {
              "text": {
                "type": "string",
                "maxLength": 100,
                "description": "Link text."
              },
              "url": {
                "type": "string",
                "maxLength": 500,
                "description": "Link destination. Must start with `http://` or `https://`. Send an empty string to link to your share URL."
              }
            }
          }
        },
        "isBannerEnabled": {
          "type": "boolean",
          "description": "Whether referred visitors see the welcome banner, a sticky banner that follows them as they scroll your Share URL."
        },
        "bannerText": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "Welcome banner copy. Accepts HTML and the same dynamic-text variables as the dashboard editor."
        },
        "bannerPlacement": {
          "type": "string",
          "enum": [
            "TOP",
            "BOTTOM"
          ],
          "description": "Where the welcome banner sits on the page."
        },
        "isBannerClickableToSignupUrl": {
          "type": "boolean",
          "description": "Whether clicking the welcome banner opens the program's signup URL. It has no effect until a signup URL is configured."
        },
        "isHeadingEnabled": {
          "type": "boolean",
          "description": "Whether referred visitors see the inline welcome message added to the main heading on your Share URL."
        },
        "headingText": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "Inline welcome message copy. Accepts HTML and the same dynamic-text variables as the dashboard editor."
        },
        "headingTarget": {
          "type": "string",
          "enum": [
            "H1",
            "H2",
            "H3",
            "H4",
            "H5"
          ],
          "description": "Which heading element on the page the inline welcome message attaches to. GrowSurf uses the first match."
        },
        "headingPlacement": {
          "type": "string",
          "enum": [
            "PREPEND",
            "APPEND",
            "REPLACE"
          ],
          "description": "Whether the inline welcome message goes before the target heading, after it, or replaces it."
        },
        "isHeadingStyled": {
          "type": "boolean",
          "description": "Whether the inline welcome message keeps its own styling. When `false` it renders as plain text."
        },
        "isHeadingClickableToSignupUrl": {
          "type": "boolean",
          "description": "Whether clicking the inline welcome message opens the program's signup URL. It has no effect until a signup URL is configured."
        },
        "isPageTitleReplacementActive": {
          "type": "boolean",
          "description": "Whether referred visitors see `pageTitleReplacement` as the browser tab title. New referral programs start with `true` and new affiliate programs with `false`. Older programs without this setting read back `false`."
        },
        "pageTitleReplacement": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "Browser tab title shown to referred visitors on your Share URL, in place of the page's own title."
        },
        "referrerNameFormat": {
          "type": "string",
          "enum": [
            "NONE",
            "FIRST",
            "FIRST_LAST_INITIAL",
            "FIRST_LAST"
          ],
          "description": "How the referrer's name appears in referred-visitor copy — `NONE` uses `referrerNameFallback`, `FIRST` shows `Gavin`, `FIRST_LAST_INITIAL` shows `Gavin B`, and `FIRST_LAST` shows `Gavin Belson`."
        },
        "referrerNameFallback": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 100,
          "description": "Name used in referred-visitor copy when the referrer has no name on file or `referrerNameFormat` is `NONE`."
        }
      }
    },
    "widget": {
      "type": "object",
      "description": "The website widget — the invite that sits in a corner of your own site. It renders as a button or as a card, and its card folds back into the button when a visitor closes it. Both audience switches start off, so a program shows nothing until you turn one on.",
      "properties": {
        "isShownToNewVisitors": {
          "type": "boolean",
          "description": "Whether people who have not joined your program see the widget."
        },
        "isShownToParticipants": {
          "type": "boolean",
          "description": "Whether people who have already joined see the widget."
        },
        "appearance": {
          "type": "string",
          "enum": [
            "BUTTON",
            "CARD"
          ],
          "description": "`BUTTON` is a single button in the corner. `CARD` is a small card with a heading, a line of text, and a button, which a visitor can close."
        },
        "isArtShown": {
          "type": "boolean",
          "description": "Whether the card shows a picture above its text. Ignored by the button."
        },
        "artImageUrl": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 500,
          "description": "The picture shown at the top of the card."
        },
        "newVisitorText": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 100,
          "description": "What people who have not joined read. It is the button's label, and the card's heading."
        },
        "participantText": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 100,
          "description": "What people who have already joined read. It is the button's label, and the card's heading."
        },
        "newVisitorDescription": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "The line under the heading for people who have not joined. Card only."
        },
        "participantDescription": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "The line under the heading for people who have already joined. Card only."
        },
        "buttonText": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 100,
          "description": "The label on the card's button, which opens your program."
        },
        "markKey": {
          "type": [
            "string",
            "null"
          ],
          "enum": [
            "GIFT",
            "TICKET",
            "DISCOUNT",
            "CASH",
            "PERK",
            "SHARE",
            "LINK",
            "INVITE",
            "FRIENDS",
            "THANKS",
            null
          ],
          "description": "The small drawing on the widget. It takes the color you chose for the widget, so it matches on any background. Send `null` for no drawing."
        },
        "icon": {
          "type": "string",
          "enum": [
            "CUSTOM",
            "NONE",
            "DEFAULT"
          ],
          "description": "Your own uploaded image instead of a `markKey` drawing. `CUSTOM` uses `iconImageUrl`; `NONE` shows no image. `DEFAULT` is the old GrowSurf image: a program already set to it keeps it and can read it back, but it cannot be set."
        },
        "iconImageUrl": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 500,
          "description": "Your own image, used when `icon` is `CUSTOM`."
        },
        "placement": {
          "type": "string",
          "enum": [
            "TOP_LEFT",
            "TOP_CENTER",
            "TOP_RIGHT",
            "BOTTOM_LEFT",
            "BOTTOM_CENTER",
            "BOTTOM_RIGHT"
          ],
          "description": "Which corner or edge of the page the widget sits against."
        },
        "offsetSide": {
          "type": "integer",
          "minimum": 0,
          "maximum": 400,
          "description": "How far in from the left or right edge, in pixels, following `placement`. For a centered placement it becomes an even gap on both sides."
        },
        "offsetEdge": {
          "type": "integer",
          "minimum": 0,
          "maximum": 400,
          "description": "How far in from the top or bottom edge, in pixels, following `placement`. Raise it to clear a chat button that already sits in that corner."
        },
        "reveal": {
          "type": "string",
          "enum": [
            "IMMEDIATE",
            "DELAY",
            "SCROLL"
          ],
          "description": "What the element waits for before it appears: the page loading (`IMMEDIATE`) or the visitor scrolling halfway down the page (`SCROLL`). `revealDelaySeconds` then counts from that moment. `DELAY` is accepted for older programs and behaves as `IMMEDIATE`."
        },
        "revealDelaySeconds": {
          "type": "integer",
          "minimum": 0,
          "maximum": 120,
          "description": "Seconds to wait after the `reveal` trigger before showing the element. `0` shows it the moment the trigger fires."
        },
        "returnAfterDays": {
          "type": "integer",
          "minimum": 0,
          "maximum": 365,
          "description": "Days before the card is offered again to someone who closed it. Until then they keep the button, so they can still open your program. `0` never offers it again."
        },
        "isHiddenOnMobile": {
          "type": "boolean",
          "description": "Whether to leave phones alone. On small screens the card fills the bottom of the page, so some programs turn it off there."
        },
        "clickAction": {
          "type": "string",
          "enum": [
            "WINDOW",
            "PROGRAM_PAGE"
          ],
          "description": "What opens when someone clicks the card's button or the bubble. `WINDOW` opens the GrowSurf Window on top of your page. `PROGRAM_PAGE` opens your Program Page in a new tab, where participants may need to sign in. New programs start with `PROGRAM_PAGE`; programs that never set it read back as `WINDOW`."
        },
        "pageRules": {
          "type": "object",
          "description": "Which pages the widget appears on. This controls the widget only — referral tracking, embedded elements, and opening the window from your own code keep working on every page where GrowSurf is installed.",
          "properties": {
            "mode": {
              "type": "string",
              "enum": [
                "ALL",
                "ONLY",
                "EXCEPT"
              ],
              "description": "`ALL` shows it everywhere. `ONLY` shows it just on the listed pages. `EXCEPT` shows it everywhere but the listed pages. With no pages listed, `ONLY` and `EXCEPT` behave as `ALL`."
            },
            "patterns": {
              "type": "array",
              "maxItems": 20,
              "items": {
                "type": "string",
                "maxLength": 500
              },
              "description": "The pages to match. Use `*` to stand in for anything, as in `/portal/*`. A path on its own, such as `/pricing`, matches that path on every domain you have installed. Web addresses are matched without their query string."
            }
          }
        }
      }
    },
    "trafficInsights": {
      "type": "object",
      "description": "The Traffic report participants can open from the GrowSurf window. It shows visits to their share link over time and where those visits came from. It starts on for new affiliate programs and hidden for referral programs. `GET` returns every setting, with the default copy for anything you have not changed. `PATCH` only the settings you want to change; the rest keep their current values. Labels cannot be blank.",
      "properties": {
        "isPublicDisplayed": {
          "type": "boolean",
          "description": "Whether participants can open the Traffic report."
        },
        "title": {
          "type": "string",
          "maxLength": 100,
          "description": "The report heading."
        },
        "trafficForLabel": {
          "type": "string",
          "maxLength": 100,
          "description": "The label in front of the share link picker."
        },
        "allLinksLabel": {
          "type": "string",
          "maxLength": 100,
          "description": "The share link picker option that combines all of the participant's links."
        },
        "visitsOverTimeTitle": {
          "type": "string",
          "maxLength": 100,
          "description": "The heading above the visits chart."
        },
        "breakdownsTitle": {
          "type": "string",
          "maxLength": 100,
          "description": "The heading above the breakdown table."
        },
        "viewTrafficInsightsLinkText": {
          "type": "string",
          "maxLength": 100,
          "description": "The text of the row or tab that opens the report."
        },
        "backLinkText": {
          "type": "string",
          "maxLength": 100,
          "description": "The text of the link back from the report."
        },
        "emptyState": {
          "type": "string",
          "maxLength": 255,
          "description": "The message shown before the participant's link has any visits. Can be empty."
        },
        "notSetLabel": {
          "type": "string",
          "maxLength": 100,
          "description": "The row name for visits that have no value for the chosen breakdown, such as a visit without `utm_source`."
        },
        "messages": {
          "type": "object",
          "description": "Messages shown when the report cannot show everything.",
          "properties": {
            "error": {
              "type": "string",
              "maxLength": 255,
              "description": "Shown when the report fails to load."
            },
            "unavailable": {
              "type": "string",
              "maxLength": 255,
              "description": "Shown when there is no traffic history for the chosen period."
            },
            "partial": {
              "type": "string",
              "maxLength": 255,
              "description": "Shown when only part of the chosen period is available."
            },
            "partialFrom": {
              "type": "string",
              "maxLength": 255,
              "description": "Like `partial`, with the first available date. `{{date}}` is replaced with that date."
            },
            "breakdownPartial": {
              "type": "string",
              "maxLength": 255,
              "description": "Shown when some breakdown rows may be missing."
            },
            "breakdownEmpty": {
              "type": "string",
              "maxLength": 255,
              "description": "Shown when the chosen breakdown has no rows."
            }
          }
        },
        "dateRangeLabels": {
          "type": "object",
          "additionalProperties": {
            "type": "string",
            "maxLength": 100
          },
          "description": "The date range picker labels, keyed by `LAST_7_DAYS`, `LAST_30_DAYS`, `LAST_90_DAYS`, and `ALL_TIME`."
        },
        "defaultDateRange": {
          "type": "string",
          "enum": [
            "LAST_7_DAYS",
            "LAST_30_DAYS",
            "LAST_90_DAYS",
            "ALL_TIME"
          ],
          "description": "The date range the report opens with."
        },
        "metrics": {
          "type": "object",
          "description": "The two visit counts at the top of the report.",
          "properties": {
            "visits": {
              "$ref": "#/$defs/CampaignDesignTrafficInsightsMetric",
              "description": "Every time someone opened the participant's link."
            },
            "uniqueVisitors": {
              "$ref": "#/$defs/CampaignDesignTrafficInsightsMetric",
              "description": "People who opened the participant's link, counted once each."
            }
          }
        },
        "breakdowns": {
          "type": "object",
          "description": "The tables that show where visits came from. You can hide or rename each one.",
          "properties": {
            "utm": {
              "$ref": "#/$defs/CampaignDesignTrafficInsightsBreakdown",
              "description": "Visits by UTM parameter."
            },
            "referrer": {
              "$ref": "#/$defs/CampaignDesignTrafficInsightsBreakdown",
              "description": "Visits by the site that linked to the participant's link."
            },
            "destination": {
              "$ref": "#/$defs/CampaignDesignTrafficInsightsBreakdown",
              "description": "Visits by the page the link opened."
            },
            "geo": {
              "$ref": "#/$defs/CampaignDesignTrafficInsightsBreakdown",
              "description": "Visits by location."
            },
            "technology": {
              "$ref": "#/$defs/CampaignDesignTrafficInsightsBreakdown",
              "description": "Visits by device, browser, and operating system."
            },
            "trigger": {
              "$ref": "#/$defs/CampaignDesignTrafficInsightsBreakdown",
              "description": "Visits by how the visitor arrived, such as a page visit or a QR code."
            }
          }
        }
      }
    },
    "referralSummary": {
      "type": "object",
      "description": "Referral programs only. The row of summary tiles a participant sees (clicks, leads, referrals, invites sent, rewards earned, and more). Controls each tile's label, tooltip, and visibility, plus the section's title and icon. `stats.<tile>.icon` sets a tile's icon, in the `CampaignDesignSummaryIcon` shape: `type` (`DEFAULT`, `IMAGE`, or `NONE`), `key` (the starter icon used when `type` is `DEFAULT`), and `imageUrl` (the HTTP or HTTPS image used when `type` is `IMAGE`; send `null` to clear it). Tile keys are `referrals`, `leads`, `invitesSent`, `expiredReferrals`, `clicks`, `pendingRewards`, or `rewardsEarned`. `stats.rewardsEarned.displayMode` selects `COUNT` (approved reward count) or `AMOUNT` (total of the approved rewards’ saved recipient amounts, using `referredRewardAmount` for a friend when present and otherwise `rewardAmount`, with saved currency matching the program currency). Telehealth programs default to `AMOUNT`; other referral programs default to `COUNT`. An explicit choice overrides the default. If any approved reward lacks a valid saved amount or matching currency, the participant sees the reward count. Changing a Campaign Reward's amount affects future rewards only."
    },
    "affiliateSummary": {
      "type": "object",
      "description": "Affiliate programs only. The row of summary tiles an affiliate sees (clicks, leads, referrals, referral revenue, upcoming payout, and total paid out). Controls each tile's label, tooltip, and visibility, plus the section's title and icon. `stats.<tile>.icon` sets a tile's icon, in the `CampaignDesignSummaryIcon` shape: `type` (`DEFAULT`, `IMAGE`, or `NONE`), `key` (the starter icon used when `type` is `DEFAULT`), and `imageUrl` (the HTTP or HTTPS image used when `type` is `IMAGE`; send `null` to clear it). Tile keys are `referralRevenue`, `totalPaidOut`, `upcomingPayout`, `referrals`, `leads`, or `clicks`."
    },
    "commissions": {
      "type": "object",
      "description": "Affiliate programs only. The Commissions section of the participant portal, which lists an affiliate's earned commissions and their status (pending, approved, paid, or reversed). Controls its title, icon, column labels, status labels and tooltips, and empty-state text. `emptyStateGraphic` sets the graphic shown when the section has no entries, in the `CampaignDesignEmptyStateGraphic` shape: `type` (`DEFAULT`, `IMAGE`, or `NONE`), `key` (the starter graphic used when `type` is `DEFAULT`), and `imageUrl` (the HTTP or HTTPS image used when `type` is `IMAGE`; send `null` to clear it)."
    },
    "payouts": {
      "type": "object",
      "description": "Affiliate programs only. The Payouts section of the participant portal, which lists an affiliate's payouts and their status (upcoming, queued, issued, failed, or reversed). Controls its title, icon, column labels, status labels and tooltips, and empty-state text. `emptyStateGraphic` sets the graphic shown when the section has no entries, in the `CampaignDesignEmptyStateGraphic` shape: `type` (`DEFAULT`, `IMAGE`, or `NONE`), `key` (the starter graphic used when `type` is `DEFAULT`), and `imageUrl` (the HTTP or HTTPS image used when `type` is `IMAGE`; send `null` to clear it)."
    },
    "rewards": {
      "type": "object",
      "description": "The heading, icon, and empty-state text of the rewards panel shown to participants. Which rewards appear, and the order they appear in, are managed through the campaign rewards endpoints, not here. `emptyStateGraphic` sets the graphic shown when the section has no entries, in the `CampaignDesignEmptyStateGraphic` shape: `type` (`DEFAULT`, `IMAGE`, or `NONE`), `key` (the starter graphic used when `type` is `DEFAULT`), and `imageUrl` (the HTTP or HTTPS image used when `type` is `IMAGE`; send `null` to clear it)."
    },
    "resources": {
      "type": "object",
      "additionalProperties": false,
      "description": "Participant presentation settings for Resources. Resource content and publication order are managed through the program Resources endpoints.",
      "properties": {
        "emptyStateGraphic": {
          "$ref": "#/$defs/CampaignDesignEmptyStateGraphic",
          "description": "The starter graphic, uploaded image, or hidden graphic for an empty section."
        },
        "isPublicDisplayed": {
          "type": "boolean",
          "description": "Whether the Resources destination is enabled. It remains hidden until at least one valid resource is published."
        },
        "title": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 100,
          "description": "Participant-visible Resources section title."
        },
        "viewResourcesLinkText": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 100,
          "description": "LIST-mode row text."
        },
        "backLinkText": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 100,
          "description": "LIST-mode detail Back text."
        },
        "copyButtonText": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 100,
          "description": "TEXT resource copy action."
        },
        "copiedText": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 100,
          "description": "TEXT resource copy confirmation."
        },
        "emptyState": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 500,
          "description": "Text shown in place of the list when no resources are published."
        },
        "icon": {
          "type": "object",
          "additionalProperties": false,
          "description": "Icon configuration for the Resources destination.",
          "properties": {
            "type": {
              "type": "string",
              "enum": [
                "DEFAULT",
                "IMAGE",
                "NONE"
              ],
              "description": "Whether to use the default icon, a configured image, or no icon."
            },
            "imageUrl": {
              "type": [
                "string",
                "null"
              ],
              "maxLength": 500,
              "description": "LIST-mode icon image URL when `type` is `IMAGE`."
            }
          }
        }
      }
    },
    "participantSettings": {
      "type": "object",
      "description": "The participant's account settings area in the portal. Controls the labels and messages for logging out, picking a language (`languageSectionTitle`, `languageSectionInstructionsText`, shown when the program has more than one language), managing PayPal and Wise payout emails (including confirmation, connected, expired, and repair states), and completing tax details (country of residency, VAT, and tax-form status). `taxSection.instructionsText` and `taxSection.residencyLabel` show when tax forms are required; `taxSection.vatOnlyInstructionsText` and `taxSection.vatOnlyResidencyLabel` show instead when an affiliate program collects VAT numbers without requiring tax forms. Wise payout copy applies to affiliate programs; the other fields also apply to eligible referral programs."
    },
    "landingPages": {
      "type": "object",
      "description": "Portal and landing pages — company info, `content` (FAQ/terms/labels), `styles` (fonts), third-party script IDs (GA/GTM/Facebook/HubSpot), and SEO meta tags. Custom code, CSS, and JS are not editable via the API. `content.howItWorksTitle` is the plain-text heading above the How it works steps (up to 255 characters). Your Program Page shows it. The GrowSurf window shows it too while `content.howItWorks` is still the starter heading `<h5>How It Works</h5>`. Programs that never set it read back `How it works`; send `null` or blank text to show `How it works` again. `content.howItWorksSteps` replaces the full ordered step array; each step supports `text`, an optional bold `title`, `imageUrl`, and `iconKey`. Use `iconKey: NUMBER` for sequential step numbers, or a starter icon key such as `SHARE` or `REWARD`. `isProgramPageEnabled` turns on the Program Page for a referral program. After you turn it on, you cannot turn it off. `programPage` holds the Program Page content: `hero` (including `headlineAccent`, a phrase in the headline to highlight; `headlineAccentStyle`, `SOLID` or `GRADIENT`; and `headlineAccentColor`, a six-digit hex color used by `SOLID`), `blocks`, `sectionOrder`, `sectionVisibility` (`false` hides a section), `headerLinks` (referral programs only, up to 3), `howItWorks` (referral programs only), `applicationForm.fields` (affiliate programs only, 1 to 30 fields), `requirements`, and `messages`. `howItWorks` sets the style of the How it works card. `layout` is `scrim` (photo across the top of the card, title on the photo), `titleAbove` (title above the card, no photo), `none` (title inside the card, no photo), or `imageAbove` (title above the card, photo across the top of the card). `imageUrl` is an `https` photo URL or `null`; without a photo, `scrim` and `imageAbove` show as `titleAbove`. `scrimColor` is the fade color behind the title: `brand`, `brandDeeper`, or `brandDeepest` (shades of `theme.buttons.backgroundColor` that follow later color changes), `black`, or a `#RRGGBB` hex color. `null` saves `brand`. `scrimStrength` (`30` to `100`, default `100`) sets how far up the photo the fade reaches; the fade right behind the title stays strong. A program with no card has no `howItWorks` object, and its first `howItWorks` update must include `layout`. Updates get the same checks as a dashboard save. An array replaces the stored array. Blank `messages` text reads back as the default text."
    },
    "theme": {
      "type": "object",
      "description": "Visual theme styling (colors, shadows, and similar).",
      "properties": {
        "widget": {
          "type": "object",
          "description": "Color settings for the website widget.",
          "properties": {
            "color": {
              "type": [
                "string",
                "null"
              ],
              "maxLength": 255,
              "description": "Text and drawing color on the button, and on the card's own button."
            },
            "backgroundColor": {
              "type": [
                "string",
                "null"
              ],
              "maxLength": 255,
              "description": "Fill color of the button, and of the card's own button."
            },
            "borderRadius": {
              "type": [
                "string",
                "null"
              ],
              "maxLength": 255,
              "description": "Corner rounding, as a CSS length such as `12px`."
            }
          }
        },
        "referredExperienceOfferPopup": {
          "type": "object",
          "description": "Color settings for the Claim Offer Popup.",
          "properties": {
            "color": {
              "type": [
                "string",
                "null"
              ],
              "maxLength": 255,
              "description": "Popup text color."
            },
            "backgroundColor": {
              "type": [
                "string",
                "null"
              ],
              "maxLength": 255,
              "description": "Popup background color."
            }
          }
        }
      }
    }
  },
  "$defs": {
    "CampaignDesignEmptyStateGraphic": {
      "type": "object",
      "description": "The graphic shown when a section has no entries.",
      "properties": {
        "type": {
          "type": "string",
          "enum": [
            "DEFAULT",
            "IMAGE",
            "NONE"
          ],
          "description": "Use a starter graphic, an uploaded image, or no graphic."
        },
        "key": {
          "type": "string",
          "enum": [
            "SOFT_DISC",
            "DASHED_RING",
            "GHOST_ROWS",
            "CARD_STACK",
            "DOT_FIELD",
            "HALO"
          ],
          "description": "The starter graphic used when `type` is `DEFAULT`. When no key is saved, the section uses its default graphic."
        },
        "imageUrl": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 500,
          "description": "An HTTP or HTTPS image URL used when `type` is `IMAGE`. Send `null` to clear it."
        }
      }
    },
    "CampaignDesignTrafficInsightsBreakdown": {
      "type": "object",
      "description": "One breakdown table in the Traffic report.",
      "properties": {
        "isVisible": {
          "type": "boolean",
          "description": "Whether participants can choose this breakdown."
        },
        "label": {
          "type": "string",
          "maxLength": 100,
          "description": "The breakdown's name in the picker."
        },
        "levels": {
          "type": "object",
          "additionalProperties": {
            "type": "string",
            "maxLength": 100
          },
          "description": "Names for the levels this breakdown can be read at. `utm` has `source`, `medium`, `campaign`, `term`, `content`, `id`, `sourcePlatform`, `creativeFormat`, and `marketingTactic`. `geo` has `country`, `region`, `city`, and `continent`. `technology` has `device`, `browser`, and `os`. Other breakdowns have no levels."
        },
        "values": {
          "type": "object",
          "additionalProperties": {
            "type": "string",
            "maxLength": 100
          },
          "description": "Names for rows from a fixed set. `referrer` has `DIRECT`. `technology` has `DESKTOP`, `MOBILE`, and `TABLET`. `trigger` has `PAGE_ENTRY`, `QR_CODE`, `DEEP_LINK`, `DEFERRED_DEEP_LINK`, `INSTALL_REFERRER`, and `MANUAL`. Other breakdowns have no fixed rows."
        }
      }
    },
    "CampaignDesignTrafficInsightsMetric": {
      "type": "object",
      "description": "One visit count at the top of the Traffic report.",
      "properties": {
        "isVisible": {
          "type": "boolean",
          "description": "Whether participants see this count."
        },
        "label": {
          "type": "string",
          "maxLength": 100,
          "description": "The count's name."
        },
        "helperText": {
          "type": "string",
          "maxLength": 255,
          "description": "A short explanation under the name. Send an empty string to hide it."
        }
      }
    },
    "ParticipantLoginDesign": {
      "type": "object",
      "description": "Customizable sign-in text shown when an existing participant asks GrowSurf for a secure sign-in link.",
      "properties": {
        "verificationHeading": {
          "type": "string",
          "maxLength": 100,
          "description": "The heading when an identified participant verifies their email without entering an address."
        },
        "verificationDescription": {
          "type": "string",
          "maxLength": 500,
          "description": "Explains that the sign-in link goes to the email address on the participant’s account."
        },
        "verificationSuccessBody": {
          "type": "string",
          "maxLength": 500,
          "description": "The instructions shown after an identified participant requests a sign-in link."
        },
        "heading": {
          "type": "string",
          "maxLength": 100,
          "description": "The heading above the participant sign-in form."
        },
        "description": {
          "type": "string",
          "maxLength": 500,
          "description": "The supporting text that explains how participant sign-in works."
        },
        "fieldLabel": {
          "type": "string",
          "maxLength": 100,
          "description": "The label for the participant email field."
        },
        "fieldPlaceholder": {
          "type": "string",
          "maxLength": 100,
          "description": "The placeholder shown inside the participant email field."
        },
        "buttonText": {
          "type": "string",
          "maxLength": 100,
          "description": "The text on the button that requests a secure sign-in link."
        },
        "successHeading": {
          "type": "string",
          "maxLength": 100,
          "description": "The heading shown after a sign-in link request is accepted."
        },
        "successBody": {
          "type": "string",
          "maxLength": 500,
          "description": "The privacy-safe message shown after a sign-in link request is accepted."
        },
        "resendPrompt": {
          "type": "string",
          "maxLength": 100,
          "description": "The prompt shown before the resend action."
        },
        "resend": {
          "type": "string",
          "maxLength": 100,
          "description": "The text on the action that requests another sign-in link."
        },
        "resent": {
          "type": "string",
          "maxLength": 500,
          "description": "The confirmation shown after another sign-in link is requested."
        },
        "invalidEmail": {
          "type": "string",
          "maxLength": 500,
          "description": "The validation message shown for an invalid email address."
        },
        "cooldown": {
          "type": "string",
          "maxLength": 500,
          "description": "The message shown when sign-in link requests are temporarily rate limited."
        },
        "serverError": {
          "type": "string",
          "maxLength": 500,
          "description": "The message shown when GrowSurf cannot request a sign-in link."
        },
        "invalidLink": {
          "type": "string",
          "maxLength": 500,
          "description": "The message shown when a sign-in link is invalid or expired."
        }
      }
    },
    "PayoutDestinationConfirmationDesign": {
      "type": "object",
      "additionalProperties": false,
      "description": "Customizable text for the participant-facing payout-destination confirmation page. One shared set applies to every payout provider enabled for the program. Provider-aware text may use `{{payoutProvider}}`; send `null` or an empty string to restore that field's default text. Use provider-neutral text for `errorMessages.generic` because an unknown link may not identify a payout provider.",
      "properties": {
        "headline": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "The page heading shown above the confirmation form."
        },
        "description": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 500,
          "description": "The instructions shown below the page heading."
        },
        "emailLabel": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "The label for the participant's payout email field."
        },
        "emailPlaceholder": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "The placeholder shown inside the payout email field."
        },
        "emailAgainLabel": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "The label for the field where the participant confirms their payout email."
        },
        "emailAgainPlaceholder": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "The placeholder shown inside the payout-email confirmation field."
        },
        "legalNameLabel": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "The label for the payout recipient's legal name."
        },
        "legalNamePlaceholder": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "The placeholder shown inside the legal-name field."
        },
        "legalTypeLabel": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "The label for choosing whether the payout recipient is an individual or business."
        },
        "legalTypeIndividual": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "The option text for an individual payout recipient."
        },
        "legalTypeBusiness": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "The option text for a business payout recipient."
        },
        "button": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "The text on the button that submits the confirmation form."
        },
        "success": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 500,
          "description": "The message shown after payout details are confirmed."
        },
        "claimPending": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 500,
          "description": "The message shown while GrowSurf finishes confirming the payout destination."
        },
        "errorMessages": {
          "description": "Validation and link-status messages shown on the payout-destination confirmation page.",
          "$ref": "#/$defs/PayoutDestinationConfirmationErrorMessages"
        }
      }
    },
    "PayoutDestinationConfirmationErrorMessages": {
      "type": "object",
      "additionalProperties": false,
      "description": "Customizable validation and link-status messages on the payout-destination confirmation page. Provider-aware states may use `{{payoutProvider}}`; send `null` or an empty string to restore that message's default text.",
      "properties": {
        "invalidEmail": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "The validation message shown when the participant enters an invalid email address."
        },
        "emailMismatch": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "The validation message shown when the two email entries do not match."
        },
        "tokenExpired": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "The message shown when the confirmation link has expired."
        },
        "tokenUsed": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "The message shown when the confirmation link has already been used."
        },
        "alreadyConfirmed": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "The message shown when the payout destination is no longer waiting for confirmation."
        },
        "generic": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 255,
          "description": "The fallback message shown when the confirmation link cannot be used for another reason. Use provider-neutral text because an unknown link may not identify a payout provider."
        }
      }
    }
  }
}