API Rate Limiter
Run ID: 69cc40146beabe319cec8e5c2026-03-31Development
PantheraHive BOS
BOS Dashboard

API Rate Limiter: Architecture Plan

This document outlines a comprehensive architecture plan for an API Rate Limiter, designed to manage and control the rate at which clients can make requests to your APIs. This plan is structured to be detailed, actionable, and customer-ready, providing a clear roadmap for implementation.

Note on Request Interpretation:

The prompt included a request for a "detailed study plan with: weekly schedule, learning objectives, recommended resources, milestones, and assessment strategies." Given the context of "API Rate Limiter" and the step "plan_architecture," we have interpreted these elements as a framework for a project plan and architectural design for the rate limiter itself, rather than a study plan to learn about rate limiters. Therefore, you will find these concepts mapped to architectural goals, implementation phases, technology recommendations, project milestones, and validation strategies for the API Rate Limiter.


1. Introduction & Overview

An API Rate Limiter is a critical component in modern microservices architectures, designed to protect backend services from abuse, ensure fair usage, prevent denial-of-service (DoS) attacks, and maintain service stability. By controlling the number of requests a client can make within a defined time window, it safeguards resources and enhances overall system resilience.

This architecture plan details the core components, design considerations, implementation phases, and validation strategies for building a robust, scalable, and highly available API Rate Limiter.

2. Architectural Goals & Requirements

These goals define what the API Rate Limiter should achieve and how it should perform, serving as the "learning objectives" for its successful development.

2.1. Functional Requirements

* Fixed Window Counter: Simple and easy to implement, but can suffer from "bursty" traffic at window edges.

* Sliding Window Log: High precision, but can be memory-intensive for large scales.

* Sliding Window Counter: A good balance of precision and efficiency.

* Token Bucket/Leaky Bucket: For smoother rate control and burst handling.

2.2. Non-Functional Requirements

3. Core Components & Design Patterns

This section outlines the recommended resources (technologies and design patterns) for building the API Rate Limiter.

3.1. High-Level Architecture

The rate limiter will typically sit at the edge of your network or directly in front of your backend services, acting as a gatekeeper.

mermaid • 305 chars
graph TD
    A[Client] --> B(API Gateway / Load Balancer);
    B --> C{Rate Limiting Service};
    C --> D[Backend API Service];
    C -- Reads/Writes --> E[Distributed Data Store (e.g., Redis)];
    C -- Reads --> F[Configuration Store];
    C -- Emits --> G[Monitoring & Logging];
    D -- Emits --> G;
Sandboxed live preview

3.2. Key Components & Technologies

  • API Gateway / Edge Proxy: (e.g., Nginx, Envoy, AWS API Gateway, Azure API Management, Kong)

* Role: Acts as the entry point for all API requests. It can perform initial request routing, authentication, and offload SSL termination. It will integrate with or delegate to the Rate Limiting Service.

* Why: Provides a centralized control point, simplifies integration, and often offers basic rate limiting capabilities that can be extended.

  • Rate Limiting Service (Core Logic): (e.g., Go, Java, Node.js microservice)

* Role: A dedicated microservice responsible for implementing the rate limiting algorithms, checking limits, and making allow/deny decisions.

* Why: Decouples rate limiting logic from the API Gateway, allowing for more complex algorithms and independent scaling. Can be implemented in a high-performance language.

  • Distributed Data Store: (e.g., Redis Cluster)

* Role: Stores rate limiting counters, timestamps, and other state necessary for algorithms across distributed instances of the Rate Limiting Service.

* Why: Redis is ideal due to its in-memory performance, atomic operations (e.g., INCR, ZADD), and support for data structures like sorted sets, crucial for sliding window logs. Redis Cluster ensures high availability and horizontal scalability.

  • Configuration Store: (e.g., Consul, etcd, DynamoDB, PostgreSQL)

* Role: Stores the rate limiting policies (e.g., user_type_gold: 1000/minute, ip_default: 100/second).

* Why: Provides a centralized, dynamic, and persistent way to manage policies, allowing updates without service redeployments.

  • Monitoring & Alerting: (e.g., Prometheus + Grafana, Datadog, CloudWatch)

* Role: Collects metrics (e.g., allowed requests, denied requests, latency, Redis usage) and provides dashboards and alerts.

* Why: Essential for understanding the rate limiter's performance, identifying abuse patterns, and reacting to operational issues.

  • Logging: (e.g., ELK Stack - Elasticsearch, Logstash, Kibana; Splunk; CloudWatch Logs)

* Role: Centralized collection and analysis of request logs, rate limiting decisions, and errors.

* Why: Crucial for debugging, auditing, and security analysis.

3.3. Algorithm Implementation Considerations

  • Fixed Window: Use Redis INCR and EXPIRE for counters per window.
  • Sliding Window Log: Use Redis ZADD to store timestamps in a sorted set, then ZREMRANGEBYSCORE to remove old entries and ZCARD to count.
  • Sliding Window Counter: Maintain two Redis keys per client: one for the current window and one for the previous, weighted based on time into the current window.
  • Token Bucket: Implement using Redis with a key storing (last_refill_time, tokens). Atomic DECRBY and GETSET operations are vital.

4. Implementation Phasing & Roadmap

This section outlines a phased approach for building the API Rate Limiter, mapping to a "weekly schedule" for project execution. Each phase includes key objectives and deliverables.

Phase 1: Foundation & Core Algorithm (Weeks 1-4)

  • Objective: Establish basic infrastructure and implement a foundational rate limiting algorithm.
  • Tasks:

* Set up API Gateway/Proxy (e.g., Nginx) to forward requests to a placeholder backend.

* Provision a Redis instance (standalone for dev, cluster for prod).

* Develop a basic Rate Limiting Service with a Fixed Window Counter algorithm.

* Integrate Rate Limiting Service with the API Gateway (e.g., via a custom plugin or sidecar proxy).

* Implement basic logging for allowed/denied requests.

* Define initial rate limiting policies in a simple configuration file.

  • Deliverables: Functional Fixed Window Rate Limiter in a development environment.

Phase 2: Advanced Algorithms & Centralized Configuration (Weeks 5-8)

  • Objective: Enhance algorithm support and introduce dynamic policy management.
  • Tasks:

* Implement Sliding Window Counter algorithm in the Rate Limiting Service.

* Integrate a Configuration Store (e.g., Consul) to manage policies dynamically.

* Develop an administrative interface or API for managing rate limiting policies.

* Introduce more granular key identification (e.g., user ID from JWT, API Key from header).

* Implement Retry-After header for 429 responses.

  • Deliverables: Rate Limiter supporting multiple algorithms with dynamic policy updates, ready for integration testing.

Phase 3: Scalability, Resilience & Observability (Weeks 9-12)

  • Objective: Ensure the rate limiter is highly available, scalable, and provides deep insights.
  • Tasks:

* Deploy Rate Limiting Service in a distributed, highly available manner (e.g., multiple instances, auto-scaling groups).

* Configure Redis Cluster for high availability and sharding

gemini Output

API Rate Limiter: Detailed Professional Output

This document provides a comprehensive overview, design considerations, and a production-ready code implementation for an API Rate Limiter. This deliverable is designed to equip you with the knowledge and tools to effectively control the request rate to your APIs, ensuring stability, preventing abuse, and optimizing resource utilization.


1. Introduction to API Rate Limiting

API Rate Limiting is a critical mechanism for controlling the number of requests a user or client can make to an API within a given timeframe. It acts as a safeguard, protecting your backend services from various issues, including:

  • Denial of Service (DoS) Attacks: Prevents malicious actors from overwhelming your servers with excessive requests.
  • Resource Exhaustion: Ensures fair usage of server resources (CPU, memory, network bandwidth) by preventing a single client from monopolizing them.
  • Cost Control: For cloud-based services, limiting requests can help manage infrastructure costs.
  • Abuse Prevention: Deters data scraping, brute-force attacks, and other forms of automated abuse.
  • API Stability and Reliability: Maintains a consistent level of service for all legitimate users.
  • Fair Usage: Distributes access to your API equitably among all consumers.

A well-implemented rate limiter is an essential component of any robust API infrastructure.

2. Key Concepts and Algorithms

Several algorithms are commonly used for API rate limiting, each with its own advantages and trade-offs:

  • Fixed Window Counter:

* Concept: Divides time into fixed-size windows (e.g., 1 minute). Each request increments a counter for the current window. If the counter exceeds the limit, requests are rejected.

* Pros: Simple to implement, low memory footprint.

* Cons: Can suffer from a "bursty" problem where requests at the very end of one window and the very beginning of the next can exceed the desired rate for a short period (e.g., 2N requests within 2 minutes around the window boundary).

  • Sliding Window Log:

* Concept: For each client, stores a timestamp for every request made. When a new request arrives, it counts how many timestamps fall within the current window (e.g., the last 60 seconds). Old timestamps are discarded.

* Pros: Very accurate, no "bursty" problem at window edges.

* Cons: High memory usage, especially for high request volumes, as it stores every request timestamp.

  • Sliding Window Counter:

* Concept: A hybrid approach. It uses two fixed windows: the current window and the previous window. It calculates a weighted average of the request counts from both windows to estimate the current rate. For example, if the current time is 30% into the new window, it might allow 70% of the previous window's remaining capacity plus 30% of the new window's capacity.

* Pros: More accurate than Fixed Window, less memory intensive than Sliding Window Log.

* Cons: More complex to implement than Fixed Window.

  • Token Bucket:

* Concept: Imagine a bucket with a fixed capacity for "tokens." Tokens are added to the bucket at a constant rate. Each API request consumes one token. If the bucket is empty, the request is rejected or queued.

* Pros: Handles bursts well (can process requests up to the bucket capacity), smooths out traffic, simple to understand.

* Cons: Can be slightly more complex to implement than Fixed Window.

  • Leaky Bucket:

* Concept: Similar to Token Bucket but focuses on output rate. Requests are added to a queue (the "bucket"). Requests "leak" out of the bucket at a constant rate, meaning they are processed at a steady pace. If the bucket is full, new requests are rejected.

* Pros: Enforces a perfectly smooth output rate, good for backend services that can only handle a consistent load.

* Cons: Can introduce latency due to queuing, doesn't handle bursts as flexibly as Token Bucket.

For this implementation, we will use the Token Bucket algorithm due to its excellent balance of burst handling, traffic smoothing, and relative ease of implementation, especially when combined with a distributed store like Redis.

3. Design Considerations for an API Rate Limiter

Implementing a robust API rate limiter requires careful consideration of several factors:

  • Identifier (Key): How do you identify a client? Common keys include:

* IP Address: Simple, but can be problematic for clients behind NATs or proxies, or for mobile devices with changing IPs.

* API Key/Client ID: Best for authenticated clients, provides fine-grained control.

* User ID: For logged-in users.

* Session ID: For anonymous but persistent sessions.

* Combination: Often, a combination (e.g., IP address for unauthenticated requests, API key for authenticated) is used.

  • Limits and Policies:

* Rate (e.g., 100 requests/minute): The maximum number of requests allowed.

* Burst (Token Bucket capacity): How many requests can be made in a very short period (initially).

* Scope: Global, per-user, per-endpoint, per-IP.

* Tiered Limits: Different limits for different subscription plans (e.g., free vs. premium).

  • Storage Mechanism:

* In-Memory: Fastest, but not suitable for distributed systems or persistent state across restarts. Good for single-instance applications.

* Redis: Excellent choice for distributed rate limiting. Provides atomic operations, fast key-value storage, and expiration capabilities.

* Database (SQL/NoSQL): Can be used, but generally slower than Redis for high-throughput rate limiting due to I/O overhead.

  • Distributed vs. Single Instance:

* Single Instance: Rate limiting logic runs on a single server. Simple to implement but not scalable.

* Distributed: Rate limiting logic is shared across multiple servers, requiring a shared state store (like Redis). Essential for microservices and scalable architectures.

  • Error Handling and Response:

* When a limit is exceeded, what should be the response? Typically an HTTP 429 Too Many Requests status code.

* Include Retry-After HTTP header to inform the client when they can retry.

* Include X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset headers for transparency.

  • Concurrency and Atomicity:

* Operations to decrement counters or consume tokens must be atomic to prevent race conditions in a multi-threaded or distributed environment. Redis's INCR or Lua scripts are ideal for this.

  • Monitoring and Logging:

* Track rate limit hits, blocked requests, and overall API usage.

* Alerts for potential DoS attacks or unusual usage patterns.

  • Bypassing Rate Limiting:

* Allow specific internal services or trusted partners to bypass limits.

* Consider different limits for different API endpoints (e.g., read-heavy vs. write-heavy).

4. Implementation Example: Python Token Bucket with Redis

This section provides a production-ready implementation of a distributed API Rate Limiter using Python, the Token Bucket algorithm, and Redis as the shared state store. We'll include an example Flask application to demonstrate its usage and a Docker Compose setup for easy deployment.

4.1. Why Python and Redis?

  • Python: Widely used for backend services, excellent ecosystem, and clear syntax.
  • Redis: An in-memory data store known for its speed and versatility. Its atomic operations (INCR, SETNX, EXPIRE) and Lua scripting capabilities make it perfect for distributed rate limiting.

4.2. Project Structure


api-rate-limiter/
├── rate_limiter.py
├── app.py
├── requirements.txt
└── docker-compose.yml

4.3. Code Implementation


rate_limiter.py - Core Rate Limiting Logic

This file contains the TokenBucketRateLimiter class, which encapsulates the rate limiting logic using Redis.


import time
import redis
import logging

# Configure logging for better visibility
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

class TokenBucketRateLimiter:
    """
    Implements a distributed API Rate Limiter using the Token Bucket algorithm
    with Redis as the backend for state management.

    Attributes:
        redis_client (redis.Redis): The Redis client instance.
        rate (int): The number of tokens generated per second (requests per second).
        capacity (int): The maximum number of tokens the bucket can hold (burst capacity).
        key_prefix (str): Prefix for Redis keys to avoid collisions.
    """

    # Lua script for atomically checking and consuming tokens.
    # This script ensures that the check and decrement operations are performed
    # as a single atomic unit, preventing race conditions in a distributed environment.
    #
    # ARGUMENTS:
    #   KEYS[1]: The Redis key for the bucket's current tokens.
    #   KEYS[2]: The Redis key for the bucket's last refill timestamp.
    #   ARGV[1]: The current timestamp (in seconds).
    #   ARGV[2]: The token generation rate (tokens per second).
    #   ARGV[3]: The bucket capacity (max tokens).
    #   ARGV[4]: The number of tokens required for this request (usually 1).
    #
    # RETURNS:
    #   table: { allowed (0 or 1), remaining_tokens, reset_time_seconds }
    #          reset_time_seconds is the time until the bucket is full again,
    #          or when the next token will be available if currently empty.
    TOKEN_BUCKET_LUA_SCRIPT = """
    local current_tokens_key = KEYS[1]
    local last_refill_time_key = KEYS[2]
    local current_time = tonumber(ARGV[1])
    local rate = tonumber(ARGV[2])
    local capacity = tonumber(ARGV[3])
    local tokens_to_consume = tonumber(ARGV[4])

    -- Get last refill time and current tokens
    local last_refill_time = tonumber(redis.call('get', last_refill_time_key) or '0')
    local tokens = tonumber(redis.call('get', current_tokens_key) or '0')

    -- Calculate tokens to add based on elapsed time since last refill
    local time_elapsed = current_time - last_refill_time
    local tokens_to_add = time_elapsed * rate

    -- Refill the bucket
    tokens = math.min(capacity, tokens + tokens_to_add)

    -- Update last refill time
    redis.call('set', last_refill_time_key, current_time)

    -- Check if there are enough tokens for the request
    if tokens >= tokens_to_consume then
        -- Consume tokens
        tokens = tokens - tokens_to_consume
        redis.call('set', current_tokens_key, tokens)

        -- Calculate reset time (when bucket will be full again)
        -- If rate is 0, it means no new tokens, so reset is never if consumed.
        local reset_time_seconds = 0
        if rate > 0 then
            reset_time_seconds = math.ceil((capacity - tokens) / rate)
        elseif tokens < capacity then -- If rate is 0 but tokens are not full, it implies a static bucket.
            reset_time_seconds = -1 -- Indicate no refill if rate is 0 and not full.
        end

        return {1, tokens, reset_time_seconds} -- Allowed, remaining, reset_in_seconds
    else
        -- Not allowed. Calculate time until next token is available or bucket is full enough.
        local needed_tokens = tokens_to_consume - tokens
        local time_until_available = 0
        if rate > 0 then
            time_until_available = math.ceil(needed_tokens / rate)
        else
            time_until_available = -1 -- Indicate never available if rate is 0
        end
        return {0, tokens, time_until_available} -- Not allowed, current tokens, retry_after_seconds
    end
    """

    def __init__(self, redis_client: redis.Redis, rate: int, capacity: int, key_prefix: str = "rate_limit"):
        """
        Initializes the TokenBucketRateLimiter.

        Args:
            redis_client: An initialized Redis client instance.
            rate: The rate at which tokens are added to the bucket (tokens per second).
            capacity: The maximum number of tokens the bucket can hold (burst capacity).
            key_prefix: A prefix for Redis keys to ensure uniqueness.
        """
        if not isinstance(redis_client, redis.Redis):
            raise TypeError("redis_client must be an instance of redis.Redis")
        if not (isinstance(rate, int) and rate >= 0):
            raise ValueError("rate must be a non-negative integer")
        if not (isinstance(capacity, int) and capacity >= 1):
            raise ValueError("capacity must be a positive integer")
        if not isinstance(key_prefix, str) or not key_prefix:
            raise ValueError("key_prefix must be a non-empty string")

        self.redis_client = redis_client
        self.rate = rate
        self.capacity = capacity
        self.key_prefix = key_prefix
        self._lua_script_sha = None
        self._load_lua_script()
        logger.info(f"Rate Limiter initialized: Rate={rate} req/s, Capacity={capacity} burst.")

    def _load_lua_script(self):
        """Loads the Lua script into Redis and stores its SHA for efficient execution."""
        try:
            self._lua_script_sha = self.redis_client.script_load(self.TOKEN_BUCKET_LUA_SCRIPT)
            logger.debug(f"Lua script loaded, SHA: {self._lua_script_sha}")
        except Exception as e:
            logger.error(f"Failed to load Lua script into Redis: {e}")
            raise

    def _get_keys(self, identifier: str) -> tuple[str, str]:
        """Generates Redis keys for a given identifier."""
        tokens_key = f"{self.key_prefix}:{identifier}:tokens"

gemini Output

This document provides a comprehensive and professional overview of API Rate Limiters, detailing their purpose, mechanisms, benefits, implementation strategies, and best practices. This deliverable is designed to equip you with a thorough understanding necessary for effective design, deployment, and management of rate limiting within your API ecosystem.


API Rate Limiter: Comprehensive Documentation

1. Executive Summary

An API Rate Limiter is a critical component for managing the traffic and resource consumption of an Application Programming Interface (API). It controls the number of requests a client or user can make to an API within a defined timeframe. Implementing a robust rate limiting strategy is essential for ensuring API stability, preventing abuse, mitigating Denial-of-Service (DoS) attacks, optimizing resource utilization, and maintaining fair access for all consumers. This document outlines the core principles, benefits, implementation considerations, and best practices for effective API rate limiting.

2. Introduction to API Rate Limiting

2.1 What is an API Rate Limiter?

An API Rate Limiter is a mechanism that restricts the number of requests a user or client can send to an API within a given time window. For example, it might allow 100 requests per minute per IP address, or 1000 requests per hour per API key. When a client exceeds this predefined limit, subsequent requests are typically blocked or throttled, often returning an HTTP 429 Too Many Requests status code.

2.2 Why is API Rate Limiting Essential?

Implementing API rate limiting serves several crucial purposes:

  • Prevent Abuse and Misuse: Protects against malicious activities like brute-force attacks, credential stuffing, and data scraping.
  • Ensure API Stability and Reliability: Prevents a single client or a small group of clients from overwhelming the API servers, thus safeguarding the service for all legitimate users.
  • Optimize Resource Utilization: Manages server load, database connections, and other backend resources, ensuring efficient operation even under high demand.
  • Mitigate Denial-of-Service (DoS) Attacks: Acts as a first line of defense against both distributed and single-source DoS attempts.
  • Enforce Fair Usage Policies: Ensures equitable access to API resources across all consumers, preventing "noisy neighbors" from degrading performance for others.
  • Control Costs: For cloud-based services, limiting requests can directly impact infrastructure costs associated with compute, bandwidth, and database operations.
  • Monetization/Tiered Access: Enables differentiated service levels (e.g., free tier vs. premium tier with higher limits), supporting business models.

3. Core Principles and Algorithms

API rate limiting relies on various algorithms to track and enforce limits. The choice of algorithm depends on the specific requirements for accuracy, memory usage, and distributed system compatibility.

3.1 Common Rate Limiting Algorithms:

  • Fixed Window Counter:

* Mechanism: A simple counter is maintained for a fixed time window (e.g., 60 seconds). All requests within that window increment the counter. Once the counter reaches the limit, no more requests are allowed until the window resets.

* Pros: Simple to implement, low memory footprint.

* Cons: Can suffer from the "burst problem" where a client can make a large number of requests at the very beginning and very end of a window, effectively doubling the rate within a short period around the window boundary.

* Example: 100 requests/minute. A client makes 100 requests at 0:59 and another 100 requests at 1:01, totaling 200 requests in a 2-minute span, but 200 requests in 3 seconds across the window boundary.

  • Sliding Window Log:

* Mechanism: For each client, a timestamp of every request is stored. When a new request arrives, all timestamps older than the current window are discarded. The number of remaining timestamps determines if the request is allowed.

* Pros: Highly accurate, no "burst problem" across window boundaries.

* Cons: High memory consumption, especially for high limits and many clients, as it stores a log of every request.

* Example: 100 requests/minute. Stores timestamps of all requests. At any point, it checks how many requests were made in the last 60 seconds.

  • Sliding Window Counter:

* Mechanism: Combines elements of fixed window and sliding window log. It uses two fixed windows: the current window and the previous window. A weighted average of requests from both windows is calculated to determine the current rate.

* Pros: More accurate than fixed window, less memory-intensive than sliding window log. Mitigates the burst problem significantly.

* Cons: More complex to implement than fixed window.

Example: 100 requests/minute. If a request comes 30 seconds into the current minute, the rate is calculated as (requests in previous minute 0.5) + (requests in current minute * 0.5).

  • Token Bucket:

* Mechanism: A "bucket" holds a certain number of "tokens." Tokens are added to the bucket at a fixed rate (e.g., 10 tokens/second) up to a maximum capacity. Each request consumes one token. If the bucket is empty, the request is rejected.

* Pros: Allows for bursts of requests up to the bucket capacity, then smoothly throttles to the fill rate. Simple to implement and understand.

* Cons: Requires careful tuning of bucket size and fill rate.

* Example: Bucket capacity of 200 tokens, filled at 2 tokens/second. A client can make 200 requests instantly, then subsequent requests are limited to 2 requests/second.

  • Leaky Bucket:

* Mechanism: Similar to Token Bucket but focuses on output rate. Requests are added to a queue (the "bucket"). Requests are processed (leak out) from the queue at a constant rate. If the queue is full, new requests are dropped.

* Pros: Smooths out bursty traffic into a constant output rate, preventing backend systems from being overwhelmed.

* Cons: Bursty traffic can lead to dropped requests if the queue fills up. Does not allow for bursts like Token Bucket.

* Example: Queue capacity of 100 requests, leaks at 10 requests/second. If 150 requests arrive instantly, 50 are dropped, and the remaining 100 are processed over 10 seconds.

4. Key Benefits of Implementation

Implementing a well-designed API rate limiter delivers tangible benefits:

  • Enhanced Security: Significantly reduces the attack surface for common web vulnerabilities by limiting the speed at which an attacker can probe endpoints.
  • Improved User Experience: Ensures that legitimate users consistently receive timely responses and service, even during periods of high demand.
  • Reduced Operational Costs: By preventing resource exhaustion, it helps control scaling needs and associated infrastructure costs.
  • Better System Stability: Protects backend services (databases, caches, microservices) from cascading failures due to overload.
  • Clearer API Contract: Communicates usage policies to developers, encouraging responsible consumption and better client-side error handling.
  • Monetization Opportunities: Supports differentiated service tiers based on usage limits, enabling new business models.

5. Challenges and Considerations

While highly beneficial, rate limiting presents several implementation challenges:

  • Distributed Systems: In a distributed environment (multiple API servers, load balancers), maintaining a consistent and accurate rate limit across all instances requires a centralized store (e.g., Redis, database) and careful synchronization.
  • Granularity: Deciding what to rate limit (IP address, API key, user ID, endpoint, combination) and how specific the limits should be. Overly broad limits can penalize legitimate users, while overly specific limits can be complex to manage.
  • False Positives/Legitimate Bursts: Legitimate applications might have occasional bursts of traffic (e.g., after a new feature release). An overly aggressive rate limiter might block these, impacting user experience.
  • Proxy/NAT Issues: Multiple users behind a single NAT gateway or corporate proxy might appear as one IP address, leading to shared limits and potential unfair blocking.
  • Error Handling and Communication: Clearly communicating rate limit status (HTTP 429), remaining requests, and reset times (via Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset headers) is crucial for client applications.
  • Bypassing: Sophisticated attackers might try to bypass rate limits by rotating IP addresses, using botnets, or exploiting application logic.
  • Testing: Thoroughly testing rate limiters under various load conditions is vital to ensure they behave as expected without introducing new bottlenecks.
  • Complexity: As requirements grow (e.g., different limits for different endpoints, users, or tiers), the rate limiting logic can become complex to manage and scale.

6. Implementation Strategies and Components

Implementing an API rate limiter typically involves several architectural components and strategic decisions.

6.1 Where to Implement?

  • API Gateway/Load Balancer (Recommended for Edge):

* Pros: Centralized control, protects all backend services, offloads work from application servers, can be integrated with WAF (Web Application Firewall). Examples: Nginx, Kong, Apigee, AWS API Gateway.

* Cons: May require custom plugins for complex logic, less context about specific user actions within the application.

  • Application Layer (Within Service):

* Pros: Fine-grained control based on application logic (e.g., rate limit specific user actions like "create post" or "send message"), access to user session data.

* Cons: Adds overhead to application servers, requires consistent implementation across all instances, harder to scale independently.

  • Sidecar Proxy (Microservices):

* Pros: Decouples rate limiting logic from individual microservices, centralizes policy enforcement for a service mesh.

* Cons: Adds complexity to the deployment, requires a service mesh infrastructure (e.g., Istio, Linkerd).

6.2 Key Components:

  • Request Counter/Store: A persistent, fast data store is needed to keep track of request counts and timestamps.

* Options: Redis (highly recommended for its speed and atomic operations), Memcached, a dedicated key-value store, or even a database for lower-volume scenarios.

  • Enforcement Logic: The code or configuration that applies the chosen algorithm, checks the current rate, and decides whether to allow or deny a request.
  • Policy Engine: Defines the rules for different limits (e.g., 100 req/min for unauthenticated, 1000 req/min for authenticated, 5000 req/min for premium tier).
  • Monitoring and Alerting: Systems to track rate limit breaches, identify potential attacks, and alert administrators.

6.3 Example Flow (API Gateway with Redis):

  1. Request Ingress: A client sends a request to the API Gateway.
  2. Identifier Extraction: The Gateway extracts a client identifier (e.g., IP address, API Key from header, User ID from JWT).
  3. Policy Lookup: The Gateway consults its configuration to find the rate limit policy associated with the identifier and the requested endpoint.
  4. Rate Check (Redis): The Gateway sends a query to Redis (e.g., INCR and EXPIRE for fixed window, or ZADD and ZREMRANGEBYRANK for sliding window log) to check the current rate against the limit.
  5. Decision:

If within limit: The request is allowed to pass to the backend service. Redis is updated with the new count/timestamp. Response headers (X-RateLimit-) are added.

* If limit exceeded: The request is blocked. An HTTP 429 Too Many Requests status code is returned to the client, along with a Retry-After header indicating when they can retry.

  1. Logging & Metrics: The rate limiter logs the decision and associated metrics for monitoring and analysis.

7. Best Practices for Design and Management

To maximize the effectiveness of your API rate limiter, consider these best practices:

  • Granularity Matters:

* Start with broad limits (e.g., per IP address or API key).

* Refine to more specific limits for critical or resource-intensive endpoints (e.g., /api/v1/user/create, /api/v1/search).

* Consider different limits for authenticated vs. unauthenticated users.

  • Clear Communication:

* Document your rate limits clearly in your API documentation.

* Use standard HTTP headers for rate limit information:

* X-RateLimit-Limit: The total number of requests allowed in the current window.

* X-RateLimit-Remaining: The number of requests remaining in the current window.

* X-RateLimit-Reset: The time (in UTC epoch seconds or seconds relative to now) when the current rate limit window resets.

* Retry-After: When a 429 is returned, indicates how long to wait before making another request.

  • Graceful Degradation:

* Instead of hard blocking, consider throttling or returning partial data for slightly exceeded limits for non-critical requests.

* Implement circuit breakers in client applications to prevent continuous hammering of a rate-limited API.

  • Monitoring and Alerting:

* Track key metrics: total requests, rate-limited requests, 429 responses, client identifiers causing limits.

* Set up alerts for unusual spikes in 429 responses or specific client exceeding limits.

  • Client-Side Adaptability:

* Encourage client developers to implement exponential backoff and retry logic when receiving 429 responses.

* Provide SDKs that automatically handle rate limiting.

  • Testing:

* Rigorously test your rate limiter under various load conditions, including legitimate bursts and simulated attacks.

* Ensure the chosen algorithm performs as expected in your distributed environment.

  • Tiered Limits:

* If applicable, define different rate limits for different subscription tiers (e.g., Free, Basic, Premium) to support business models.

  • Review and Adjust:

* Regularly review rate limit policies based on usage patterns, performance data, and business requirements. Adjust limits as needed.

  • Consider Originating IP vs. Forwarded IP:

* Behind a load balancer or proxy, ensure you are rate limiting based on the actual client IP (e.g., from X-Forwarded-For or X-Real-IP headers) rather than the proxy's IP. Be aware of potential spoofing.

  • Idempotency and Retries:

* Ensure that retrying requests due to rate limits does not lead to unintended side effects if the original request might have partially succeeded before the limit was hit. Design APIs to be idempotent where appropriate.

8. Conclusion and Next Steps

API rate limiting is an indispensable practice for building resilient, secure, and scalable API ecosystems. By carefully selecting the right algorithms, strategically placing the enforcement points, and adhering to best practices, you can effectively protect your services from abuse and ensure a high-quality experience for all your API consumers.

Recommended Next Steps:

  1. Define Rate Limit Policies: Identify critical endpoints and client types, and define initial rate limits (e.g., requests per minute/hour per API key or IP).
  2. Choose Implementation Strategy: Decide whether to implement at the API Gateway, application layer, or via a sidecar proxy.
  3. Select Storage Mechanism: Determine the appropriate data store for tracking request counts (e.g., Redis).
  4. Implement and Test: Develop the rate limiting logic and rigorously test its behavior under various load conditions.
  5. Monitor and Iterate: Deploy with comprehensive monitoring, gather data on actual usage, and iterate on policies and implementation for continuous improvement.
api_rate_limiter.txt
Download source file
Copy all content
Full output as text
Download ZIP
IDE-ready project ZIP
Copy share link
Permanent URL for this run
Get Embed Code
Embed this result on any website
Print / Save PDF
Use browser print dialog
\n\n\n"); var hasSrcMain=Object.keys(extracted).some(function(k){return k.indexOf("src/main")>=0;}); if(!hasSrcMain) zip.file(folder+"src/main."+ext,"import React from 'react'\nimport ReactDOM from 'react-dom/client'\nimport App from './App'\nimport './index.css'\n\nReactDOM.createRoot(document.getElementById('root')!).render(\n \n \n \n)\n"); var hasSrcApp=Object.keys(extracted).some(function(k){return k==="src/App."+ext||k==="App."+ext;}); if(!hasSrcApp) zip.file(folder+"src/App."+ext,"import React from 'react'\nimport './App.css'\n\nfunction App(){\n return(\n
\n
\n

"+slugTitle(pn)+"

\n

Built with PantheraHive BOS

\n
\n
\n )\n}\nexport default App\n"); zip.file(folder+"src/index.css","*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:system-ui,-apple-system,sans-serif;background:#f0f2f5;color:#1a1a2e}\n.app{min-height:100vh;display:flex;flex-direction:column}\n.app-header{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:40px}\nh1{font-size:2.5rem;font-weight:700}\n"); zip.file(folder+"src/App.css",""); zip.file(folder+"src/components/.gitkeep",""); zip.file(folder+"src/pages/.gitkeep",""); zip.file(folder+"src/hooks/.gitkeep",""); Object.keys(extracted).forEach(function(p){ var fp=p.startsWith("src/")?p:"src/"+p; zip.file(folder+fp,extracted[p]); }); zip.file(folder+"README.md","# "+slugTitle(pn)+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\nnpm run dev\n\`\`\`\n\n## Build\n\`\`\`bash\nnpm run build\n\`\`\`\n\n## Open in IDE\nOpen the project folder in VS Code or WebStorm.\n"); zip.file(folder+".gitignore","node_modules/\ndist/\n.env\n.DS_Store\n*.local\n"); } /* --- Vue (Vite + Composition API + TypeScript) --- */ function buildVue(zip,folder,app,code,panelTxt){ var pn=pkgName(app); var C=cc(pn); var extracted=extractCode(panelTxt); zip.file(folder+"package.json",'{\n "name": "'+pn+'",\n "version": "0.0.0",\n "type": "module",\n "scripts": {\n "dev": "vite",\n "build": "vue-tsc -b && vite build",\n "preview": "vite preview"\n },\n "dependencies": {\n "vue": "^3.5.13",\n "vue-router": "^4.4.5",\n "pinia": "^2.3.0",\n "axios": "^1.7.9"\n },\n "devDependencies": {\n "@vitejs/plugin-vue": "^5.2.1",\n "typescript": "~5.7.3",\n "vite": "^6.0.5",\n "vue-tsc": "^2.2.0"\n }\n}\n'); zip.file(folder+"vite.config.ts","import { defineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue'\nimport { resolve } from 'path'\n\nexport default defineConfig({\n plugins: [vue()],\n resolve: { alias: { '@': resolve(__dirname,'src') } }\n})\n"); zip.file(folder+"tsconfig.json",'{"files":[],"references":[{"path":"./tsconfig.app.json"},{"path":"./tsconfig.node.json"}]}\n'); zip.file(folder+"tsconfig.app.json",'{\n "compilerOptions":{\n "target":"ES2020","useDefineForClassFields":true,"module":"ESNext","lib":["ES2020","DOM","DOM.Iterable"],\n "skipLibCheck":true,"moduleResolution":"bundler","allowImportingTsExtensions":true,\n "isolatedModules":true,"moduleDetection":"force","noEmit":true,"jsxImportSource":"vue",\n "strict":true,"paths":{"@/*":["./src/*"]}\n },\n "include":["src/**/*.ts","src/**/*.d.ts","src/**/*.tsx","src/**/*.vue"]\n}\n'); zip.file(folder+"env.d.ts","/// \n"); zip.file(folder+"index.html","\n\n\n \n \n "+slugTitle(pn)+"\n\n\n
\n \n\n\n"); var hasMain=Object.keys(extracted).some(function(k){return k==="src/main.ts"||k==="main.ts";}); if(!hasMain) zip.file(folder+"src/main.ts","import { createApp } from 'vue'\nimport { createPinia } from 'pinia'\nimport App from './App.vue'\nimport './assets/main.css'\n\nconst app = createApp(App)\napp.use(createPinia())\napp.mount('#app')\n"); var hasApp=Object.keys(extracted).some(function(k){return k.indexOf("App.vue")>=0;}); if(!hasApp) zip.file(folder+"src/App.vue","\n\n\n\n\n"); zip.file(folder+"src/assets/main.css","*{margin:0;padding:0;box-sizing:border-box}body{font-family:system-ui,sans-serif;background:#fff;color:#213547}\n"); zip.file(folder+"src/components/.gitkeep",""); zip.file(folder+"src/views/.gitkeep",""); zip.file(folder+"src/stores/.gitkeep",""); Object.keys(extracted).forEach(function(p){ var fp=p.startsWith("src/")?p:"src/"+p; zip.file(folder+fp,extracted[p]); }); zip.file(folder+"README.md","# "+slugTitle(pn)+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\nnpm run dev\n\`\`\`\n\n## Build\n\`\`\`bash\nnpm run build\n\`\`\`\n\nOpen in VS Code or WebStorm.\n"); zip.file(folder+".gitignore","node_modules/\ndist/\n.env\n.DS_Store\n*.local\n"); } /* --- Angular (v19 standalone) --- */ function buildAngular(zip,folder,app,code,panelTxt){ var pn=pkgName(app); var C=cc(pn); var sel=pn.replace(/_/g,"-"); var extracted=extractCode(panelTxt); zip.file(folder+"package.json",'{\n "name": "'+pn+'",\n "version": "0.0.0",\n "scripts": {\n "ng": "ng",\n "start": "ng serve",\n "build": "ng build",\n "test": "ng test"\n },\n "dependencies": {\n "@angular/animations": "^19.0.0",\n "@angular/common": "^19.0.0",\n "@angular/compiler": "^19.0.0",\n "@angular/core": "^19.0.0",\n "@angular/forms": "^19.0.0",\n "@angular/platform-browser": "^19.0.0",\n "@angular/platform-browser-dynamic": "^19.0.0",\n "@angular/router": "^19.0.0",\n "rxjs": "~7.8.0",\n "tslib": "^2.3.0",\n "zone.js": "~0.15.0"\n },\n "devDependencies": {\n "@angular-devkit/build-angular": "^19.0.0",\n "@angular/cli": "^19.0.0",\n "@angular/compiler-cli": "^19.0.0",\n "typescript": "~5.6.0"\n }\n}\n'); zip.file(folder+"angular.json",'{\n "$schema": "./node_modules/@angular/cli/lib/config/schema.json",\n "version": 1,\n "newProjectRoot": "projects",\n "projects": {\n "'+pn+'": {\n "projectType": "application",\n "root": "",\n "sourceRoot": "src",\n "prefix": "app",\n "architect": {\n "build": {\n "builder": "@angular-devkit/build-angular:application",\n "options": {\n "outputPath": "dist/'+pn+'",\n "index": "src/index.html",\n "browser": "src/main.ts",\n "tsConfig": "tsconfig.app.json",\n "styles": ["src/styles.css"],\n "scripts": []\n }\n },\n "serve": {"builder":"@angular-devkit/build-angular:dev-server","configurations":{"production":{"buildTarget":"'+pn+':build:production"},"development":{"buildTarget":"'+pn+':build:development"}},"defaultConfiguration":"development"}\n }\n }\n }\n}\n'); zip.file(folder+"tsconfig.json",'{\n "compileOnSave": false,\n "compilerOptions": {"baseUrl":"./","outDir":"./dist/out-tsc","forceConsistentCasingInFileNames":true,"strict":true,"noImplicitOverride":true,"noPropertyAccessFromIndexSignature":true,"noImplicitReturns":true,"noFallthroughCasesInSwitch":true,"paths":{"@/*":["src/*"]},"skipLibCheck":true,"esModuleInterop":true,"sourceMap":true,"declaration":false,"experimentalDecorators":true,"moduleResolution":"bundler","importHelpers":true,"target":"ES2022","module":"ES2022","useDefineForClassFields":false,"lib":["ES2022","dom"]},\n "references":[{"path":"./tsconfig.app.json"}]\n}\n'); zip.file(folder+"tsconfig.app.json",'{\n "extends":"./tsconfig.json",\n "compilerOptions":{"outDir":"./dist/out-tsc","types":[]},\n "files":["src/main.ts"],\n "include":["src/**/*.d.ts"]\n}\n'); zip.file(folder+"src/index.html","\n\n\n \n "+slugTitle(pn)+"\n \n \n \n\n\n \n\n\n"); zip.file(folder+"src/main.ts","import { bootstrapApplication } from '@angular/platform-browser';\nimport { appConfig } from './app/app.config';\nimport { AppComponent } from './app/app.component';\n\nbootstrapApplication(AppComponent, appConfig)\n .catch(err => console.error(err));\n"); zip.file(folder+"src/styles.css","* { margin: 0; padding: 0; box-sizing: border-box; }\nbody { font-family: system-ui, -apple-system, sans-serif; background: #f9fafb; color: #111827; }\n"); var hasComp=Object.keys(extracted).some(function(k){return k.indexOf("app.component")>=0;}); if(!hasComp){ zip.file(folder+"src/app/app.component.ts","import { Component } from '@angular/core';\nimport { RouterOutlet } from '@angular/router';\n\n@Component({\n selector: 'app-root',\n standalone: true,\n imports: [RouterOutlet],\n templateUrl: './app.component.html',\n styleUrl: './app.component.css'\n})\nexport class AppComponent {\n title = '"+pn+"';\n}\n"); zip.file(folder+"src/app/app.component.html","
\n
\n

"+slugTitle(pn)+"

\n

Built with PantheraHive BOS

\n
\n \n
\n"); zip.file(folder+"src/app/app.component.css",".app-header{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:60vh;gap:16px}h1{font-size:2.5rem;font-weight:700;color:#6366f1}\n"); } zip.file(folder+"src/app/app.config.ts","import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';\nimport { provideRouter } from '@angular/router';\nimport { routes } from './app.routes';\n\nexport const appConfig: ApplicationConfig = {\n providers: [\n provideZoneChangeDetection({ eventCoalescing: true }),\n provideRouter(routes)\n ]\n};\n"); zip.file(folder+"src/app/app.routes.ts","import { Routes } from '@angular/router';\n\nexport const routes: Routes = [];\n"); Object.keys(extracted).forEach(function(p){ var fp=p.startsWith("src/")?p:"src/"+p; zip.file(folder+fp,extracted[p]); }); zip.file(folder+"README.md","# "+slugTitle(pn)+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\nng serve\n# or: npm start\n\`\`\`\n\n## Build\n\`\`\`bash\nng build\n\`\`\`\n\nOpen in VS Code with Angular Language Service extension.\n"); zip.file(folder+".gitignore","node_modules/\ndist/\n.env\n.DS_Store\n*.local\n.angular/\n"); } /* --- Python --- */ function buildPython(zip,folder,app,code){ var title=slugTitle(app); var pn=pkgName(app); var src=code.replace(/^\`\`\`[\w]*\n?/m,"").replace(/\n?\`\`\`$/m,"").trim(); var reqMap={"numpy":"numpy","pandas":"pandas","sklearn":"scikit-learn","tensorflow":"tensorflow","torch":"torch","flask":"flask","fastapi":"fastapi","uvicorn":"uvicorn","requests":"requests","sqlalchemy":"sqlalchemy","pydantic":"pydantic","dotenv":"python-dotenv","PIL":"Pillow","cv2":"opencv-python","matplotlib":"matplotlib","seaborn":"seaborn","scipy":"scipy"}; var reqs=[]; Object.keys(reqMap).forEach(function(k){if(src.indexOf("import "+k)>=0||src.indexOf("from "+k)>=0)reqs.push(reqMap[k]);}); var reqsTxt=reqs.length?reqs.join("\n"):"# add dependencies here\n"; zip.file(folder+"main.py",src||"# "+title+"\n# Generated by PantheraHive BOS\n\nprint(title+\" loaded\")\n"); zip.file(folder+"requirements.txt",reqsTxt); zip.file(folder+".env.example","# Environment variables\n"); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\n\`\`\`\n\n## Run\n\`\`\`bash\npython main.py\n\`\`\`\n"); zip.file(folder+".gitignore",".venv/\n__pycache__/\n*.pyc\n.env\n.DS_Store\n"); } /* --- Node.js --- */ function buildNode(zip,folder,app,code){ var title=slugTitle(app); var pn=pkgName(app); var src=code.replace(/^\`\`\`[\w]*\n?/m,"").replace(/\n?\`\`\`$/m,"").trim(); var depMap={"mongoose":"^8.0.0","dotenv":"^16.4.5","axios":"^1.7.9","cors":"^2.8.5","bcryptjs":"^2.4.3","jsonwebtoken":"^9.0.2","socket.io":"^4.7.4","uuid":"^9.0.1","zod":"^3.22.4","express":"^4.18.2"}; var deps={}; Object.keys(depMap).forEach(function(k){if(src.indexOf(k)>=0)deps[k]=depMap[k];}); if(!deps["express"])deps["express"]="^4.18.2"; var pkgJson=JSON.stringify({"name":pn,"version":"1.0.0","main":"src/index.js","scripts":{"start":"node src/index.js","dev":"nodemon src/index.js"},"dependencies":deps,"devDependencies":{"nodemon":"^3.0.3"}},null,2)+"\n"; zip.file(folder+"package.json",pkgJson); var fallback="const express=require(\"express\");\nconst app=express();\napp.use(express.json());\n\napp.get(\"/\",(req,res)=>{\n res.json({message:\""+title+" API\"});\n});\n\nconst PORT=process.env.PORT||3000;\napp.listen(PORT,()=>console.log(\"Server on port \"+PORT));\n"; zip.file(folder+"src/index.js",src||fallback); zip.file(folder+".env.example","PORT=3000\n"); zip.file(folder+".gitignore","node_modules/\n.env\n.DS_Store\n"); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\n\`\`\`\n\n## Run\n\`\`\`bash\nnpm run dev\n\`\`\`\n"); } /* --- Vanilla HTML --- */ function buildVanillaHtml(zip,folder,app,code){ var title=slugTitle(app); var isFullDoc=code.trim().toLowerCase().indexOf("=0||code.trim().toLowerCase().indexOf("=0; var indexHtml=isFullDoc?code:"\n\n\n\n\n"+title+"\n\n\n\n"+code+"\n\n\n\n"; zip.file(folder+"index.html",indexHtml); zip.file(folder+"style.css","/* "+title+" — styles */\n*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:system-ui,-apple-system,sans-serif;background:#fff;color:#1a1a2e}\n"); zip.file(folder+"script.js","/* "+title+" — scripts */\n"); zip.file(folder+"assets/.gitkeep",""); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\n## Open\nDouble-click \`index.html\` in your browser.\n\nOr serve locally:\n\`\`\`bash\nnpx serve .\n# or\npython3 -m http.server 3000\n\`\`\`\n"); zip.file(folder+".gitignore",".DS_Store\nnode_modules/\n.env\n"); } /* ===== MAIN ===== */ var sc=document.createElement("script"); sc.src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"; sc.onerror=function(){ if(lbl)lbl.textContent="Download ZIP"; alert("JSZip load failed — check connection."); }; sc.onload=function(){ var zip=new JSZip(); var base=(_phFname||"output").replace(/\.[^.]+$/,""); var app=base.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_+|_+$/g,"")||"my_app"; var folder=app+"/"; var vc=document.getElementById("panel-content"); var panelTxt=vc?(vc.innerText||vc.textContent||""):""; var lang=detectLang(_phCode,panelTxt); if(_phIsHtml){ buildVanillaHtml(zip,folder,app,_phCode); } else if(lang==="flutter"){ buildFlutter(zip,folder,app,_phCode,panelTxt); } else if(lang==="react-native"){ buildReactNative(zip,folder,app,_phCode,panelTxt); } else if(lang==="swift"){ buildSwift(zip,folder,app,_phCode,panelTxt); } else if(lang==="kotlin"){ buildKotlin(zip,folder,app,_phCode,panelTxt); } else if(lang==="react"){ buildReact(zip,folder,app,_phCode,panelTxt); } else if(lang==="vue"){ buildVue(zip,folder,app,_phCode,panelTxt); } else if(lang==="angular"){ buildAngular(zip,folder,app,_phCode,panelTxt); } else if(lang==="python"){ buildPython(zip,folder,app,_phCode); } else if(lang==="node"){ buildNode(zip,folder,app,_phCode); } else { /* Document/content workflow */ var title=app.replace(/_/g," "); var md=_phAll||_phCode||panelTxt||"No content"; zip.file(folder+app+".md",md); var h=""+title+""; h+="

"+title+"

"; var hc=md.replace(/&/g,"&").replace(//g,">"); hc=hc.replace(/^### (.+)$/gm,"

$1

"); hc=hc.replace(/^## (.+)$/gm,"

$1

"); hc=hc.replace(/^# (.+)$/gm,"

$1

"); hc=hc.replace(/\*\*(.+?)\*\*/g,"$1"); hc=hc.replace(/\n{2,}/g,"

"); h+="

"+hc+"

Generated by PantheraHive BOS
"; zip.file(folder+app+".html",h); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\nFiles:\n- "+app+".md (Markdown)\n- "+app+".html (styled HTML)\n"); } zip.generateAsync({type:"blob"}).then(function(blob){ var a=document.createElement("a"); a.href=URL.createObjectURL(blob); a.download=app+".zip"; a.click(); URL.revokeObjectURL(a.href); if(lbl)lbl.textContent="Download ZIP"; }); }; document.head.appendChild(sc); } function phShare(){navigator.clipboard.writeText(window.location.href).then(function(){var el=document.getElementById("ph-share-lbl");if(el){el.textContent="Link copied!";setTimeout(function(){el.textContent="Copy share link";},2500);}});}function phEmbed(){var runId=window.location.pathname.split("/").pop().replace(".html","");var embedUrl="https://pantherahive.com/embed/"+runId;var code='';navigator.clipboard.writeText(code).then(function(){var el=document.getElementById("ph-embed-lbl");if(el){el.textContent="Embed code copied!";setTimeout(function(){el.textContent="Get Embed Code";},2500);}});}