POST /api/v2/outbound/campaigns returns 400 on dry-run clone

headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
payload = {**base_config, "dialPlanId": "general_test", "rateLimit": 50, "simulationMode": True}
res = requests.post(f"{org}/api/v2/outbound/campaigns", headers=headers, json=payload)

The endpoint throws a 400 every time the active CONTACTS array gets stripped out. Cloning the CAMPAIGN settings for a general setup should be straightforward, but the DIAL PLAN validation keeps rejecting the zero count and doesn’t honor the RATE LIMITS override. SDK docs don’t show a dry-run switch anyway.

The 400 usually hits because simulationMode isn’t a recognized payload key. The API expects isEnabled set to false along with a valid contact list array. Stripping it completely breaks the schema validation. Honestly, trying to force a dry-run clone through raw requests is a bit of a headache. Instead of hitting the raw endpoint from a script, it’s often cleaner to route this through a Salesforce Data Action. Similar validation errors pop up in a few community threads about outbound cloning. Building a quick Apex wrapper that maps the base config to the exact Genesys Cloud schema usually catches these mismatches before the request leaves Salesforce. The managed package already handles the CTI adapter handshake and screen pop routing, so keeping the campaign dry-run inside that same ecosystem saves a lot of headache. Try swapping the payload to match the actual v2 schema. Something like this gets past the validator:

{
 "name": "Dry-Run Clone",
 "isEnabled": false,
 "contactListIds": ["keep-one-valid-id"],
 "dialPlanId": "general_test",
 "rateLimit": { "perMinute": 50 }
}

If you’re still seeing the 400, check the dialPlanId against what’s actually provisioned in the org. Sometimes the package caches old references and throws a silent validation error on the backend. Enable debug logging on the CTI adapter to catch where the payload drops. Usually the error traces back to a missing contact list UUID.

Cause: The outbound campaign schema rejects simulationMode. That key doesn’t exist in the v2 spec. Stripping the contacts array triggers a 400 because the API requires at least one valid contact list ID before it processes the clone request. My Celery workers hit this exact validation wall when bulk-provisioning test environments. The error payload just screams missing required fields.

Solution:

{
 "name": "Dry-Run Clone",
 "isEnabled": false,
 "campaignType": "PREDICTIVE",
 "contacts": [{"id": "your-active-list-id"}],
 "dialPlanId": "general_test",
 "rateLimit": 50
}

Pass that payload to POST /api/v2/outbound/campaigns with the outbound:campaign scope. The isEnabled flag handles the dry-run state without breaking the schema. You’ll still need to attach a valid wrap-up code later if you actually queue calls. Celery task just times out if the schema validation fails again.

{
 "contacts": ["dummy_list_id_123"],
 "isEnabled": false
}

Try swapping the contacts array for a single dummy list ID before hitting the endpoint, and what timezone are you setting for the callable window. The dialer engine still checks abandonment thresholds and pacing rules even during test runs, so keeping a valid list ID stops the schema rejection, and you’ll avoid the compliance flag triggers too.