# Loturi (/ro/docs/gateway/batches)

Umpli un șablon sau o creație dintr-un fișier CSV ori dintr-o foaie de calcul — câte o creație pe rând, cu limbă și randare opționale pe rând, export de stare și o arhivă ZIP cu videoclipurile.

Un **lot** transformă un tabel în creații: fiecare rând dintr-un fișier CSV (sau dintr-o creație de tip foaie de calcul)
umple variabilele unui [șablon](./creatives.mdx#șabloane) sau ale unei creații și devine o creație separată. Rândurile
pot fi traduse în limba lor și randate în MP4. Loturile rulează în fundal; le urmărești prin interogări repetate.

Crearea unui lot este **gratuită**. Traducerile și randările făcute pentru rânduri se facturează exact ca aceleași
apeluri făcute manual (vezi [Facturare](#facturare)).

| Metodă | Cale | Rezultat |
| --- | --- | --- |
| `POST` | `/v1/studio/batches` | Validează (`dry_run`) sau creează un lot (`201`) |
| `GET` | `/v1/studio/batches?limit=&before=` | `{ batches, next_before }`, cele mai noi primele |
| `GET` | `/v1/studio/batches/{id}?rows=&status=&offset=&limit=` | Lotul, opțional cu rândurile lui |
| `POST` | `/v1/studio/batches/{id}/cancel` | Anulează rândurile care nu au început încă |
| `POST` | `/v1/studio/batches/{id}/retry` | Reîncearcă rândurile eșuate |
| `GET` | `/v1/studio/batches/{id}/export.csv` | Descarcă starea fiecărui rând ca CSV |
| `POST` | `/v1/studio/batches/{id}/zip` | Construiește o arhivă ZIP cu randările terminate (`202`) |

## Surse și date [#surse-și-date]

**Sursa** este exact una dintre `{ "template_id": "<id>" }` (un șablon cu cel puțin o variabilă) sau
`{ "creative_id": "<id>", "seq"?: <versiune> }` (o creație cu cel puțin o variabilă neblocată; implicit capul).
Altfel răspunsul este `400 no_vars`.

**Datele** sunt exact una dintre:

- `csv` — text CSV cu un rând de antet, de cel mult **2 MB**. Analizatorul respectă RFC 4180: câmpuri între ghilimele,
  `""` în interiorul ghilimelelor, rânduri noi în celulele între ghilimele, sfârșituri de rând CRLF / LF / CR; un BOM
  UTF-8 este eliminat. Separatorul este detectat din antet (`,`, `;` sau tab — o foaie exportată cu setări regionale
  românești folosește de obicei `;`) sau impus cu `delimiter`. Celulele din antet sunt curățate de spații, rândurile
  scurte sunt completate cu celule goale, iar rândurile goale de la final sunt eliminate.
- `sheet` — `{ "creative_id": "<creație foaie>", "node_id"?: "<id grilă>" }`: prima (sau cea indicată) grilă ori
  tabel dintr-o creație de tip foaie de calcul, document sau prezentare. Antetul este format din titlurile coloanelor
  când fiecare coloană are unul, altfel din primul rând; datele calendaristice devin date ISO, valorile booleene
  `true` / `false`, numerele text simplu, iar celulele cu formule rămân goale.

**Limite.** Cel mult **500 de rânduri de date** și **50 de coloane**. Erorile CSV răspund `400` cu codul: `empty`,
`ragged` (un rând mai lung decât antetul, cu indicele lui), `unterminated_quote`, `too_many_columns`, `too_many_rows`.

## Asocierea coloanelor [#asocierea-coloanelor]

Fiecare coloană se asociază unei **variabile**, unei **coloane rezervate** sau niciunei ținte. Fără `mapping`, coloanele
sunt potrivite automat: diacriticele sunt eliminate, literele trecute la litere mici și orice altă secvență de caractere
devine `_`, apoi numele coloanei este comparat cu cheia și eticheta fiecărei variabile (`Titlu principal` se potrivește
cu variabila `titlu_principal`).

| Coloană rezervată | Se potrivește și cu | Semnificație |
| --- | --- | --- |
| `title` | `titlu` | Titlul creației rândului (implicit: titlul sursei) |
| `locale` | `limba`, `language`, `lang` | Limba rândului (BCP-47, de exemplu `en`, `de-DE`) — vezi [Limbi](#limbi) |
| `format` | `aspect` | Formatul implicit al creației rândului (un id de format al sursei) |

`mapping` înlocuiește potrivirea automată pentru fiecare coloană: `{ "Headline EN": "headline", "Notes": null, "Lang": "locale" }`
(`null` ignoră coloana). O țintă care nu este nici variabilă, nici coloană rezervată răspunde `400 invalid_mapping`.

**Valorile celulelor.** O celulă goală păstrează valoarea implicită a variabilei. Numerele acceptă `1.5`, `1,5` și
`1 234,5`; valorile booleene `true` / `false`, `yes` / `no`, `da` / `nu`, `1` / `0` și `x`; culorile `#rgb`, `#rrggbb`
și `#rrggbbaa`; textele au cel mult 5000 de caractere.

**Celule de imagine.** O variabilă de tip imagine primește una dintre:

- un **id de asset** al uneia dintre imaginile tale gata;
- un **nume de asset** — cea mai nouă dintre imaginile tale gata al cărei nume sau nume de fișier se potrivește (fără a
  ține cont de majuscule);
- o **adresă URL https** — importată în biblioteca ta Studio când rulează rândul (doar adrese publice, fiecare
  redirecționare verificată din nou; PNG, JPEG, WebP sau GIF de cel mult 10 MB). Aceeași adresă este importată o singură
  dată pe lot.

## Rulare de probă [#rulare-de-probă]

Trimite `"dry_run": true` ca să verifici totul fără să salvezi nimic (gratuit):

```bash
curl https://ai.codai.ro/v1/studio/batches \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "source": { "template_id": "<id șablon>" }, "csv": "headline,photo,locale\nAutumn sale,https://example.com/a.jpg,en\nReduceri de toamnă,poza-1,ro\n", "dry_run": true }'
```

```json
{
  "dry_run": true,
  "columns": [{ "name": "headline", "index": 0, "maps_to": "headline", "kind": "var" }],
  "unmapped_vars": [],
  "rows": 2,
  "valid": 2,
  "errors": [],
  "preview": [{ "row": 0, "title": "Promo 9:16", "locale": "en", "format": null, "vars": { "headline": "Autumn sale" } }],
  "estimate": { "renders": 0, "localized_rows": 2 }
}
```

`errors` enumeră cel mult 100 de probleme sub forma `{ "row", "column", "message" }` (un număr invalid, o imagine
necunoscută, o limbă greșită, un format pe care sursa nu îl are); `preview` arată primele trei rânduri valide, iar
`estimate` numără randările și traducerile pe care lotul le-ar factura. Corectează asocierea și încearcă din nou până
când rezultatul arată bine.

## Crearea și urmărirea unui lot [#crearea-și-urmărirea-unui-lot]

Același corp fără `dry_run` creează lotul și răspunde **201** `{ "batch": { … } }`. Rândurile invalide sunt salvate ca
`failed`, cu eroarea lor, iar restul rulează; când toate rândurile sunt invalide, răspunsul este `400 invalid_rows`,
cu erorile.

| Câmp | Semnificație |
| --- | --- |
| `source` | `{ template_id }` sau `{ creative_id, seq? }` (obligatoriu) |
| `csv` / `sheet` | Datele, exact una dintre ele (obligatoriu) |
| `delimiter` | `,`, `;` sau `\t` (implicit: detectat) |
| `mapping` | Coloană → cheie de variabilă, `title`, `locale`, `format` sau `null` |
| `title` | Titlul lotului, cel mult 200 de caractere |
| `render` | `true` randează fiecare rând în MP4 (doar surse video, altfel `400 unsupported_kind`; implicit `false`) |
| `quality` | `draft` (implicit) sau `final` |
| `format` | Id-ul formatului randărilor (implicit: formatul implicit al rândului sau al sursei) |
| `localize` | `{ "tone"?, "glossary"? }` folosite pentru rândurile cu limbă |
| `dry_run` | `true` doar validează |

Fiecare rând trece prin `pending` → `created` → (`localized`) → (`rendering`) → `done` sau se termină ca `failed` ori
`skipped`. `GET /v1/studio/batches/{id}` avansează puțin lotul la fiecare interogare și
răspunde `{ "batch": { "id", "title", "status", "source", "total", "done", "failed", "pending", "skipped", "options",
"columns", "cost_micro_usd", "zip", "created_at", "updated_at", "finished_at" } }`; cu `rows=1` adaugă `rows` (`offset`,
`limit` cel mult 100, filtru opțional `status`), fiecare de forma
`{ "idx", "status", "creative_id", "render_id", "render_url", "locale", "title", "error", "cost_micro_usd" }`
(`render_url` este o legătură semnată după ce randarea este gata). `columns` are forma `{ "header", "columns",
"unmapped_vars" }`: antetul CSV, aceeași potrivire a coloanelor ca la simulare și variabilele pe care nu le completează
nicio coloană. `cost_micro_usd` este un șir zecimal (micro-USD). `status` al lotului este `queued`, `running`,
`completed`, `failed` sau `cancelled`. Un lot care nu îți aparține răspunde `404 Batch not found.`.

**Anulare și reîncercare.** `POST …/cancel` marchează rândurile care nu au început încă drept `skipped`, iar lotul
drept `cancelled` (`409 not_cancellable` după ce s-a terminat). `POST …/retry` pune rândurile eșuate înapoi în coadă —
fiecare rând este încercat de cel mult 3 ori (`409 nothing_to_retry` când niciun rând nu mai poate fi reîncercat).
Ambele răspund cu lotul.

## Limbi [#limbi]

Cu o coloană `locale`, creația fiecărui rând este mai întâi tradusă în acea limbă cu modelul (aceeași traducere ca la
[Localizare](./creatives.mdx#localizare), cu `localize.tone` și `localize.glossary` ale lotului) și apoi devine o
creație **în acea limbă**, gata de randat. Lasă celula goală ca să păstrezi limba sursă.

## Export, ZIP și limite [#export-zip-și-limite]

`GET …/export.csv` descarcă coloanele originale plus `creative_id`, `status`, `error` și `render_url` pentru fiecare
rând (`text/csv; charset=utf-8`, `<titlu>-status.csv`). Celulele care încep cu `=`, `+`, `-`, `@`, tab sau retur de car
primesc un `'` în față, ca o foaie de calcul să nu le execute niciodată ca formule. Gratuit.

`POST …/zip` construiește în fundal o arhivă ZIP cu fiecare randare terminată (`<idx>-<titlu>.mp4`, cel mult 500 de
fișiere) și răspunde **202** cu lotul (`zip.status: "building"`); interoghează lotul până când `zip.status` este
`ready` și descarcă `zip.url` (semnat pentru o oră) — sau `failed`, cu `zip.error`. Fără nicio randare terminată
răspunsul este `409 no_renders`. Gratuit.

| Limită | Valoare |
| --- | --- |
| Rânduri de date | 500 |
| Coloane | 50 |
| Mărimea fișierului CSV | 2 MB |
| Imagine importată | 10 MB, PNG / JPEG / WebP / GIF |
| Încercări pe rând | 3 |
| Fișiere în arhiva ZIP | 500 |

## Facturare [#facturare]

- Crearea, validarea, interogarea, anularea, reîncercarea, exportul și construirea arhivei ZIP sunt **gratuite**.
- Fiecare rând cu limbă este tradus și facturat ca `POST …/localize` (rânduri de utilizare `codai::localize`).
- Fiecare randare de rând se facturează ca orice randare de aceeași calitate, după ce a reușit.
- `cost_micro_usd` însumează cât au costat până acum lotul și fiecare rând.

## SDK-uri și unelte de agent [#sdk-uri-și-unelte-de-agent]

```ts
const check = await codai.batches.dryRun({ source: { template_id: tpl.id }, csv });
if (check.valid === check.rows) {
  const { batch } = await codai.batches.create({ source: { template_id: tpl.id }, csv, render: true });
  await codai.batches.wait(batch.id); // interoghează la 3 s, cel mult 10 min
  const statusCsv = await codai.batches.exportCsv(batch.id);
  await codai.batches.zip(batch.id);
}
```

```python
check = client.batches.dry_run(source={"template_id": tpl["id"]}, csv=csv_text)
batch = client.batches.create(source={"template_id": tpl["id"]}, csv=csv_text, render=True)["batch"]
client.batches.wait(batch["id"])  # interoghează la 3 s, cel mult 10 min
status_csv = client.batches.export_csv(batch["id"])
```

Agenții de pe desktop, telefon și VS Code folosesc `creative_batch` (`template_id` sau `creative_id`, `csv` sau
`sheet_creative_id`, `mapping`, `render`, `quality`, `format`, `dry_run`) și `creative_batch_status` (`batch_id`,
`rows`).

## Hub (intern) [#hub-intern]

Hub-ul apelează aceleași rute sub `/internal/studio/batches…`, cu un token de serviciu și un `user_id`; corpurile și
erorile sunt identice.
