Skip to main content

Launch an external campaign with automation

External campaign automation composes a campaign from a draft and a set of facets, then commits it to a send with a single guarded call. This guide walks through every step from discovering the IDs you need to confirming the job outcome, using one running example throughout: campaign externalId 42 ("Summer Retargeting"), audience source 555, account 10, brand 1, destination 789 (a Meta Custom Audience destination), and job 9001.

Before you create your first campaign, make sure you review External campaign automation prerequisites.

For more information about the compositional model, data variables, and error conventions, see How external campaign automation works.


Steps to launch a campaign​

Step 1: Discover the IDs you need​

Before creating a campaign, confirm the IDs for the audience source and destination you intend to use.

GET /messagegears-api/v1/audience-source?page=0&size=50
apiKey: your-api-key
GET /messagegears-api/v1/destination?page=0&size=50
apiKey: your-api-key

Each endpoint returns a paged list of entities visible within the calling key's brand scope. To read the full details of an entity — including an audience source's availableFields or a destination's settingsSchema and exampleAssociation — call the per-entity detail endpoint:

GET /messagegears-api/v1/audience-source/555
GET /messagegears-api/v1/destination/789

Step 2: Create the campaign draft​

POST /messagegears-api/v1/external creates the campaign and returns the externalId you will use on every subsequent call.

POST /messagegears-api/v1/external
apiKey: your-api-key
Content-Type: application/json

{
"name": "Summer Retargeting",
"description": "Retargeting campaign for Q3",
"category": "Paid Social",
"notificationEmailAddress": "you@example.com",
"statusNotifications": true
}
FieldPresenceDescription
nameRequiredNon-blank, max 75 characters, unique within its folder.
descriptionOptionalFree-text description of the campaign.
categoryOptionalMax 255 characters.
notificationEmailAddressOptionalDefaults to the configured notification address, or the caller's email.
statusNotificationsOptionalDefaults to false.

MessageGears returns 201 Created with the new campaign, including the externalId:

{
"externalId": 42,
"name": "Summer Retargeting",
"archived": false,
"associations": {
"accountId": 10,
"brandIds": [1],
"destinationIds": [],
"scheduled": false
}
}

Note that associations.accountId and associations.brandIds already carry values — the platform's default account is auto-assigned, and where the brands feature is enabled, the campaign's brands are seeded from the brand association of the user who owns the API key. Neither needs a separate call to become launch-ready.

There is no endpoint to change name, description, or category after creation. Confirm these are right before you move on, or delete and recreate the campaign.

Errors: 409 CAMPAIGN_NAME_NOT_UNIQUE if the name is already in use in the folder; 400 VALIDATION_FAILED for a blank, missing, or over-long field.


Step 3: Adjust brands (optional)​

Skip this step if the auto-seeded brand set is correct. To add a brand beyond the default, POST to the brand facet — additive, and re-adding an already-attached brand is a no-op:

POST /messagegears-api/v1/external/42/brand
apiKey: your-api-key
Content-Type: application/json

[{ "brandId": 2 }]

MessageGears returns 200 with the campaign's full current brand set. Adding brands is all-or-nothing — one out-of-scope brandId in the array rejects the whole call with 403.

To remove an inherited brand you don't want, use DELETE /external/42/brand/{brandId}. Removing the last remaining brand is rejected with 409 INVALID_STATE.


Step 4: Attach the audience​

PUT /messagegears-api/v1/external/{externalId}/audience attaches the audience source and declares which fields from that source the campaign requires.

PUT /messagegears-api/v1/external/42/audience
apiKey: your-api-key
Content-Type: application/json

{
"audienceSource": { "audienceSourceId": 555 },
"requiredFields": ["EMAILADDRESS", "FIRSTNAME"]
}

requiredFields lists the field names the campaign requires from the audience source — each must appear in availableFields on GET /audience-source/555. Both audienceSource and requiredFields are required in the body; to declare no required fields, send "requiredFields": [] — omitting the field entirely is 400 VALIDATION_FAILED.

MessageGears returns 200 with the audience association. Read it back with GET /external/42/audience and confirm unresolvedLaunchVariables is empty before moving on, if the source uses data variables. For more information, see Data variables.

Destination coverage guard. Every field in requiredFields must be delivered by at least one attached destination's fieldMappings. If a required field isn't covered, launch or schedule activation returns 409 INVALID_STATE with details.unmappedRequiredFields listing the uncovered fields.


Step 5: Attach a destination​

POST /messagegears-api/v1/external/{externalId}/destination attaches one or more destinations. Each item in the body array combines a destination id with its field mappings and vendor-specific settings, sent as flat siblings — never nested under a wrapper. For the full rules governing fieldMappings, excludedFieldMappings, and vendorSettings, see Destination field mappings.

POST /messagegears-api/v1/external/42/destination
apiKey: your-api-key
Content-Type: application/json

[
{
"destinationId": 789,
"fieldMappings": {
"EMAILADDRESS": "email",
"FIRSTNAME": "fn"
},
"excludedFieldMappings": { "PHONE": true },
"vendorSettings": {
"name": "Summer Retargeting Q3",
"action": "Add",
"appIds": ["act_123456"],
"limitedDataUse": false,
"description": "Social retargeting campaign audience for Q3"
}
}
]

fieldMappings must be sent explicitly for every column you want delivered — an empty or omitted value delivers zero columns, not all of them. vendorSettings is type-checked against the destination's settingsSchema, returned as data on GET /destination/{destinationId}. Start from the destination's exampleAssociation and adjust.

The fields above match the Meta Custom Audience destination's settingsSchema: name is the sole required property, action is one of Add, Remove, Replace, or Use Audience Data (Title Case; defaults to Add), appIds is an array of Meta ad account IDs, and limitedDataUse is the CCPA flag. These replace the earlier applicationIds and applyLimitedDataUse field names.

warning

vendorSettings accepts and stores any key you send, even one not declared in settingsSchema.properties — it's echoed back verbatim on read, so a misspelled property name looks like a successful write while every schema-driven consumer resolves the declared name and finds nothing. Spell keys exactly as settingsSchema.properties declares them.

MessageGears returns 200 with the campaign's current destination list. If you're setting destination-specific fields like description and action for the first time, include them directly in vendorSettings on this same POST — there's nothing to preserve yet, so no follow-up call is needed.

To update an existing destination association, use PATCH instead — re-POSTing an already-attached destinationId is a no-op. PATCH fully replaces any attribute you send, including vendorSettings as a whole object, so to change one setting without losing the others, read the current object first:

GET /messagegears-api/v1/external/42/destination
apiKey: your-api-key

Then send the complete object back on PATCH, with only description and action changed:

PATCH /messagegears-api/v1/external/42/destination/789
apiKey: your-api-key
Content-Type: application/json

{
"vendorSettings": {
"name": "Summer Retargeting Q3",
"action": "Add",
"appIds": ["act_123456"],
"limitedDataUse": false,
"description": "Q3 retargeting audience"
}
}

Read the association back with GET /external/42/destination to confirm the update landed.


Step 6: Reassign the account (optional)​

Skip this step if the auto-assigned default account is correct. To use a different one, PUT /messagegears-api/v1/external/{externalId}/account:

PUT /messagegears-api/v1/external/42/account
apiKey: your-api-key
Content-Type: application/json

{ "accountId": 10 }

MessageGears returns 200 with the updated campaign. An unknown or out-of-scope accountId returns 404 ACCOUNT_NOT_FOUND — an account outside your brand scope also returns 404, not 403, to prevent id enumeration. There is no DELETE for this facet; an account is always required.


Step 7: Set triggers and audience recording (optional)​

Two more facets are available if your campaign needs them:

  • Triggers - Pre- and post-campaign automation, set with PATCH /external/42/trigger and cleared one role at a time with DELETE /external/42/trigger?role=pre or ?role=post.
  • Audience recording - The configuration that captures the audience extraction, set with PUT /external/42/audienceRecording. This requires an audience to already be attached.

For the exact request body for each, see the External Campaign Agent API reference.


Step 8: Schedule or launch​

The campaign is ready to either be scheduled for a future send with the schedule endpoint, or sent immediately with the launch endpoint.

Option 1: Schedule for a future send​

To arm the campaign for a future send without a manual launch call, attach a schedule with PUT /messagegears-api/v1/external/{externalId}/schedule. confirm: true is required only when the write activates the schedule — arming a fresh or paused one. Pausing or re-saving an already-armed schedule needs no confirm.

For a recurring send every Monday and Thursday at 9:30 AM:

PUT /messagegears-api/v1/external/42/schedule
apiKey: your-api-key
Content-Type: application/json

{
"type": "RECURRING",
"startDate": "2026-09-01",
"dateRule": { "frequency": "WEEKLY", "daysOfWeek": ["MONDAY", "THURSDAY"], "every": 1 },
"timeRule": { "mode": "SIMPLE", "time": "09:30" },
"timezone": "America/New_York",
"confirm": true
}

For a raw cron expression instead, use the CRON type with Quartz 7-field syntax — not a Unix 5-field cron. Every weekday at 09:30:

{
"type": "CRON",
"cronExpression": "0 30 9 ? * MON-FRI *",
"timezone": "America/New_York",
"confirm": true
}

A malformed schedule, or one whose cadence yields no upcoming run, returns 400 VALIDATION_FAILED with the offending field named. To cancel a schedule, call DELETE /messagegears-api/v1/external/42/schedule — it is unguarded and takes no body.

warning

A manual launch doesn't disarm an active schedule — both fire if you launch while a schedule is armed, sending the audience twice.

For more information about all four schedule types and the activation gate, see The schedule model.


Option 2: Launch immediately​

POST /messagegears-api/v1/external/{externalId}/launch commits the campaign to a send. The body must include confirm: true on every call — without it, the call returns 409 CONFIRMATION_REQUIRED and nothing is sent.

POST /messagegears-api/v1/external/42/launch
apiKey: your-api-key
Content-Type: application/json

{ "confirm": true }

MessageGears returns 202 Accepted with a jobId:

{ "jobId": 9001, "campaignId": 42, "success": true, "message": "Launch accepted." }

Retrying launch re-executes it exactly as sent — this version has no idempotency key to make a retry safe. Its only double-fire protection is 409 CONFLICT when a job is already running for the campaign; poll the existing job rather than retrying.

If the campaign is not launchable, MessageGears returns 409 INVALID_STATE. For more information, see Troubleshoot a failed launch.


Step 9: Check the launch job status​

Launch is asynchronous. Poll GET /messagegears-api/v1/external/job/{jobId} until the aggregate status reaches a terminal value — for the full list of statuses and what each means, see Launch and job status.

GET /messagegears-api/v1/external/job/9001
apiKey: your-api-key
{
"jobId": 9001,
"campaignId": 42,
"status": "PROCESSING",
"startedAt": "2026-09-01T09:30:00Z",
"completedAt": null,
"completedDestinations": 0,
"failedDestinations": 0,
"totalDestinations": 1,
"destinations": [
{ "destinationId": 789, "name": "Meta Custom Audience", "status": "PROCESSING" }
]
}

Troubleshoot a failed launch​

A 409 INVALID_STATE response from POST /launch or an activating PUT /schedule means the campaign isn't launch-ready — the details object names the gap. For the full error shape and what each details key means, see Commit points and the validation check. The most common cause is unmappedRequiredFields: a field in the audience's requiredFields that no attached destination's fieldMappings covers. Add it to a destination's mapping and retry.


Avoid these common mistakes​

  • Assuming empty fieldMappings shares everything. It shares nothing. Always send explicit mappings for every column you want delivered.
  • Re-POSTing a destination to update it. It's a no-op. Use PATCH to change an existing association.
  • Sending a partial vendorSettings object on PATCH. It's a full replace, not a merge — any field you leave out is dropped. GET the current object, edit it locally, then send the whole thing back.
  • Nesting fieldMappings under vendorSettings or a vendorSchema wrapper. Both are silently accepted and write nothing — send fieldMappings, excludedFieldMappings, and vendorSettings as flat siblings.
  • Sending excludedFieldMappings as an array. It must be an object map, for example { "PHONE": true }.
  • Launching without checking for an armed schedule. A manual launch does not disarm a schedule — both fire, sending the audience twice.
  • Trying to rename a campaign after creation. There is no endpoint for it. Delete and recreate instead.
  • Removing the last brand or destination. Both are rejected with 409 INVALID_STATE — a launch-required facet can't be emptied out from under a campaign.

Complete example​

The following curl sequence creates a campaign, attaches its required facets, and launches it. Replace the placeholder values with your actual host, credentials, and entity IDs.

# 1. Create the draft
curl -s -X POST https://your-mg-host/messagegears-api/v1/external \
-H "apiKey: your-api-key" \
-H "Content-Type: application/json" \
-d '{"name":"Summer Retargeting","notificationEmailAddress":"you@example.com"}'
# → 201 { "externalId": 42, "associations": { "accountId": 10, "brandIds": [1] }, … }

# 2. Attach the audience
curl -s -X PUT https://your-mg-host/messagegears-api/v1/external/42/audience \
-H "apiKey: your-api-key" \
-H "Content-Type: application/json" \
-d '{"audienceSource":{"audienceSourceId":555},"requiredFields":["EMAILADDRESS"]}'
# → 200

# 3. Attach the destination, with an explicit column list
curl -s -X POST https://your-mg-host/messagegears-api/v1/external/42/destination \
-H "apiKey: your-api-key" \
-H "Content-Type: application/json" \
-d '[{"destinationId":789,"fieldMappings":{"EMAILADDRESS":"email"},"vendorSettings":{"name":"Summer Retargeting Q3","action":"Add","appIds":["act_123456"]}}]'
# → 200

# 4. Launch
curl -s -X POST https://your-mg-host/messagegears-api/v1/external/42/launch \
-H "apiKey: your-api-key" \
-H "Content-Type: application/json" \
-d '{"confirm":true}'
# → 202 { "jobId": 9001, "campaignId": 42, "success": true, … }

# 5. Poll for status
curl -s https://your-mg-host/messagegears-api/v1/external/job/9001 \
-H "apiKey: your-api-key"
# → { "status": "COMPLETED", … }

After the poll returns a terminal status, the campaign audience has been delivered to the destination. A COMPLETED status means all destinations received the audience successfully. A PARTIAL_FAILURE means at least one destination failed — check the destinations array in the job response for the specific destination and error details.


Next steps​