Ability to Bulk Remove Line Items

Status: Idea Submitted

It would be great to have more control of Line Items. For example, having 200 Line Items on a Deal can cause trouble in case they need to be removed, because that can only be done manually or via API.

Meanwhile I built this Python script with ChatGPT that might help others to bulk delete Lined Items associated to any given Deal. You will need Access Token and scopes that allows reading Deals and reading/writing Line Items.
You can run this via a Visual Code:

import requests

# Configuration
ACCESS_TOKEN = 'YOUR_TOKEN' # Replace with your actual access token
HUBSPOT_API_BASE_URL = 'https://api.hubapi.com'

headers = {
 'Authorization': f'Bearer {ACCESS_TOKEN}',
 'Content-Type': 'application/json'
}

def get_deal_info(deal_id):
 """ Retrieve basic information about the deal. """
 url = f"{HUBSPOT_API_BASE_URL}/crm/v3/objects/deals/{deal_id}"
 response = requests.get(url, headers=headers)
 response.raise_for_status()
 return response.json()

def get_associated_line_items(deal_id):
 """ Retrieve all line items associated with a specific deal using the CRM Associations API. """
 url = f"{HUBSPOT_API_BASE_URL}/crm/v3/objects/deals/{deal_id}/associations/line_items"
 response = requests.get(url, headers=headers)
 response.raise_for_status()
 return [assoc['id'] for assoc in response.json()['results']]

def delete_line_item(line_item_id):
 """ Delete a specific line item. """
 url = f"{HUBSPOT_API_BASE_URL}/crm-objects/v1/objects/line_items/{line_item_id}"
 response = requests.delete(url, headers=headers)
 response.raise_for_status()
 return response.status_code == 204

def confirm(prompt):
 """ Prompt the user for confirmation. """
 return input(prompt).lower() == 'yes'

# Script Execution
try:
 deal_id = input("Please enter the deal ID: ")
 deal_info = get_deal_info(deal_id)
 print(f"Deal Information: ID - {deal_info['id']}, Name - {deal_info.get('properties', {}).get('dealname', 'N/A')}")

 if not confirm("Is this the correct deal you want to delete line items from? (yes/no): "):
 print("Operation cancelled by the user.")
 exit(0)

 line_items = get_associated_line_items(deal_id)
 print(f"Total line items found: {len(line_items)}")
 for line_item_id in line_items:
 print(f"Line Item ID: {line_item_id}")

 if not confirm("Do you want to DELETE all these line items? (yes/no): "):
 print("Deletion cancelled by the user.")
 exit(0)

 for line_item_id in line_items:
 if delete_line_item(line_item_id):
 print(f"Deleted line item: {line_item_id}")
 else:
 print(f"Failed to delete line item: {line_item_id}")

except requests.HTTPError as err:
 print(f"HTTP error occurred: {err}")
except Exception as e:
 print(f"An error occurred: {e}")​

@RRoseberg58 - You’re a genius. :folded_hands:t3:

Updated to accept a string of id’s separated by commas:

import requests

# Configuration
ACCESS_TOKEN = 'pat-add-token-here' # Replace with your actual access token
HUBSPOT_API_BASE_URL = 'https://api.hubapi.com'

headers = {
 'Authorization': f'Bearer {ACCESS_TOKEN}',
 'Content-Type': 'application/json'
}

def get_deal_info(deal_id):
 """ Retrieve basic information about the deal. """
 url = f"{HUBSPOT_API_BASE_URL}/crm/v3/objects/deals/{deal_id}"
 response = requests.get(url, headers=headers)
 response.raise_for_status()
 return response.json()

def get_associated_line_items(deal_id):
 """ Retrieve all line items associated with a specific deal using the CRM Associations API. """
 url = f"{HUBSPOT_API_BASE_URL}/crm/v3/objects/deals/{deal_id}/associations/line_items"
 response = requests.get(url, headers=headers)
 response.raise_for_status()
 return [assoc['id'] for assoc in response.json()['results']]

def delete_line_item(line_item_id):
 """ Delete a specific line item. """
 url = f"{HUBSPOT_API_BASE_URL}/crm-objects/v1/objects/line_items/{line_item_id}"
 response = requests.delete(url, headers=headers)
 response.raise_for_status()
 return response.status_code == 204

def confirm(prompt):
 """ Prompt the user for confirmation. """
 return input(prompt).lower() == 'yes'

# Script Execution
try:
 deal_ids = input("Please enter the deal IDs separated by commas: ").split(',')
 all_line_items = []

 for deal_id in deal_ids:
 deal_id = deal_id.strip()
 deal_info = get_deal_info(deal_id)
 print(f"Deal Information: ID - {deal_info['id']}, Name - {deal_info.get('properties', {}).get('dealname', 'N/A')}")

 if not confirm(f"Is this the correct deal {deal_id} you want to delete line items from? (yes/no): "):
 print(f"Skipping deal ID: {deal_id}")
 continue

 line_items = get_associated_line_items(deal_id)
 print(f"Total line items found for deal {deal_id}: {len(line_items)}")
 all_line_items.extend(line_items)

 if not all_line_items:
 print("No line items to delete.")
 exit(0)

 print(f"Total line items found across all deals: {len(all_line_items)}")
 for line_item_id in all_line_items:
 print(f"Line Item ID: {line_item_id}")

 if not confirm("Do you want to DELETE all these line items? (yes/no): "):
 print("Deletion cancelled by the user.")
 exit(0)

 for line_item_id in all_line_items:
 if delete_line_item(line_item_id):
 print(f"Deleted line item: {line_item_id}")
 else:
 print(f"Failed to delete line item: {line_item_id}")

except requests.HTTPError as err:
 print(f"HTTP error occurred: {err}")
except Exception as e:
 print(f"An error occurred: {e}")

@Rockland any chance you know how to get this code working in a custom coded workflow with ops hub? :slightly_smiling_face: