The issue I am facing involves Klaviyo's rate limits, which restrict the number of API calls I can make within specific time frames. I am sending around 22 requests per run (with 10 images), and depending on my tier, I might hit these limits. For example, i dont know if im on the XS tier, but i think i am and you can only make 15 requests per minute. If I exceed this, the API will throttle or block your requests, causing errors. I need to know how to either adjusting my program to stay within these limits or figure out how to get a higher rate limit. Basically my program down below is paired with a flask website that takes in around 10 images to be placed in a new template. That means there are 10 Post requests and 10 Get requests and 1 request to create the template. Help me figure out how to fix.
from flask import Flask, render_template, request
from flask_wtf import FlaskForm
from wtforms import SubmitField, StringField, MultipleFileField
from werkzeug.utils import secure_filename
import os
import json
import requests
from wtforms.validators import InputRequired
app = Flask(__name__)
app.config['SECRET_KEY'] = 'supersecretkey'
app.config['UPLOAD_FOLDER'] = 'uploads'
class UploadFileForm(FlaskForm):
file = MultipleFileField("File", validators=[InputRequired()])
apiKey = StringField("String", validators=[InputRequired()])
tempName = StringField("String", validators=[InputRequired()])
SendToKlaviyo = SubmitField('Send To Klaviyo')
SendToHome = SubmitField('Go Back Home')
@app.route('/', methods=['GET',"POST"])
@app.route('/FluidCreatives', methods=['GET',"POST"])
def FluidCreatives():
numOfPhotos = 0
form = UploadFileForm()
clear_upload_folder()
if 'SendToKlaviyo' in request.form:
# Check if any files are uploaded (including folder files)
photos = form.file.data
for file in photos:
# If you want to process only image files, check the file extension
if file.filename.lower().endswith(('png', 'jpg', 'jpeg')):
numOfPhotos += 1
print(f"Image file: {file.filename}")
# Optionally, save the image to the server
file.save(os.path.join(os.path.abspath(os.path.dirname(__file__)),app.config['UPLOAD_FOLDER'],secure_filename(file.filename))) # Then save the file
else:
print(f"Not an image: {file.filename}")
apiKey = str(form.apiKey.data)
tempName = str(form.tempName.data)
responseCode = klaviyoConnect(apiKey, tempName, app.config['UPLOAD_FOLDER'], numOfPhotos)
if responseCode == 201:
return render_template('success.html', form=form)
else:
return render_template('fail.html', form=form)
return render_template('index.html', form=form)
def clear_upload_folder():
""" Clears the upload folder before processing new files. """
upload_folder = app.config['UPLOAD_FOLDER']
if os.path.exists(upload_folder):
# Remove all files in the folder
for filename in os.listdir(upload_folder):
file_path = os.path.join(upload_folder, filename)
try:
if os.path.isfile(file_path):
os.unlink(file_path) # Remove file
elif os.path.isdir(file_path):
shutil.rmtree(file_path) # Remove directory and its contents
except Exception as e:
print(f"Error removing file {file_path}: {e}")
def klaviyoConnect(api_key: str, template_name: str, folder_path: str, numberOfPhotos: int):
# Klaviyo API Configuration
upload_url = "https://a.klaviyo.com/api/image-upload"
get_image_url = "https://a.klaviyo.com/api/images/"
template_url = "https://a.klaviyo.com/api/templates/"
headers = {
"accept": "application/json",
"revision": "2024-10-15",
"Authorization": f"Klaviyo-API-Key {api_key}",
}
# Supported image file extensions
supported_extensions = {".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp"}
# Get a sorted list of all image files in the folder
image_files = sorted(
[f for f in os.listdir(folder_path) if os.path.splitext(f)[1].lower() in supported_extensions]
)
# List to store uploaded image URLs
uploaded_image_urls = []
# Step 1: Upload Images
for image_file in image_files:
file_path = os.path.join(folder_path, image_file)
print(f"Uploading: {image_file}")
# Open the image file and prepare the payload
with open(file_path, "rb") as file:
files = {"file": file}
data = {"hidden": "false", "name": image_file}
# Send the POST request to upload the image
response = requests.post(upload_url, data=data, files=files, headers=headers)
# Check the response and fetch the image URL using the image ID
if response.status_code == 201:
response_data = response.json()
image_id = response_data.get("data", {}).get("id")
if image_id:
# Fetch the image URL using the image ID
image_response = requests.get(f"{get_image_url}{image_id}", headers=headers)
if image_response.status_code == 200:
image_url = image_response.json().get("data", {}).get("attributes", {}).get("image_url")
if image_url:
uploaded_image_urls.append(image_url)
print(f"Successfully uploaded: {image_file} -> {image_url}")
else:
print(f"Failed to get URL for: {image_file}")
else:
print(f"Failed to fetch image details for ID: {image_id}. Status Code: {image_response.status_code}")
else:
print(f"Failed to get image ID for: {image_file}")
else:
print(f"Failed to upload: {image_file}. Status Code: {response.status_code}")
print(response.text)
# Step 2: Create Drag-and-Drop Template with Uploaded Images
if len(uploaded_image_urls) == numberOfPhotos:
print("Creating drag-and-drop template with the uploaded images...")
# Create HTML content with all the uploaded image URLs using the image block structure
html_content = f"""
<!DOCTYPE html>
<html>
<body style="word-spacing:normal;background-color:#f7f7f7;">
<table align="center" width="600" style="border-spacing: 0; margin: 0 auto; padding: 0; width: 600px;">
{"".join(f'''
<tr style="margin: 0; padding: 0;">
<td style="margin: 0; padding: 0;" data-klaviyo-region="true" data-klaviyo-region-width-pixels="600">
<div class="klaviyo-block klaviyo-image-block" style="margin: 0; padding: 0;">
<img src="{url}" alt="Image {i+1}" style="width: 600px; height: auto; display: block; margin: 0; padding: 0;">
</div>
</td>
</tr>
''' for i, url in enumerate(uploaded_image_urls))}
</table>
</body>
</html>
"""
# JSON payload for creating the template
payload = {
"data": {
"type": "template",
"attributes": {
"name": f"{template_name}",
"editor_type": "USER_DRAGGABLE", # Set as user-draggable
"html": html_content,
"text": "This is a fallback plain text version of the email.",
},
}
}
# Send the POST request to create the template
response = requests.post(template_url, headers={**headers, "content-type": "application/vnd.api+json"}, data=json.dumps(payload))
# Print the response
if response.status_code == 201:
print("Drag-and-drop template created successfully!")
template_id = response.json().get("data", {}).get("id")
print(f"Template ID: {template_id}")
return 201
else:
print("Failed to create the template.")
print(f"Status Code: {response.status_code}")
print(f"Response: {response.json()}")
return response.status_code
else:
print("Forgetting an Image")
return 0
if __name__ == "__main__":
app.run(port=7000, debug=True)