Ad Campaigns

Update ad

Patch one or more fields on an ad. Status, budget, targeting, and creative changes are propagated to the platform.

Per-platform support:

  • Meta (Facebook + Instagram): all fields supported.
  • TikTok: status, budget, name (renames the ad), targeting (via /v2/adgroup/update/), and creative (via /v2/ad/update/ patch-style: headline is ignored, body becomes ad_text).
  • Google: status, budget, KEYWORD edits via targeting.keywords / targeting.negativeKeywords, DEVICE bid adjustments via targeting.devices, LOCATION edits via targeting.locations (or the equivalent top-level targeting.countries / regions / cities / zips / metros), and LANGUAGE edits via targeting.languages. Each list you send becomes the FULL new set of its kind (criteria not in the list are removed, except devices, which Google cannot remove and which are switched off with a bid modifier of 0 instead); a kind left out is untouched. Any other targeting field returns 400: Google cannot mutate it post-create without recreating the campaign. Creative edits are dispatched on the ad's advertisingChannelType, and every supported field replaces a whole set; a field you omit is preserved.
    • Search: top-level headlines, descriptions and finalUrls. Use 3-15 headlines (1-30 characters) and 2-4 descriptions (1-90 characters). Omit an asset to remove it; omit pinnedField on an included asset to unpin it. Updates do not pad or truncate text. The legacy creative fields remain unsupported.
    • Display: top-level headlines (1-5, no pinnedField, display ads have no pinned positions), descriptions (1-5) and finalUrls, plus creative.longHeadline, creative.businessName, creative.imageUrl (the landscape marketing image) and creative.squareImageUrl. Each image URL is uploaded as a new Google asset and the ad is pointed at it; Google assets are immutable, so the previous asset stays in the account's asset library.
    • Performance Max: top-level assetGroup, which swaps asset roles on the ad's asset group. The other creative fields return 422 for this channel, and assetGroup returns 422 on any other channel.
  • LinkedIn: status, budget, targeting (countries or regions, excludedLocations (countries), the B2B facets, and audience segments; applied to the LinkedIn Campaign via PARTIAL_UPDATE, and REPLACES the campaign's entire targetingCriteria, not a merge), and creative (uploads new media, creates a replacement inline creative on the same campaign, pauses the old one).
  • Pinterest / X / OpenAI Ads: status + budget only. Sending targeting or creative returns 501 with code unsupported_platform_operation. OpenAI Ads budget is lifetime-only (see budget.type below).

Google location and language replacement: locations, languages and devices are campaign-level criteria on Google, so these edits apply to every ad group and ad in the ad's campaign. Send the complete list you want to keep. Zernio diffs it against the campaign's live criteria and sends the removes and the creates in ONE googleAds:mutate, so the campaign is never left with a half-applied set; criteria already in the list keep their criterion ID and history. Excluded (negative) locations are left untouched. An empty location list returns 400 (a Google campaign with no location criteria targets every country, which is never what "remove my locations" means, so omit the field instead). Send either targeting.locations or the top-level geo fields, not both: mixing them returns 400.

Google radius targeting: customLocations is editable and is replaced the same way, but as its OWN set. Google models a place (LOCATION) and a point plus radius (PROXIMITY) as different criterion types, so the two are independent: sending customLocations replaces every radius and leaves the cities and countries alone, and sending places replaces those and leaves the radius alone. Send customLocations: [] to drop radius targeting entirely. A circle you re-send unchanged keeps its criterion ID rather than being removed and recreated.

Google keyword replacement: These edits affect the ad's entire ad group, including sibling ads. Positive (targeting.keywords) and negative (targeting.negativeKeywords) sets are independent: omit a field to leave that set unchanged, or send [] to remove every keyword of that kind.

Zernio compares each supplied set with Google's live criteria by case-insensitive keyword text and match type. A matching criterion is left untouched, retaining its criterion ID, enabled/paused status, keyword-level bid overrides, labels, and criterion-associated history/statistics. Zernio does not reset its quality score; Google continues to calculate scores and statistics normally. Text comparison does not trim whitespace.

A bare string or an object without matchType means broad, not the existing criterion's match type. For example, resending an existing { "text": "plumber", "matchType": "exact" } preserves it; sending "plumber" instead removes that EXACT criterion and requests a BROAD one. Changing text or match type removes criteria no longer requested and creates any missing criteria. New criteria get new IDs and do not inherit removed criteria's bid overrides, labels, or history. Historical reporting for a removed criterion is not transferred to its replacement.

To add keywords without replacing a set, use POST /v1/ads/keywords. Use PATCH /v1/ads/keywords/{keywordId} to pause/enable one keyword, or DELETE /v1/ads/keywords/{keywordId} to remove it.

put/v1/ads/{adId}

Path parameters

adIdstring required

Request body

finalUrlsstring[]

Google Search and Display only. Replaces final URLs. Omitted lists stay unchanged. For Performance Max use assetGroup.finalUrl.

status'active' | 'paused'
namestring

Rename the ad. Now propagated to Meta (POST /{ad-id}); non-Meta platforms return 501.

Example request

{
  "assetGroup": {
    "headlines": [
      "Schedule posts",
      "One social API",
      "Build with Zernio"
    ],
    "images": {
      "landscape": [
        "https://example.com/new-landscape.png"
      ]
    }
  },
  "creative": {
    "creativeFeatures": {
      "auto_promotion_tag": "OPT_IN"
    }
  }
}

Response

Ad updated

messagestring

Example response

{
  "ad": {
    "configuredStatus": "ACTIVE",
    "creativeType": "video",
    "metrics": {
      "actions": {
        "link_click": 160,
        "post_engagement": 300,
        "offsite_conversion.fb_pixel_purchase": 42
      },
      "actionValues": {
        "offsite_conversion.fb_pixel_purchase": 2456.78,
        "offsite_conversion.fb_pixel_add_to_cart": 980.5
      },
      "costPerAction": {
        "link_click": 0.1052,
        "offsite_conversion.fb_pixel_purchase": 4.0114
      }
    },
    "platformObjective": "OUTCOME_SALES",
    "optimizationGoal": "OFFSITE_CONVERSIONS",
    "costType": "CPC",
    "servingStatuses": [
      "ACCOUNT_TOTAL_BUDGET_HOLD"
    ],
    "platformAdAccountName": "Zernio - previously Late",
    "bidAmount": 5,
    "roasAverageFloor": 2,
    "promotedObject": {
      "custom_event_type": "PURCHASE"
    },
    "creative": {
      "assetGroup": {
        "finalUrl": "https://zernio.com",
        "headlines": [
          "Schedule posts",
          "One social API",
          "Build with Zernio"
        ],
        "longHeadline": "Schedule social content from your app with Zernio",
        "descriptions": [
          "Connect your social accounts.",
          "Publish and manage social content through one API."
        ],
        "businessName": "Zernio",
        "images": {
          "landscape": [
            "https://example.com/landscape.png"
          ],
          "square": [
            "https://example.com/square.png"
          ],
          "logo": [
            "https://example.com/logo.png"
          ]
        }
      },
      "assetGroupResourceName": "customers/9122445560/assetGroups/123456789",
      "servingHoldReasons": [
        "UNDER_REVIEW"
      ],
      "whatsappPhoneNumber": "+12025550123"
    }
  }
}

Changes