While automating the removal of Salesforce users during our account off-boarding process, I ran into a curious issue where I had to figure out how to programmatically delete reports & dashboards that are stored in “My Personal Custom Reports” & “My Personal Dashboards” folders. This was quite a challenge since reports and dashboards are considered metadata and not regular sObjects in Salesforce. To add to that – any existing API endpoints in the documentation listed here and here only worked for non-private reports & dashboards and I literally couldn’t find a single thing on this anywhere on Google.

After googling a bunch and spending hours trying to make the Salesforce REST API work, I ended up ending up on this API reference page that gives you a Salesforce sOQL query to get the Id’s of private reports & dashboards for a specific user
SELECT Id FROM Report USING SCOPE allPrivate WHERE OwnerId = '01abcdefghijk'
A simple GET call will let you retrieve the IDs of reports/dashboards but what do you do after that? A DELETE rest call definitely does NOT work for these IDs since they are metadata objects. Googling some more, I ended up discovering that Salesforce Workbench is able to delete these report / dashboard IDs without a hitch. Since Workbench is open-source PHP tool so off-course I looked up what API endpoint it was calling and discovered it was calling the Salesforce SOAP API.
SOAP API’s are tedious, ugly but they work well when setup and used correctly but unlike REST API’s they use XML instead of JSON and are generally considered to be legacy.
The solution:
Since my language of choice was Python, I opted to use the Zeep library that allows your to make SOAP calls without having the hassle of handling XML data. You’ll need a few things to get started:
- Ensure you have Python 3.6 or higher installed on your machine
- Install Zeep to your machine (
pip3 install zeep
) - access_token obtained from your Salesforce OAuth flows (if you already use REST api’s – you can use the same one). Follow one of many flows to obtain this.
- You partner.wsdl file from your Salesforce org. To get this go into Salesforce Setup >> Quick Find >> API >> Generate Partner WSDL. Store this file in the same folder as the python script (or adjust the script accordingly)
- List of IDs you want to delete

Finally the magic bits!
import zeep
from zeep import Client
from zeep.helpers import serialize_object
# Setup SOAP Client
# Ensure partner.wsdl exists in the same folder
soap_client = Client("partner.wsdl")
sfdc_service_binding = "{urn:partner.soap.sforce.com}SoapBinding"
# Replace your SFDC instance URL here
soap_api_url = "https://YOUR-SALESFORCE-INSTANCE-URL/services/Soap/u/49.0"
# Replace value with your access token
sfdc_access_token = "YOUR-SFDC-ACCESS-TOKEN"
# Setup SOAP headers
soap_headers = {}
# Retrieve SessionHeader element from the SOAP client and add access_token to it
soap_session_header_element = soap_client.get_element("ns1:SessionHeader")
soap_header = soap_session_header_element(sfdc_access_token)
soap_headers["SessionHeader"] = soap_header
soap_client_service = soap_client.create_service(sfdc_service_binding, soap_api_url)
# Run our report IDs through the SOAP API
object_ids = ["reportID1", "reportID2"]
try:
result = soap_client_service.delete(object_ids, _soapheaders=soap_headers)
print(serialize_object(result)).get("body", {})
except zeep.exceptions.Error:
log.exception(
f"A zeep exception occurred while attempting to delete object IDs: {object_ids}."
)
except Exception:
log.exception(
f"A general exception occurred while attempting to delete object IDs: {object_ids}."
)
If everything goes well, you should see the results of the deletion for each of the IDs!
Happy coding!
Recent Comments