Limits & Restrictions

Rate limits

  • `POST /api/auth/register` per-IP (agent channels — `mcp`/`a2a`/`form`): 3 signups per hour per source IP (Redis-tracked; fails open — i.e. does not block — if Redis is unreachable).
  • Activation (any channel): after email confirmation, a corporate email + matching live website may auto-activate — capped at max 1 activation/hour platform-wide. Otherwise the account stays PENDING and requires a questionnaire + admin review.

429 response shape from the per-IP gate:

HTTP/1.1 429 Too Many Requests

{
  "error": "rate_limited",
  "message": "Too many signups from this address. Max 3 per hour."
}

Field whitelist (request schema)

Every endpoint enforces a strict DTO whitelist via class-validator. Fields not declared on the DTO are rejected with 400.

Fields accepted by POST /api/auth/register:

  • email — required, valid email format.
  • termsAccepted — required, must be true.
  • channel — optional, one of web | mcp | a2a | form (default web).
  • password — required for channel=web (8–128 chars); optional for agent channels.
  • firstName, lastName — optional, max 100 chars each.
  • org — optional, max 200 chars. use_case — optional, max 500 chars. website — optional, max 300 chars.

Quotas

  • Self-signup database quota: 50MB per tenant. Over quota, POST/PUT/PATCH return 403; reads and DELETE keep working.
  • Agent key scopes: keys auto-issued at registration are limited — reads work immediately, email:send / mailing:write and other unavailable writes wait for activation — the account owner expands scopes in CRM Settings → API Keys once the workspace is active.
  • Starting credit: new self-signup workspaces may start with a small platform credit — check GET /api/billing/balance after registering; the amount is configured platform-side and not fixed here.

Lifecycle limits (full-account flow)

  • Email-confirmation link: expires in 24 hours.
  • ToS-acceptance link: expires in 24 hours.
  • Pending admin approval: auto-deleted after 7 days.
  • Inactive registration: auto-deleted after 24 hours of inactivity.

Authentication brute-force

  • All 401 cases (missing / malformed / unknown / revoked bearer) return the same generic {"statusCode":401,"message":"Invalid credentials"}. Distinct messages would let attackers enumerate valid key formats.
  • Repeated 401s on protected endpoints share the general nginx /v1/* burst-limit zone. Sustained brute-force is dropped at nginx with 429.
  • Bearer tokens are stored as SHA-256 hashes — even with database read access, plaintext keys are not recoverable.

Backoff recipe

import time, random, requests

def call(url, **kwargs):
    for attempt in range(6):
        r = requests.post(url, **kwargs)
        if r.status_code != 429:
            return r
        # Exponential backoff with jitter; signup window is 1 hour, so cap at 60s
        time.sleep(min(60, 2 ** attempt) + random.uniform(0, 1))
    raise RuntimeError("rate-limited after 6 retries")