Skip to main content
Solved

Klaviyo's rate limits

  • December 27, 2024
  • 2 replies
  • 70 views

Forum|alt.badge.img


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)

 

Best answer by talhahussain

Key Strategies:

  1. Optimize API Calls:

    • Batch Requests: Combine multiple operations into fewer requests whenever possible.
    • Cache Responses: Store frequently retrieved data to reduce redundant calls.
  2. Implement Throttling:

    • Use a queue or rate limiter in your application to ensure API calls stay within the allowed thresholds.
    • Add dynamic delays (e.g., based on the Retry-After header) between calls to prevent hitting limits.
  3. Handle Rate Limit Errors (HTTP 429):

    • Retry with Backoff: When you hit a limit, implement exponential backoff and respect the Retry-After header for retry timing.
  4. Monitor and Scale:

    • Regularly monitor your API usage against limits.
    • If you consistently hit limits, consider reaching out to Klaviyo for account-specific rate limit adjustments or optimized solutions.

Practical Example:

If your app performs 22 requests per run and exceeds limits:

  • Introduce a delay (e.g., 50ms between requests) or process in smaller batches.
  • Adjust your logic to handle retries based on API responses dynamically.

Tools and Libraries:

  • Use libraries like axios-retry or retry-axios for automatic retries.
  • Implement rate-limiting libraries such as Bottleneck in Node.js.

These approaches ensure smoother operation without breaching Klaviyo's limits.

View original
Did this topic or the replies in the thread help you find an answer to your question?

2 replies

talhahussain
Problem Solver IV
Forum|alt.badge.img+8
  • Problem Solver IV
  • 76 replies
  • December 27, 2024

To manage Klaviyo's API rate limits effectively, consider the following strategies:

  1. Understand Rate Limits: Klaviyo enforces rate limits per account to maintain system stability. These limits vary by endpoint and are categorized into tiers such as XS, S, M, L, and XL, each with specific burst (per second) and steady (per minute) thresholds. For instance, the XS tier allows 1 request per second and 15 per minute.

    Klaviyo Developers

  2. Optimize API Calls: Given your application's 22 requests per run, you may exceed the XS tier's limits. To mitigate this, implement a queuing system that spaces out requests to stay within the allowed rate. Introducing a delay between requests can help prevent hitting the rate limit. For example, adding a 17-millisecond pause between requests can help avoid rate limits.

    Klaviyo Community

  3. Handle Rate Limit Responses: Monitor API responses for HTTP status code 429, which indicates rate limiting. When this occurs, check the Retry-After header in the response to determine how long to wait before retrying the request. Implementing an exponential backoff strategy with randomized intervals can also help manage retries effectively.

    Klaviyo Developers

  4. Review API Usage: Assess your application's API usage patterns to identify opportunities for reducing the number of requests. Batching multiple operations into a single request where possible or caching frequent data can decrease the demand on the API.

  5. Consult Klaviyo Documentation: Refer to Klaviyo's official API documentation for detailed information on rate limits and best practices for error handling.

    Klaviyo Developers

By implementing these strategies, you can adjust your application's behavior to comply with Klaviyo's API rate limits, ensuring smoother operation and reducing the likelihood of encountering rate limit errors.


talhahussain
Problem Solver IV
Forum|alt.badge.img+8
  • Problem Solver IV
  • 76 replies
  • Answer
  • December 27, 2024

Key Strategies:

  1. Optimize API Calls:

    • Batch Requests: Combine multiple operations into fewer requests whenever possible.
    • Cache Responses: Store frequently retrieved data to reduce redundant calls.
  2. Implement Throttling:

    • Use a queue or rate limiter in your application to ensure API calls stay within the allowed thresholds.
    • Add dynamic delays (e.g., based on the Retry-After header) between calls to prevent hitting limits.
  3. Handle Rate Limit Errors (HTTP 429):

    • Retry with Backoff: When you hit a limit, implement exponential backoff and respect the Retry-After header for retry timing.
  4. Monitor and Scale:

    • Regularly monitor your API usage against limits.
    • If you consistently hit limits, consider reaching out to Klaviyo for account-specific rate limit adjustments or optimized solutions.

Practical Example:

If your app performs 22 requests per run and exceeds limits:

  • Introduce a delay (e.g., 50ms between requests) or process in smaller batches.
  • Adjust your logic to handle retries based on API responses dynamically.

Tools and Libraries:

  • Use libraries like axios-retry or retry-axios for automatic retries.
  • Implement rate-limiting libraries such as Bottleneck in Node.js.

These approaches ensure smoother operation without breaching Klaviyo's limits.