Skip to main content

Rate Limit and Quota

API Reference

Rate Limit and Quota

EnerOS implements layered rate limiting through eneros-gateway to protect kernel stability and enable fair multi-tenant scheduling. The rate limiting policy covers four dimensions: IP, tenant, user, and endpoint, and can be adjusted in configuration files with quotas dynamically distributed at runtime via eneros-tenant. All rate-limited responses include standard X-RateLimit-* response headers, enabling clients to implement adaptive rate reduction.

Rate Limiting Architecture

EnerOS adopts a four-layer rate limiting model, with each layer having independent rate and burst limits:

Request → ┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐
       │ IP Limit  │ →  │ Tenant   │ →  │ User     │ →  │ Endpoint │ → Kernel
       │           │    │ Limit    │    │ Limit    │    │ Limit    │
       └─────────┘    └─────────┘    └─────────┘    └─────────┘
         Token Bucket     Token Bucket    Token Bucket    Token Bucket
LayerDimensionAlgorithmPurpose
L1Client IPToken BucketPrevent malicious API abuse, DDoS mitigation
L2TenantToken BucketMulti-tenant fair scheduling, quota management
L3User/AgentToken BucketPer-user rate limiting, abuse prevention
L4EndpointLeaky BucketProtect compute-intensive endpoints

Each layer counts independently. If any layer is triggered, 429 is returned, with the response header carrying detailed information about the triggered layer.

Rate Limiting Policies

1. Global IP Rate Limiting

Counted by IP dimension, prevents malicious API abuse and DDoS attacks. All IPs share the same policy, with blacklisted IPs directly rejected.

Endpoint TypeRateBurstDescription
Public endpoints (/health)60 req/min10No authentication required
Authentication endpoints (/auth/token)30 req/min5Brute force prevention
Authenticated endpoints (other)600 req/min50Default
Compute endpoints (/powerflow, /opf)30 req/min5Protect compute capacity
Time series write (/timeseries/batch)1000 req/min100High throughput
WebSocket connection establishment10 req/min2Prevent connection flooding
SSE connection establishment20 req/min5Prevent connection flooding

2. Tenant Quota

Resource quotas allocated by tenant dimension, exceeding limits returns 429 or queues for waiting. Quotas can be configured in eneros.toml or dynamically adjusted via eneros-tenant.

ResourceDefault QuotaExceed BehaviorAdjustable
API requests5000 req/hQueue (max 100)Yes
WebSocket connections20 concurrentReject new connectionsYes
SSE connections50 concurrentReject new connectionsYes
Power flow calculation tasks100 concurrentQueue (FIFO)Yes
Time series writes1M points/dayReject writesYes
Time series storage10 GB/dayReject writesYes
Audit log retention365 daysAuto archiveNo
Agent count100Reject creationYes
Network model count50Reject creationYes

3. User/Agent Rate Limiting

Rate limiting for individual users or Agents, preventing a single entity from consuming too many resources.

ResourceDefault LimitDescription
API requests per user1200 req/minRegular user
Command frequency per Agent10 cmd/sPrevent Agent runaway
WebSocket per user5 concurrent
SSE per user10 concurrent

4. Endpoint Rate Limiting

Fine-grained rate limiting for specific endpoints, protecting compute-intensive or high-resource-consumption operations.

EndpointRateBurstDescription
POST /networks/{id}/powerflow30 req/min5Power flow calculation
POST /networks/{id}/opf10 req/min2Optimal power flow
POST /networks/{id}/validate60 req/min10Topology validation
POST /agents/{id}/command100 req/min20Agent command
POST /timeseries/batch1000 req/min100Batch write
DELETE /networks/{id}10 req/min2Delete protection

5. Command Rate Limiting

Agent command dispatch is subject to secondary validation by the safety gateway:

Command TypeLimitDescription
Normal commands≤ 10 cmd/sPer Agent
Control commands (switch/adjust)100ms minimum interval + two-factor confirmationHigh-risk operations
Emergency shutdown commandsBypass rate limit, execute immediatelyEmergency scenarios
Batch commands≤ 50 cmd/batchSingle batch limit

Response Header Description

All rate-limit-related response headers follow the IETF draft draft-ietf-httpapi-ratelimit-headers:

Response HeaderTypeDescription
X-RateLimit-LimitintMaximum requests allowed in the current window
X-RateLimit-RemainingintRemaining requests in the current window
X-RateLimit-ResetintWindow reset time (Unix timestamp, seconds)
X-RateLimit-PolicystringRate limit policy description, e.g., 60;w=60 means 60 times in a 60-second window
Retry-AfterintSuggested retry wait seconds (429 responses only)
X-RateLimit-Tenant-LimitintTenant quota limit
X-RateLimit-Tenant-RemainingintTenant quota remaining
X-RateLimit-ConcurrentintCurrent concurrent connections (WS/SSE only)

Success response example:

HTTP/1.1 200 OK
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 598
X-RateLimit-Reset: 1783277460
X-RateLimit-Policy: 600;w=60
X-RateLimit-Tenant-Limit: 5000
X-RateLimit-Tenant-Remaining: 4998
Content-Type: application/json

Rate Limit Response

When rate limiting is triggered, HTTP 429 is returned, with the response body containing detailed rate limit information:

{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded: 600 req/min",
    "details": {
      "limit": 600,
      "window_seconds": 60,
      "retry_after": 12,
      "reset_at": "2026-07-06T08:31:00Z",
      "scope": "ip",
      "ip": "192.168.1.10"
    },
    "trace_id": "trace_abc123"
  },
  "request_id": "req_20260706_001"
}
FieldTypeDescription
error.codestringFixed as RATE_LIMITED
error.details.limitintMaximum requests allowed in the current window
error.details.window_secondsintRate limit window (seconds)
error.details.retry_afterintSuggested retry wait seconds
error.details.reset_atstringWindow reset time (RFC 3339)
error.details.scopestringTriggered layer: ip/tenant/user/endpoint
error.details.ipstringTriggered IP (scope=ip only)
error.details.tenant_idstringTriggered tenant (scope=tenant only)

Quota Management

Query Current Quota

Query the current tenant’s quota and usage via GET /api/v1/tenant/quota:

Request Parameters: None

Response Fields:

FieldTypeDescription
tenant_idstringTenant ID
planstringPlan: free/pro/enterprise
quotasobjectQuota limits for each item
usageobjectCurrent usage
reset_atstringQuota reset time

cURL Example:

curl http://localhost:8080/api/v1/tenant/quota \
  -H "Authorization: Bearer <token>"

Response Example:

{
  "data": {
    "tenant_id": "tenant_default",
    "plan": "pro",
    "quotas": {
      "api_requests_per_hour": 5000,
      "ws_connections": 20,
      "sse_connections": 50,
      "concurrent_powerflow": 100,
      "timeseries_daily_gb": 10,
      "agent_count": 100,
      "network_count": 50
    },
    "usage": {
      "api_requests_this_hour": 1234,
      "ws_connections": 3,
      "sse_connections": 12,
      "concurrent_powerflow": 2,
      "timeseries_today_gb": 2.3,
      "agent_count": 15,
      "network_count": 8
    },
    "reset_at": "2026-07-06T09:00:00Z"
  },
  "request_id": "req_20260706_002",
  "timestamp": "2026-07-06T08:30:00Z"
}

Quota Application

When the default quota is insufficient, you can apply for an increase through the following methods:

PlanAPI RequestsWS ConnectionsSSE ConnectionsTime Series StorageApplicable Scenarios
free1000/h5101 GB/dayTrial, development
pro5000/h205010 GB/daySmall production
enterprise50000/h100200100 GB/dayLarge production
customCustomCustomCustomCustomSpecial requirements

Application process:

  1. Contact EnerOS sales team (sales@openeneros.com)
  2. Provide tenant ID, expected usage, business scenario
  3. Sign supplementary agreement
  4. Operations dynamically distributes new quota via eneros-tenant, no restart required

Dynamic Quota Adjustment

Administrators can dynamically adjust tenant quotas via API:

curl -X PUT http://localhost:8080/api/v1/admin/tenants/tenant_default/quota \
  -H "Authorization: Bearer <admin-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "api_requests_per_hour": 10000,
    "ws_connections": 50,
    "concurrent_powerflow": 200,
    "reason": "promotion_campaign",
    "expires_at": "2026-07-13T00:00:00Z"
  }'
FieldTypeRequiredDefaultDescription
api_requests_per_hourintNoAPI request quota
ws_connectionsintNoWebSocket connection quota
sse_connectionsintNoSSE connection quota
concurrent_powerflowintNoConcurrent power flow tasks
timeseries_daily_gbintNoTime series storage quota
reasonstringYesAdjustment reason (for audit)
expires_atdatetimeNoTemporary quota expiration time

Configuration Example

Configure rate limiting policies in eneros.toml:

[gateway.rate_limit]
# Global IP rate limiting
global_rpm = 600              # Max requests per minute
global_burst = 50             # Burst limit
auth_rpm = 30                 # Authentication endpoints
auth_burst = 5
compute_rpm = 30              # Compute endpoints
compute_burst = 5
timeseries_rpm = 1000         # Time series write
timeseries_burst = 100

# Tenant quota defaults
[gateway.tenant_default]
api_requests_per_hour = 5000
ws_connections = 20
sse_connections = 50
concurrent_powerflow = 100
timeseries_daily_gb = 10
agent_count = 100
network_count = 50

# Endpoint-level rate limiting
[gateway.endpoint_limits]
"/networks/{id}/powerflow" = { rpm = 30, burst = 5 }
"/networks/{id}/opf" = { rpm = 10, burst = 2 }
"/agents/{id}/command" = { rpm = 100, burst = 20 }

# IP blacklist/whitelist
[gateway.ip_filter]
whitelist = ["127.0.0.1", "10.0.0.0/8"]
blacklist = []
blacklist_ttl_seconds = 3600

# WebSocket rate limiting
[gateway.ws]
max_connections_per_tenant = 20
max_connections_per_user = 5
max_subscriptions_per_connection = 50
message_rate_per_second = 100

# SSE rate limiting
[gateway.sse]
max_connections_per_tenant = 50
max_connections_per_user = 10
max_topics_per_connection = 10

Client Best Practices

1. Exponential Backoff Retry

import time
import random
import requests

def request_with_retry(url, headers, max_retries=5):
    for attempt in range(max_retries):
        response = requests.get(url, headers=headers)
        if response.status_code == 429:
            retry_after = int(response.headers.get("Retry-After", 60))
            # Add jitter to avoid retry storms
            jitter = random.uniform(0, 0.5)
            sleep_time = retry_after + jitter
            print(f"Rate limited, retrying in {sleep_time:.1f}s (attempt {attempt + 1})")
            time.sleep(sleep_time)
            continue
        return response
    raise Exception(f"Max retries reached: {max_retries}")

2. Adaptive Rate Reduction

import time
import requests

class AdaptiveClient:
    def __init__(self, base_url, token):
        self.base_url = base_url
        self.headers = {"Authorization": f"Bearer {token}"}
        self.min_interval = 0.01  # 10ms
        self.max_interval = 1.0   # 1s
        self.current_interval = self.min_interval

    def request(self, path):
        time.sleep(self.current_interval)
        response = requests.get(f"{self.base_url}{path}", headers=self.headers)

        remaining = int(response.headers.get("X-RateLimit-Remaining", 100))
        if remaining < 10:
            # Insufficient quota remaining, slow down
            self.current_interval = min(self.current_interval * 1.5, self.max_interval)
        else:
            # Sufficient quota, speed up
            self.current_interval = max(self.current_interval * 0.9, self.min_interval)

        return response

3. Batch Requests to Reduce Call Count

# Wrong: write one by one
for point in points:
    client.timeseries.write(point)  # 1000 requests

# Correct: batch write
client.timeseries.batch_write(points)  # 1 request

4. Long Connection Reuse

# Wrong: repeatedly establish WebSocket connections
for alert in alerts:
    ws = connect_websocket()
    ws.send(alert)
    ws.close()

# Correct: reuse long connection
ws = connect_websocket()
for alert in alerts:
    ws.send(alert)
# Connection maintained, subscribed to events

5. Monitor Remaining Quota

response = client.get("/networks")
remaining = response.headers.get("X-RateLimit-Remaining")
tenant_remaining = response.headers.get("X-RateLimit-Tenant-Remaining")

if int(remaining) < 50:
    logger.warning(f"IP rate limit about to trigger, remaining: {remaining}")
if int(tenant_remaining) < 500:
    logger.warning(f"Tenant quota about to be exhausted, remaining: {tenant_remaining}")

Rust Client Example

use std::time::{Duration, Instant};
use tokio::time::sleep;

pub struct RateLimitedClient {
    client: reqwest::Client,
    base_url: String,
    token: String,
    last_request: Instant,
    min_interval: Duration,
}

impl RateLimitedClient {
    pub fn new(base_url: &str, token: &str, rpm: u32) -> Self {
        Self {
            client: reqwest::Client::new(),
            base_url: base_url.to_string(),
            token: token.to_string(),
            last_request: Instant::now() - Duration::from_secs(1),
            min_interval: Duration::from_secs(60) / rpm,
        }
    }

    pub async fn get(&mut self, path: &str) -> anyhow::Result<reqwest::Response> {
        let elapsed = self.last_request.elapsed();
        if elapsed < self.min_interval {
            sleep(self.min_interval - elapsed).await;
        }
        self.last_request = Instant::now();

        let resp = self.client
            .get(format!("{}{}", self.base_url, path))
            .bearer_auth(&self.token)
            .send()
            .await?;

        if resp.status() == 429 {
            let retry_after = resp.headers()
                .get("Retry-After")
                .and_then(|v| v.to_str().ok())
                .and_then(|v| v.parse::<u64>().ok())
                .unwrap_or(60);
            sleep(Duration::from_secs(retry_after)).await;
            return self.get(path).await;
        }

        Ok(resp)
    }
}

Monitoring and Alerting

EnerOS exposes the following rate limiting metrics, which can be scraped via Prometheus:

MetricTypeDescription
eneros_rate_limit_requests_totalcounterTotal requests (by status, scope dimensions)
eneros_rate_limit_rejected_totalcounterRequests rejected by rate limiting
eneros_rate_limit_remaininggaugeCurrent remaining quota
eneros_ws_connectionsgaugeCurrent WebSocket connection count
eneros_sse_connectionsgaugeCurrent SSE connection count
eneros_powerflow_runninggaugeRunning power flow task count
eneros_tenant_quota_usage_ratiogaugeTenant quota usage ratio (0-1)

Recommended alerting rules:

AlertConditionSeverity
High rate limit rejection raterate(rejected_total[5m]) / rate(requests_total[5m]) > 0.1warn
Tenant quota about to be exhaustedquota_usage_ratio > 0.9warn
High WebSocket connection countws_connections > 0.8 * limitwarn
Power flow task queuingpowerflow_running > 0.8 * limitwarn