Just like what can happen to any of us, Tandem ran into a time suck. One of our team members was tasked with auditing all the call tracking numbers across every account in our Google MCC. That sounds simple until you realize that those numbers can live in multiple places. They can be found in customer-level assets, campaign assets, and ad group assets. And they’re not always active. Some are paused, some are duplicated, and some only exist in one out-of-the-way ad group.
You can’t just click around and trust that you’ll catch everything. And we’re not in the business of guesswork.
I figured there had to be a better way. So, I opened up ChatGPT, laid out the problem, and started building a solution that could pull everything into one place.
Many Accounts, Not Enough Time, and the Script That Solved It
We ended up with a script that loops through all accounts in the MCC and pulls every phone number from all three asset levels. It formats each number and reports its status, prioritizing enabled numbers when duplicates are found.
The idea was simple. Get a single view of every unique phone number that might be triggering phone calls in any MCC campaign without sorting through them manually.
Once the script runs, it produces a clean list with account names and their corresponding numbers. It only shows each number once per account, even if it shows up in multiple spots. And if that number is enabled anywhere, that’s the status it reports.
Using AI to Format the Output
Here’s where ChatGPT came back into the picture. The raw script output is readable but not pretty. So we dropped it into ChatGPT and used a short prompt to clean it up and turn it into a downloadable CSV.
Using AI made it easy to share with the client and with our internal team. It gave us a reliable source of truth for which numbers were currently in play across every campaign.
Why Not Using AI Is No Longer a Choice
If you manage even a handful of accounts, you know how messy phone call tracking can get. Numbers get reused, campaigns get paused, and assets linger long after they’ve stopped performing.
Running an audit like this gives you a full view of what’s live, what’s duplicated, and what needs cleanup. It also keeps you from making a reporting mistake, such as counting calls from a paused asset or missing an active one buried in a single ad group.
Utilizing artificial intelligence isn’t just a time-saver. It’s a visibility tool. And in multi-account PPC management, visibility is everything.
Preparing for the Future, One Script at a Time
We saved the script inside each of the client’s MCCs for future use. If your team ever needs to pull tracking numbers across multiple accounts, this is one of the fastest and cleanest ways to do it. And with ChatGPT handling the formatting, you get a polished report in seconds.
Bringing It Back to Strategy
At Tandem, we don’t just run PPC campaigns. We build smart, scalable systems that solve real operational headaches. Whether it’s auditing a call tracking asset system or simplifying data workflows, our team looks for ways to eliminate busy work and make marketing efforts more efficient.
This script is one example of how we turn technical roadblocks into reusable solutions. We’re always looking for new ways to optimize your digital marketing, reduce friction, and surface the data that actually matters.
If your brand needs a partner who understands the backend and the big picture, we’re ready when you are.
Let’s take your PPC strategy further. Connect with our Fort Lauderdale digital marketing agency today.

ChatGPT MCC Script
function main() {
var accountIterator = MccApp.accounts().get();
var accountPhoneNumbers = {}; // To store phone numbers with statuses per account
while (accountIterator.hasNext()) {
var account = accountIterator.next();
MccApp.select(account);
var accountName = account.getName();
var accountId = account.getCustomerId();
Logger.log(“Scanning Account: ” + accountName + ” (” + accountId + “)”);
var phoneNumberMap = new Map(); // Map for unique phone numbers and their best status
// 1. Pull Customer-Level Assets
var customerQuery = `
SELECT
asset.id,
asset.name,
asset.type,
asset.call_asset.phone_number,
asset.call_asset.call_conversion_reporting_state,
customer_asset.status
FROM customer_asset
WHERE asset.type = ‘CALL’
`;
var customerIterator = AdsApp.search(customerQuery);
while (customerIterator.hasNext()) {
var row = customerIterator.next();
var phoneNumber = row.asset.callAsset.phoneNumber;
var status = row.customerAsset.status;
if (phoneNumber) {
var formattedPhone = formatPhoneNumber(phoneNumber);
updatePhoneStatus(phoneNumberMap, formattedPhone, status);
}
}
// 2. Pull Campaign-Level Assets
var campaignQuery = `
SELECT
asset.id,
asset.name,
asset.type,
asset.call_asset.phone_number,
asset.call_asset.call_conversion_reporting_state,
campaign_asset.status
FROM campaign_asset
WHERE asset.type = ‘CALL’
`;
var campaignIterator = AdsApp.search(campaignQuery);
while (campaignIterator.hasNext()) {
var row = campaignIterator.next();
var phoneNumber = row.asset.callAsset.phoneNumber;
var status = row.campaignAsset.status;
if (phoneNumber) {
var formattedPhone = formatPhoneNumber(phoneNumber);
updatePhoneStatus(phoneNumberMap, formattedPhone, status);
}
}
// 3. Pull Ad Group-Level Assets
var adGroupQuery = `
SELECT
asset.id,
asset.name,
asset.type,
asset.call_asset.phone_number,
asset.call_asset.call_conversion_reporting_state,
ad_group_asset.status
FROM ad_group_asset
WHERE asset.type = ‘CALL’
`;
var adGroupIterator = AdsApp.search(adGroupQuery);
while (adGroupIterator.hasNext()) {
var row = adGroupIterator.next();
var phoneNumber = row.asset.callAsset.phoneNumber;
var status = row.adGroupAsset.status;
if (phoneNumber) {
var formattedPhone = formatPhoneNumber(phoneNumber);
updatePhoneStatus(phoneNumberMap, formattedPhone, status);
}
}
// Save final unique set
if (phoneNumberMap.size > 0) {
var phoneNumberList = [];
phoneNumberMap.forEach(function(status, phone) {
phoneNumberList.push(phone + ” (” + status + “)”);
});
accountPhoneNumbers[accountName + ” (” + accountId + “)”] = phoneNumberList;
}
}
// Final Output: Nice table
Logger.log(“==== FINAL TABLE ====”);
Logger.log(“Account | Phone Numbers (with Status)”);
for (var account in accountPhoneNumbers) {
var phones = accountPhoneNumbers[account].join(“, “);
Logger.log(account + ” | ” + phones);
}
}
// Helper function to format phone numbers to (XXX) XXX-XXXX
function formatPhoneNumber(phoneNumber) {
var cleaned = (” + phoneNumber).replace(/\D/g, ”);
if (cleaned.length === 10) {
return ‘(‘ + cleaned.substring(0,3) + ‘) ‘ + cleaned.substring(3,6) + ‘-‘ + cleaned.substring(6);
} else if (cleaned.length === 11 && cleaned.startsWith(‘1’)) {
return ‘(‘ + cleaned.substring(1,4) + ‘) ‘ + cleaned.substring(4,7) + ‘-‘ + cleaned.substring(7);
} else {
return phoneNumber;
}
}
// Helper function to update the status map following the ENABLED priority rule
function updatePhoneStatus(map, phone, status) {
if (!map.has(phone)) {
map.set(phone, status);
} else {
var existingStatus = map.get(phone);
// If any appearance is ENABLED, make sure it stays ENABLED
if (existingStatus !== “ENABLED” && status === “ENABLED”) {
map.set(phone, “ENABLED”);
}
}
}
Take the output and put into ChatGPT with this prompt:
Format the following call asset data into a downloadable CSV file. Only include phone numbers marked as ENABLED. If there are multiple enabled numbers for the same account, list them in separate columns (e.g., Phone 1, Phone 2, etc.). Output should be a table and provide a download link.
Why this works well:
- It specifies only enabled numbers, avoiding any ambiguity.
- It instructs to split multiple phone numbers into their own columns (not stacked rows).
- It requests a downloadable file, not just a visual table.
- It works well even with raw, mixed-status phone listings.
There many other ways to further customize the output. Seeing all the paused or removed call extensions could also be valuable to see.








