Rate Limits
Zelta implements rate limiting at multiple layers to protect the platform and ensure fair usage.
Overview
Rate limits are applied per device (or per IP for unauthenticated endpoints) using a sliding window algorithm. This prevents any single device from overwhelming the system.
Edge Function Limits
Each API endpoint has specific rate limits:
| Endpoint | Limit | Window | Identifier |
|----------|-------|--------|------------|
| /check-update | 10 | 1 minute | Device ID |
| /report-status | 20 | 1 minute | Device ID |
| /provision-device | 5 | 1 minute | IP Address |
| /device-heartbeat | 5 | 1 minute | Device ID |
| /log-events | 60 | 1 minute | Device ID |
| /device-command | 20 | 1 minute | Device ID |
Internal/Cron Endpoints
These endpoints are called by scheduled jobs:
| Endpoint | Limit | Window | Notes |
|----------|-------|--------|-------|
| /process-rollouts | 5 | 1 minute | Internal cron |
| /process-scheduled-updates | 5 | 1 minute | Internal cron |
| /report-usage | 10 | 1 minute | Stripe billing |
MQTT Broker Limits
The MQTT bridge also enforces rate limits:
Per-Device Limits
| Topic Pattern | Limit | Window |
|---------------|-------|--------|
| heartbeat | 2 | 1 minute |
| status | 10 | 1 minute |
| logs | 20 | 1 minute |
| check | 6 | 1 minute |
| download | 120 | 1 minute |
| provision | 3 | 1 minute |
Broker-Level Limits
max_queued_messages: 100
max_inflight_messages: 20
max_inflight_bytes: 65536
max_queued_bytes: 262144
Rate Limit Response
When a request exceeds the rate limit, the API returns:
HTTP Status: 429 Too Many Requests
Response Body:
{
"error": "Rate limit exceeded",
"message": "Too many requests. Limit: 10/min",
"limit": 10,
"current": 11,
"remaining": 0,
"retry_after": "2025-01-15T10:31:00Z"
}
Response Headers:
| Header | Description |
|--------|-------------|
| X-RateLimit-Limit | Maximum requests allowed per window |
| X-RateLimit-Remaining | Requests remaining in current window |
| X-RateLimit-Reset | ISO 8601 timestamp when window resets |
| Retry-After | Seconds until requests are allowed again |
Device-Side Handling
Your embedded code should handle rate limits gracefully:
// Example: Exponential backoff on rate limit
int retry_delay_ms = 1000;
while (true) {
int status = zelta_check_for_update(callback, NULL);
if (status == ZELTA_RATE_LIMITED) {
LOG_WRN("Rate limited, retrying in %d ms", retry_delay_ms);
k_sleep(K_MSEC(retry_delay_ms));
retry_delay_ms = MIN(retry_delay_ms * 2, 60000); // Max 60s
} else {
retry_delay_ms = 1000; // Reset on success
break;
}
}
Abuse Detection
Devices that repeatedly exceed rate limits are automatically flagged:
How It Works
- Each rate limit violation increments a counter on the device record
- After 10 violations within 1 hour, the device is flagged as "rate limit abuse"
- Flagged devices appear with an orange warning icon (⚠️) in the dashboard
- A log entry is created for each violation
Viewing Flagged Devices
- Go to Devices in the dashboard
- Look for the "Rate Limited" stat card showing the count
- Flagged devices show an orange warning icon
- Hover over the icon to see the hit count
Clearing the Flag
Admins can clear the rate limit flag:
- Open the device dropdown menu (⋮)
- Click "Clear Rate Limit Flag"
- The flag and hit counter are reset
Database Fields
The following fields track rate limit abuse on devices:
| Field | Type | Description |
|-------|------|-------------|
| rate_limit_hits | integer | Total violations |
| last_rate_limited_at | timestamp | Most recent violation |
| rate_limit_abuse_flag | boolean | True if flagged |
Best Practices
For Firmware Developers
- Use appropriate intervals - Don't check for updates every second
- Implement backoff - Increase delay after rate limit errors
- Cache responses - Store update check results locally
- Batch operations - Combine multiple log entries into one request
Recommended Check Intervals
| Operation | Recommended Interval | |-----------|---------------------| | Update check | Every 1-4 hours | | Heartbeat | Every 60 seconds | | Status report | On state change only | | Log upload | Batch every 5 minutes |
For Dashboard Users
- Monitor flagged devices - May indicate misconfigured firmware
- Review logs - Check which endpoint is being hit
- Update firmware - Fix aggressive polling in device code
- Contact support - If legitimate use case needs higher limits
Customizing Limits
Rate limits are configured in the database and can be adjusted:
Default Configuration
Located in cloud/functions/_shared/rate-limit.ts:
export const RATE_LIMITS = {
'check-update': { limit: 10, windowSeconds: 60 },
'device-heartbeat': { limit: 5, windowSeconds: 60 },
'report-status': { limit: 20, windowSeconds: 60 },
'log-events': { limit: 60, windowSeconds: 60 },
'device-command': { limit: 20, windowSeconds: 60 },
'provision-device': { limit: 5, windowSeconds: 60 },
// ...
}
Enterprise Customization
Enterprise customers can request custom rate limits. Contact support with:
- Your organization ID
- Which endpoints need adjustment
- Expected request patterns
- Use case justification
Monitoring
Logs
Rate limit events are logged to:
- Supabase Edge Function logs
- Device logs table (for violations)
- MQTT bridge logs (for MQTT limits)
Metrics
Track rate limiting via:
- Dashboard "Rate Limited" device count
- Device detail page showing violation history
- Supabase dashboard → Edge Functions → Logs