Eric Nagel

Eric Nagel

CTO, PHP Programmer, Affiliate Marketer & IT Consultant

Using the Microsoft adCenter API to Automate Reporting in PHP

I have been, or can be if you click on a link and make a purchase, compensated via a cash payment, gift, or something else of value for writing this post. Regardless, I only recommend products or services I use personally and believe will be good for my readers.

Microsoft Advertising LogoAfter nearly three months of on & off coding, banging my head against the table and thumbing through the most comprehensive yet incomprehensible documentation I’ve ever seen, I was finally able to automate downloading stats (impressions, clicks, cost) for my coupon site.

For the longest time, the adCenter API was very closed. However, it now seems they’re opening it up, and I quickly was able to get an API account, but wasn’t able to get anything to work. Even adCenter’s most simple script doesn’t work out of the box, and it’s nearly impossible to figure out what has to be done. Hopefully this post will help you.

Once you’re signed up for the adCenter API, you’ll receive an email with your User Name, Password and API Access Key. In addition to this information, you’ll also need your accountId, which is easiest to obtain by logging into your adCenter account and simply looking at the URI (ex: https://adcenter.microsoft.com/home.aspx?cid=xxxxx&aid=xxxxx)

If you’re going to limit the report to just one campaign, you can get the CampaignId the same way. When you’re logged into your adCenter account, click on a campaign and you’ll be taken to a URL such as https://adcenter.microsoft.com/campaign/OrderSummary.aspx?cid=xxxxx&aid=xxxxx&cmpid=20767846

Most of the script that I finally ended up using came from this post in the Microsoft Advertising Community. Thanks, Sudheer, whomever you are!

Here’s the script I’m using, with some extras removed. Of course, this isn’t going to automate reporting for you, but it will get you the data you need to plug into your system.

<?php
$bDebug = false;

ini_set("soap.wsdl_cache_enabled", "0");

try {

	//===============================================================
	//=========== VARIABLES TO CHANGE ===============================

	// ACCOUNT SETTINGS =============================

	$username			=	'API_xxxxx'; // Your USERNAME
	$password			=	'xxxxxxxxxxx'; // Your PASSWORD
	$developerToken		=	'xxxxxxxxxxx'; // Your TOKEN
	$applicationToken	=	""; // Leave as it is. I don't know why. Just leave it.
	$accountId			=	45782;
	$CampaignId			=	20767846;

	//================================================
	// REPORT SETTINGS =============================
	// Name of the Report
	$reportName = 'MSNAdCenterReport';


	//===============================================================
	//======= URLS ==================================================

	// The sandbox URI.
	//$URI = "https://sandboxapi.adcenter.microsoft.com/Api/Advertiser/v6/";
	// The production URI.
	$URI = "https://adcenterapi.microsoft.com/api/advertiser/v7/";

	// The Microsoft adCenter API namespace.
	$xmlns = "https://adcenter.microsoft.com/v7";

	$reportProxy = $URI . "Reporting/ReportingService.svc?wsdl";

	//===============================================================
	//====== PROXY SETTINGS =========================================

	/* Use this in case you run behind a proxy.
	$proxy = array('proxy_host' =>'XXXXXX.XXXXXX.XXXXX.XXX.XXXX',
					'proxy_port' => XXXX,
					'trace' => true);
	*/

	// In case you dont, use the following
	$proxy = array('trace' => true);

	//===============================================================
	//=========== MSN AdCenter Script ===============================

	$action = "SubmitGenerateReport";

	// Create the SOAP headers.
	$headerApplicationToken = new SoapHeader($xmlns,
											'ApplicationToken',
											$applicationToken,
											false);

	$headerDeveloperToken = new SoapHeader($xmlns,
											'DeveloperToken',
											$developerToken,
											false);

	$headerUserName = new SoapHeader($xmlns,
											'UserName',
											$username,
											false);

	$headerPassword = new SoapHeader($xmlns,
											'Password',
											$password,
											false);

	// Create the SOAP input header array.
	$inputHeaders = array($headerApplicationToken,
							$headerDeveloperToken,
							$headerUserName,
							$headerPassword);

	// Create the SOAP client.
	$client = new SOAPClient($reportProxy, $proxy);

	//===============================================================

	$request=new AdGroupPerformanceReportRequest();

	//===============================================================
	//=========== Obtain an XML report, in english with a custom name

	$request->Format					=	'Xml';
    $request->Language					=	'English';
    $request->ReportName				=	$reportName;
    $request->ReturnOnlyCompleteData	=	false;

	//===============================================================
	//=========== Lets set the aggregation to the variable defined at the top

	// See http://msdn.microsoft.com/en-us/library/bb671686.aspx#Aggregation_Time
	$request->Aggregation= 'Daily';

	// To use the predefined Time Functions By MSN AdCenter===================
	// erase the above and uncomment the following============================
	// See http://msdn.microsoft.com/en-us/library/bb671686.aspx#Aggregation_Time
	$request->Time=array('PredefinedTime' => 'Yesterday');

	/* If custom...
	$request->Time=array('CustomDateRangeStart'	=>
						array("Day"	=>	$dayStart,
								"Month"	=>	$monthStart,
								"Year"	=>	$YearStart),
						'CustomDateRangeEnd'	=>
							array("Day"	=>	$dayEnd,
								"Month"	=>	$monthEnd,
								"Year"	=>	$YearEnd)
						);
	*/

	$request->Columns=array(
							"AccountName",
							"AccountNumber",
							"TimePeriod",
							"LanguageAndRegion",
							"Status",
							"CampaignName",
							"CampaignId",
							"AdGroupName",
							"AdGroupId",
							"CurrencyCode",
							"AdDistribution",
							"Impressions",
							"Clicks",
							"Ctr",
							"AverageCpc",
							"Spend",
							"AveragePosition",
							"Conversions",
							"ConversionRate",
							"CostPerConversion",
							"AverageCpm",
							"PricingModel",
							"DeviceType"
							);

	//===========================================================
	//============= Lets Set the Filter and Scope ==============
	/*
	$request->Filter=array(
							'AdDistribution'=>'Search',
							'LanguageAndRegion'=>'UnitedStates'
							);
	*/
	$request->Filter=array();

	// $request->Scope=array('AccountIds'	=>	array($accountId));
	$request->Scope = array('AccountIds'	=>	$accountId,
							'Campaigns'		=>	array('CampaignReportScope' => array(
																						'CampaignId' => $CampaignId,
																						'ParentAccountId' => $accountId
																					)
														)
							);
	$soapstruct = new SoapVar($request,
								SOAP_ENC_OBJECT,
								'AdGroupPerformanceReportRequest',
								$xmlns);
	if ($bDebug) {
		var_dump($soapstruct);
	} // ends if ($bDebug)

	//===============================================================
	//=========== MSN AdCenter Script ===============================

	$params=array('ReportRequest'=>$soapstruct);

	$result = $client->__soapCall($action,
									array($action.'Request'	=>	$params),
									null,
									$inputHeaders,
									$outputHeaders);

	if ($bDebug) {
		print $client->__getLastRequest();
	} // ends if ($bDebug)

	$reportRequestId = $result->ReportRequestId;

	//===============================================================

	// Poll to get the status of the report until it is complete.
	// This Minutes variables were changed, you can compare them to the original script.
	$waitMinutes = 1;
	$maxWaitMinutes = 10;
	$elapsedMinutes = 0;
	$action = "PollGenerateReport";
	$params=array('ReportRequestId'=>$reportRequestId);

	//===============================================================
	//=========== MSN AdCenter Script ===============================
	while (true) {

		sleep($waitMinutes);
        $elapsedMinutes += $waitMinutes;

        $result = $client->__soapCall($action,
									array($action.'Request' =>$params),
									null,
									$inputHeaders,
									$outputHeaders);


        $reportStatus=$result->ReportRequestStatus->Status;

		//===============================================================
		//=========== Let's start working if the report was found========

		if ($reportStatus == 'Success') {

			// The name of the zip file to-be
			$fileName = $reportRequestId.".zip";

			// Lets get our link to the Zip file
			$downloadURL=$result->ReportRequestStatus->ReportDownloadUrl;

			//======================================
			//=========== cURL =====================

			// create a curl resource with the link provided from MSN AdCenter
			$ch = curl_init($downloadURL);

			// Error handling
			if (! $ch) {
				die( "Cannot allocate a new PHP-CURL handle" );
			}

			// This is where we define where will we save the zip file,
			// In this case we save on the same folder as this file
			// Use the @ in fopen to skip errors.
			$fp = @fopen (dirname(__FILE__) . '/temp/' . $fileName, 'w+');

			// Don’t return the headers, we dont need them
			curl_setopt($ch, CURLOPT_HEADER, false);

			// But return the contents
			curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

			// And we will need the binary transfer in order to save the zip locally
			curl_setopt($ch, CURLOPT_BINARYTRANSFER,true);

			// Stops it if an error occurs
			curl_setopt($ch, CURLOPT_FAILONERROR, true);

			// Cant tell exactly what these two are for, just let them be...
			curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
			curl_setopt($ch, CURLOPT_AUTOREFERER, true);

			// The most important part, in here we tell cURL to save the file
			curl_setopt($ch, CURLOPT_FILE, $fp);

			// If it doesn't work by this time cancel the whole thing
			curl_setopt($ch, CURLOPT_TIMEOUT, 50);


			// If you run into this error :
			// cURL error number:60
			// cURL error:SSL certificate problem, verify that the CA cert is OK.
			// Either uncomment the next line (NOT RECOMENDED):
			curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //Shouldn't be used

			// Or download the certificate from MSN, this URL contains the tutorial for that
			// http://unitstep.net/blog/2009/05/05/using-curl-in-php-to-access-https-ssltls-protected-sites/
			// And save the certificate (.crt) on the same folder as this file and leave
			// these 3 lines uncommented:
			/*
			curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
			curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
			curl_setopt($ch, CURLOPT_CAINFO, getcwd() . "/GTECyberTrustGlobalRoot.crt");
			*/


			// If you are running under a proxy uncomment the following and use your data

			// curl_setopt($ch, CURLOPT_PROXY, 'XXXXX.XXXX.XXXX.XXX.XXXX");
			// curl_setopt($ch, CURLOPT_PROXYPORT, XXX);

			// Execute the cURL
			$data = curl_exec ($ch);


			// If something is wrong tell us what it is
			if (!$data) {
				echo "<br />cURL error number:" .curl_errno($ch);
				echo "<br />cURL error:" . curl_error($ch);
				exit;
			} // ends if (!$data)

			//=========================================================
			//======= OPEN THE ZIP FILE, AND READ THE XML WITHOUT UNZIPPING IT

			// Select our file in order to unzip
			$zip = zip_open('./temp/' . $fileName);

			// If it is valid start reading the contents
			if (is_resource($zip)) {
				while ($zip_entry = zip_read($zip)) {
					// Echo some of it's data just to know everything is ok.
					/*
					echo "<br>Name:               " . zip_entry_name($zip_entry) . "\n";
					echo "<br>Actual Filesize:    " . zip_entry_filesize($zip_entry) . "\n";
					echo "<br>Compressed Size:    " . zip_entry_compressedsize($zip_entry) . "\n";
					echo "<br>Compression Method: " . zip_entry_compressionmethod($zip_entry) . "\n";
					*/

					// Here is the most important part
					if (zip_entry_open($zip, $zip_entry, "r")) {
						$contentsXML = zip_entry_read($zip_entry, zip_entry_filesize($zip_entry));

						// Load our XML!
						$data = simplexml_load_string($contentsXML);

						zip_entry_close($zip_entry);
					} // ends if (zip_entry_open($zip, $zip_entry, "r"))
				} // ends while ($zip_entry = zip_read($zip))

				zip_close($zip);

			} // ends if (is_resource($zip))

			foreach ($data->Table->Row as $oRow) {
				/*
				SimpleXMLElement Object
				(
					[AccountName] => SimpleXMLElement Object
						(
							[@attributes] => Array
								(
									[value] => Eric Nagel & Associates
								)

						)
				*/
				$rsData = array();
				foreach ($oRow->children() as $oChild) {
					$cVar = $oChild->getName();
					$oElement = $oRow->$cVar;
					$rsAttributes = $oElement->attributes();
					$rsData[$cVar] = (string)$rsAttributes['value'];
				} // ends

				if ($bDebug) {
					print_r($rsData);
					exit();
				} // ends
				/*
				Array
				(
					[AccountName] => Eric Nagel & Associates
					[AccountNumber] => XXXXXXXX
					[GregorianDate] => 3/8/2011
					[LanguageAndRegion] => English - United States
					[Status] => Active
					[CampaignName] => Club Coupon Code
					[CampaignId] => 20767846
					[AdGroupName] => XXXXXXXXXXXXXXXXXXXXXXXXXXXX
					[AdGroupId] => 263921452
					[CurrencyCode] => USD
					[AdDistribution] => Search
					[Impressions] => 1
					[Clicks] => 0
					[Ctr] => 0.0000
					[AverageCpc] => 0.00
					[Spend] => 0.00
					[AveragePosition] => 3.00
					[Conversions] => 0
					[ConversionRate] =>
					[CostPerConversion] =>
					[AverageCpm] => 0.00
					[PricingModel] => Cost per click
					[Devicetype] => Computer
				)
				*/

				echo("Now you have to do something with \$rsData\n");

			} // ends foreach ($data->Table->Row as $row)

			unlink('./temp/' . $fileName);
			break;

			//====== MSN AdCenter Script ====================================
			// ===== Checks for status report ===============================
		} // ends if ($reportStatus == 'Success')
		else {
			if ($reportStatus == 'Pending') {
				// The report is not yet ready.
				continue;
			} // ends if ($reportStatus == 'Pending')
			else {
				// An error occurred.
				printf("An error occurred in %s\n", $action);
				break;
			} // ends else from if ($reportStatus == 'Pending')
		} // ends else from if ($reportStatus == 'Success')
	} // ends while (true)
 } // ends trying. Just give up.

//===============================================================

//====== MSN AdCenter Script ====================================
// ===== catches errors =========================================
catch (Exception $e)
{
    print "$action failed.\n";

    if (isset($e->detail->ApiFaultDetail))
    {
      print "ApiFaultDetail exception encountered\n";
      print "Tracking ID: " .
          $e->detail->ApiFaultDetail->TrackingId . "\n";

      // Process any operation errors.
      if (isset(
          $e->detail->ApiFaultDetail->OperationErrors->OperationError
          ))
      {
          if (is_array(
              $e->detail->ApiFaultDetail->OperationErrors->OperationError
              ))
          {
              // An array of operation errors has been returned.
              $obj = $e->detail->ApiFaultDetail->OperationErrors->OperationError;
          }
          else
          {
              // A single operation error has been returned.
              $obj = $e->detail->ApiFaultDetail->OperationErrors;
          }
          foreach ($obj as $operationError)
          {
              print "Operation error encountered:\n";
              print "\tMessage: ". $operationError->Message . "\n";
              print "\tDetails: ". $operationError->Details . "\n";
              print "\tErrorCode: ". $operationError->ErrorCode . "\n";
              print "\tCode: ". $operationError->Code . "\n";
          }
      }

      // Process any batch errors.
      if (isset(
          $e->detail->ApiFaultDetail->BatchErrors->BatchError
          ))
      {
          if (is_array(
              $e->detail->ApiFaultDetail->BatchErrors->BatchError
              ))
          {
              // An array of batch errors has been returned.
              $obj = $e->detail->ApiFaultDetail->BatchErrors->BatchError;
          }
          else
          {
              // A single batch error has been returned.
              $obj = $e->detail->ApiFaultDetail->BatchErrors;
          }
          foreach ($obj as $batchError)
          {
              print "Batch error encountered for array index " . $batchError->Index . ".\n";
              print "\tMessage: ". $batchError->Message . "\n";
              print "\tDetails: ". $batchError->Details . "\n";
              print "\tErrorCode: ". $batchError->ErrorCode . "\n";
              print "\tCode: ". $batchError->Code . "\n";
          }
      }
    }

    if (isset($e->detail->AdApiFaultDetail))
    {
      print "AdApiFaultDetail exception encountered\n";
      print "Tracking ID: " .
          $e->detail->AdApiFaultDetail->TrackingId . "\n";

      // Process any operation errors.
      if (isset(
          $e->detail->AdApiFaultDetail->Errors
          ))
      {
          if (is_array(
              $e->detail->AdApiFaultDetail->Errors
              ))
          {
              // An array of errors has been returned.
              $obj = $e->detail->AdApiFaultDetail->Errors;
          }
          else
          {
              // A single error has been returned.
              $obj = $e->detail->AdApiFaultDetail->Errors;
          }
          foreach ($obj as $Error)
          {
              print "Error encountered:\n";
              print "\tMessage: ". $Error->Message . "\n";
              print "\tDetail: ". $Error->Detail . "\n";
              print "\tErrorCode: ". $Error->ErrorCode . "\n";
              print "\tCode: ". $Error->Code . "\n";
          }
      }

    }

    // Display the fault code and the fault string.
    print $e->faultcode . " " . $e->faultstring . ".\n";

    // Output the last request.
    print "Last SOAP request:\n";
    print $client->__getLastRequest() . "\n";
}
//===============================================================

// ADDED CLASS ==================================================
// Definition for the classes that are used by the Reporting service.
class AdGroupPerformanceReportRequest {
    public $Format;
    public $Language;
    public $ReportName;
    public $ReturnOnlyCompleteData;
    public $Aggregation;
    public $Columns;
    public $Filter;
    public $Scope;
    public $Time;
} // ends class AdGroupPerformanceReportRequest

?>

Good luck – you’ll need it!

Comments
  • Vipin
    Posted March 7, 2013 1:36 am 0Likes

    Hello Eric,

    If I want to use “AdGroupStatusReport”, what are the changes i need to do in the above code? I have tried with some changes but still getting error.

    Please help.

    Thanks,
    Vipin

    • Eric Nagel
      Posted March 8, 2013 9:08 am 0Likes

      Vipin,

      The API has been updated a few times since I wrote this, but the format should be very similar. Unfortunately, it’s not something I use anymore, so I’m not 100% sure.

      If you’re still stuck, you can hire me to do it for you, but I won’t be able to post a solution to my blog at this time, sorry.

Leave A Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.