---
title: "Attach campaign assets"
method: POST
path: "/v1/ads/campaigns/{campaignId}/assets"
tags: ["Ad Campaigns"]
---

# Attach campaign assets

`POST /v1/ads/campaigns/{campaignId}/assets`

Creates and attaches sitelinks, callouts and structured snippets in one Google mutation.

## Path parameters

- `campaignId` string, required

## Request body

- object — Provide at least one of sitelinks, callouts or structuredSnippets. Sitelink description1 and description2 must be supplied together.
  - `accountId` string, required — Zernio Google Ads connection id.
  - `customerId` string — Google customer id without dashes. Required when the connection has multiple customers.
  - `sitelinks` GoogleSitelink[]
    - `text` string, required
    - `linkUrl` string, uri, required
    - `description1` string
    - `description2` string
  - `callouts` string[]
  - `structuredSnippets` GoogleStructuredSnippet[]
    - `header` 'Amenities' | 'Brands' | 'Courses' | 'Degree programs' | 'Destinations' | 'Featured hotels' | 'Insurance coverage' | 'Models' | 'Neighborhoods' | 'Service catalog' | 'Shows' | 'Styles' | 'Types', required
    - `values` string[], required

## Response `201`

Assets created and attached.

- object
  - `campaignId` string
  - `sitelinkAssetResourceNames` string[]
  - `calloutAssetResourceNames` string[]
  - `structuredSnippetAssetResourceNames` string[]

## Other responses

- `400` — Invalid request
- `401` — Unauthorized
- `403` — Ads access is required.
- `404` — The account or requested resource was not found or is not accessible. An account ID may have been disconnected and removed. Read GET /v1/accounts for current account IDs.
- `409` — The account exists but is inactive or needs reconnection. Reconnect it, then read GET /v1/accounts for its current account ID before retrying. Code: ads_connection_required.
- `429` — Google Ads operations budget or platform quota exhausted.
- `501` — Only supported on Google Ads.

## Changes

- **2026-09-16** `3e6ddf2a99ea` — 3 info
  - added the optional property `details/budgetScope` to the response with the `400` status
  - added the optional property `details/budgetScope` to the response with the `404` status
  - added the optional property `details/budgetScope` to the response with the `409` status
- **2026-09-15** `0dba7d004d75` — 6 info
  - added the optional property `details/quotaExhausted` to the response with the `400` status
  - added the optional property `details/quotaExhausted` to the response with the `404` status
  - added the optional property `details/quotaExhausted` to the response with the `409` status
  - added the optional property `details/quotaScope` to the response with the `400` status
  - …2 more
- **2026-09-10** `e70ed06e7150` — 7 info
  - added the non-success response with the status `409`
  - added the optional property `code` to the response with the `404` status
  - added the optional property `details` to the response with the `404` status
  - added the optional property `param` to the response with the `404` status
  - …3 more
- **2026-09-09** `222e3a38a382` — 3 breaking, 4 info
  - added the pattern `^\d+$` to the `path` request parameter `campaignId`
  - added the pattern `^[a-fA-F0-9]{24}$` to the request property `accountId`
  - added the pattern `^\d+$` to the request property `customerId`
  - added the media type `application/json` for the response with the status `400`
  - …3 more
- …earlier changes not shown

[Full history](https://skmtc.dev/zernio/apis/zernio-api/changes/v1/ads/campaigns/:campaignId/assets/post.md)

---

[API](https://skmtc.dev/zernio/apis/zernio-api.md) · [All operations](https://skmtc.dev/zernio/apis/zernio-api/llms.txt) · [OpenAPI document](https://skmtc.dev/zernio/apis/zernio-api/revisions/eded32b62823?raw)
