Przejdź do głównej zawartości

Rate limits

Pandoktorek używa sliding window rate limit (Redis ZSET). Po przekroczeniu → 429 Too Many Requests z headerem Retry-After: <sec>.

Limity​

ScopeLimitWindowKiedy stosowany
API key (per klucz)10 000 requestsdobaWszystkie /api/v1/clinic/*
Public discovery (per IP)600 requestsminuta/search/*, /doctors/{slug}*, /clinics/{slug}*
Auth (per IP)30 requests15 min/auth/login, /auth/register, /auth/2fa/*
Booking POST (per IP)120 requestsgodzina/api/v1/bookings (anti-bot na guest)
SMS send (per pacjent)3 SMSdobaReminders + cancel — anti-spam
Lead capture (per IP)5 requests15 min/api/v1/leads (landing form)

:::tip Defaults Limity są customizowalne per klinika dla VIP plan'ów (Faza 2). Default'y wystarczą nawet dla dużych klinik (10k requestów / dobę = pełen sync co 5 min, 7 dni / tydzień, z dużym zapasem). :::

Headers w response​

Każda response zawiera:

X-RateLimit-Limit: 10000
X-RateLimit-Remaining: 9874
X-RateLimit-Reset: 1715184000

X-RateLimit-Reset to unix timestamp kiedy okno się resetuje.

Po 429:

HTTP/1.1 429 Too Many Requests
Retry-After: 3600
Content-Type: application/json

{"detail": "Rate limit exceeded (10000 req/dobę). Spróbuj ponownie za 60 minut."}

Best practices​

Bulk operations zamiast pojedynczych​

❌ Źle: 500 requestów POST /clinic/slots z 1 slotem ✅ Dobrze: 1 request z slots: [...500] (limit 2000 per request)

Push delta zamiast full sync​

POST /api/v1/clinic/slots jest idempotent → możesz pushować TYLKO sloty zmienione od ostatniego sync'a. W kolejnej iteracji (Faza 2) dodamy since query param dla pull endpointów.

Backoff przy 429​

import time, requests

def push_with_retry(url, json, headers, max_retries=3):
for attempt in range(max_retries):
r = requests.post(url, json=json, headers=headers, timeout=10)
if r.status_code != 429:
return r
retry_after = int(r.headers.get("Retry-After", "60"))
time.sleep(retry_after)
raise Exception(f"Rate limited after {max_retries} attempts")

Cache po stronie klienta​

Public endpointy (/search/doctors, /doctors/{slug}) — używaj HTTP cache headers (Cache-Control, ETag). Pandoktorek zwraca Cache-Control: public, max-age=60 dla discovery — twój CDN może cache'ować.

Co liczy się jako request​

Liczy się każdy completed request — niezależnie od kodu (200 / 422 / 500 wszystkie tak samo). Dlatego waliduj input po swojej stronie ZANIM puścisz POST.

Wyjątek: 429 nie liczy się do limitu (żeby uniknąć dead-locka).

Skąd Pandoktorek zna twój IP​

Caddy (reverse proxy) parsuje X-Forwarded-For od trusted upstream. Jeśli twój system dzwoni przez własny proxy, request idzie z IP proxy — co może trafić w shared bucket dla różnych klinik. Best practice: użyj API key (per-klucz limit 10k/dobę) zamiast publicznych endpointów dla maszynowego sync'u.