---
title: "Launch Ad Campaign"
method: POST
path: "/audience-syncs/{config_id}/launch"
tags: ["Ad Campaigns"]
---

# Launch Ad Campaign

`POST /audience-syncs/{config_id}/launch`

## Request

- Base URL: `https://be.graph8.com/api/v1`
- URL: `https://be.graph8.com/api/v1/audience-syncs/{config_id}/launch`
- Auth: HTTP bearer

Launch a paid campaign on the platform, targeting this sync's audience.

THIS SPENDS THE CUSTOMER'S ADVERTISING BUDGET. The campaign goes live with
`daily_budget` in `currency` from `start_date`, and the platform starts
charging as soon as it serves. `spend_cap` and `auto_pause_at` are the
guard rails; set them. Upload the creative first with
`POST /ad-campaigns/images`.

READ `status` IN THE BODY. The app answers a refused launch (campaign not
found, the platform rejecting it) with a 200 whose `status` is `error`, and
this route passes that through unchanged rather than guessing an HTTP code
for it. A launch can also partly succeed - the platform accepts the campaign
and then rejects a creative in review - so follow up with
`POST /ad-campaigns/{campaign_id}/status-refresh`.

## Path parameters

- `config_id` integer, required: The sync config whose audience the campaign targets.

## Headers

- `Authorization` string, nullable
- `X-Target-Org-Id` string, nullable

## Request body

- LaunchAdCampaignRequest
  - `name` string, nullable: User-provided campaign name; auto-generated if empty
  - `objective` string
  - `daily_budget` number
  - `currency` string
  - `start_date` string, date
  - `end_date` string, date, nullable
  - `ad_copy_variant_index` integer, nullable
  - `ad_image_document_id` integer, nullable
  - `creative_image_url` string, nullable
  - `creative_image_url_square` string, nullable
  - `creative_image_url_portrait` string, nullable
  - `logo_image_url` string, nullable
  - `headline` string, nullable
  - `primary_text` string, nullable
  - `cta` string, nullable
  - `destination_url` string, nullable
  - `customer_match_mode` string, nullable: Google Customer Match mode: observation (default) | targeting | exclusion
  - `geo_target_country` string, nullable: ISO 3166-1 alpha-2 country code for the location criterion (Google observation/exclusion modes)
  - `google_campaign_subtype` string, nullable: Google campaign subtype: display (default) | demand_gen | search
  - `include_lookalike_audiences` boolean, nullable: Demand Gen only: attach the lookalike segment derived from the Customer Match list
  - `custom_segment_keywords` string[], nullable: Demand Gen only: intent keywords for a custom segment (people who search these)
  - `custom_segment_urls` string[], nullable: Demand Gen only: competitor/relevant URLs for a custom segment (people who browse these)
  - `search_keywords` object[], nullable: Google Search only: [{text, match_type: broad|phrase|exact, negative: bool}]
  - `rsa_headlines` string[], nullable: Google Search only: 3-15 Responsive Search Ad headlines (≤30 chars each)
  - `rsa_descriptions` string[], nullable: Google Search only: 2-4 Responsive Search Ad descriptions (≤90 chars each)
  - `video_asset_ids` integer[], nullable: Google Video only: 1-5 ids of the org's ad_video_assets rows to advertise
  - `video_headlines` string[], nullable: Google Video only: 3-5 headlines (≤40 chars each)
  - `video_long_headlines` string[], nullable: Google Video only: 3-5 long headlines (≤90 chars each)
  - `video_descriptions` string[], nullable: Google Video only: 3-5 descriptions (≤90 chars each)
  - `video_business_name` string, nullable: Google Video only: advertiser/brand name shown on the ad (≤25 chars)
  - `video_cta` string, nullable: Google Video only: CallToActionType enum value (e.g. LEARN_MORE, SIGN_UP)
  - `selected_channels` object, nullable: Google Video only: placement switches — youtube_in_stream | youtube_in_feed | youtube_shorts | discover | gmail | display | maps (true/false)
  - `auto_pause_at` string, date-time, nullable: Absolute UTC instant to auto-pause at (ISO-8601 with offset)
  - `auto_pause_tz` string, nullable: IANA zone the user picked in (display/intent only)
  - `spend_cap` number, nullable: Auto-pause once lifetime SERVED spend reaches this (account currency)
  - `special_ad_categories` string[], nullable: Meta special ad categories: HOUSING | EMPLOYMENT | CREDIT | FINANCIAL_PRODUCTS_SERVICES | ISSUES_ELECTIONS_POLITICS | ONLINE_GAMBLING_AND_GAMING | NONE

## Response `200`

Successful Response

- ApiResponseAdPayload
  - `data` AdPayload, required: One ad-platform object, or one handler response.
  - `pagination` PaginationMeta
    - `page` integer, required: Current page number (1-indexed)
    - `limit` integer, required: Items per page
    - `total` integer, required: Total number of items
    - `has_next` boolean, required: Whether there are more pages
    - `next_cursor` string, nullable: Opaque cursor for the next page; null when there are no more items. Pass it back as the `cursor` query param to fetch the following page without computing page numbers.
    - `total_available` integer, nullable: Search endpoints only: how many records really match, when known. Can be larger than `total`, which is capped at the 10,000 results that can be paged. For company search this is a distinct-company estimate. Null when unknown; absent outside search.
    - `total_is_lower_bound` boolean, nullable: Search endpoints only: true when more records match than `total` shows (the result set is larger than the 10,000 results that can be paged). Absent outside search.
    - `notes` string[], nullable: Search endpoints only: plain-language notes about the result, e.g. that more records match than can be paged, or that a reversed `between` range was put in order.

## Other responses

- `422`: Validation Error

---

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