Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
This guide helps you migrate from the Bing Ads PHP SOAP SDK (microsoft/bingads) to the new Bing Ads PHP REST SDK (microsoft/msads). The new REST SDK offers improved performance, modern architecture, and native async support.
Overview
The Microsoft Advertising REST SDK for PHP replaces the legacy SOAP-based SDK. Key benefits include:
- No SOAP dependency: Uses GuzzleHttp for REST API calls, eliminating the need for the
ext-soapPHP extension - Modern PHP support: Requires PHP 7.4 or higher with full PHP 8.x compatibility
- Async support: Built-in async methods for non-blocking API calls
- OpenAPI-generated models: Consistent, type-safe model classes with full documentation
- Simpler installation: No complex SOAP configuration required
Package Changes
Composer Package Name
| SDK Version | Package Name |
|---|---|
| SOAP SDK (Legacy) | microsoft/bingads |
| REST SDK (New) | microsoft/msads |
Installation
Old SDK (SOAP):
composer require microsoft/bingads
New SDK (REST):
composer require microsoft/msads
Dependencies
Old SDK Requirements:
- PHP >= 7.1
- ext-curl
- ext-openssl
- ext-soap
New SDK Requirements:
- PHP ^7.4 || ^8.0
- ext-curl
- ext-json
- ext-mbstring
- guzzlehttp/guzzle ^7.3
- guzzlehttp/psr7 ^1.7 || ^2.0
Namespace Changes
All namespaces have changed from Microsoft\BingAds\ to Microsoft\MsAds\Rest\.
| Component | SOAP SDK | REST SDK |
|---|---|---|
| Base namespace | Microsoft\BingAds\ |
Microsoft\MsAds\Rest\ |
| Authentication | Microsoft\BingAds\Auth\ |
Microsoft\MsAds\Rest\Auth\ |
| Campaign Management Models | Microsoft\BingAds\V13\CampaignManagement\ |
Microsoft\MsAds\Rest\Model\CampaignManagementService\ |
| Customer Management Models | Microsoft\BingAds\V13\CustomerManagement\ |
Microsoft\MsAds\Rest\Model\CustomerManagementService\ |
| Bulk Models | Microsoft\BingAds\V13\Bulk\ |
Microsoft\MsAds\Rest\Model\BulkService\ |
| Reporting Models | Microsoft\BingAds\V13\Reporting\ |
Microsoft\MsAds\Rest\Model\ReportingService\ |
| Service APIs | N/A (uses ServiceClient) | Microsoft\MsAds\Rest\Api\* |
Example Namespace Migration
Old SDK:
use Microsoft\BingAds\Auth\AuthorizationData;
use Microsoft\BingAds\Auth\OAuthDesktopMobileAuthCodeGrant;
use Microsoft\BingAds\Auth\ServiceClient;
use Microsoft\BingAds\Auth\ServiceClientType;
use Microsoft\BingAds\V13\CampaignManagement\Campaign;
use Microsoft\BingAds\V13\CampaignManagement\AddCampaignsRequest;
New SDK:
use Microsoft\MsAds\Rest\Auth\AuthorizationData;
use Microsoft\MsAds\Rest\Auth\OAuthDesktopMobileAuthCodeGrant;
use Microsoft\MsAds\Rest\Configuration;
use Microsoft\MsAds\Rest\Api\CampaignManagementServiceApi;
use Microsoft\MsAds\Rest\Model\CampaignManagementService\Campaign;
use Microsoft\MsAds\Rest\Model\CampaignManagementService\AddCampaignsRequest;
Authentication Changes
The authentication pattern remains similar but with some structural differences.
OAuth Authentication Setup
Old SDK:
use Microsoft\BingAds\Auth\AuthorizationData;
use Microsoft\BingAds\Auth\OAuthDesktopMobileAuthCodeGrant;
use Microsoft\BingAds\Auth\ApiEnvironment;
use Microsoft\BingAds\Auth\OAuthScope;
// Create OAuth authentication
$authentication = (new OAuthDesktopMobileAuthCodeGrant())
->withEnvironment(ApiEnvironment::Production)
->withClientId('YOUR_CLIENT_ID')
->withOAuthScope(OAuthScope::MSADS_MANAGE);
// Create authorization data
$authorizationData = (new AuthorizationData())
->withAuthentication($authentication)
->withDeveloperToken('YOUR_DEVELOPER_TOKEN');
// Store tokens and refresh when needed
if (isset($refreshToken)) {
$authentication->RequestOAuthTokensByRefreshToken($refreshToken);
}
New SDK:
use Microsoft\MsAds\Rest\Auth\AuthorizationData;
use Microsoft\MsAds\Rest\Auth\OAuthDesktopMobileAuthCodeGrant;
use Microsoft\MsAds\Rest\Auth\ApiEnvironment;
use Microsoft\MsAds\Rest\Auth\OAuthScope;
use Microsoft\MsAds\Rest\Configuration;
// Create OAuth authentication
$authentication = (new OAuthDesktopMobileAuthCodeGrant())
->withEnvironment(ApiEnvironment::PRODUCTION)
->withClientId('YOUR_CLIENT_ID')
->withOAuthScope(OAuthScope::MSADS_MANAGE);
// Create authorization data (now includes AccountId and CustomerId)
$authorizationData = (new AuthorizationData())
->withAuthentication($authentication)
->withDeveloperToken('YOUR_DEVELOPER_TOKEN')
->withAccountId('YOUR_ACCOUNT_ID')
->withCustomerId('YOUR_CUSTOMER_ID');
// Configure the API client
$config = Configuration::getDefaultConfiguration();
$config->setAuthorizationData($authorizationData);
// Store tokens and refresh when needed
if (isset($refreshToken)) {
$authentication->RequestOAuthTokensByRefreshToken($refreshToken);
// Refresh the configuration after token update
$config->refreshAuthorizationData();
}
Key Authentication Differences
| Aspect | SOAP SDK | REST SDK |
|---|---|---|
| AuthorizationData properties | Uses setter/getter methods | Uses public properties |
| AccountId/CustomerId | Set per request | Set in AuthorizationData |
| Configuration | Part of ServiceClient | Separate Configuration class |
| Token refresh | Implicit in ServiceClient | Requires Configuration::refreshAuthorizationData() |
| Environment constants | ApiEnvironment::Production |
ApiEnvironment::PRODUCTION |
Service Client Changes
The most significant change is how you interact with the API services.
Creating Service Clients
Old SDK (ServiceClient with Proxy):
use Microsoft\BingAds\Auth\ServiceClient;
use Microsoft\BingAds\Auth\ServiceClientType;
// Create service client
$campaignManagementProxy = new ServiceClient(
ServiceClientType::CampaignManagementVersion13,
$authorizationData,
ApiEnvironment::Production
);
// Access the service
$service = $campaignManagementProxy->GetService();
New SDK (Direct API Classes):
use Microsoft\MsAds\Rest\Api\CampaignManagementServiceApi;
use Microsoft\MsAds\Rest\Configuration;
// Create configuration with auth
$config = new Configuration();
$config->setAuthorizationData($authorizationData);
// Create service API directly
$campaignManagementApi = new CampaignManagementServiceApi(
null, // GuzzleHttp\ClientInterface (optional)
$config, // Configuration
null, // HeaderSelector (optional)
ApiEnvironment::PRODUCTION // Host environment
);
Available Service APIs
| Service | SOAP SDK ServiceClientType | REST SDK API Class |
|---|---|---|
| Campaign Management | CampaignManagementVersion13 |
CampaignManagementServiceApi |
| Customer Management | CustomerManagementVersion13 |
CustomerManagementServiceApi |
| Customer Billing | CustomerBillingVersion13 |
CustomerBillingServiceApi |
| Bulk | BulkVersion13 |
BulkServiceApi |
| Reporting | ReportingVersion13 |
ReportingServiceApi |
| Ad Insight | AdInsightVersion13 |
AdInsightServiceApi |
API Calling Pattern Changes
Method Naming Convention
Methods in the REST SDK use camelCase instead of PascalCase:
| SOAP SDK Method | REST SDK Method |
|---|---|
AddCampaigns |
addCampaigns |
GetCampaignsByAccountId |
getCampaignsByAccountId |
UpdateAdGroups |
updateAdGroups |
DeleteAds |
deleteAds |
Making API Calls
Old SDK:
use Microsoft\BingAds\V13\CampaignManagement\Campaign;
use Microsoft\BingAds\V13\CampaignManagement\CampaignType;
use Microsoft\BingAds\V13\CampaignManagement\BudgetLimitType;
use Microsoft\BingAds\V13\CampaignManagement\AddCampaignsRequest;
use Microsoft\BingAds\V13\CampaignManagement\GetCampaignsByAccountIdRequest;
// Create a campaign
$campaign = new Campaign();
$campaign->Name = "My Campaign";
$campaign->DailyBudget = 50.00;
$campaign->BudgetType = BudgetLimitType::DailyBudgetStandard;
$campaign->TimeZone = "PacificTimeUSCanadaTijuana";
// Create the request
$request = new AddCampaignsRequest();
$request->AccountId = $accountId;
$request->Campaigns = [$campaign];
// Make the call
$response = $campaignManagementProxy->GetService()->AddCampaigns($request);
// Access results
$campaignIds = $response->CampaignIds;
$partialErrors = $response->PartialErrors;
New SDK:
use Microsoft\MsAds\Rest\Model\CampaignManagementService\Campaign;
use Microsoft\MsAds\Rest\Model\CampaignManagementService\CampaignType;
use Microsoft\MsAds\Rest\Model\CampaignManagementService\BudgetLimitType;
use Microsoft\MsAds\Rest\Model\CampaignManagementService\AddCampaignsRequest;
use Microsoft\MsAds\Rest\Model\CampaignManagementService\GetCampaignsByAccountIdRequest;
// Create a campaign (using setter methods or constructor)
$campaign = new Campaign([
'Name' => 'My Campaign',
'DailyBudget' => 50.00,
'BudgetType' => BudgetLimitType::DAILY_BUDGET_STANDARD,
'TimeZone' => 'PacificTimeUSCanadaTijuana'
]);
// Or using setter methods
$campaign = new Campaign();
$campaign->setName("My Campaign");
$campaign->setDailyBudget(50.00);
$campaign->setBudgetType(BudgetLimitType::DAILY_BUDGET_STANDARD);
$campaign->setTimeZone("PacificTimeUSCanadaTijuana");
// Create the request
$request = new AddCampaignsRequest([
'AccountId' => $accountId,
'Campaigns' => [$campaign]
]);
// Make the call
$response = $campaignManagementApi->addCampaigns($request);
// Access results (using getter methods)
$campaignIds = $response->getCampaignIds();
$partialErrors = $response->getPartialErrors();
Async API Calls
The REST SDK provides built-in async support:
use GuzzleHttp\Promise\Utils;
// Async call
$promise = $campaignManagementApi->addCampaignsAsync($request);
// Execute and get result
$response = $promise->wait();
// Or handle with callbacks
$promise->then(
function ($response) {
$campaignIds = $response->getCampaignIds();
// Handle success
},
function ($exception) {
// Handle error
}
);
// Execute multiple async calls in parallel
$promises = [
'campaigns' => $campaignManagementApi->getCampaignsByAccountIdAsync($getCampaignsRequest),
'adGroups' => $campaignManagementApi->getAdGroupsByCampaignIdAsync($getAdGroupsRequest),
];
$results = Utils::unwrap($promises);
WithHttpInfo Variants
The REST SDK also provides *WithHttpInfo methods that return additional HTTP response details:
// Returns: [response, statusCode, headers]
list($response, $statusCode, $headers) = $campaignManagementApi->addCampaignsWithHttpInfo($request);
echo "Status Code: " . $statusCode;
echo "Request ID: " . ($headers['X-Request-Id'][0] ?? 'N/A');
Model Changes
Property Access
Old SDK (Direct property access):
$campaign = new Campaign();
$campaign->Name = "My Campaign";
$campaign->DailyBudget = 50.00;
echo $campaign->Name;
echo $campaign->DailyBudget;
New SDK (Getter/Setter methods):
$campaign = new Campaign();
$campaign->setName("My Campaign");
$campaign->setDailyBudget(50.00);
echo $campaign->getName();
echo $campaign->getDailyBudget();
// Or use constructor with array
$campaign = new Campaign([
'Name' => 'My Campaign',
'DailyBudget' => 50.00
]);
Enum Changes
Enum values use uppercase constants with underscores:
| SOAP SDK | REST SDK |
|---|---|
CampaignStatus::Active |
CampaignStatus::ACTIVE |
CampaignStatus::Paused |
CampaignStatus::PAUSED |
BudgetLimitType::DailyBudgetStandard |
BudgetLimitType::DAILY_BUDGET_STANDARD |
AdGroupStatus::Active |
AdGroupStatus::ACTIVE |
Flag Enums
The REST SDK introduces Flag Enums - enums that allow combining multiple values. This is a significant change from the SOAP SDK's space-separated string approach.
Identifying Flag Enums:
In the REST SDK, you can check if an enum is a Flag Enum by looking at the $isFlags property in the class definition. If isFlags() returns true, you can combine multiple values.
Old SDK (Space-separated strings):
// In the SOAP SDK, multiple enum values were combined with spaces
use Microsoft\BingAds\V13\CampaignManagement\CampaignType;
use Microsoft\BingAds\V13\CampaignManagement\AdGroupCriterionType;
$campaignTypes = CampaignType::Audience . ' ' . CampaignType::Search . ' ' . CampaignType::Shopping;
$criterionTypes = AdGroupCriterionType::Age . ' ' . AdGroupCriterionType::Gender . ' ' . AdGroupCriterionType::Device;
New SDK (Comma-separated or Array):
use Microsoft\MsAds\Rest\Model\CampaignManagementService\AdExtensionsTypeFilter;
use Microsoft\MsAds\Rest\Model\BulkService\DataScope;
// Option 1: Use an array of enum constants
$filter = [
AdExtensionsTypeFilter::ACTION_AD_EXTENSION,
AdExtensionsTypeFilter::APP_AD_EXTENSION,
AdExtensionsTypeFilter::SITELINK_AD_EXTENSION
];
// Option 2: Use a comma-separated string
$filter = 'ActionAdExtension,AppAdExtension,SitelinkAdExtension';
// Option 3: Concatenate constants with commas
$filter = AdExtensionsTypeFilter::ACTION_AD_EXTENSION . ',' .
AdExtensionsTypeFilter::APP_AD_EXTENSION;
// Option 4: Create a new enum instance with the value
$filter = new AdExtensionsTypeFilter([
AdExtensionsTypeFilter::ACTION_AD_EXTENSION,
AdExtensionsTypeFilter::APP_AD_EXTENSION
]);
// For DataScope (another flag enum)
$dataScope = DataScope::ENTITY_DATA; // Single value
$dataScope = [DataScope::ENTITY_DATA, DataScope::QUALITY_SCORE_DATA]; // Multiple values
Common Flag Enums to migrate:
| Purpose | SOAP SDK | REST SDK |
|---|---|---|
| Ad Extension Types | Space-separated | AdExtensionsTypeFilter (array/comma) |
| Campaign Types | Space-separated | CampaignType (array/comma) |
| Download Data Scope | Space-separated | DataScope (array/comma) |
| Criterion Types | Space-separated | Various *CriterionType (array/comma) |
| Report Filters | Space-separated | *ReportFilter classes (array/comma) |
Polymorphic Types (SoapVar Migration)
One of the most significant simplifications in the REST SDK is the removal of SoapVar wrappers for polymorphic types. In the SOAP SDK, you had to wrap derived types (like specific ad types or ad extensions) in SoapVar objects. The REST SDK handles this automatically through JSON serialization.
Old SDK (Required SoapVar wrapper):
use SoapVar;
use Microsoft\BingAds\V13\CampaignManagement\ExpandedTextAd;
use Microsoft\BingAds\V13\CampaignManagement\ResponsiveSearchAd;
use Microsoft\BingAds\V13\CampaignManagement\ImageAdExtension;
// Creating an Expanded Text Ad required SoapVar wrapping
$expandedTextAd = new ExpandedTextAd();
$expandedTextAd->TitlePart1 = "Contoso";
$expandedTextAd->TitlePart2 = "Quick & Easy Setup";
$expandedTextAd->Text = "Find New Customers & Increase Sales!";
$expandedTextAd->FinalUrls = ["http://www.contoso.com"];
// Must wrap in SoapVar for SOAP serialization
$ads[] = new SoapVar(
$expandedTextAd,
SOAP_ENC_OBJECT,
'ExpandedTextAd',
$GLOBALS['CampaignManagementProxy']->GetNamespace()
);
// Same for ad extensions
$imageAdExtension = new ImageAdExtension();
$imageAdExtension->ImageMediaIds = [12345];
$adExtensions[] = new SoapVar(
$imageAdExtension,
SOAP_ENC_OBJECT,
'ImageAdExtension',
$GLOBALS['CampaignManagementProxy']->GetNamespace()
);
// Pass the SoapVar-wrapped arrays to the API
$response = $service->AddAds($request);
New SDK (Direct object usage):
use Microsoft\MsAds\Rest\Model\CampaignManagementService\ExpandedTextAd;
use Microsoft\MsAds\Rest\Model\CampaignManagementService\ResponsiveSearchAd;
use Microsoft\MsAds\Rest\Model\CampaignManagementService\ImageAdExtension;
use Microsoft\MsAds\Rest\Model\CampaignManagementService\AddAdsRequest;
// Create an Expanded Text Ad - no SoapVar needed!
$expandedTextAd = new ExpandedTextAd();
$expandedTextAd->setTitlePart1("Contoso");
$expandedTextAd->setTitlePart2("Quick & Easy Setup");
$expandedTextAd->setText("Find New Customers & Increase Sales!");
$expandedTextAd->setFinalUrls(["http://www.contoso.com"]);
// Or use constructor with array
$responsiveSearchAd = new ResponsiveSearchAd([
'Headlines' => [...],
'Descriptions' => [...],
'FinalUrls' => ["http://www.contoso.com"]
]);
// Simply add to array - no SoapVar wrapper required
$ads = [$expandedTextAd, $responsiveSearchAd];
// Same for ad extensions - just create and use directly
$imageAdExtension = new ImageAdExtension([
'ImageMediaIds' => [12345]
]);
$adExtensions = [$imageAdExtension];
// Pass directly to the API
$request = new AddAdsRequest([
'AdGroupId' => $adGroupId,
'Ads' => $ads
]);
$response = $campaignManagementApi->addAds($request);
Types that previously required SoapVar:
| Category | Examples |
|---|---|
| Ad Types | ExpandedTextAd, ResponsiveSearchAd, ResponsiveAd, DynamicSearchAd, ProductAd |
| Ad Extensions | ImageAdExtension, SitelinkAdExtension, CallAdExtension, LocationAdExtension, ActionAdExtension, PriceAdExtension |
| Audiences | RemarketingList, CustomAudience, InMarketAudience, SimilarRemarketingList |
| Bid Strategies | MaxClicksBiddingScheme, TargetCpaBiddingScheme, MaxConversionsBiddingScheme |
| Criterions | BiddableAdGroupCriterion, NegativeAdGroupCriterion, BiddableCampaignCriterion |
Migration tip: Search your SOAP SDK code for new SoapVar( - each instance needs to be simplified to direct object usage in the REST SDK.
Model Serialization
The new models implement JsonSerializable and ArrayAccess:
// Convert to array
$campaignArray = $campaign->jsonSerialize();
// Access like array
$name = $campaign['Name'];
// Convert to JSON
$json = json_encode($campaign);
Error Handling Changes
Exception Types
Old SDK (SOAP Faults):
use SoapFault;
try {
$response = $service->AddCampaigns($request);
} catch (SoapFault $e) {
echo "SOAP Fault: " . $e->getMessage();
// Access fault details via $e->detail
}
New SDK (ApiException):
use Microsoft\MsAds\Rest\ApiException;
try {
$response = $campaignManagementApi->addCampaigns($request);
} catch (ApiException $e) {
echo "API Exception: " . $e->getMessage();
echo "HTTP Status Code: " . $e->getCode();
// Access response body (may contain ApplicationFault)
$responseBody = $e->getResponseBody();
if ($responseBody) {
$errorDetails = json_decode($responseBody);
// Handle specific error codes
}
// Access response headers
$headers = $e->getResponseHeaders();
$requestId = $headers['X-Request-Id'][0] ?? 'N/A';
}
Error Response Structure
The REST API returns errors in JSON format:
{
"ApplicationFault": {
"Type": "ApiFaultDetail",
"TrackingId": "abc123",
"OperationErrors": [
{
"Code": 1234,
"Message": "Error message description",
"Details": "Additional details"
}
],
"BatchErrors": [
{
"Index": 0,
"Code": 5678,
"Message": "Batch error for first item"
}
]
}
}
Comprehensive Error Handling Example
use Microsoft\MsAds\Rest\ApiException;
use Microsoft\MsAds\Rest\Model\CampaignManagementService\ApplicationFault;
use Microsoft\MsAds\Rest\Model\CampaignManagementService\ApiFaultDetail;
try {
$response = $campaignManagementApi->addCampaigns($request);
// Check for partial errors in successful response
$partialErrors = $response->getPartialErrors();
if ($partialErrors) {
foreach ($partialErrors as $error) {
echo "Partial Error at index " . $error->getIndex() . ": ";
echo $error->getMessage() . " (Code: " . $error->getCode() . ")\n";
}
}
} catch (ApiException $e) {
$statusCode = $e->getCode();
switch ($statusCode) {
case 400:
echo "Bad Request - Check your request parameters\n";
break;
case 401:
echo "Unauthorized - Check your authentication\n";
break;
case 403:
echo "Forbidden - You don't have permission\n";
break;
case 429:
echo "Rate Limited - Too many requests\n";
// Implement retry logic
break;
case 500:
echo "Server Error - Try again later\n";
break;
default:
echo "Unexpected error: " . $e->getMessage() . "\n";
}
// Parse error details
$responseBody = $e->getResponseBody();
if ($responseBody) {
$errorData = json_decode($responseBody, true);
if (isset($errorData['ApplicationFault'])) {
$fault = $errorData['ApplicationFault'];
echo "Tracking ID: " . ($fault['TrackingId'] ?? 'N/A') . "\n";
if (isset($fault['OperationErrors'])) {
foreach ($fault['OperationErrors'] as $opError) {
echo "Operation Error: " . $opError['Code'] . " - " . $opError['Message'] . "\n";
}
}
}
}
}
Complete Migration Examples
There are test examples in the test folder of the new SDK.
The examples extend off a RestApiTestBase class where it holds some reusable functions during testing as well as a central auth setup. The examples also make use of PHPUnit test Framework.
Migration Checklist
Use this checklist to ensure a complete migration:
[ ] Update composer.json
- Replace
microsoft/bingadswithmicrosoft/msads - Run
composer update
- Replace
[ ] Update use statements
- Change namespace from
Microsoft\BingAds\toMicrosoft\MsAds\Rest\ - Update service model imports
- Change namespace from
[ ] Update authentication code
- Add
AccountIdandCustomerIdtoAuthorizationData - Create
Configurationobject and set authorization data - Update environment constants (e.g.,
Production→PRODUCTION)
- Add
[ ] Replace ServiceClient with API classes
- Replace
ServiceClientinstantiation with specific*ServiceApiclasses - Pass
Configurationto API constructors
- Replace
[ ] Update API method calls
- Change method names from PascalCase to camelCase
- Replace direct calls through
GetService()with direct API object calls
[ ] Update model usage
- Replace direct property access with getter/setter methods
- Update enum constants to uppercase format
- Use constructor arrays for object initialization
[ ] Update error handling
- Replace
SoapFaultcatch blocks withApiException - Update error parsing for JSON response bodies
- Handle HTTP status codes appropriately
- Replace
[ ] Test thoroughly
- Test all API operations
- Verify authentication flow
- Test error scenarios
- Validate async operations if used