Skip to main content
Sensitive routes apply conservative per-endpoint rate limits to protect against abuse. The limits use a sliding-window counter backed by Valkey. When the limit is exceeded the API returns 429 Too Many Requests.

Rate-limited endpoints

RouteLimitWindowKey
POST /api/v1/hub/{hub_id}/checkout10 requests60 sper IP (per contact if authenticated)
GET /api/v1/hubs/{hub_id}/search/community (community search)30 requests60 sper contact
POST /api/v1/conversations/{conversation_id}/messages (DM send)30 requests60 sper contact
POST /api/v1/comments (comment create)30 requests60 sper contact
POST /api/v1/hubs/{hub_id}/spaces/{space_id}/discussions (discussion create)20 requests60 sper contact
POST /api/v1/discussions/{discussion_id}/reactions (reaction add)60 requests60 sper contact

429 response

When the limit is exceeded the response body follows the standard JSON:API error envelope:
{
  "errors": [
    {
      "status": "429",
      "code": "rate_limited",
      "title": "Too Many Requests",
      "detail": "Rate limit exceeded. Retry after 42 seconds.",
      "meta": {
        "request_id": "01J..."
      }
    }
  ]
}
The Retry-After response header contains the number of seconds until the window resets. Read this header to back off correctly instead of polling.

Fail-open behaviour

Rate limiting is DoS mitigation, not authorization. If the Valkey backing store is temporarily unavailable the API fails open — requests pass through rather than returning 500. A structured log event (valkey.connection_error) is emitted for alerting. A Valkey misconfiguration (bad credentials) is treated as a hard error and is not swallowed.

Handling 429 in your client

mio CLI (automatic)

The mio CLI handles 429 automatically from v0.4.4. When a request is rate-limited the CLI reads the Retry-After header and retries up to 2 times, waiting the indicated number of seconds between attempts (capped at 60 s). If all retries are exhausted the command exits with code 6 (ExitRateLimited) and a message that includes the suggested wait time. No configuration is needed. If you are scripting mio commands and branching on the exit code, exit 6 now means “rate limit not recovered after retries” rather than “first 429 received”.

Direct API callers (curl / SDK)

# Check Retry-After before retrying
retry_after=$(curl -s -o /dev/null -D - \
  -X POST "$MIO_BASE_URL/api/v1/hub/$HUB_ID/checkout" \
  -H "Authorization: Bearer $TOKEN" \
  -d '...' | grep -i "retry-after" | awk '{print $2}')

sleep "$retry_after"
Do not immediately retry on 429 without honouring Retry-After. Back-to-back retries consume the same window and will be rate-limited again.