# Konvence

> Formát požadavků, společná pole, dotazy na seznamy a filtry, chyby a limity požadavků společné všem endpointům.

Tato stránka popisuje pravidla, kterými se řídí každý endpoint integračního API.
Stránky endpointů zmiňují jen to, v čem se od nich endpoint liší.

## Požadavky

API je RPC přes HTTP `POST`, ne REST. Endpointy jsou požadavky `POST` na cestu, která
pojmenovává akci, například:

| Cesta | Akce |
|---|---|
| `/api/v3/conversation/find` | výpis konverzací |
| `/api/v3/conversation/get` | čtení jedné konverzace |
| `/api/v3/conversation/delete` | smazání jedné konverzace |

Nejsou zde žádné endpointy `PUT`, `PATCH` ani `DELETE` a v cestě nejsou id zdrojů;
id záznamu je parametr. Každý endpoint přijímá `POST`. Dva endpointy jen pro čtení
bez parametrů, [zůstatek kreditu](account.md#zjisteni-zustatku-kreditu)
a [verze](account.md#zjisteni-verze-serveru), odpovídají kvůli kompatibilitě se
staršími klienty i na `GET`; v novém kódu používejte `POST`.

Základní URL je:

```text
https://ayeto.ai/api/v3
```

### Hlavičky

| Hlavička | Povinná | Popis |
|---|---|---|
| `uni-api-key` | ano | Váš API klíč, viz [Autentizace](authentication.md). |
| `Content-Type` | ano, s tělem | `application/json`. |
| `language` | ne | Jazyk požadavku: `EN`, `CZ` nebo `FR` (na velikosti písmen nezáleží). Výchozí `EN`. |
| `theme` | ne | `light` nebo `dark`. Výchozí `light`. V [chatu](chat.md) se předává modelu jako nápověda, kde se odpověď zobrazí. |

Hlavička `language` sděluje modelu v [chatu](chat.md), jakým jazykem uživatel píše,
a určuje jazyk přeložených textů, které některé endpointy vracejí (například názvy
nástrojů v [modelech a nástrojích](models-and-tools.md)). Chybové zprávy jsou vždy
anglicky.

Hlavička platí jen pro daný požadavek. Nikdy nemění jazyk, který si uživatel zvolil
v aplikaci AYETO a který AYETO používá pro jeho e-maily a naplánovanou práci.

### Parametry

Parametry se posílají v těle JSON, pokud stránka endpointu neuvádí jinak. Endpointy,
které pracují s jedním záznamem podle id (`.../get`, `.../delete`), přijímají id
v těle jako `{"id": "<uuid>"}`:

```bash
curl -X POST "https://ayeto.ai/api/v3/conversation/get" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id": "0b8f3c2e-5d41-4a7e-9c1f-6e2d8a4b7c90"}'
```

Id přijímají i jako parametr dotazu `entity_id`, jak to vyžadovaly dřívější verze
API; tělo pak lze vynechat nebo poslat `{}`. Tělo, které pošlete, musí být JSON
s `Content-Type: application/json`, stejně jako u každého endpointu:

```bash
curl -X POST "https://ayeto.ai/api/v3/conversation/get?entity_id=0b8f3c2e-5d41-4a7e-9c1f-6e2d8a4b7c90" \
  -H "uni-api-key: $AYETO_API_KEY"
```

Požadavek bez obojího, s oběma nastavenými na různá id nebo s `id`, které není UUID,
se odmítne s `422`.

Endpoint, jehož parametry jsou objekt JSON, potřebuje tělo, i když chcete všechny
výchozí hodnoty: pošlete `{}`. Požadavek bez těla se odmítne s `422`.

### Hodnoty

- **Id** jsou UUID zapsaná jako řetězce: `"0b8f3c2e-5d41-4a7e-9c1f-6e2d8a4b7c90"`.
  Odpovědi vždy používají tvar s malými písmeny a pomlčkami.
- **Časová razítka** jsou celá čísla: milisekundy od epochy Unixu (UTC), například
  `1790846045000` pro 2026-10-01 09:14:05 UTC. `0` znamená „nikdy“.

### Odpovědi

Úspěšný požadavek vrací `200` s tělem JSON: objekt, pole objektů (endpointy `find`),
číslo (endpointy `count`) nebo id dotčeného záznamu jako řetězec JSON (endpointy
`delete`). [Streamovací](#streamovani) endpointy místo toho vracejí stream. Chyby
jsou popsány v části [Chyby](#chyby).

## Společná pole

Každý uložený záznam, který API vrací (konverzace, asistent, workflow, běh
workflow, ...), nese své id a tři objekty metadat:

| Pole | Typ | Popis |
|---|---|---|
| `id` | UUID | Id záznamu. |
| `created` | objekt | Kdy a kdo záznam vytvořil. |
| `updated` | objekt | Kdy a kdo ho naposledy změnil. `timestamp` je `0`, pokud se nikdy nezměnil. |
| `accessed` | objekt | Kdy a kdo do něj naposledy zapsal nebo ho otevřel. Čtení některých záznamů (například konverzace přes `conversation/get`) ho aktualizuje, nejvýše zhruba jednou za minutu. |

Každý objekt metadat má stejný tvar:

| Pole | Typ | Popis |
|---|---|---|
| `timestamp` | časové razítko | Čas události, `0`, pokud nenastala. |
| `user_id` | UUID nebo `null` | Uživatel, který ji způsobil; `null`, když ji provedl systém. |

```json
{
  "id": "0b8f3c2e-5d41-4a7e-9c1f-6e2d8a4b7c90",
  "created": { "timestamp": 1790846045000, "user_id": "5a1d7e3c-9b2f-4c8e-a6d0-3f7b1e9c2a54" },
  "updated": { "timestamp": 1790846052000, "user_id": "5a1d7e3c-9b2f-4c8e-a6d0-3f7b1e9c2a54" },
  "accessed": { "timestamp": 1790846052000, "user_id": "5a1d7e3c-9b2f-4c8e-a6d0-3f7b1e9c2a54" }
}
```

Většina endpointů vrací veřejný pohled na záznam: tato pole plus pole uvedená na
stránce endpointu. Endpointy, které vracejí celý záznam (například
[workflow](workflows.md) a běhy workflow), obsahují navíc tato obecná pole:

| Pole | Typ | Popis |
|---|---|---|
| `owner` | UUID nebo `null` | Obecný odkaz, obvykle `null`. |
| `parent` | UUID nebo `null` | Obecný odkaz, obvykle `null`. |
| `source` | UUID nebo `null` | Záznam, ke kterému tento patří, pokud nějaký má. |
| `seq` | celé číslo | Pořadové číslo záznamu v rámci jeho typu. |
| `enabled` | boolean | Obvykle `true`. |
| `note` | řetězec | Volná textová poznámka, obvykle prázdná. |
| `owner_group` | UUID nebo `null` | Skupina, která záznam vlastní (například administrátoři organizace); `null`, když ho vlastní uživatel, který ho vytvořil. |
| `permissions` | objekt | Příznaky přístupu `group`, `all` a `other`, každý `{ "read": boolean, "write": boolean }`. |
| `joined_collections` | `null` | V tomto API vždy `null`. |

`created`, `updated`, `accessed`, `permissions` a `owner_group` spravuje server.
Hodnoty, které pro ně pošlete v požadavcích na vytvoření nebo úpravu, se ignorují.

## Dotazy na seznamy

Endpointy, které záznamy vypisují (`.../find`) a počítají (`.../count`), přijímají
jako tělo stejný objekt dotazu. Průběžným příkladem je
`POST /api/v3/conversation/find`.

| Pole | Typ | Povinné | Popis |
|---|---|---|---|
| `filters` | pole | ne | Podmínky, které musí záznamy splnit, viz [Filtry](#filtry). Musí platit všechny podmínky v poli. |
| `sort_key` | řetězec | ne | Pole, podle kterého se řadí, například `"name"` nebo `"updated.timestamp"` (tečková notace pro vnořená pole). Musí to být pole záznamu; neznámé pole se odmítne s `422`. |
| `sort_order` | celé číslo | ne | `0` = vzestupně (výchozí, když je nastaveno `sort_key`), `1` = sestupně. Bez `sort_key` se ignoruje. |
| `limit_from` | celé číslo | ne | Index prvního vraceného záznamu, počítáno od `0`. Stránkování se použije, jen když je toto pole nastaveno. |
| `limit_to` | celé číslo | ne | Index **za** posledním vraceným záznamem. Je to absolutní pozice, ne velikost stránky: `limit_from: 20, limit_to: 40` vrátí záznamy 20 až 39. `null` nebo `0` znamená „až do konce“. |
| `fetch_dict` | boolean | ne | Nemá vliv na vrácená data; můžete ho vynechat. |
| `join` | pole | ne | Integrační API ho nepodporuje; ignoruje se. |

Bez `sort_key` se záznamy vracejí od nejnovějších (podle `created.timestamp`,
sestupně). Bez `limit_from` se všechny odpovídající záznamy vrátí v jedné odpovědi.

### Stránkování

Pro čtení stránky *n* (počítáno od 0) o velikosti *s* pošlete `limit_from: n * s`
a `limit_to: (n + 1) * s`. Pravidla:

- `limit_from` musí být `0` nebo víc a `limit_to` musí být větší než `limit_from`.
  Záporné `limit_from` nebo `limit_to` nižší než `limit_from` se odmítne s `422`.
  Když se obě hodnoty rovnají, žádná horní mez se nepoužije a vrátí se všechny
  záznamy od `limit_from` dál.
- `limit_to` bez `limit_from` se ignoruje: vrátí se celý výsledek.
- Na straně serveru neexistuje maximální velikost stránky. Udržujte stránky rozumně
  malé (nanejvýš několik set záznamů); velké záznamy, jako konverzace s dlouhou
  historií, dávají velké odpovědi.

```bash
curl -X POST "https://ayeto.ai/api/v3/conversation/find" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": [["updated.timestamp", ">=", 1790812800000]],
    "sort_key": "updated.timestamp",
    "sort_order": 1,
    "limit_from": 0,
    "limit_to": 20
  }'
```

Odpovědí je pole záznamů; prázdné pole, když nic neodpovídá:

```json
[
  {
    "id": "0b8f3c2e-5d41-4a7e-9c1f-6e2d8a4b7c90",
    "name": "Quarterly report outline",
    "model": "gpt-5-mini",
    "summary": "",
    "organization_id": null,
    "assistant_id": "c4e2a9b1-7f3d-4e6a-8b5c-1d9f0a2e3b47",
    "task_id": null,
    "task_execution_id": null,
    "workflow_id": null,
    "workflow_run_id": null,
    "messages": [
      {
        "id": "7d3a1f9e-2b4c-4e8a-9f6d-0c5b8e1a2d36",
        "timestamp": 1790846045000,
        "role": "user",
        "content": "Draft an outline for the Q3 report.",
        "reasoning_content": null,
        "model": null,
        "attachments": [],
        "tool_runs": {}
      },
      {
        "id": "e1b9c7a3-5d2f-4a6e-8c0b-9f4d3e2a1b58",
        "timestamp": 1790846052000,
        "role": "assistant",
        "content": "1. Summary\n2. Revenue\n3. Costs\n4. Outlook",
        "reasoning_content": null,
        "model": "gpt-5-mini",
        "attachments": [],
        "tool_runs": {}
      }
    ],
    "public_sharing": false,
    "created": { "timestamp": 1790846045000, "user_id": "5a1d7e3c-9b2f-4c8e-a6d0-3f7b1e9c2a54" },
    "updated": { "timestamp": 1790846052000, "user_id": "5a1d7e3c-9b2f-4c8e-a6d0-3f7b1e9c2a54" },
    "accessed": { "timestamp": 1790846052000, "user_id": "5a1d7e3c-9b2f-4c8e-a6d0-3f7b1e9c2a54" }
  }
]
```

Chcete-li přečíst všechno, žádejte o stránky, dokud nepřijde stránka kratší, než je
velikost stránky. Řaďte vzestupně podle `created.timestamp`, aby se záznamy vytvořené
během stránkování přidaly na konec, místo aby posunuly stránky, které jste ještě
nepřečetli:

```python
import os
import requests

BASE = "https://ayeto.ai/api/v3"
HEADERS = {"uni-api-key": os.environ["AYETO_API_KEY"]}
PAGE = 50

start = 0
while True:
    r = requests.post(f"{BASE}/conversation/find", headers=HEADERS, json={
        "sort_key": "created.timestamp",
        "sort_order": 0,
        "limit_from": start,
        "limit_to": start + PAGE,
    })
    r.raise_for_status()
    page = r.json()
    for conversation in page:
        print(conversation["id"], conversation["name"])
    if len(page) < PAGE:
        break
    start += PAGE
```

### Počítání

Endpointy `.../count` přijímají stejné tělo a vracejí počet záznamů, které odpovídají
`filters`, jako prosté celé číslo. Ignorují `limit_from`, `limit_to` a pole pro
řazení, takže můžete poslat stejné tělo jako pro zobrazovanou stránku a dostat celkový
počet pro stránkovač.

```bash
curl -X POST "https://ayeto.ai/api/v3/workflow/count" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"filters": [["name", "regex", "invoice"]], "limit_from": 0, "limit_to": 20}'
```

```json
42
```

Ne každý seznam má endpoint pro počítání; stránky endpointů uvádějí ty, které existují.

## Filtry

`filters` je pole. Každý prvek je podmínka nebo skupina podmínek a záznam se vrátí,
jen když odpovídá **všem** prvkům.

### Podmínky

Podmínka je pole `[field, operator, value]` s volitelným čtvrtým prvkem
(viz [Převod UUID](#prevod-uuid)):

```json
["model", "==", "gpt-5-mini"]
```

`field` je název pole záznamu. Pro vnořená pole použijte tečkovou notaci
(`created.timestamp`, `created.user_id`). Cesta do seznamu objektů odpovídá, když
odpovídá kterýkoli prvek seznamu (`messages.role`). `id` je id záznamu.

| Operátor | Odpovídá, když pole... | Hodnota |
|---|---|---|
| `==` | se rovná hodnotě | jakákoli povolená hodnota |
| `!=` | se nerovná hodnotě | jakákoli povolená hodnota |
| `<` | je menší než hodnota | číslo nebo řetězec |
| `>` | je větší než hodnota | číslo nebo řetězec |
| `<=` | je menší nebo rovno hodnotě | číslo nebo řetězec |
| `>=` | je větší nebo rovno hodnotě | číslo nebo řetězec |
| `regex` | obsahuje shodu s regulárním výrazem bez ohledu na velikost písmen | řetězec |
| `in` | se rovná kterékoli z hodnot | neprázdné pole, nejvýše 100 hodnot |

Poznámky:

- `regex` nikdy nerozlišuje velikost písmen a není ukotvený: `"invoice"` odpovídá
  `"Invoices 2026"`. K ukotvení použijte ve vzoru `^` a `$` a znaky regulárních výrazů
  (`.`, `*`, `+`, `?`, `(`, `)`, `[`, `]`, `\`) escapujte, pokud se mají shodovat
  doslova.
- Řetězce se porovnávají abecedně (podle kódu znaku), čísla číselně. Časová razítka
  porovnávejte jako čísla.
- `["field", "==", null]` odpovídá záznamům, kde je pole `null` nebo chybí.

### Skupiny

Skupina je objekt s právě jedním klíčem, `AND` nebo `OR`, jehož hodnotou je neprázdné
pole podmínek nebo dalších skupin. Skupiny lze vnořovat.

```json
{"OR": [["model", "==", "gpt-5-mini"], ["model", "==", "gpt-5"]]}
```

```json
{"AND": [
  ["updated.timestamp", ">=", 1788220800000],
  {"OR": [["name", "regex", "report"], ["summary", "regex", "report"]]}
]}
```

Klíč musí být napsán velkými písmeny. **Objekt s jakýmkoli jiným klíčem (například
`"or"`) se ignoruje a odpovídá každému záznamu**, takže překlep tiše vypne danou část
filtru. Objekt bez klíče nebo s více než jedním klíčem a skupina, jejíž hodnota není
neprázdné pole, se odmítnou s `422`. Skupiny lze vnořovat až do 32 úrovní.

### Příklady

```json
{
  "filters": [
    ["assistant_id", "==", "c4e2a9b1-7f3d-4e6a-8b5c-1d9f0a2e3b47"],
    ["created.timestamp", ">=", 1788220800000],
    ["name", "regex", "^draft"]
  ]
}
```

```json
{
  "filters": [
    ["id", "in", [
      "0b8f3c2e-5d41-4a7e-9c1f-6e2d8a4b7c90",
      "9e7a5c3b-1d2f-4b6a-8e0c-4f3d2a1b9c87"
    ]]
  ]
}
```

```json
{
  "filters": [
    {"OR": [["organization_id", "==", null], ["organization_id", "==", "2f9c4b7e-8a1d-4e3c-b6f0-5d7a9e2c1b34"]]}
  ]
}
```

Server každou podmínku před spuštěním dotazu zkontroluje a normalizuje. Většina
překvapivě prázdných výsledků pochází z některého z pravidel níže.

### Převod UUID

Řetězcová hodnota, která je platným UUID, se před porovnáním převede na UUID. Pole,
která obsahují id (`id`, `assistant_id`, `created.user_id`, ...), se ukládají jako
UUID, takže u nich je to přesně to, co chcete. Pole, které obsahuje **text**, jenž
náhodou vypadá jako UUID (včetně 32 šestnáctkových číslic bez pomlček), po převodu
neodpovídá. Přidejte `true` jako čtvrtý prvek, aby se hodnota porovnala jako prostý
text:

```json
["external_ref", "==", "0b8f3c2e5d414a7e9c1f6e2d8a4b7c90", true]
```

U `in` platí čtvrtý prvek pro každou hodnotu v seznamu. Hodnoty uvnitř pole použitého
s `==` nebo `!=` se nikdy nepřevádějí.

### Escapování HTML

V řetězcových hodnotách se `<`, `>`, `"` a `'` před porovnáním nahradí za `&lt;`,
`&gt;`, `&quot;` a `&#x27;`. Filtr na text, který tyto znaky obsahuje, proto neodpovídá
záznamům uloženým s prostými znaky; filtrujte na část textu bez nich, například
pomocí `regex`.

### Limity hodnot

| Hodnota | Pravidlo |
|---|---|
| řetězec | Nejvýše 1 000 znaků. Nesmí začínat na `$`. Nulové znaky se odstraní. |
| pole | Nejvýše 100 prvků (i pro `in`). Každý prvek musí být povolená hodnota. `in` potřebuje aspoň jeden prvek. |
| povolené typy | řetězec, číslo, boolean, `null`, pole. Objekt jako hodnota se odmítne. |

### Názvy polí

Název pole musí začínat písmenem a obsahovat jen písmena, číslice, `_` a `.`
(nejvýše 128 znaků). Jiné názvy se odmítnou s `422`.

Názvy polí ve filtrech se proti záznamu nekontrolují: pole s překlepem neodpovídá
ničemu s `==` a všemu s `!=`. (`sort_key` se naopak kontroluje.)

### Chybně utvořené podmínky

Podmínka s méně než třemi nebo více než čtyřmi prvky nebo prvek `filters` (nebo
skupiny), který není pole ani objekt, se odmítne s `422`. `detail` říká, co je
špatně, například
`A filter condition must be [field, operator, value] with an optional fourth element, got 2 elements`.

## Chyby

Chyby používají standardní stavové kódy HTTP a tělo JSON s polem `detail`:

```json
{"detail": "entity not found, id: 0b8f3c2e-5d41-4a7e-9c1f-6e2d8a4b7c90"}
```

`detail` je anglická zpráva čitelná pro člověka. Neparsujte ji, s výjimkou případů,
kdy stránka endpointu dokumentuje konkrétní zprávu.

Když samotný požadavek neodpovídá schématu endpointu (chybějící pole nebo pole
špatného typu, neplatné UUID, chybějící hlavička, žádné tělo), stav je `422` a `detail`
je seznam, který ukazuje na každý problém:

```json
{
  "detail": [
    {
      "loc": ["body", "sort_order"],
      "msg": "value is not a valid enumeration member; permitted: 0, 1",
      "type": "type_error.enum",
      "ctx": {"enum_values": [0, 1]}
    }
  ]
}
```

`loc` je umístění problému: `body`, `query` nebo `header`, následované cestou k poli.

Jedna odpověď se od tohoto tvaru liší: neočekávané selhání serveru může vrátit `500`
s textovým tělem `Internal Server Error`.

Čtěte nejprve stavový kód a tělo berte jako nepovinnou informaci.

| Stav | Význam | Co dělat |
|---|---|---|
| `401` | API klíč je prázdný, neznámý, smazaný, vypnutý nebo mu chybí rozsah, který endpoint vyžaduje. `detail` je `API key not provided` nebo `API key is invalid`. | Zkontrolujte klíč a jeho [rozsahy](authentication.md#rozsahy). Neopakujte beze změny. |
| `403` | Klíč je platný, ale jeho uživatel tohle dělat nesmí: záznam patří někomu jinému, nebo účtu chybí oprávnění. `detail` je obvykle `permission denied`. | Neopakujte. |
| `404` | Záznam neexistuje (nebo byl smazán). | Neopakujte. |
| `422` | Požadavek je neplatný: chyby schématu (`detail` jako seznam, viz výše), odmítnutý filtr nebo klíč řazení, nebo obchodní pravidlo. Nedostatek kreditů je také `422`, s `detail` `not enough user credit` nebo `not enough organization credit`. | Opravte požadavek, nebo doplňte kredity. |
| `423` | Zdroj je zamčený jinou operací (například záznam [booster databáze](booster-database.md)). | Zkuste to znovu po krátké pauze. |
| `429` | Překročen limit požadavků, viz [Limity požadavků](#limity-pozadavku). | Počkejte `Retry-After` sekund. |
| `500` | Chyba serveru. | Zkuste to později znovu s odstupem (backoff); pokud chyba přetrvává, nahlaste ji. |
| `502`, `503`, `504`, `529` | Poskytovatel AI nebo jiná navazující služba selhala nebo je přetížená. | Zkuste to později znovu s odstupem (backoff). |

API nepoužívá `402`; chyby kreditu jsou `422`, jak je popsáno výše. Chybějící
hlavička `uni-api-key` (na rozdíl od prázdné nebo chybné hodnoty) je chyba schématu
a vrací `422`, ne `401`.

Odpovědi na neúspěšné kontroly API klíče (`401`) a na požadavky omezené limitem
(`429`) se záměrně zpožďují zhruba o jednu sekundu, aby se zpomalilo hádání klíčů.
Počítejte s tím v časových limitech a nepovažujte zpoždění za problém serveru.

Chyby, které nastanou po zahájení [streamu](streaming.md), se hlásí uvnitř streamu,
ne jako stav HTTP.

## Limity požadavků

Požadavky jsou omezeny podle IP adresy klienta a podle endpointu: každá cesta
endpointu má vlastní čítače a všechny požadavky z jedné IP adresy na daný endpoint se
počítají dohromady bez ohledu na to, jaký API klíč používají. Požadavky se počítají
i tehdy, když selžou, včetně požadavků odmítnutých kvůli špatnému API klíči.

Každý endpoint má tři limity kontrolované současně: za minutu, za hodinu a za den.
Jsou to klouzavá okna: požadavek se do okna započítává po celou délku okna od chvíle,
kdy byl odeslán, takže kapacita se uvolňuje postupně, ne na začátku celé minuty nebo
hodiny. Každá stránka endpointu uvádí jeho úroveň:

| Úroveň | Za minutu | Za hodinu | Za den |
|---|---|---|---|
| low | 10 | 100 | 1 000 |
| default | 60 | 2 000 | 20 000 |
| high | 120 | 4 000 | 40 000 |
| static | 1 200 | 30 000 | 300 000 |
| webhook | 6 000 | 120 000 | 1 500 000 |

Úroveň webhook platí jen pro veřejný [webhook workflow](workflows.md), který navíc
omezuje každý spouštěč zvlášť. Úroveň static je ochrana proti zahlcení u endpointů,
které se často dotazují v pravidelných intervalech, jako je
[verze serveru](account.md#zjisteni-verze-serveru).

Při překročení limitu API vrátí `429` s hlavičkou `Retry-After`: počet celých sekund,
než bude mít okno místo pro další požadavek.

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 17
Content-Type: application/json

{"detail": "Too many requests"}
```

Úspěšné odpovědi nenesou žádné hlavičky limitů požadavků, takže si rychlost svých
požadavků hlídejte sami. Jak se vejít do limitů:

- Před opakováním počkejte aspoň `Retry-After` sekund; nikdy neopakujte `429`
  v těsné smyčce.
- Při opakovaných selháních (`429`, `5xx`) prodlužujte odstup exponenciálně
  s náhodným rozptylem (jitter).
- Dávkovou práci rozložte v čase, místo abyste ji posílali v nárazech, a seznamy,
  které čtete často, si ukládejte do cache, místo abyste je stahovali při každém použití.
- Více workerů za jednou IP adresou sdílí limity; počítejte se všemi.

## Streamování

Chat a některé endpointy workflow mohou svůj výstup streamovat: odpověď se posílá
po částech (chuncích) už během generování, místo jednoho těla JSON na konci. Formát
chunků, signál konce streamu a způsob hlášení chyb uprostřed streamu popisuje
stránka [Streamování](streaming.md).
