# Rate limiting

After a caller authenticates, the Worker counts their requests and rejects them with HTTP 429 if they go too fast. That stops runaway loops, misconfigured publishers, and casual abuse from taking the service down.

# What we enforce

Who What Limit How we identify them
Signed-in user Inbox + self-notify (GET/POST/DELETE under /v2/notifications…, except cross-user publish) 300 requests per 60 seconds Cognito sub
Publisher Cross-user publish (POST /v2/notifications) 600 requests per 60 seconds API key name, or M2M client_id, or publisher sub

Numbers live in packages/api/wrangler.toml. Staging and production use the same Cloudflare rate-limit namespaces (1001 / 1002) on this account.

# What is not limited

  • /health, /docs, /openapi.json (public; no auth)
  • Calls that fail auth (401) — we never get as far as counting them
  • Local wrangler dev / unit tests when the rate-limit binding is absent — requests are allowed so laptop work stays simple

# Legacy /api/Notification/*

Those old compatibility routes are not rate-limited today. Limits apply to the current V2 API (/v2/*) that the npm client and new apps use. Legacy V1 callers (/api/Notification/*) still go through auth; they just skip this counter. See V1 compatibility.


# When a caller is over the limit

HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/problem+json

{
  "type": "about:blank",
  "title": "Too Many Requests",
  "status": 429,
  "detail": "Rate limit exceeded. Retry after 60 seconds."
}

Wait (or honour Retry-After) and try again. @inovus-medical/notifications already retries 429 when retries are allowed — including publish only if dedupeKey is set.


# How the Worker decides

After JWT / API-key auth:

  1. POST /v2/notifications → publish limiter, key = clientId ?? sub
  2. Any other authenticated /v2/* request → user limiter, key = sub

Code: packages/api/src/auth/rate-limit.ts. Wired with app.use('/v2/*', rateLimitMiddleware()) in app.ts.


# Changing the numbers

  1. Edit simple.limit / simple.period in packages/api/wrangler.toml (Cloudflare only allows period 10 or 60 seconds).
  2. Redeploy staging/production.
  3. Update this page (and DESIGN.md if the design note still mentions the old figures).