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")