# Limite

Câte cereri primește platforma, de la cine, cum vezi cât mai ai până la prag și ce răspuns primești când treci de el.

## Limita generală

Fiecare cerere trece printr-o limită comună, numărată pe fereastră fixă de un minut: ceasul pornește la prima cerere, iar după 60 de secunde numărătoarea o ia de la zero. Scrierile și citirile au numărători separate:

| Cerere | Pe sesiune (cookie) | Pe adresă IP |
| --- | --- | --- |
| Scrieri: `POST`, `PUT`, `PATCH`, `DELETE` | 300 / minut | 1200 / minut |
| Citiri: `GET`, `HEAD` | 1200 / minut | 6000 / minut |

Un om lucrează normal sub 30 de scrieri pe minut, deci pragul pe sesiune este de 10 ori mai mare. Pe IP pragul este mai mare fiindcă un birou întreg poate ieși pe aceeași adresă; IP-ul prinde și scripturile fără cookie. O cerere cu o [cheie API](#chei-api) (`Authorization: Bearer ilk_…`) se numără pe cheie, cu aceleași praguri ca o sesiune, și pe IP. O cerere fără cookie de sesiune și fără cheie API se numără doar pe IP. `OPTIONS` nu se numără. Rutele care au limita lor (intrare în cont, OAuth, MCP) o păstrează și pe a lor, peste cea generală.

## Antetele de limită

Fiecare răspuns la o scriere spune cât mai ai, după propunerea IETF [RateLimit headers](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/) (draft 11):

```http title="Antetele unei scrieri"
RateLimit-Policy: "sesiune";q=300;w=60, "ip";q=1200;w=60
RateLimit: "sesiune";r=297;t=42, "ip";r=1188;t=42
```

- `RateLimit-Policy`: regulile, fixe; `q` = câte cereri, `w` = fereastra în secunde.
- `RateLimit`: starea acum, pe fiecare regulă; `r` = câte cereri mai ai, `t` = în câte secunde se golește fereastra.

Partea `"sesiune"` apare doar când cererea are cookie de sesiune. Citirile nu poartă aceste antete.

## Răspunsul la depășire

```http title="Răspunsul la depășire"
HTTP/1.1 429 Too Many Requests
Retry-After: 42
Content-Type: application/json

{"eroare": "prea multe cereri"}
```

La o scriere, `Retry-After` este numărul de secunde până se golește fereastra epuizată (cea mai lungă, dacă sunt două). La citiri și la limitele proprii ale rutelor, care nu au valoarea lor, `Retry-After` este `60`. Orice `429` al platformei are `Retry-After`.

> [!TIP]
> Citește `RateLimit` la fiecare scriere și încetinește când `r` se apropie de zero. Când primești `429`, așteaptă câte secunde spune `Retry-After`. Nu reîncerca imediat în buclă: fiecare încercare intră tot în numărătoare. Pentru o scriere pe care o repeți, folosește [o cheie de idempotență](#idempotenta).

## Limitele rutelor speciale

| Rută | Prag | Cheie |
| --- | --- | --- |
| `POST /v1/oauth/inregistreaza` | 10 / oră | IP |
| `GET /v1/oauth/autorizeaza` | 60 / 10 minute | IP |
| `POST /v1/oauth/jeton` | 120 / 10 minute | IP |
| `POST /v1/mcp` fără jeton valid | 120 / 10 minute | IP |

Grupurile de mai jos au și ele limita lor, peste cea generală. În afară de cazurile spuse la fiecare grup, depășirea dă `429` cu un corp `{"eroare": "..."}` și `Retry-After: 60`. Cu `Accept: application/problem+json`, `prea_multe_incercari`, `prea_multe_cereri` și `prea-multe` primesc același cod stabil, `prea_multe_cereri`; `limita_utilizator`, `limita_firma` și `limita_furnizor` își păstrează numele (vezi [Erori](#api-erori)).

## Intrarea în cont

Se numără încercările **greșite**, nu toate cererile. Fereastra este fixă, de 15 minute.

| Rută | Prag | Cheie | Ce primești peste prag |
| --- | --- | --- | --- |
| `POST /v1/auth/intrare` (email sau nume + parolă) | 8 eșecuri / 15 minute | IP | `429` `prea_multe_incercari` |
| `POST /v1/auth/intrare` | 8 eșecuri / 15 minute | contul + IP | `401`, același refuz ca la o parolă greșită |
| `POST /v1/auth/intrare-google` | 8 eșecuri / 15 minute | IP | `429` `prea_multe_incercari` |
| `POST /v1/passkey/optiuni-intrare`, `POST /v1/passkey/intra` (Face ID / Touch ID) | 30 de cereri / 15 minute | IP | `429` `prea_multe_cereri` |
| `POST /v1/passkey/optiuni-inregistrare` (adaugi o cheie pe cont) | 20 de cereri / 15 minute | contul din sesiune | `429` `prea_multe_cereri` |

Blocarea pe cont ține de IP: opt parole greșite de pe altă adresă nu blochează contul pentru cei care intră de la restaurant. Pentru că refuzul pe cont arată la fel ca o parolă greșită, nu afli din răspuns dacă un cont există.

## Codurile pe email și schimbarea parolei

Toate rutele de aici numără pe IP, cu fereastră fixă de 15 minute. Pe lângă ele, codul în sine are limitele lui, păstrate în baza de date.

| Rută | Prag | Cheie | Ce primești peste prag |
| --- | --- | --- | --- |
| `POST /v1/auth/cod-email/trimite` | 10 / 15 minute | IP | tot `204`, dar emailul nu mai pleacă |
| `POST /v1/auth/cod-email/verifica` | 30 / 15 minute | IP | `429` `prea_multe_incercari` |
| `POST /v1/auth/parola-noua`, `POST /v1/auth/parola/schimba`, `POST /v1/auth/parola-uitata/confirma` | 10 / 15 minute, numărate împreună | IP | `429` `prea_multe_incercari` |
| `POST /v1/resetare/cere` | 10 / 15 minute pe IP și 3 / 15 minute pe adresa de email | IP, email | `429` `prea_multe_cereri` |
| `POST /v1/resetare/confirma` | 10 / 15 minute | IP | `429` `prea_multe_cereri` |
| `POST /v1/acces/oameni/:id/parola-temporara` (un admin dă o parolă temporară) | 10 / 10 minute | adminul | `429` `prea_multe_cereri` |

Limitele codului primit pe email:

- Un cod e valabil 10 minute. După expirare, verificarea dă `410` `cod_expirat`.
- Un cod acceptă cel mult 5 încercări greșite. După ele, verificarea dă `429` `prea_multe_incercari`, chiar dacă trimiți codul corect. Ceri un cod nou.
- Un cont primește cel mult 5 coduri pe oră. Peste ele, `trimite` răspunde tot `204`, dar codul nu se mai creează.
- Un cod nou le anulează pe cele vechi, de același tip, ale contului.

`cod-email/trimite` și `resetare/cere` răspund în cel puțin 400 de milisecunde și la fel, fie că emailul are cont, fie că nu. Un `204` sau un `202` nu înseamnă deci că a plecat un email.

## Invitațiile

| Rută | Prag | Cheie |
| --- | --- | --- |
| `GET /v1/invitatii/:cod`, `POST /v1/invitatii/:cod/accepta` | 20 / minut | IP + metodă + rută |

Se numără separat pe fiecare rută, cu fereastră fixă de un minut. Crearea unei invitații (`POST /v1/acces/invitatii`, cu sesiune) nu intră aici. Peste prag: `429` `prea_multe_cereri`.

## Asistentul, AI-ul și chatul

Aici se numără pe **cont**, ca un cont să nu consume bugetul altuia. Ferestrele de un minut sunt glisante.

| Rută | Prag | Cheie | Ce primești peste prag |
| --- | --- | --- | --- |
| `POST /v1/meniu/ai/ingrediente`, `POST /v1/meniu/ai/poza` | 20 / minut, numărate împreună | contul | `429` `prea_multe_cereri` |
| `POST /v1/meniu/ai/imagine` | 100 / minut | contul | `429` `prea_multe_cereri` |
| `POST /v1/facturi/citeste` (factură citită din poză sau PDF) | 20 / minut | contul | `429` `prea_multe_cereri` |
| `POST /v1/facturi/scanare` | 20 / minut | contul | `429` `prea_multe_cereri` |
| `POST /v1/chat/conversatii/:id/mesaje` | 30 de mesaje / minut | contul | `429` `prea-multe` |
| `POST /v1/asistent/mesaj` | 50 de întrebări / zi pe utilizator și 500 / zi pe firmă | contul, firma | `429` `limita_utilizator` sau `limita_firma`, cu un `mesaj` de afișat |

Pentru asistent, ziua este ziua UTC. Numărătoarea pornește de la zero la miezul nopții UTC, adică la 02:00 sau 03:00, ora României. Rutele care doar citesc starea unei imagini generate, o anulează sau decid asupra ei nu consumă din limită.

Când furnizorul de AI își atinge propria limită, primești `429` `limita_furnizor`. Asta nu ține de numărătoarea ta: reîncearcă după `Retry-After`.

## Mărimea încărcărilor

Fiecare rută care primește fișiere are o limită pentru tot corpul cererii. Corpul mai mare de atât e oprit înainte de rută și primești `413` `{"eroare": "cerere invalida"}`. Unele rute verifică după aceea și fișierul în sine.

| Rută | Corpul cererii | Fișierul | Peste limita fișierului |
| --- | --- | --- | --- |
| `POST /v1/meniu/produse/:id/poze/:slot` (poza unui produs) | 21 MiB (poza în base64 + JSON) | 15 MiB, cel mult 80 de milioane de pixeli | `400` `poza_refuzata` |
| `PUT /v1/profil/poza` | 5 MiB + 16 KiB | 5 MiB | `413` `poza_prea_mare` |
| `PUT /v1/angajati/:id/poza` | 5 MiB + 16 KiB | 5 MiB | `413` `poza_prea_mare` |
| `POST /v1/facturi/citeste` | 9 000 000 de octeți | — | — |
| `POST /v1/facturi/scanare` | 9 500 000 de octeți | — | — |
| `POST /v1/meniu/ai/poza` | 9 000 000 de octeți | — | — |
| `POST /v1/meniu/ai/imagine` | 6 000 000 de octeți | — | — |
| `POST /v1/evidenta/tranzactii/:id/documente` | 10 MiB + 4096 octeți | 10 MiB | `413` `prea_mare` |
| `POST /v1/evidenta/import` (extrasul de cont) | 5 MiB + 4096 octeți | 5 MiB | `413` `prea_mare` |

Diferența dintre corp și fișier lasă loc pentru restul cererii, de exemplu antetele formularului `multipart`. 1 MiB = 1 048 576 de octeți.

## Limitele de mărime și timp

| Unde | Limită |
| --- | --- |
| Corpul cererii la înregistrarea unui client OAuth | 2048 octeți |
| Corpul cererii la `POST /v1/oauth/jeton` | 4096 octeți |
| Răspunsul unei unelte MCP | 100 000 de caractere |
| Durata unui apel de unealtă MCP | 20 de secunde |

## Numărul cererii

Fiecare răspuns are antetul `x-request-id`. Dacă trimiți tu un `x-request-id` (8–64 de caractere: litere, cifre, `-` sau `_`), platforma îl păstrează; altfel pune unul nou. Când raportezi o problemă cu limitele sau o eroare, trimite acest număr: cererea se găsește după el în jurnalul serverului.
