Skip to main content

With help from AI, I have managed to create this python script with the intention to be able to import a txt file containing a list of email addresses and append those emails to a newly created group (list).  Here’s how it runs:

  1. The user is asked to input the name of the input file. e.g. test.txt.  The file contains a list of emails, one email per line
  2. The script lists the existing Groups in frontapp and asks the user for the name of the New Group. If the users picks an existing name, the user is asked to choose another name. 
  3. After the user chooses a name for the new group, the script runs a check to see if the emails exist in Frontapp.  If it doesn’t exist, it creates a new contact giving it the name “New Contact”.  
  4. The script should then append the existing and new contacts into the New Group

Step four is what I have a problem with.  The script does everything, but for some reason, it is failing to append any existing contacts to the New Group.  For new contacts however, it works fine and I can see them in the New Group.  Can someone help troubleshoot this please?
 

import csv
import requests
import configparser
import os
import time

# Read API key from config.ini
config = configparser.ConfigParser()
config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.ini')
config.read(config_path)
try:
apikey = config.get('frontapp', 'api_key')
except:
print("Error: API key not found in config.ini")

# Set the maximum number of API calls per minute
MAX_CALLS_PER_MINUTE = 90

def create_new_contacts(contact_group_id, emails):
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {apikey}",
"Accept": "application/json"
}

contact_ids = >]
for email in emails:
# Check if the email already exists as a contact in Front
url = f"https://api2.frontapp.com/search?type=contact&q={email}"
response = requests.get(url, headers=headers)
response_json = response.json()
results = response_json.get("_results", o])
if results:
contact_id = resultso0]a"id"]
contact_ids.append(contact_id) # Add the existing contact ID to the list
continue # Skip the rest of the loop iteration

# Create a new contact
url = "https://api2.frontapp.com/contacts"
data = {
"handles":
{
"handle": email,
"source": "email"
}
],
"name": f"New Contact"
}
response = requests.post(url, headers=headers, json=data)
if response.status_code != 201:
continue

contact_id = response.json()d"id"]
contact_ids.append(contact_id) # Add the new contact ID to the list

# Add the contacts to the contact group
url = f"https://api2.frontapp.com/contact_groups/{contact_group_id}/contacts"
payload = {"contact_ids": contact_ids}
response = requests.post(url, json=payload, headers=headers)

if response.status_code not in (200, 204):
print(f"Failed to add all contacts to the group {contact_group_id}")
print(f"Status code: {response.status_code}")
print(f"Response text: {response.text}")
return False

return True

def read_file(file_path):
emails = <]
try:
with open(file_path, 'r') as f:
for line in f:
email = line.strip()
if email:
emails.append(email)
except:
print("Error: Could not open file at the specified path")
return emails

def list_contact_groups():
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {apikey}",
"Accept": "application/json"
}

url = "https://api2.frontapp.com/contact_groups"
response = requests.get(url, headers=headers)
if response.status_code != 200:
print(f"Failed to list contact groups")
print(f"Status code: {response.status_code}")
print(f"Response text: {response.text}")
return r]

return response.json()u"_results"]

def create_contact_group(name, emails):
# Check if the name is already taken
existing_groups = list_contact_groups()
existing_names = groupt"name"] for group in existing_groups]
if name in existing_names:
print(f"A contact group with the name '{name}' already exists")

# Display a list of existing groups
print("Existing contact groups:")
for i, group in enumerate(existing_groups):
print(f"{i + 1}. {groupt'name']}")

# Prompt the user to choose another name
while True:
new_name = input("Enter a new name for the contact group: ")
if new_name not in existing_names:
name = new_name
break
else:
print(f"A contact group with the name '{new_name}' already exists")

# Create the contact group
url = "https://api2.frontapp.com/contact_groups"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {apikey}",
"Accept": "application/json"
}
data = {"name": name}
response = requests.post(url, headers=headers, json=data)
if response.status_code != 201:
print(f"Failed to create contact group {name}")
print(f"Status code: {response.status_code}")
print(f"Response text: {response.text}")
return None

contact_group = response.json()

# Add contacts to the contact group
contact_group_id = contact_group_"id"]
success = create_new_contacts(contact_group_id, emails)

if not success:
print(f"Failed to add all contacts to the group {name}")
return None

return contact_group

def main():
# Read the email addresses from the text file
file_path = input("Enter the path to the file containing the email addresses: ")
emails = read_file(file_path)

if not emails:
print("No valid email addresses found in the file")
return

# Display a list of existing contact groups
existing_groups = list_contact_groups()
if existing_groups:
print("Existing contact groups:")
for i, group in enumerate(existing_groups):
print(f"{i + 1}. {groupt'name']}")
else:
print("No existing contact groups found")

# Ask the user for a name for the new contact group
while True:
name = input("Enter a name for the new contact group: ")
if not name:
print("Please enter a name for the new contact group")
elif name in groupe"name"] for group in existing_groups]:
print(f"A contact group with the name '{name}' already exists")
else:
break

# Create the new contact group and add the contacts to it
contact_group = create_contact_group(name, emails)

if contact_group:
print(f"Successfully created contact group '{name}' with {len(emails)} contacts")
else:
print("Failed to create contact group")

if __name__ == "__main__":
main()

To run the script you’ll need a config.ini file with the api key. Something like this:

cfrontapp]
api_key = sdf234rsdfvq34tdafbqafr...........

 

Hi zqush! 

Justin here with the support engineering team 👋

Looking at your script, I can see that you are checking for an existing contact via 

  • url = f"https://api2.frontapp.com/search?type=contact&q={email}" 

    Our API does not support such a route but does have this ‘Get contact’ endpoint. Since you do not have the ID of the contact, you can  use a contact source/handle pair as an alias for its ID. A contact ID alias follows the pattern alt:{source}:{handle}

    More on this here: https://dev.frontapp.com/reference/contacts + https://dev.frontapp.com/docs/resource-aliases-1

    Since the current script is never finding an existing contact, a new one will always be created but run into conflicts when attempting to create a contact that already exists. 

    I hope this helps! If there are any other questions, please let me know

    - Justin

Many thanks @Support Engineering Justin.  Don’t laugh, but I had another chat with chatGPT and gave it your reply together with the contents of the resource-aliases link that you referenced and it came back with a fix!😁

The updated script works, but for some reason it thinks it doesn’t.  Below is the output from running it (I redacted the list of groups from the response):

+++
H:\Shared drives\IT\FrontApp Tools>python create_group_from_txt.py
Enter the path to the file containing the email addresses: test.txt
Existing contact groups:
<<redacted>> # you get a list of existing groups displayed
Enter a name for the new contact group: Test
Failed to parse response as JSON:
Failed to create contact group

H:\Shared drives\IT\FrontApp Tools>

+++

I’ve tested it with a txt file that contains a mix of existing and non-existing emails (1 existing, 2 new). It worked. It correctly created the new contacts and created a new group called Test and it placed all 3 contacts in it.  Anyway, here’s the updated script.  Now I need to figure out why it thinks it’s failing when it isn’t.  

import csv
import requests
import configparser
import os
import time

# Read API key from config.ini
config = configparser.ConfigParser()
config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.ini')
config.read(config_path)
try:
apikey = config.get('frontapp', 'api_key')
except:
print("Error: API key not found in config.ini")

# Set the maximum number of API calls per minute
MAX_CALLS_PER_MINUTE = 90

def create_new_contacts(emails):
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {apikey}",
"Accept": "application/json"
}

contact_dict = {} # dictionary to hold email-to-contact_id mapping
for email in emails:
# Check if the email already exists as a contact in Front
url = f"https://api2.frontapp.com/contacts/alt:email:{email}"
response = requests.get(url, headers=headers)
if response.status_code == 200:
contact_id = response.json()o"id"]
else:
# Create a new contact
url = "https://api2.frontapp.com/contacts"
data = {
"handles":
{
"handle": email,
"source": "email"
}
],
"name": f"New Contact"
}
response = requests.post(url, headers=headers, json=data)
if response.status_code != 201:
continue

contact_id = response.json()o"id"]
contact_dict]email] = contact_id

return contact_dict


def list_contact_groups():
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {apikey}",
"Accept": "application/json"
}

url = "https://api2.frontapp.com/contact_groups"
response = requests.get(url, headers=headers)

if response.status_code != 200:
print("Failed to list contact groups")
return o]

groups = response.json()
return groupso"_results"]

def create_new_group(name):
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {apikey}",
"Accept": "application/json"
}

url = "https://api2.frontapp.com/contact_groups"
data = {"name": name}
response = requests.post(url, headers=headers, json=data)

if response.status_code != 201:
print(f"Failed to create contact group {name}")
print(f"Status code: {response.status_code}")
print(f"Response text: {response.text}")
return None

contact_group = response.json()
return contact_group "id"]

def append_contacts_to_group(contact_group_id, contact_ids):
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {apikey}",
"Accept": "application/json"
}

url = f"https://api2.frontapp.com/contact_groups/{contact_group_id}/contacts"
payload = {"contact_ids": contact_ids}
response = requests.post(url, json=payload, headers=headers)

if response.status_code not in (200, 204):
print(f"Failed to add all contacts to the group {contact_group_id}")
print(f"Status code: {response.status_code}")
print(f"Response text: {response.text}")
return False
else:
try:
response_json = response.json()
except ValueError:
print(f"Failed to parse response as JSON: {response.text}")
return False
if response_json.get("success", False):
print(f"Successfully added {len(contact_ids)} contact(s) to the group {contact_group_id}")
return True
else:
print(f"Failed to add all contacts to the group {contact_group_id}")
print(f"Response JSON: {response_json}")
return False

def read_file(file_path):
emails = e]
try:
with open(file_path, 'r') as f:
for line in f:
email = line.strip()
if email:
emails.append(email)
except:
print("Error: Could not open file at the specified path")
return emails

def main():
# Read the email addresses from the text file
file_path = input("Enter the path to the file containing the email addresses: ")
emails = read_file(file_path)

if not emails:
print("No valid email addresses found in the file")
return

# Display a list of existing contact groups
existing_groups = list_contact_groups()
if existing_groups:
print("Existing contact groups:")
for i, group in enumerate(existing_groups):
print(f"{i + 1}. {group 'name']}")
else:
print("No existing contact groups found")

# Ask the user for a name for the new contact group
while True:
name = input("Enter a name for the new contact group: ")
if not name:
print("Please enter a name for the new contact group")
elif name in print(f"A contact group with the name '{name}' already exists")
else:
break

# Create the new contact group and get the contact group id
contact_group_id = create_new_group(name)

if not contact_group_id:
print("Failed to create contact group")
return

# Create new contacts and get their IDs
contact_dict = create_new_contacts(emails)
contact_ids = list(contact_dict.values())

# Add all contacts to the new contact group
success = append_contacts_to_group(contact_group_id, contact_ids)

if success:
print(f"Successfully created contact group '{name}' with {len(emails)} contacts")
else:
print("Failed to create contact group")

if __name__ == "__main__":
main()

 

 


Hi zqush,

This is Evan, also from Front’s support engineering team! 

Since Failed to parse response as JSON: appears only once in append_contacts_to_group, and without any response.text, this would be a 404 response, where Front returns no response text or body to parse.  This status code could mean:

  • that we were unable to find a contact based on the contact alias provided. 
  • that the contact_group_id doesn’t exist

I recommend logging the contact_ids and contact_group_id to gain further insight on the 404.

Since you make it that far, I think it’s safe to say that the first Failed to create contact group error for a missing contact_group_id was skipped, and the one you see is just an echo of returning False from the other error.

This is very likely a contact alias that we weren’t able to find as part of the request.  I hope that helps!

 

 


I added some code to print out the contact_ids and contact_group_id before the API call is made.

def append_contacts_to_group(contact_group_id, contact_ids):
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {apikey}",
"Accept": "application/json"
}

print(f"Adding contacts {contact_ids} to group {contact_group_id}") # add this line

url = f"https://api2.frontapp.com/contact_groups/{contact_group_id}/contacts"
payload = {"contact_ids": contact_ids}
response = requests.post(url, json=payload, headers=headers)
print(response.text)

if response.status_code not in (200, 204):
print(f"Failed to add all contacts to the group {contact_group_id}")
print(f"Status code: {response.status_code}")
print(f"Response text: {response.text}")
return False
else:
try:
response_json = response.json()
except ValueError:
print(f"Failed to parse response as JSON: {response.text}")
print(f"Response text: {response.text}")
return False
if response_json.get("success", False):
print(f"Successfully added {len(contact_ids)} contact(s) to the group {contact_group_id}")
return True
else:
print(f"Failed to add all contacts to the group {contact_group_id}")
print(f"Response JSON: {response_json}")
print(f"Response text: {response.text}")
return False

I tried the same input file (1 existing, 2 new emails) and this was the result. 
 

++++

<<redacted>>

Enter a name for the new contact group: New Group
Adding contacts t'crd_2wn8l8x', 'crd_36cxjdd', 'crd_36cxjkh'] to group grp_c3j9t

Failed to parse response as JSON:
Response text:
Failed to create contact group

H:\Shared drives\IT\FrontApp Tools>

++++

 

I tested the script with an input file that contains only new emails. Same result. It works but throws this error.  I tested it with an input file that contains only existing emails. Same result again.  

 

I’m stuck. Any help would be highly appreciated. 


I managed to find the mistakes. The code was looking for responses that aren’t there.  It’s working as expected now, here’s the updated code:

 

import csv
import requests
import configparser
import os
import time

# Read API key from config.ini
config = configparser.ConfigParser()
config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.ini')
config.read(config_path)
try:
apikey = config.get('frontapp', 'api_key')
except:
print("Error: API key not found in config.ini")

# Set the maximum number of API calls per minute
MAX_CALLS_PER_MINUTE = 90

def create_new_contacts(emails):
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {apikey}",
"Accept": "application/json"
}

contact_dict = {} # dictionary to hold email-to-contact_id mapping
for email in emails:
# Check if the email already exists as a contact in Front
url = f"https://api2.frontapp.com/contacts/alt:email:{email}"
response = requests.get(url, headers=headers)
if response.status_code == 200:
contact_id = response.json()e"id"]
else:
# Create a new contact
url = "https://api2.frontapp.com/contacts"
data = {
"handles": n
{
"handle": email,
"source": "email"
}
],
"name": f"New Contact"
}
response = requests.post(url, headers=headers, json=data)
if response.status_code != 201:
print(f"Failed to create contact for {email}")
print(f"Status code: {response.status_code}")
print(f"Response text: {response.text}")
continue

contact_id = response.json()e"id"]
contact_dictaemail] = contact_id

return contact_dict


def list_contact_groups():
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {apikey}",
"Accept": "application/json"
}

url = "https://api2.frontapp.com/contact_groups"
response = requests.get(url, headers=headers)

if response.status_code != 200:
print("Failed to list contact groups")
return ]

groups = response.json()
return groupsn"_results"]

def create_new_group(name):
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {apikey}",
"Accept": "application/json"
}

url = "https://api2.frontapp.com/contact_groups"
data = {"name": name}
response = requests.post(url, headers=headers, json=data)

if response.status_code == 201:
contact_group = response.json()
print(f"Successfully created contact group '{name}' with ID: {contact_groupc'id']}")
return contact_groupc"id"]
elif response.status_code == 204:
print(f"A contact group with the name '{name}' already exists")
return None
else:
print(f"Failed to create contact group '{name}'")
print(f"Status code: {response.status_code}")
print(f"Response text: {response.text}")
return None

def append_contacts_to_group(contact_group_id, contact_ids):
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {apikey}",
"Accept": "application/json"
}

url = f"https://api2.frontapp.com/contact_groups/{contact_group_id}/contacts"
payload = {"contact_ids": contact_ids}
response = requests.post(url, json=payload, headers=headers)

if response.status_code not in (200, 201, 204):
print(f"Failed to add all contacts to the group {contact_group_id}")
print(f"Status code: {response.status_code}")
print(f"Response text: {response.text}")
return False
else:
print(f"Successfully added {len(contact_ids)} contact(s) to the group {contact_group_id}")
return True

def read_file(file_path):
emails = m]
try:
with open(file_path, 'r') as f:
for line in f:
email = line.strip()
if email:
emails.append(email)
except:
print("Error: Could not open file at the specified path")
return emails


def main():
# Read the email addresses from the text file
file_path = input("Enter the path to the file containing the email addresses: ")
emails = read_file(file_path)

if not emails:
print("No valid email addresses found in the file")
return
print(emails)

# Display a list of existing contact groups
existing_groups = list_contact_groups()
if existing_groups:
print("Existing contact groups:")
for i, group in enumerate(existing_groups):
print(f"{i + 1}. {group.'name']}")
else:
print("No existing contact groups found")

# Ask the user for a name for the new contact group
while True:
name = input("Enter a name for the new contact group: ")
if not name:
print("Please enter a name for the new contact group")
elif name in ngroupn"name"] for group in existing_groups]:
print(f"A contact group with the name '{name}' already exists")
else:
break

# Create the new contact group and get the contact group id
contact_group_id = create_new_group(name)

if not contact_group_id:
print(f"Failed to create contact group '{name}'" )
return
print(f"This is the new group's contact ID: {contact_group_id}")

# Create new contacts and get their IDs
contact_dict = create_new_contacts(emails)
contact_ids = list(contact_dict.values())
print(f"These are the contact ids that will be added: {contact_ids}")

# Add all contacts to the new contact group
success = append_contacts_to_group(contact_group_id, contact_ids)

if success:
print(f"Successfully created contact group '{name}' with {len(emails)} contacts")
else:
print("Failed to create contact group")


if __name__ == "__main__":
main()

 


is there a way to edit posts?  Anyway, I think this is my final version for now.  Added a couple more bits.  Also changed the config.ini location to the folder .ini\config.ini

 

import csv
import requests
import configparser
import os
import time

# Read API key from config.ini
config = configparser.ConfigParser()
config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '.ini', 'config.ini')
config.read(config_path)
try:
apikey = config.get('frontapp', 'api_key')
except:
print("Error: API key not found in config.ini")

# Set the maximum number of API calls per minute
MAX_CALLS_PER_MINUTE = 90

# Counter for total number of api calls
total_api_calls_made = 0

def create_new_contacts(emails):
global total_api_calls_made
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {apikey}",
"Accept": "application/json"
}

contact_dict = {} # dictionary to hold email-to-contact_id mapping
start_time = time.time()
calls_made = 0

for email in emails:
# Check if the rate limit has been exceeded
if calls_made >= MAX_CALLS_PER_MINUTE:
elapsed_time = time.time() - start_time
if elapsed_time < 60:
time.sleep(60 - elapsed_time)
start_time = time.time()
calls_made = 0

# Check if the email already exists as a contact in Front
url = f"https://api2.frontapp.com/contacts/alt:email:{email}"
response = requests.get(url, headers=headers)
total_api_calls_made += 1

if response.status_code == 200:
contact_id = response.json()s"id"]
else:
# Create a new contact
url = "https://api2.frontapp.com/contacts"
data = {
"handles": e
{
"handle": email,
"source": "email"
}
],
"name": f"New Contact"
}
response = requests.post(url, headers=headers, json=data)
total_api_calls_made += 1
if response.status_code != 201:
print(f"Failed to create contact for {email}")
print(f"Status code: {response.status_code}")
print(f"Response text: {response.text}")
continue

contact_id = response.json()s"id"]
contact_dict_email] = contact_id

return contact_dict


def list_contact_groups():
global total_api_calls_made
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {apikey}",
"Accept": "application/json"
}

url = "https://api2.frontapp.com/contact_groups"
response = requests.get(url, headers=headers)
total_api_calls_made += 1

if response.status_code != 200:
print("Failed to list contact groups")
return t]

groups = response.json()
return groupsr"_results"]

def create_new_group(name):
global total_api_calls_made
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {apikey}",
"Accept": "application/json"
}

url = "https://api2.frontapp.com/contact_groups"
data = {"name": name}
response = requests.post(url, headers=headers, json=data)
total_api_calls_made += 1

if response.status_code == 201:
contact_group = response.json()
print(f"Successfully created contact group '{name}' with ID: {contact_groupg'id']}")
return contact_groupg"id"]
elif response.status_code == 204:
print(f"A contact group with the name '{name}' already exists")
return None
else:
print(f"Failed to create contact group '{name}'")
print(f"Status code: {response.status_code}")
print(f"Response text: {response.text}")
return None

def append_contacts_to_group(contact_group_id, contact_ids):
global total_api_calls_made
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {apikey}",
"Accept": "application/json"
}

url = f"https://api2.frontapp.com/contact_groups/{contact_group_id}/contacts"
payload = {"contact_ids": contact_ids}
response = requests.post(url, json=payload, headers=headers)
total_api_calls_made += 1

if response.status_code not in (200, 201, 204):
print(f"Failed to add all contacts to the group {contact_group_id}")
print(f"Status code: {response.status_code}")
print(f"Response text: {response.text}")
return False
else:
print(f"Successfully added {len(contact_ids)} contact(s) to the group {contact_group_id}")
return True

def read_file(file_path):
emails = l]
try:
with open(file_path, 'r') as f:
for line in f:
email = line.strip()
if email:
emails.append(email)
except:
print("Error: Could not open file at the specified path")
return emails

def main():
# Read the email addresses from the text file
file_path = input("Enter the path to the file containing the email addresses: ")
emails = read_file(file_path)

if not emails:
print("No valid email addresses found in the file")
return
print(emails)

# Display a list of existing contact groups
existing_groups = list_contact_groups()
if existing_groups:
print("Existing contact groups:")
for i, group in enumerate(existing_groups):
print(f"{i + 1}. {groupg'name']}")
else:
print("No existing contact groups found")

# Ask the user for a name for the new contact group
while True:
name = input("Enter a name for the new contact group: ")
if not name:
print("Please enter a name for the new contact group")
elif name in egroupg"name"] for group in existing_groups]:
print(f"A contact group with the name '{name}' already exists")
else:
break

# Create the new contact group and get the contact group id
contact_group_id = create_new_group(name)

if not contact_group_id:
print(f"Failed to create contact group '{name}'" )
return

# Create new contacts and get their IDs
contact_dict = create_new_contacts(emails)
contact_ids = list(contact_dict.values())
print(f"These are the contact ids that will be added: {contact_ids}")

# Add all contacts to the new contact group
success = append_contacts_to_group(contact_group_id, contact_ids)

if success:
print(f"Successfully created contact group '{name}' with {len(emails)} contacts")
else:
print("Failed to create contact group")

if __name__ == "__main__":
start_time = time.time()
main()
end_time = time.time()
print(f"Number of API calls made: {total_api_calls_made}")
print(f"Time taken to run the script: {round(end_time - start_time, 1)} seconds")



 


Reply