Create campaigns via the API

From the client to the published (paused) ad — selection, lookups, spec, validation and submission.

🇧🇷 Ler em português

1. Find the selection (Step 0)

# active clients
curl -s $BASE/clients -H "Authorization: Bearer $KEY"

# the client's Business Managers and assets (id or exact name)
curl -s $BASE/clients/<client_id>/businesses -H "Authorization: Bearer $KEY"

From each Business Manager, use id as business_manager_id and one assets.ad_accounts[].id as ad_account_id. Page, Instagram, pixel and WhatsApp are optional: without them the server uses the Business Manager's defaults. Only usable ad accounts are listed; sending any other account results in 403.

"selection": { "business_manager_id": "6f1c…", "ad_account_id": "1234567890" }

2. Look up what you need

NeedCall
InterestsPOST /meta/audiences/search { selection, search_type: "interests", query: "marketing" }
LocationsPOST /meta/audiences/search { selection, search_type: "geo", query: "Goiânia", location_types: ["city"] } — Meta may also return subcities and neighborhoods; filter on type
Interest suggestionsPOST /meta/audiences/search { selection, search_type: "interest_suggestions", interest_list: ["Medical residency"] }
Custom audiencesPOST /meta/audiences/search { selection, search_type: "audiences" } — use only the ones with usable: true; saved audiences only with include_saved: true
Existing campaignsPOST /meta/campaigns/search { selection, effective_status: ["ACTIVE", "PAUSED"] }
Lead formPOST /meta/leadforms/search { selection }
Instagram postsPOST /meta/posts/search { selection, action: "search", query: "…" }
Targeting templatesGET /targeting-templates

Pass the returned objects (interests, locations) through as they are — they are already in the shape the creation expects.

Custom audiences and existing campaigns are paged: 50 items per page by default (limit, up to 100). When paging.has_more is true, send paging.after back as after to get the next page.

The job_id returned by a lookup only groups that call's logs (GET /executions/{id}/logs); it is not a campaign execution.

3. Build the spec

{
  "selection": { "business_manager_id": "6f1c…", "ad_account_id": "1234567890" },
  "mode": "new_campaign",
  "campaign": {
    "name": "ACME | Traffic | Nov",
    "objective": "OUTCOME_TRAFFIC",
    "budget_level": "CBO",
    "budget_type": "daily_budget",
    "budget": 80,
    "isolate_creatives": true
  },
  "adsets": [
    {
      "conversion_objective": "website_traffic",
      "start_time": "2026-11-10T08:00",
      "targeting": {
        "age_min": 25,
        "age_max": 45,
        "locations": [{ "key": "2430536", "name": "São Paulo", "type": "city", "country_code": "BR" }]
      }
    }
  ],
  "ads": {
    "source": "drive",
    "folder_url": "https://drive.google.com/drive/folders/…",
    "copy": "Primary text",
    "headline": "Headline",
    "cta_type": "LEARN_MORE",
    "link_url": "https://acme.com.br"
  }
}

Campaign and ad sets

  • objective: OUTCOME_AWARENESS, OUTCOME_TRAFFIC, OUTCOME_ENGAGEMENT, OUTCOME_LEADS, OUTCOME_SALES.
  • budget_level: CBO (budget on the campaign, campaign.budget) or ABO (budget on each ad set, adsets[].budget).
  • conversion_objective and custom_event_type (pixel event) have defaults per objective; values outside the allowed set are rejected.
  • start_time defaults to now; end_time is required for a lifetime budget.
  • targeting_template_id applies a saved template; any targeting fields you send override the template.
  • Ad set and ad names follow the web app's automatic naming unless you send name.

isolate_creatives

  • true (default, except for ads.source: "media_urls", where the default is false): send one template ad set; it is duplicated for each creative (1 creative = 1 ad set). A name on the template becomes the base of every copy with a sequence suffix (00_SP_25-45 → 00_SP_25-45_01, _02…); without it, the automatic names are used.
  • false: every creative goes into every ad set you send.

Ad source (ads.source)

sourceFieldsNotes
drivefolder_url or feed_folder_url + stories_folder_url; copy, headline, description, cta_type, link_url, phone_number, url_tags, advantage_creativeSame rules as the folder used in the web app: a CARROSSEL/ subfolder becomes a carousel; FEED/STORIES folders turn on placement asset customization (placement_customization forces it on/off). Videos are prepared automatically.
existing_postspost_ids, link_url, phone_number, url_tagsEach post becomes one ad in every ad set.
media_urlsitems[] with creative_type (image/video/carousel), image_url/video_url/thumb_url/cards, texts and link_urlDoes not support isolate_creatives: true (the default for this source is false). URLs must be public (https).

UTMs

Every ad ships with Infotráfego's default url_tags. If link_url already has UTMs or you change url_tags, send confirm_custom_url_tags: true — otherwise validation rejects it (the same confirmation the web app asks for).

Extend what already exists

{ "mode": "extend_campaign", "existing_campaign_id": "120210…", "selection": {…}, "adsets": [{…}], "ads": {…} }
{ "mode": "extend_adset",    "existing_campaign_id": "120210…", "existing_adset_id": "120211…", "selection": {…}, "ads": {…} }

With extend_campaign the server mirrors the optimization of the existing ad sets and continues their numbering; with extend_adset no ad set is created.

4. Validate first (recommended)

curl -s -X POST $BASE/campaigns/validate -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" -d @campaign.json
{ "valid": false, "errors": { "ad_0_0_cta_type": "Botão (CTA) não disponível para o objetivo de conversão" }, "preview": null }

Validation messages come back in Brazilian Portuguese, keyed by field (the example reads "Call-to-action button not available for the conversion objective").

When the spec is valid, preview shows what will be created exactly as the server resolved it (defaults, templates and names applied): campaign budget (CBO), and for each ad set its name, conversion objective and event, budget (ABO), schedule, targeting (ages, genders, locations, interests, audiences), placements, frequency cap and the ads with their CTA, link and UTMs.

5. Create the campaign

curl -s -X POST $BASE/campaigns -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" -d @campaign.json
# 202 → { "job_id": "…", "status": "queued", "status_url": "/api/v1/executions/…" }

The creation runs on the server, in the background. Keep the job_id and continue with Track and retry.


Did this page help you?