Kodwen docs
Gateway

Creații

Documente creative editabile în Kodwen Studio — creezi un video, o imagine sau o prezentare ca JSON, le editezi prin modificări mici, versionate, păstrezi fiecare versiune și le randezi în MP4.

O creație este un singur document editabil pentru fiecare produs Studio (video, imagine, prezentare, document, foaie de calcul sau audio — melodii și podcasturi, vezi Creații audio), păstrat ca JSON simplu — Editable Creative Document (ECD). Agenții și editoarele nu rescriu niciodată HTML: trimit operații mici, adresate prin id. Fiecare editare adaugă o versiune, deci poți oricând să te uiți înapoi, să revii sau să copiezi. Intrarea pentru randare este o funcție pură și deterministă a documentului.

Crearea, citirea și editarea creațiilor sunt gratuite. Costă doar randările și cadrele statice peste cota zilnică gratuită.

MetodăCaleRezultat
POST/v1/studio/creativesCreație nouă la versiunea 1 (201)
GET/v1/studio/creatives?kind=&before=&limit={ object: "list", data, next_before }, cele modificate recent primele
GET/v1/studio/creatives/{id}?seq=Documentul curent (sau de la seq) + asset-urile semnate
GET/v1/studio/creatives/{id}/outlineSchiță text compactă, formate și variabile
POST/v1/studio/creatives/{id}/findId-urile nodurilor care se potrivesc
POST/v1/studio/creatives/{id}/subtreeJSON-ul complet al unor noduri sau al unei scene
POST/v1/studio/creatives/{id}/opsAplică un lot de operații → versiune nouă
GET/v1/studio/creatives/{id}/versionsIstoricul versiunilor
POST/v1/studio/creatives/{id}/revertVersiune curentă nouă cu documentul unei versiuni vechi
GET/v1/studio/creatives/{id}/branchesRamuri cu nume (main prima)
POST/v1/studio/creatives/{id}/branchesRamură nouă dintr-o versiune, 201
DELETE/v1/studio/creatives/{id}/branches/{name}Șterge o ramură și versiunile ei
POST/v1/studio/creatives/{id}/filmstripO miniatură pe clip (compozitorul comun, cota de cadre)
GET/v1/studio/jobsLucrările Studio în curs și recente (coada)
POST/v1/studio/creatives/{id}/forkCreație nouă dintr-o versiune (201)
POST/v1/studio/creatives/{id}/lintContrast, zonă sigură, brand, accesibilitate, asset-uri lipsă…
GET/v1/studio/creatives/{id}/compiled?format=&scene=HTML de previzualizare cu URL-uri semnate
POST/v1/studio/creatives/{id}/renderRandare MP4 (202, codai-render; 200 când randarea identică există deja)
POST/v1/studio/creatives/{id}/frameCadre PNG statice la cel mult 6 momente (200 sau 202 + un job de interogat)
GET/v1/studio/creatives/{id}/frames/{jobId}Interoghează un job de cadre (200 cadre, 202 încă rulează)
POST/v1/studio/creatives/{id}/commentsTransformă un comentariu pe un cadru într-o editare (facturat pe tokeni)
POST/v1/studio/creatives/{id}/variantsGenerează variante A/B ca derivate + randări draft (facturat pe tokeni)
GET/v1/studio/creatives/{id}/variantsUltimul grup de variante, cu stări și starea randării
POST/v1/studio/creatives/{id}/variants/{variantId}/pickAplică varianta câștigătoare pe original
DELETE/v1/studio/creatives/{id}Ștergere
POST/v1/studio/brand-kitsCreează un kit de brand din tokeni sau dintr-un fișier DTCG (201)
POST/v1/studio/brand-kits/extractKit de brand (paletă, fonturi, logo) dintr-o pagină web sau dintr-o imagine de logo (201, facturat pe tokeni)
GET/v1/studio/brand-kits?limit=Kiturile tale de brand, cele mai noi primele
GET/v1/studio/brand-kits/{id}Un kit de brand cu URL semnat al logoului
PATCH/v1/studio/brand-kits/{id}Editează un kit de brand (nume, tokeni, DTCG, logo)
GET/v1/studio/brand-kits/{id}/dtcgDescarcă un kit de brand ca fișier DTCG .tokens.json
DELETE/v1/studio/brand-kits/{id}Șterge un kit de brand
POST/v1/studio/creatives/{id}/restyleReaplică un kit de brand pe o creație → versiune nouă
POST/v1/studio/creatives/{id}/captionsSubtitrări automate dintr-un clip de voce → versiune nouă (transcrierea se facturează pe minut)
GET/v1/studio/creatives/{id}/captions.srt?node_id=&seq=Descarcă subtitrările în format SubRip
GET/v1/studio/creatives/{id}/captions.vtt?node_id=&seq=Descarcă subtitrările în format WebVTT
POST/v1/studio/creatives/{id}/adaptAdaptează la preseturi de platformă (dimensiuni + zone sigure) → versiune nouă
POST/v1/studio/creatives/{id}/lint/fixCorectează automat problemele lint (gratuit; tură de agent plătită, opțională)
GET/v1/studio/creatives/{id}/controlsModelul panoului de ajustare: un control pentru fiecare variabilă
GET/v1/studio/creatives/{id}/locales?locale=&units=Limbile: progresul pe fiecare limbă și, la cerere, unitățile de traducere
POST/v1/studio/creatives/{id}/localizeTraducere în cel mult 10 limbi → versiune nouă (facturată pe tokeni)
POST/v1/studio/creatives/{id}/locales/{locale}/forkCreație de sine stătătoare într-o singură limbă (201)
POST/v1/studio/creatives/{id}/templateSalvează o creație ca șablon privat (201)
GET/v1/studio/templates?kind=&limit=Șabloanele tale, cele mai noi primele
GET/v1/studio/templates/{id}Un șablon cu documentul lui
DELETE/v1/studio/templates/{id}Șterge un șablon
GET/v1/studio/creatives/{id}/export.fcpxml?format=&seq=Pune în coadă o arhivă cu proiect FCPXML (202)
GET/v1/studio/creatives/{id}/export.lottie?format=&formats=&seq=&container=&download=Export Lottie JSON / dotLottie, gratuit și gata imediat (200)
GET/v1/studio/exports/{id}Starea exportului; URL semnat al arhivei după succeeded
POST/v1/studio/documents/{id}/creativeEditează ca o creație o prezentare, un document sau o foaie de calcul (201 sau 200 dacă e deja legată)
POST/v1/studio/creatives/{id}/buildGenerează dintr-o creație o versiune nouă PPTX / DOCX / XLSX
POST/v1/studio/creatives/{id}/sharesCreează un link de vizualizare sau de revizuire (201, tokenul apare o singură dată) — vezi Partajare, revizuire și remix
GET/v1/studio/creatives/{id}/sharesLinkurile de partajare active
DELETE/v1/studio/creatives/{id}/shares/{shareId}Revocă un link de partajare (204)
GET/v1/studio/creatives/{id}/reviewStarea revizuirii și versiunea aprobată
POST/v1/studio/creatives/{id}/reviewCere o revizuire sau revino la ciornă
GET/v1/studio/creatives/{id}/review/threads?status=Firele de comentarii ale revizuirii
POST/v1/studio/creatives/{id}/review/threadsFir nou de comentarii ancorat (201)
POST/v1/studio/creatives/{id}/review/threads/{threadId}/repliesRăspunde într-un fir (201)
POST/v1/studio/creatives/{id}/review/threads/{threadId}/resolveRezolvă un fir
POST/v1/studio/creatives/{id}/review/threads/{threadId}/reopenRedeschide un fir
DELETE/v1/studio/creatives/{id}/review/comments/{commentId}Șterge un comentariu de revizuire (204)
POST/v1/studio/shares/{token}/remixCopiază o creație partajată în contul tău (201)

Documentul

{
  "schemaVersion": 1,
  "kind": "video",
  "title": "Teaser de lansare",
  "formats": [{ "id": "16x9", "width": 1920, "height": 1080 }],
  "defaultFormat": "16x9",
  "fps": 30,
  "brand": { "tokens": { "primary": "#ff5500" } },
  "vars": { "headline": { "type": "text", "value": "Salut, lume" } },
  "assets": { "3f2b8c1e-0000-4000-8000-000000000001": { "kind": "image", "mime": "image/png" } },
  "scenes": [
    {
      "id": "intro",
      "orderKey": "V",
      "durationMs": 4000,
      "nodes": [
        {
          "id": "title",
          "type": "text",
          "orderKey": "V",
          "layout": { "x": 200, "y": 400, "w": 1400, "h": 200 },
          "anim": [{ "preset": "fade-in" }],
          "props": { "spans": [{ "text": { "$var": "headline" } }], "size": 96, "color": { "$token": "primary" } }
        }
      ]
    }
  ],
  "audio": { "tracks": [] }
}
  • Scenele rulează în ordinea orderKey; o scenă video durează între 0,5 și 30 s, iar un video cel mult 180 s.
  • Nodurile sunt text, shape, image, video, chart, captions, group și html_block, fiecare cu un id, un layout și, opțional, timing, anim (preseturi precum fade-in, slide-up, ken-burns, zoom-in, typewriter sau cadre cheie — vezi Mișcare) și suprascrieri per format (overrides).
  • Fișierele media se referă la asset-urile tale prin id. brand.tokens și vars se referă ca { "$token": "…" } și { "$var": "…" }.
  • Un document are cel mult 2 MB de JSON canonic (413 too_large).

Fără doc, POST /v1/studio/creatives îți dă un document gol pentru tipul ales: un video 16:9 cu o scenă goală de 5 s, o imagine 1080×1080, o prezentare 16:9 cu un slide sau un document / o foaie de calcul goală.

curl https://ai.codai.ro/v1/studio/creatives \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "kind": "video", "title": "Teaser de lansare" }'

Piste (schemaVersion 2)

Documentele video folosesc piste reale: schemaVersion: 2 adaugă tracks — piste vizuale (video, overlay, text, captions) cu clipuri așezate la milisecunde absolute. Un clip este un clip media (type: "video", cu propriul interval in/out), un singur nod (type: "node") sau un clip compus (type: "compound", o scenă veche cu nodurile ei). scenes devin secțiuni cu nume, fără noduri. Migrarea face din fiecare scenă un clip compus cu același id, așa că comentariile, recenziile și cache-urile ancorate pe id-ul unei scene funcționează în continuare, iar randările rămân identice byte cu byte. Orice document salvat este versiunea 2 (un document versiunea 1 trimis de tine e migrat la salvare); schița listează tracks → clips, ancorele acceptă clip_id (cu scene_id ca alias), iar operațiile de pistă/clip (addVisualTrack, insertClip, moveVisualClip, reorderVisualClip, trimVisualClip, splitVisualClip, setClipProps, setClipAnim, flattenClip, …) le editează. reorderVisualClip { target, after } mută un clip mai devreme sau mai târziu pe pista lui și reașază începuturile (tranzițiile își păstrează suprapunerea), cum făcea mutarea unei scene. Segmentele de randare urmează grupurile de clipuri care se suprapun pe toate pistele.

Editarea prin operații

Trimite operațiile față de versiunea pe care ai citit-o (base_seq). Un lot se aplică în întregime sau deloc.

curl https://ai.codai.ro/v1/studio/creatives/$ID/ops \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "base_seq": 3,
    "intent": "Titlu mai mare, fundal nou",
    "ops": [
      { "op": "setProps", "target": "title", "props": { "size": 120 } },
      { "op": "updateScene", "target": "intro", "patch": { "background": "#101010" } }
    ]
  }'
{ "seq": 4, "doc_hash": "9a3f…", "changed": ["title", "intro"], "warnings": [], "rebased": false }

Cele 25 de operații: setProps, setLayout, setTiming, setAnim, insertNode, removeNode, moveNode, addScene, removeScene, updateScene, moveScene, setVar, removeVar, setAsset, replaceAsset, setAudioClip, removeAudioClip, setBrand, restyle, adaptFormat, setControl, setLocaleText, setLocale, setLocalization și setMeta. Traducerile se editează cu setLocaleText, setLocale și setLocalization (vezi Localizare). Adaptarea la o platformă este un singur adaptFormat (vezi Platforme); descrierea felului în care se editează o variabilă în panoul de ajustare este un singur setControl (vezi Panoul de ajustare). Schimbarea tokenilor de brand este un singur setBrand; reaplicarea unui brand pe culorile și fonturile literale este un singur restyle (vezi Restilizarea unei creații); înlocuirea unei imagini peste tot este un singur replaceAsset.

Editări simultane

Dacă altcineva a editat creația după ce ai citit versiunea base_seq, gateway-ul îți rebazează operațiile: dacă niciuna dintre țintele și proprietățile pe care le atingi nu s-a schimbat între timp, se aplică peste versiunea curentă (rebased: true). Altfel primești 409 cu ce s-a suprapus — reîncarcă și încearcă din nou:

{
  "error": {
    "code": "stale",
    "type": "invalid_request_error",
    "message": "The creative changed since base_seq and 1 change(s) overlap; reload and retry.",
    "head_seq": 5,
    "conflicts": [{ "target": "title", "head_prop": "props.size", "prop": "props.size" }]
  }
}

Operații respinse

O operație invalidă, un id necunoscut sau un nod blocat dau 400 cu code-ul operației (invalid, not_found, locked, duplicate_id), path din operație sau document și op_index. Nu se salvează nimic. Trimite force: true ca să editezi intenționat noduri și variabile blocate.

Mișcare

setAnim înlocuiește lista anim a unui nod; fiecare intrare este un preset sau o animație cu cadre cheie. O intrare de preset este { "preset", "atMs"?, "durMs"?, "ease"?, "params"? }; una cu cadre cheie este { "prop", "keyframes": [{ "tMs", "value", "ease"? }] }, unde prop este unul dintre opacity, x, y, scale și rotation.

{
  "op": "setAnim",
  "target": "bg",
  "anim": [{ "preset": "zoom-in", "params": { "scale": 1.2 }, "ease": "in-out-cubic" }]
}
PresetCe faceDurată implicită
fade-in, fade-outApare treptat; dispare treptat la sfârșitul nodului400 ms
slide-up, slide-down, slide-left, slide-rightIntră glisând pe params.distance (implicit 48 px), cu apariție treptată500 ms
scale-popApare mărindu-se de la 0,6, cu o ușoară depășire450 ms
typewriterDezvăluie textul caracter cu caracter1200 ms
word-staggerCuvintele apar unul după altul (params.wordDurMs, implicit 350)600 ms
count-upNumără de la params.from (implicit 0) la params.to (implicit: numărul din text)1200 ms
wipeDezvăluie nodul printr-o ștergere orizontală600 ms
highlight-sweepTrece culoarea de evidențiere peste text600 ms
ken-burnsZoom lent, liniar, spre punctul focal (params.scale, implicit 1,08)Tot nodul
zoom-in, zoom-outApropiere / depărtare a camerei pe media care umple cadrul, în jurul punctului focal (params.scale, implicit 1,14)Tot nodul
pan-left, pan-rightPanoramare a camerei peste media ușor mărită (params.scale, implicit 1,12)Tot nodul
riseIntrare: urcă (params.distance, implicit 64 px) și se mărește de la 0,96, cu apariție treptată700 ms
scale-outIeșire: se micșorează la 0,92 și dispare treptat, așezată la sfârșitul nodului, ca fade-out400 ms

Curbe de animație. ease, pe un cadru cheie sau pe un preset, este unul dintre cele cinci cuvinte-cheie CSS (linear, ease, ease-in, ease-out, ease-in-out) sau una dintre curbele out-cubic, out-quart, out-expo, in-cubic, in-out-cubic, in-out-quart, in-out-sine, out-back și in-out-back. Pe un preset înlocuiește curba principală a presetului (typewriter îl ignoră).

Corecție de culoare. Nodurile imagine și video acceptă grade în proprietăți (setProps): { brightness?, contrast?, saturate?, blur? }. Pentru brightness și contrast (0–2) și saturate (0–3), valoarea 1 lasă media neschimbată; blur este între 0 și 200 de pixeli ai documentului. Corecția se aplică în previzualizarea web, în randările MP4 și în compozitor, dar nu și în exportul Lottie (avertismentul grade_not_exported).

{ "op": "setProps", "target": "bg", "props": { "grade": { "contrast": 1.1, "saturate": 0.9 } } }

Tranziții între scene. transitionIn al unei scene (updateScene) este { "type", "durMs" }: type este unul dintre cut, fade, slide-left, slide-right, slide-up, slide-down, wipe, zoom și zoom-out, iar durMs este cel mult 5000.

{
  "op": "updateScene",
  "target": "intro",
  "patch": { "transitionIn": { "type": "slide-right", "durMs": 500 } }
}

Citire pentru agenți

Documentele mari nu trebuie trimise întregi. outline întoarce câte o linie pentru fiecare scenă și nod (id-uri, tipuri, etichete, text tăiat la 60 de caractere, momente); find întoarce id-uri de noduri după text, tip, scenă, asset sau blocare; subtree întoarce JSON-ul complet doar pentru id-urile cerute.

Versiuni

Fiecare editare este o versiune; istoricul doar crește. revert adaugă o versiune nouă cu un document mai vechi (intent: "revert to N"), deci și anularea, și refacerea sunt reveniri. fork pornește o creație nouă din orice versiune. GET …?seq=N citește o versiune veche.

Ramuri. main este linia implicită. POST …/branches {name, from?, seq?} pornește o ramură din orice versiune; versiunile ei continuă de la acel seq (istoricul de dedesubt este comun). Trimite branch (în corp) sau ?branch= (în interogare) la GET {id}, /ops, /versions, /revert, /fork, /frame, /filmstrip și /comments; răspunsurile numesc branch. Numele: a-z, 0-9, cratime, cel mult 40 de caractere, cel mult 20 de ramuri pe creație. Erori: branch_not_found (404), branch_exists, too_many_branches, branch_protected (main) și branch_has_children (409). SDK: creatives.branches, createBranch, deleteBranch (Python branches, create_branch, delete_branch).

Filmstrip și coadă. POST …/filmstrip desenează câte o miniatură la mijlocul fiecărui clip cu compozitorul comun (același cod ca previzualizarea din editor), pe calea lucrărilor de cadre: din cache, gratuit pentru primele 60 de lucrări de cadre pe zi, apoi la minimul de ciornă. GET /v1/studio/jobs listează lucrările tale în curs și din ultimele 15 minute, de orice tip (randări, cadre, exporturi, producții, operații media, Music Room, încărcări), cu progres și timp estimat; este gratuit.

Previzualizare și randare

GET …/compiled întoarce compoziția HyperFrames deterministă, cu asset-urile tale ca URL-uri semnate valabile o oră, gata pentru un iframe srcdoc (Cache-Control: private, no-store). POST …/render compilează versiunea curentă și pune în coadă un codai-render obișnuit:

curl https://ai.codai.ro/v1/studio/creatives/$ID/render \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "format": "16x9", "quality": "draft" }'

Răspunde 202 cu render_id; interoghează-l cu GET /v1/studio/renders/{id}, care raportează creative_id. Randarea se facturează ca orice randare, o singură dată, după ce a reușit. Formatul trebuie să fie 1920×1080, 1080×1920, 1080×1080 sau 1080×1350 (4:5). Randarea funcționează pentru creațiile video (celelalte tipuri răspund 400 not_supported_for_kind); compilarea previzualizează și creațiile de tip prezentare, document și foaie de calcul (vezi Documente).

Randările sunt compuse din segmente per scenă: scenele care nu s-au schimbat de la o randare anterioară se refolosesc, așa că o nouă randare după o editare mică randează doar scenele atinse (taxarea rămâne pe secundă de ieșire). Dacă randezi din nou exact aceeași versiune, același format, aceeași calitate și aceleași fps, răspunsul este imediat 200 cu randarea existentă (cached: true, status: "succeeded") și nu se taxează nimic; corpul 202 al unei randări noi are cached: false.

ai_label (corner, end_card sau none) imprimă în video o etichetă IA vizibilă, ca la randări. Dacă lipsește, este corner când creația folosește narațiune TTS sau imagini generate cu IA, altfel none; none cu narațiune TTS răspunde 400 ai_label_required. Conform Regulamentului UE privind IA (art. 50 alin. 4), tu, ca implementator, rămâi responsabil de etichetarea deepfake-urilor pe care le publici.

Cadre statice

POST …/frame întoarce cadre PNG statice ale creației la cel mult 6 momente, pentru un format (implicit formatul implicit al documentului) și o versiune (implicit cea curentă):

curl https://ai.codai.ro/v1/studio/creatives/$ID/frame \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "t_ms": [0, 1500, 4200], "format": "16x9", "exact": false }'
CâmpSemnificație
t_ms1–6 momente în milisecunde, fiecare sub durata totală a creației (obligatoriu)
formatUn formats[].id (implicit formatul implicit al documentului)
exactfalse (implicit): o captură rapidă a HTML-ului compilat. true: cadre decupate dintr-o randare ciornă a scenelor care acoperă acele momente — identice la pixel cu MP4-ul; umple și cache-ul de scene pentru randările următoare
versionVersiunea documentului (seq); implicit cea curentă

Apelul așteaptă cel mult aproximativ 90 de secunde. Când cadrele sunt gata, răspunde 200:

{
  "object": "studio.creative.frames",
  "frame_job_id": "5e6f7a8b-0000-4000-8000-000000000040",
  "status": "succeeded",
  "frames": [{ "t_ms": 1500, "url": "https://…semnat, 1 h…", "width": 1920, "height": 1080, "cached": false }],
  "head_seq": 3,
  "billed_micro_usd": "0"
}

Altfel răspunde 202 cu { "object": "studio.creative.frame_job", "frame_job_id": "…", "status": "running", "head_seq": 3 }; interoghează GET /v1/studio/creatives/{id}/frames/{jobId} până întoarce același corp 200 (202 cât timp rulează, 404 pentru un job necunoscut, 502 frame_failed dacă a eșuat).

Cache. Cadrele deja randate pentru același document, format și moment vin din cache instantaneu și gratuit (cached: true); când toate cadrele vin din cache, frame_job_id este null.

Cotă și preț. 60 de joburi de cadre lansate pe utilizator pe zi UTC sunt gratuite; răspunsurile servite integral din cache sunt mereu gratuite și nu se numără. După aceea, cadrele rapide costă fix 0,025 $ (25 000 µUSD) pe job, iar cele exacte prețul randării ciornă (0,005 $ pe secundă de scene randate din nou, minimum 0,025 $). billed_micro_usd arată suma.

Erori. 400 invalid_request (corp invalid, mai mult de 6 momente, un moment în afara creației), 400 unsupported_format (cadrele exacte cer 1920×1080, 1080×1920, 1080×1080 sau 1080×1350; cele rapide cel mult 3840 px pe latură), 400 not_supported_for_kind, 429 too_many_frame_jobs (cel mult 3 joburi de cadre în lucru), 502 frame_failed, 503 renders_unavailable.

Comentarii pe cadru

POST …/comments transformă comentariul unui recenzent, fixat pe un cadru, într-o editare. O tură scurtă de agent pe server citește schița, nodul comentat (sau scena, ori scena care rulează la t_ms) și comentariul, apoi propune operații aplicate exact ca la POST …/ops, pe base_seq-ul tău:

curl https://ai.codai.ro/v1/studio/creatives/$ID/comments \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "node_id": "title", "t_ms": 1200, "comment": "Mărește titlul și fă-l alb", "base_seq": 3 }'
CâmpSemnificație
t_msMomentul cursorului la comentariu (obligatoriu)
commentCe trebuie schimbat, 1–2000 de caractere (obligatoriu)
base_seqhead_seq-ul citit ultima dată (obligatoriu; o bază veche e rebazată ca la /ops)
node_id / scene_id / clip_idNodul, scena sau clipul selectat (implicit: scena — la videoclipurile schemaVersion 2 clipul primei piste video — care rulează la t_ms; scene_id e alias pentru clip_id)
applytrue (implicit) scrie o versiune; false întoarce doar ops propuse și diff-ul lor
chipsCel mult 8 chip-uri de context în care trebuie să rămână editarea: { "kind": "clip" | "node", "id" }, { "kind": "range", "start_ms", "end_ms" } sau o zonă din scenă { "kind": "area", "t_ms", "x", "y", "w", "h", "format"? } (px ai compoziției)

Răspunde 200 { "head_seq": 4, "applied_ops": 1, "summary": "Am mărit titlul.", "version": { "seq": 4, "doc_hash": "…" } }. O modificare suprapusă de la base_seq răspunde 409 stale (citește din nou documentul, apoi reîncearcă); când comentariul nu cere o editare răspunsul este 422 no_edit, cu replica modelului în error.text; după 60 s, 504 timeout. Operațiile respinse primesc automat o rundă de reparare.

Diferența op-urilor (SX-07). Cu apply: false răspunsul conține și diff: { scoped, entries }: câte o intrare pentru fiecare op propus, cu action (insert, remove, move, retime, set), targets (id + o etichetă scurtă label), fields, textul before / after, in_scope (în interiorul chip-urilor) și depends_on (op-urile anterioare de care are nevoie). Editoarele Studio bifează implicit op-urile din selecție, te lasă să accepți sau să respingi fiecare op și trimit doar ce ai acceptat la POST …/ops, cu head_seq-ul propunerii ca base_seq.

Preț. Facturat pe tokeni ca orice apel text kodwen (rânduri de utilizare codai::creative-comment, cache de prompt activ) — un comentariu obișnuit costă mult sub 0,01 $.

Variante A/B

POST …/variants cere unei ture scurte de agent pe server variante A/B ale creației la base_seq. Fiecare variantă schimbă doar aspectele din focus și este salvată ca o creație nouă derivată din base_seq (titlul "<titlu> — <etichetă>", operațiile aplicate ca versiunea 2 de agent), deci o poți deschide, edita, randa sau șterge ca pe orice creație. Originalul nu se schimbă până nu alegi o câștigătoare.

curl https://ai.codai.ro/v1/studio/creatives/$ID/variants \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "count": 3, "focus": ["hook", "cta"], "brief": "Black Friday, public tânăr", "base_seq": 4 }'
CâmpSemnificație
base_seqVersiunea din care se derivă (obligatoriu)
count1–5 variante (implicit 3)
focusOricare dintre hook (deschiderea), title, cta, visual (culori, așezare, imagini, mișcare), copy (implicit hook, title, cta)
briefIndicații opționale, cel mult 1000 de caractere
rendertrue (implicit, doar video) pune în coadă o randare draft pentru fiecare variantă
formatId-ul formatului pentru randările draft (implicit: formatul implicit al documentului)

Răspunde 200 { "group_id": "…", "base_seq": 4, "variants": [{ "creative_id": "…", "label": "Hook cu întrebare", "rationale": "…", "applied_ops": 2, "head_seq": 2, "render_id": "…" }] } fără să aștepte randările — interoghează fiecare render_id cu GET /v1/studio/renders/{id}. Propunerile invalide sunt ignorate (o rundă automată de reparare); când niciuna nu e validă răspunsul este 422 no_edit; după 90 s, 504 timeout. O randare care nu a putut fi pusă în coadă lasă varianta cu render_error.

GET …/variants întoarce ultimul grup: fiecare variantă cu status (candidate, winner, rejected) și starea salvată a randării draft (poster_url și url după ce a reușit).

POST …/variants/{variantId}/pick cu { "base_seq": <versiunea curentă a originalului> } aplică operațiile câștigătoarei pe original exact ca POST …/ops (autor agent, intenție variant: <etichetă>; o bază veche e rebazată, o modificare suprapusă dă 409 stale) și marchează câștigătoarea winner, iar celelalte rejected. Răspunde { "head_seq", "version", "winner", "rebased" }.

Preț. Generarea este facturată pe tokeni ca orice apel text kodwen (rânduri de utilizare codai::creative-variants, cache de prompt activ — mult sub 0,05 $ pentru trei variante); fiecare randare draft se facturează la interogare ca orice randare draft (0,005 $ pe secundă de ieșire, minimum 0,025 $). Listarea și alegerea sunt gratuite.

Kituri de brand

Un kit de brand este un set reutilizabil de tokeni de brand — cinci roluri de culoare primary, secondary, accent, background, text, până la 12 culori suplimentare cu nume, familiile de fonturi heading și body și o rază de colț opțională — plus un logo opțional. Kiturile sunt salvate ca tokeni de design DTCG (formatul 2025.10), așa că le poți importa din instrumentele de design și le poți exporta către ele.

Formatul DTCG

{
  "$schema": "https://www.designtokens.org/schemas/2025.10/format.json",
  "color": {
    "$type": "color",
    "primary": { "$value": { "colorSpace": "srgb", "components": [0.004, 0.439, 0.502], "hex": "#017080" } },
    "secondary": { "$value": { "colorSpace": "srgb", "components": [0.137, 0.671, 0.737], "hex": "#23abbc" } },
    "accent": { "$value": { "colorSpace": "srgb", "components": [0.902, 0.718, 0.192], "hex": "#e6b731" } },
    "background": { "$value": { "colorSpace": "srgb", "components": [0.969, 0.973, 0.98], "hex": "#f7f8fa" } },
    "text": { "$value": { "colorSpace": "srgb", "components": [0.059, 0.067, 0.082], "hex": "#0f1115" } },
    "sand": { "$value": { "colorSpace": "srgb", "components": [0.949, 0.902, 0.82], "hex": "#f2e6d1" } }
  },
  "font": {
    "$type": "fontFamily",
    "heading": { "$value": ["Manrope"] },
    "body": { "$value": ["Inter", "Arial"] }
  },
  "radius": { "$type": "dimension", "base": { "$value": { "value": 8, "unit": "px" } } }
}
  • Cele cinci roluri de culoare sunt obligatorii. Culorile suplimentare au un nume format dintr-o literă mică urmată de cel mult 31 de litere mici, cifre sau - (cel mult 12 culori).
  • Importul este tolerant: $value al unei culori poate fi și un șir "#abc" / "#aabbcc", { "hex" } sau { "colorSpace": "srgb", "components" }; $type poate sta pe token sau pe grupul lui; $value al unui font poate fi un singur șir. Kiturile salvate folosesc mereu forma canonică de mai sus.
  • $value al unui font enumeră 1–4 nume de familii (o listă goală înseamnă necunoscut); radius are 0–200 px și este opțional.
  • JSON-ul canonic are cel mult 16 KB. Un fișier de tokeni invalid primește 400 invalid_tokens, cu problemele în error.details.issues.

Fiecare kit răspunde cu dtcg, cu tokenii plați scriși în creații (mai jos), cu contrast și cu updated_at; câmpurile sunt { "id", "name", "source_url", "dtcg": { … }, "tokens": { … }, "contrast": [ … ], "logo_asset_id", "logo_url", "logo_mime_type", "created_at", "updated_at" }, adică identificatorul, numele, sursa, tokenii, contrastul și logoul.

Contrast. contrast enumeră raportul de contrast WCAG pentru cinci perechi fixe — text pe fundal, primar pe fundal, fundal pe primar, accent pe fundal și secundar pe fundal — sub forma { "foreground", "background", "ratio", "aa" } (ratio rotunjit la 2 zecimale, aa este true de la 4,5:1 în sus).

Tokeni plați. Creațiile păstrează o hartă plată în brand.tokens: cele cinci roluri de culoare, headingFont și bodyFont (prima familie, sau ""), fiecare culoare suplimentară sub numele ei, radius (px) când există, plus aliasurile de document color.bg (= fundal), color.fg (= text), color.accent (= primar), font.heading și font.body, ca și creațiile convertite din prezentări, documente și foi de calcul să urmeze kitul.

Creare, import, editare, export

POST /v1/studio/brand-kits creează manual un kit. Trimiți name (1–100 caractere) și exact unul dintre tokens (harta plată: toate cele cinci culori ca #rrggbb, plus headingFont / bodyFont de cel mult 100 de caractere) sau dtcg (un fișier DTCG de importat); opțional logo_asset_id (unul dintre asset-urile tale image gata procesate, altfel 404 Asset not found.) și source_url (https). Răspunde 201 cu kitul.

curl https://ai.codai.ro/v1/studio/brand-kits \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Acme", "tokens": { "primary": "#e4002b", "secondary": "#1d3557", "accent": "#ffb703",
        "background": "#ffffff", "text": "#111111", "headingFont": "Manrope", "bodyFont": "Inter" } }'

PATCH /v1/studio/brand-kits/{id} editează un kit: oricare dintre name, tokens (parțial — combinat peste kitul curent), dtcg (înlocuiește tokenii) și logo_asset_id (null elimină logoul); cel puțin un câmp și niciodată tokens împreună cu dtcg. Răspunde 200 cu kitul; un kit care nu e al tău sau a fost șters dă 404. Creațiile care folosesc deja kitul își păstrează tokenii până când le restilizezi.

GET /v1/studio/brand-kits/{id}/dtcg descarcă kitul ca fișier DTCG (application/json, content-disposition: attachment; filename="<slug>.tokens.json"), cu "$extensions": { "ro.codai": { "kit_id", "name", "logo_asset_id" } } adăugat, gata pentru orice instrument de design care înțelege DTCG.

POST /v1/studio/brand-kits/extract construiește un kit din exact unul dintre url (o pagină publică https) sau logo_asset_id (o imagine de logo aflată deja în biblioteca ta); name este opțional în ambele cazuri.

curl https://ai.codai.ro/v1/studio/brand-kits/extract \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://acme.example/" }'

Dintr-un URL, gateway-ul descarcă pagina și până la 3 dintre foile ei de stil (fiecare redirecționare este verificată din nou contra adreselor private și interne; 10 s, 3 MB, doar HTML), adună candidații de culori, fonturi și logo (theme-color, proprietăți CSS personalizate, font-family, linkuri Google Fonts, imagini de logo, iconițe touch, og:image), iar o singură tură limitată a agentului de pe server alege o paletă coerentă (text lizibil pe fundal) și fonturile. Logoul ales (PNG, JPEG sau WebP de cel mult 5 MB — SVG nu se importă) este salvat în biblioteca ta Studio ca asset image.

Dintr-un logo ({ "logo_asset_id": "<id asset imagine>" }), imaginea trebuie să fie unul dintre asset-urile tale PNG, JPEG sau WebP gata procesate, de cel mult 5 MB (altfel 400 unsupported_media). O singură tură de viziune citește paleta și propune fonturi potrivite logoului; logoul devine logoul kitului, iar source_url este null.

Ambele răspund 201 cu kitul (salvat ca DTCG). Erori: 400 url_blocked (gazdă privată sau blocată), 400 unsupported_media, 422 fetch_failed (pagina nu a putut fi descărcată sau nu e HTML), 422 extract_failed (niciun kit valid), 504 timeout după 60 s.

Aplicarea unui kit

  • La creare — POST /v1/studio/creatives cu "brand_kit_id": "<id kit>": versiunea 1 are deja brand = { kitId, tokens } și logoul înregistrat în assets (fără o versiune în plus). Un kit necunoscut dă 404.
  • Pe o creație existentă — POST /v1/studio/creatives/{id}/restyle (mai jos).
  • Manual — trimite [{ "op": "setBrand", "kitId": "<id kit>", "tokens": { … } }] (plus un setAsset pentru logo) la POST …/ops; asta înlocuiește doar tokenii, culorile literale rămân cum sunt.

Nodurile folosesc tokenii ca { "$token": "primary" }. GET /v1/studio/brand-kits listează kiturile tale (cele mai noi primele), GET …/{id} citește unul, DELETE …/{id} îl șterge (creațiile își păstrează tokenii pe care îi au deja; logoul rămâne în bibliotecă).

Restilizarea unei creații

POST /v1/studio/creatives/{id}/restyle reaplică un brand pe o creație făcută înaintea kitului sau cu culori scrise direct în noduri:

curl https://ai.codai.ro/v1/studio/creatives/$ID/restyle \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "brand_kit_id": "<id kit>", "snap": true }'
CâmpSemnificație
brand_kit_id / tokensExact unul: unul dintre kiturile tale sau o hartă plată de tokeni
base_seqVersiunea citită (implicit: capul); o bază veche este rebazată ca la /ops
snaptrue mută orice altă culoare literală pe cea mai apropiată culoare din kit (implicit false)
fontsfalse lasă fonturile neatinse (implicit true)
forcetrue restilizează și nodurile blocate

Gateway-ul trimite o singură operație restyle (plus un setAsset pentru logoul kitului, dacă are unul) prin aceeași cale de editare ca POST …/ops (intenția restyle with brand kit <nume>) și răspunde 200 cu același corp ca /ops, inclusiv warnings. Regulile:

  1. Transformare în tokeni. Brandul nou este vechiul brand.tokens cu tokenii kitului deasupra. Fiecare #rrggbb literal din fundalurile scenelor și din proprietățile nodurilor (inclusiv opririle de gradient) egal cu valoarea unui token vechi devine o referință de token, de exemplu { "$token": "primary" }, deci preia culoarea nouă.
  2. Aliniere (snap). Cu snap: true, orice altă culoare literală devine o referință la cel mai apropiat, perceptual, dintre cele cinci roluri de culoare (distanță OKLab). Culorile cu transparență (#rrggbbaa) rămân neschimbate și sunt numărate într-un avertisment.
  3. Fonturi după rol. Un font literal egal cu un token de font vechi devine acel token. Orice alt font literal pe un nod text, listă sau tabel devine headingFont pentru titluri (rolurile title, subtitle, heading1–heading4, column-heading, stat-value sau text de mărime 48 și peste în afara documentelor) și bodyFont în rest.
  4. Nodurile blocate (sau cele aflate într-un grup blocat) sunt sărite cu avertismentul restyle skipped locked node <id>, în afară de cazul force: true.

Aceeași operație funcționează în orice lot trimis la /ops:

{ "op": "restyle", "kitId": "<kit id>", "tokens": { "primary": "#e4002b", "headingFont": "Manrope" }, "snap": false, "fonts": true }

Verifică apoi rezultatul cu cadre statice și cu lint (contrast).

SDK-uri și unelte de agent

const kit = await codai.brandKits.create({ name: 'Acme', dtcg });
await codai.brandKits.update(kit.id, { tokens: { accent: '#ffb703' } });
const tokensFile = await codai.brandKits.exportDtcg(kit.id);
const fromLogo = await codai.brandKits.extract({ logo_asset_id: logoAssetId });
const restyled = await codai.creatives.restyle(c.id, { brand_kit_id: kit.id, snap: true });
tokens_file = client.brand_kits.export_dtcg(kit["id"])
from_logo = client.brand_kits.extract(logo_asset_id=logo_asset_id)
restyled = client.creatives.restyle(c["id"], brand_kit_id=kit["id"], snap=True)

Agenții de pe desktop, telefon și VS Code folosesc creative_brand_extract (url sau logo_asset_id), creative_brand_kit_save (fără brand_kit_id creează un kit din name + tokens sau dtcg; cu el, editează acel kit) și creative_restyle (creative_id, brand_kit_id sau tokens, snap, fonts). creative_edit acceptă și operația restyle.

Preț. Extragerea este facturată pe tokeni ca orice apel text kodwen (rând de utilizare codai::brand-kit-extract, cache de prompt activ — de obicei mult sub 0,01 $, dintr-un URL sau dintr-un logo). Crearea, editarea, exportul, listarea, citirea, ștergerea, aplicarea kiturilor și restilizarea sunt gratuite.

Subtitrări

POST /v1/studio/creatives/{id}/captions adaugă subtitrări automate unei creații video: transcrie un clip de voce cu momentul fiecărui cuvânt, corectează ortografia după scenariu când narațiunea este a ta și inserează un nod captions stilizat în zona sigură a formatului implicit (deasupra interfeței platformei când formatul este legat de o platformă, vezi Platforme).

curl https://ai.codai.ro/v1/studio/creatives/$ID/captions \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "preset": "karaoke-pop" }'
CâmpSemnificație
clip_idClipul audio de subtitrat (implicit: primul clip de voce)
presetbold-pill (implicit), clean-outline, karaoke-pop sau minimal
node_idId-ul nodului de subtitrare (implicit captions, apoi captions-2…); un nod de subtitrare existent cu acest id este înlocuit
scriptTextul exact al narațiunii, cel mult 20 000 de caractere (implicit: textul din care a fost generat asset-ul de voce)
languageLimba transcrierii (implicit: locale al documentului)
retranscribetrue ignoră cuvintele din cache și transcrie din nou
base_seq / forceCa la /ops (implicit: capul)

Momentele cuvintelor. Sunetul clipului (un asset speech, music sau video al tău, gata procesat, de cel mult 25 MB) este transcris de Whisper, cu câte un marcaj de timp pentru fiecare cuvânt. Cuvintele sunt relative la clip: se scade inMs-ul lui, iar cuvintele din afara intervalului tăiat al clipului sunt eliminate.

Corectarea după scenariu. Când clipul este narațiunea ta — vocea generată în Studio își păstrează textul sursă — sau când trimiți script, transcrierea este aliniată cuvânt cu cuvânt cu scenariul: momentele vin din transcriere, ortografia (nume, diacritice, punctuație) din scenariu. Când scenariul nu se potrivește cu ce s-a spus, transcrierea rămâne neschimbată (corrected: false).

Cuvinte în cache. Cuvintele sunt salvate pe asset-ul de voce (meta.caption_words), așa că o nouă subtitrare a aceluiași clip — alt preset, altă creație care folosește același asset, un node_id nou — le refolosește gratuit (cached: true). Trimite retranscribe: true ca să forțezi o transcriere nouă.

Editarea trece pe aceeași cale ca POST …/ops (un insertNode în scena care conține începutul clipului sau un setProps pe nodul existent) și răspunde 200 cu corpul de la /ops, plus câmpurile { "node_id", "words", "cached", "corrected", "cost_micro_usd" }.

Preseturi. Mărimile sunt o fracțiune din latura scurtă a formatului:

PresetStilMărimea textuluiCuvinte pe rândCulori
bold-pillFundal rotunjit sub text5,5 %4Alb
clean-outlineText cu contur5 %6Alb
karaoke-popEvidențiere cuvânt cu cuvânt6 %3Alb, evidențiere cu tokenul accent (sau #ffd60a)
minimalText cu contur4 %8Alb

Schimbarea ulterioară a presetului este un singur setProps pe nodul de subtitrare (stil, cuvinte pe rând, mărime, culori).

Export SRT și VTT. GET /v1/studio/creatives/{id}/captions.srt și GET …/captions.vtt descarcă subtitrările pe cronologia documentului (SubRip text/plain sau WebVTT text/vtt, content-disposition: attachment), rând cu rând, exact cum le arată videoclipul. node_id alege un singur nod de subtitrare (implicit: toate, combinate în timp), iar seq o versiune (implicit: capul). Fără subtitrări răspunsul este 404 No captions.. Exportul este gratuit.

Erori. 400 unsupported_kind (nu este o creație video), 400 unsupported_media (sunet de peste 25 MB), 404 Clip not found., 404 Asset not found., 403 residency_unavailable (cu rezidența datelor în UE transcrierea nu este disponibilă), 503 transcription_unavailable.

Preț. O transcriere nouă costă aproximativ 0,006 $ pe minut de sunet al clipului (rotunjit în sus la minut, rând de utilizare codai::captions, același tarif ca transcrierea prin /v1/audio). Cuvintele din cache, corectarea după scenariu, preseturile și exportul SRT/VTT sunt gratuite.

Platforme

Un preset de platformă înseamnă o dimensiune de ieșire plus partea din cadru acoperită de interfața platformei (bara de descriere, butoanele de acțiune, bara de progres). POST /v1/studio/creatives/{id}/adapt adaptează o creație video sau imagine la una sau mai multe platforme, într-o singură versiune:

curl https://ai.codai.ro/v1/studio/creatives/$ID/adapt \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "platforms": ["tiktok", "feed-portrait", "youtube"] }'

Zona sigură este partea din fiecare latură lăsată liberă de text; mărimea minimă a textului este în pixeli de ieșire:

PlatformăEtichetăDimensiuneZonă sigură (sus / dreapta / jos / stânga)Text minim
tiktokTikTok1080×19208 % / 13 % / 20 % / 5 %36 px
reelsInstagram Reels1080×19207 % / 10 % / 18 % / 5 %36 px
shortsYouTube Shorts1080×19207 % / 12 % / 17 % / 5 %36 px
feed-squareFeed 1:11080×10805 % pe fiecare latură32 px
feed-portraitFeed 4:51080×13505 % pe fiecare latură32 px
youtubeYouTube 16:91920×10805 % pe fiecare latură28 px

Zonele sunt aproximări intenționat prudente ale ghidurilor publicate de platforme, care se schimbă des.

CâmpSemnificație
platforms1–6 id-uri de platformă distincte (obligatoriu)
focalnone (implicit) sau vision — vezi mai jos
base_seq / forceCa la /ops (implicit: capul); force adaptează și nodurile blocate

Gateway-ul trimite câte o operație adaptFormat pentru fiecare platformă, pe aceeași cale de editare ca /ops. Pentru fiecare platformă:

  1. Un format de aceeași dimensiune este refolosit; altfel se adaugă unul cu id-ul de aspect al presetului (9x16, 1x1, 4x5, 16x9, cu sufixul -2, -3… când id-ul aparține unui format de altă dimensiune), iar legătura se notează în meta.platforms[<id format>].
  2. Imaginile și videoclipurile pe tot cadrul rămân pe tot cadrul; textele, subtitrările și grupurile sunt mutate și încadrate în zona sigură a platformei (micșorate la lățimea zonei când sunt mai late). Formatul implicit se modifică direct pe nod, orice alt format prin suprascrieri per format (overrides).
  3. Textul sub minimul platformei primește o mărime mai mare doar în acel format.
  4. Nodurile blocate sunt sărite cu un avertisment (în afară de cazul force: true).

Aceeași operație funcționează în orice lot trimis la /ops:

{ "op": "adaptFormat", "platform": "reels", "focal": { "hero": { "x": 0.62, "y": 0.4 } } }

focal (opțional) fixează punctul de interes (0–1 pe fiecare axă) al nodurilor imagine și video în acel format, ca decuparea să îl păstreze în cadru. Fără el, reîncadrarea este deterministă și centrată.

Punct focal inteligent (vision). "focal": "vision" este o opțiune la cerere, facturată: o tură de vision analizează imaginile de nivel superior care acoperă tot cadrul (asset-uri PNG, JPEG sau WebP ale tale, gata procesate, de cel mult 5 MB, maximum 6) și întoarce un punct focal pentru fiecare, folosit apoi de toate formatele noi. Rulează o singură dată pe apel, pentru toate platformele, cu o limită de 60 s; dacă eșuează, adaptarea are loc oricum, cu decupări centrate și avertismentul vision focal unavailable.

Răspunde 200 cu corpul de la /ops, plus câmpurile { "formats": [{ "platform", "format_id", "width", "height" }], "focal": { "<id nod>": { "x", "y" } } | null }. Erori: 400 unsupported_kind (doar creații video și imagine), 400 invalid_request.

Randări în 4:5. Randările și cadrele exacte acceptă formatul feed-portrait (1080×1350) alături de 1920×1080, 1080×1920 și 1080×1080, așa că o creație adaptată pentru un feed 4:5 se randează direct.

Preț. Adaptarea este gratuită. Doar focal: "vision" este facturat pe tokeni ca orice apel vision kodwen (rând de utilizare codai::adapt-focal, cache de prompt activ — de obicei mult sub 0,01 $ pe apel).

Lint și corecturi

POST /v1/studio/creatives/{id}/lint rulează verificări statice, fără randare. Erorile blochează randarea; avertismentele îți sunt arătate ție și agenților. Fiecare problemă are forma { "code", "severity", "message", "target", "value"?, "format"?, "fixable"? } — format numește formatul la care se referă o problemă specifică unui format, iar fixable: true marchează problemele pe care corectura automată gratuită le poate rezolva — iar răspunsul adaugă fixable, numărul acestor probleme.

CodSeveritateCorectabilCe înseamnă
low_contrasteroarenuContrastul textului sub pragul WCAG (4,5:1 sau 3:1 pentru text mare)
outside_safe_areaavertismentnuText sau subtitrări în afara zonei sigure (zona platformei când formatul implicit este legat de o platformă)
empty_textavertismentnuUn nod text fără text
captions_no_wordseroarenuUn nod de subtitrare fără cuvinte
captions_no_clipavertismentnuSubtitrări care trimit la un clip audio lipsă
missing_asseteroarenuUn id de asset neînregistrat în document
scene_zero_durationeroare / avertismentnuO scenă de 0 ms (eroare) sau un nod care nu apare niciodată (avertisment)
too_longeroarenuDurata totală depășește 180 s
chart_no_svgavertismentnuUn grafic fără SVG randat în prealabil
unresolved_refavertismentnuO referință la o variabilă sau la un token necunoscut
empty_listavertismentnuO listă fără text în elemente
off_brand_coloravertismentdaO culoare literală din afara paletei de brand (nuanțele neutre de gri nu sunt semnalate niciodată)
outside_platform_safe_zoneavertismentdaText sub interfața unei platforme, într-un format legat de acea platformă
text_too_smallavertismentdaText sub pragul de lizibilitate al unui format (value = px)
missing_altavertismentnuO imagine dintr-o prezentare sau dintr-un document fără text alternativ
missing_captionsavertismentnuUn clip de voce fără nod de subtitrare — folosește Subtitrări
reading_speedavertismentnuUn rând de subtitrare mai rapid de 20 de caractere pe secundă (value = caractere pe secundă)
flashingeroare / avertismentda / nuO animație care pâlpâie de peste 3 ori pe secundă (eroare, corectabilă) sau cel puțin 4 scene consecutive mai scurte de 334 ms (avertisment)
motion_off_frameavertismentnuUn text, o subtitrare sau o listă al cărei conținut stă parțial în afara cadrului ori își termină mișcarea tăiat de marginea cadrului (value = px în afară)
motion_overlapavertismentnuDouă texte din aceeași scenă, pe ecran împreună mai mult de 0,5 s, ale căror casete se suprapun cu peste 15 % din cea mai mică (value = procent)
text_reading_timeavertismentnuUn text sau o listă de cel puțin 3 cuvinte care poate fi citit(ă) mai puțin de 0,5 s + 1 s la 4 cuvinte (value = ms afișate)
motion_repeatavertismentnuTextul principal intră cu aceeași animație în cel puțin 4 scene la rând sau imaginea de fundal folosește aceeași mișcare de cameră în cel puțin 2 scene la rând (value = lungimea seriei)
narration_overrunavertismentnuUn clip de voce care se termină la peste 50 ms după sfârșitul scenei în care începe (value = ms depășiți)

Pragul de lizibilitate este minimul platformei (vezi Platforme), altfel 2,5 % din latura scurtă a formatului.

Corecturi automate. POST /v1/studio/creatives/{id}/lint/fix transformă problemele corectabile într-un singur lot de operații, aplicat ca la /ops:

curl https://ai.codai.ro/v1/studio/creatives/$ID/lint/fix \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "codes": ["off_brand_color", "text_too_small"] }'
CâmpSemnificație
codesCorectează doar aceste coduri (implicit: toate codurile corectabile)
agenttrue adaugă o tură plătită de agent pentru problemele rămase (implicit false)
base_seq / forceCa la /ops (implicit: capul); force corectează și nodurile blocate

Corecturile deterministe sunt gratuite: culorile din afara brandului devin un { "$token" } al celei mai apropiate culori de brand, textul prea mic primește o mărime mai mare (sau un adaptFormat în formatele de platformă), textul aflat sub interfața unei platforme este readaptat cu adaptFormat, iar animațiile cu cadre cheie care pâlpâie sunt eliminate. Când nu există nimic de corectat, răspunde 200 fără să scrie o versiune (changed: []).

Cu agent: true, după lotul determinist o tură scurtă de agent pe server primește problemele rămase și schița și propune operații (text alternativ, contrast, viteză de citire…), aplicate pe aceeași cale de editare, cu o rundă de reparare. Dacă agentul eșuează primești totuși rezultatul determinist și avertismentul agent fix unavailable.

Răspunde 200 cu corpul de la /ops, plus câmpurile { "fixed": [{ "code", "target" }], "remaining": [ … ], "agent": { "ops", "cost_micro_usd" } | null } — remaining este rezultatul lint de după corectură.

Preț. Lint-ul și corecturile deterministe sunt gratuite. agent: true este facturat pe tokeni ca orice apel text kodwen (rând de utilizare codai::lint-fix, cache de prompt activ — de obicei mult sub 0,01 $).

Panoul de ajustare

Editoarele din hub, desktop și telefon arată un panou de ajustare pentru variabilele documentului (vars): câte un selector de culoare, un cursor, un comutator, o listă de opțiuni, un câmp text sau un selector de imagine pentru fiecare variabilă. Variabilele își păstrează tipul și valoarea în vars; meta.controls[<cheie variabilă>] descrie doar cum se editează fiecare.

{ "op": "setControl", "key": "accent", "control": { "kind": "select", "label": "Accent", "group": "Colours", "options": [{ "value": "#e4002b", "label": "Red" }, { "value": "#1d3557", "label": "Navy" }] } }
Tip de controlSe potrivește variabilelor de tipCâmpuri suplimentare
texttext, numbermultiline
colorcolor—
slidernumbermin și max (obligatorii, min < max), step
toggleboolean—
selecttext, number, coloroptions (obligatoriu, 1–24 elemente { "value", "label"? })
imageimage—

Orice control acceptă și label (cel mult 100 de caractere) și group (cel mult 60 de caractere, grupează panoul). setControl cu "control": null șterge controlul. Ca orice operație respinsă, un control care nu se potrivește tipului variabilei răspunde 400 cu codul invalid, iar o variabilă necunoscută 400 cu codul not_found.

GET /v1/studio/creatives/{id}/controls întoarce modelul panoului, { "head_seq", "controls": [{ "key", "label", "control", "value", "type", "locked" }] }: câte o intrare pentru fiecare variabilă, cu controlul salvat când este valid, altfel unul dedus din tip (culoare → color, boolean → toggle, imagine → image, număr → slider în jurul valorii curente, text → text). Schimbarea unei valori este o operație setVar obișnuită pe POST …/ops; editarea unui control este setControl. Citirea și editarea controalelor sunt gratuite.

SDK-uri și unelte de agent (subtitrări, platforme, lint, controale)

const cap = await codai.creatives.captions(c.id, { preset: 'bold-pill' });
const srt = await codai.creatives.captionsSrt(c.id, { node_id: cap.node_id });
const adapted = await codai.creatives.adapt(c.id, { platforms: ['tiktok', 'feed-portrait'], focal: 'vision' });
const fixed = await codai.creatives.lintFix(c.id, { agent: true });
const { controls } = await codai.creatives.controls(c.id);
cap = client.creatives.captions(c["id"], preset="bold-pill")
vtt = client.creatives.captions_vtt(c["id"])
adapted = client.creatives.adapt(c["id"], ["tiktok", "feed-portrait"], focal="vision")
fixed = client.creatives.lint_fix(c["id"], agent=True)
controls = client.creatives.controls(c["id"])["controls"]

Agenții de pe desktop, telefon și VS Code folosesc creative_captions (creative_id, clip_id, preset, script, language), creative_adapt (creative_id, platforms, focal) și creative_lint_fix (creative_id, codes, agent); creative_lint enumeră codurile noi, iar creative_edit acceptă operațiile adaptFormat și setControl.

Localizare

O creație poate avea mai multe limbi. Documentul sursă rămâne așa cum este; fiecare limbă în plus este un set de suprascrieri păstrate în document sub meta.localization, iar fiecare citire care primește un locale (previzualizare, randare, cadre statice, subtitrări, lint) le aplică pe loc. Când o limbă este gata să trăiască separat, o copiezi într-o creație de sine stătătoare.

Modelul de suprascrieri

{
  "meta": {
    "localization": {
      "source": "ro",
      "glossary": ["Kodwen", "Black Friday"],
      "locales": {
        "en": {
          "tone": "informal",
          "texts": { "title:spans.0": { "t": "Autumn sale", "h": "3fa2c9d1e0b4a7f6", "by": "agent" } },
          "audio": { "voice-1": "<id asset de voce>" },
          "captions": { "captions": [{ "text": "Autumn", "startMs": 0, "endMs": 420 }] }
        }
      }
    }
  }
}
  • source este limba documentului însuși (implicit: locale al documentului, altfel en). Limbile sunt etichete BCP-47 (ro, en, de-DE, pt-BR), normalizate la intrare (ro_ro → ro-RO).
  • texts asociază cheii unei unități de traducere traducerea ei t, amprenta h a textului sursă din care a fost făcută și by (agent sau user). Când textul sursă se schimbă ulterior, amprenta nu se mai potrivește și traducerea devine învechită — este folosită în continuare până o reîmprospătezi.
  • audio înlocuiește asset-ul unui clip de voce pentru acea limbă, iar captions înlocuiește cuvintele unui nod de subtitrare (vezi Voce și subtitrări).

Unități de traducere. Fiecare text literal din document este o unitate, adresată printr-o cheie stabilă:

Tip de unitateCheieUnde
text<id nod>:spans.<i>Un segment de text
list<id nod>:items.<i>.spans.<j>Un segment dintr-un element de listă
table, cell<id nod>:columns.<c>, <id nod>:rows.<r>.<c>, <id nod>:captionAntetele, celulele text și legenda tabelului (grilele din foi: antete și celule text; formulele, datele și numerele rămân neschimbate)
chart<id nod>:spec.title, spec.alt, spec.categories.<i>, spec.series.<i>.nameEtichetele graficului
alt, caption<id nod>:alt, <id nod>:captionTextul alternativ și legenda imaginii
html, param<id nod>:html.<i>, <id nod>:params.<cheie>Fragmentele de text ale unui bloc HTML (etichetele rămân neatinse) și parametrii lui text
captions<id nod>:wordsCuvintele subtitrării, unite prin spații
notesscene:<id scenă>:notesNotele vorbitorului
titledoc:titleTitlul creației
varvar:<cheie>O variabilă text
narrationclip:<id clip>Textul narațiunii unui clip de voce (folosit doar cu voice: true)

Textele goale, numerele simple și segmentele { "$var" } sunt sărite (variabila este propria ei unitate); un document are cel mult 2000 de unități. Unitățile text au un buget de lungime (max_chars), calculat din dimensiunea casetei și mărimea fontului în formatul implicit, astfel încât traducerile care ar depăși caseta sunt semnalate.

Citirea limbilor

GET /v1/studio/creatives/{id}/locales întoarce gratuit starea fiecărei limbi:

{
  "head_seq": 7,
  "source": "ro",
  "glossary": ["Kodwen"],
  "total": 18,
  "locales": [{ "locale": "en", "tone": "informal", "translated": 17, "missing": 1, "stale": 2, "voice": 0 }]
}

Cu ?locale=en&units=1 adaugă units (offset, limit cel mult 500, implicit 200) și units_total: câte o intrare pentru fiecare unitate, cu key, node_id, kind, text (sursa), max_chars, traducerea curentă translation (sau null), by, stale și locked. O limbă necunoscută răspunde 404 Locale not found..

Traducerea cu modelul

POST /v1/studio/creatives/{id}/localize traduce creația în cel mult 10 limbi într-un singur apel și salvează totul ca o singură versiune nouă (autor agent, intenție localize: en, de):

curl https://ai.codai.ro/v1/studio/creatives/$ID/localize \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "locales": ["en", "de"], "tone": "informal", "glossary": ["Kodwen", "Black Friday"] }'
CâmpSemnificație
locales1–10 limbi țintă distincte, niciodată limba sursă (obligatoriu)
toneformal (adresare politicoasă, de exemplu dumneavoastră, Sie) sau informal (tu, du); salvat pentru fiecare limbă
glossaryCel mult 100 de termeni (fiecare de cel mult 100 de caractere) păstrați exact cum sunt scriși; salvat în document
only_missingtrue (implicit) traduce doar unitățile fără o traducere actuală; false traduce totul din nou
voicetrue (doar video) reînregistrează și narațiunea și resincronizează subtitrările pentru fiecare limbă — vezi mai jos
base_seq / forceCa la /ops (implicit: capul); force traduce și nodurile blocate

Regulile pe care le urmează modelul: substituenții, numerele, adresele URL și emoji-urile se păstrează; termenii din glosar și numele și sloganul brandului din kitul de brand al creației sunt copiate întocmai; tonul este respectat; fiecare traducere rămâne în limita max_chars a unității (mai scurt este în regulă). Unitățile sunt trimise în tranșe (cel mult 300 de unități / 30 000 de caractere pe tură de model, trei limbi în paralel); cheile necunoscute și răspunsurile goale sunt ignorate, iar un răspuns lipsă primește o rundă de reparare.

Răspunde 200 cu corpul de la /ops, plus câmpurile { "locales": [{ "locale", "translated", "remaining", "voice_clips" }], "cost_micro_usd" }. O limbă care nu a putut fi tradusă adaugă avertismentul translation unavailable for <locale>; când toate limbile au eșuat, răspunsul este 503 localize_unavailable. Erori: 400 invalid (o limbă greșită sau limba sursă), 400 nothing_to_translate (toate unitățile sunt deja traduse și actuale).

Preț. Traducerea este facturată pe tokeni ca orice apel text kodwen (rânduri de utilizare codai::localize, cache de prompt activ — o creație obișnuită costă mult sub 0,01 $ pe limbă). Citirea limbilor, editările manuale și copiile sunt gratuite.

Voce și subtitrări

Cu voice: true, pentru fiecare clip de voce care este propria ta narațiune Studio (un asset de voce gata, care și-a păstrat textul sursă), narațiunea este tradusă odată cu textele, reînregistrată cu aceleași setări de voce în limba țintă și înlocuită doar pentru acea limbă (audio). Când un nod de subtitrare afișează acel clip, vocea nouă este transcrisă, iar subtitrările traduse sunt resincronizate (captions). Narațiunea se facturează ca orice narațiune, iar transcrierea ca la Subtitrări (rând de utilizare codai::captions). Un clip care nu poate fi reînregistrat adaugă avertismentul voice unavailable for <clip> (<locale>); textele sunt traduse oricum.

Editarea manuală a traducerilor

Editările manuale sunt operații obișnuite pe POST …/ops:

{ "op": "setLocaleText", "locale": "en", "texts": { "title:spans.0": "Autumn sale", "doc:title": null } }
  • setLocaleText setează cel mult 500 de unități ale unei limbi (null șterge o traducere, care apoi contează ca lipsă); amprenta salvată este cea a textului sursă curent, deci unitatea devine din nou actuală. by are implicit valoarea user. O cheie necunoscută dă not_found, o unitate a unui nod blocat locked (fără force), iar limba sursă invalid.
  • setLocale setează tone al unei limbi, audio (id clip → id asset; asset-ul trebuie înregistrat cu setAsset) și captions (id nod de subtitrare → cuvinte); "remove": true șterge limba.
  • setLocalization setează limba sursă source a documentului, glosarul glossary și legătura forkOf; "reset": true șterge toate limbile.

Verificări

POST …/lint adaugă trei avertismente pentru creațiile traduse, fiecare cu locale:

  • missing_translation — unul pentru fiecare limbă, value = numărul de unități fără traducere.
  • stale_translation — o unitate al cărei text sursă s-a schimbat după traducere (cel mult 50 pe limbă; mesajul numește cheia).
  • text_overflow_locale — corectabil: o traducere mai lungă decât permite caseta de text în formatul implicit. POST …/lint/fix setează pe acel nod autoFit: "shrink", astfel încât textul se micșorează ca să încapă în orice limbă.

Trimite { "locale": "en" } la POST …/lint ca să verifici chiar documentul tradus (contrast, zonă sigură… în engleză).

Previzualizarea, randarea și exportul unei limbi

Parametrul locale funcționează pe GET …/compiled (în interogare), POST …/render (în corp; randarea reține limba), POST …/frame (în corp), GET …/captions.srt și GET …/captions.vtt (în interogare) și POST …/lint (în corp). Limba sursă (sau lipsa lui locale) dă documentul așa cum este; o limbă necunoscută răspunde 404 Locale not found..

curl https://ai.codai.ro/v1/studio/creatives/$ID/render \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "format": "9x16", "quality": "final", "locale": "en" }'

Randările unei limbi se facturează ca orice randare.

Copierea unei limbi

POST /v1/studio/creatives/{id}/locales/{locale}/fork cu { "title"?, "seq"? } creează o creație de sine stătătoare în acea limbă, din versiunea seq (implicit: capul): traducerile sunt scrise chiar în document, suprascrierile sunt eliminate, source devine acea limbă, iar forkOf reține { creativeId, seq, locale }. Titlul implicit este "<titlu> (<locale>)". Răspunde 201 cu creația nouă, plus câmpurile { "locale", "fork_of": { "creative_id", "seq" } }. Gratuit.

SDK-uri și unelte de agent (localizare)

const status = await codai.creatives.locales(c.id);
const units = await codai.creatives.locales(c.id, { locale: 'en', units: true });
const done = await codai.creatives.localize(c.id, { locales: ['en', 'de'], tone: 'informal', glossary: ['Kodwen'] });
const html = await codai.creatives.compiled(c.id, { locale: 'en' });
const job = await codai.creatives.render(c.id, { quality: 'draft', locale: 'en' });
const fork = await codai.creatives.forkLocale(c.id, 'en', { title: 'Teaser de lansare (EN)' });
status = client.creatives.locales(c["id"])
done = client.creatives.localize(c["id"], ["en", "de"], tone="informal", glossary=["Kodwen"])
issues = client.creatives.lint(c["id"], locale="en")
fork = client.creatives.fork_locale(c["id"], "en", title="Teaser de lansare (EN)")

Agenții de pe desktop, telefon și VS Code folosesc creative_localize (creative_id, locales, tone, glossary, voice, only_missing) și creative_locale_fork (creative_id, locale, title); creative_lint primește locale și enumeră codurile de traducere, iar creative_edit acceptă operațiile setLocaleText, setLocale și setLocalization. Ca să umpli câte o creație pentru fiecare rând dintr-un tabel — opțional într-o altă limbă pe fiecare rând — vezi Loturi.

Șabloane

Salvează oricare dintre creațiile tale ca șablon privat, reutilizabil: alegi nodurile text și imagine care se schimbă de la o folosire la alta, iar fiecare devine o variabilă a șablonului. POST /v1/studio/creatives/{id}/template ia creația de la seq (implicit: capul):

curl https://ai.codai.ro/v1/studio/creatives/$CREATIVE_ID/template \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Promo 9:16", "vars": [
        { "node_id": "n_title", "prop": "text", "key": "headline", "label": "Titlu" },
        { "node_id": "n_photo", "prop": "image", "key": "photo" } ] }'
  • prop: "text" pe un nod text: fragmentele lui devin unul singur, cu stilul primului fragment; textul curent devine valoarea implicită.
  • prop: "image" pe un nod image cu un asset literal: id-ul asset-ului curent devine valoarea implicită (asset-ul rămâne în document).
  • key este o literă urmată de cel mult 63 de litere, cifre sau _, unică în șablon; label are implicit numele nodului. Între 1 și 50 de variabile.

Răspunde 201 cu șablonul: { "id", "object": "studio.template", "title", "kind", "source_creative_id", "source_seq", "thumbnail_url", "created_at", "doc" }, unde câmpul vars_schema descrie fiecare variabilă, de exemplu { "headline": { "type": "text", "label", "default", "node_id", "prop" } }. Șablonul își păstrează propria copie a documentului; editările ulterioare ale creației nu îl schimbă. Erori: 400 node_not_found, 400 invalid_pick (tip de nod greșit, deja variabilă sau o imagine fără asset literal), 400 duplicate_key, 404 (creație sau seq necunoscute).

GET /v1/studio/templates?kind=video listează șabloanele tale (cele mai noi primele, fără doc), GET …/{id} citește unul cu documentul lui, DELETE …/{id} îl șterge (creațiile făcute din el nu sunt afectate).

Creare dintr-un șablon — POST /v1/studio/creatives cu template și valorile în vars (după cheie; cheile omise își păstrează valoarea implicită):

curl https://ai.codai.ro/v1/studio/creatives \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "kind": "video", "template": "<id șablon>", "vars": { "headline": "Reduceri de toamnă", "photo": "<id asset imagine>" } }'

Versiunea 1 este documentul șablonului cu valorile aplicate; title are implicit titlul șablonului, iar brand_kit_id funcționează în continuare (se aplică după variabile). O valoare text are cel mult 5000 de caractere, o valoare image trebuie să fie id-ul unui asset imagine al tău. Erori: 400 invalid_request (template împreună cu doc sau vars fără template), 400 kind_mismatch, 400 unknown_var, 400 invalid_var_value, 404 (șablon necunoscut; un asset imagine care nu este al tău dă 404 asset_not_found).

const tpl = await codai.creatives.saveTemplate(c.id, {
  title: 'Promo 9:16',
  vars: [{ node_id: 'n_title', prop: 'text', key: 'headline', label: 'Titlu' }],
});
const { data } = await codai.templates.list({ kind: 'video' });
const fromTpl = await codai.creatives.create({ kind: 'video', template: tpl.id, vars: { headline: 'Reduceri de toamnă' } });
await codai.templates.delete(tpl.id);
tpl = client.creatives.save_template(
    c["id"], vars=[{"node_id": "n_title", "prop": "text", "key": "headline", "label": "Titlu"}], title="Promo 9:16"
)
templates = client.templates.list(kind="video")["data"]
from_tpl = client.creatives.create({"kind": "video", "template": tpl["id"], "vars": {"headline": "Reduceri de toamnă"}})
client.templates.delete(tpl["id"])

Preț. Salvarea, listarea, citirea, ștergerea și folosirea șabloanelor sunt gratuite.

Export FCPXML

Predă o creație video unui editor desktop: GET /v1/studio/creatives/{id}/export.fcpxml pune în coadă o arhivă zip cu un proiect FCPXML 1.10 și toate fișierele la care trimite:

  • project.fcpxml — o secvență cu fps-ul (24, 25 sau 30) și dimensiunea documentului; câte un clip pentru fiecare scenă, în ordinea redării, numit după scenă, cu un marcaj care poartă id-ul scenei; vocea, muzica și efectele sonore sunt clipuri audio conectate, pe benzi proprii (voce -1, muzică -2, efecte -3), cu amplificarea lor.
  • media/seg-NNNN.mp4 — segmentele video pe scene, randate la calitate finală, fără sunet.
  • media/audio-* — fișierele de voce, muzică și efecte sonore folosite de cronologie.
  • project.xml — aceeași cronologie ca Final Cut Pro 7 XML (xmeml v5) pentru Adobe Premiere Pro: o pistă video cu bucățile de scenă și marcajele lor, piste audio pe roluri (dialog, muzică, efecte), cu amplificarea lor.
  • project.edl — lista de tăieturi video ca EDL CMX 3600 (timecode non-drop de la 00:00:00:00, câte un eveniment pe bucată de scenă), pentru orice editor; sunetul rămâne în XML / FCPXML.
  • captions.srt — subtitrările creației în format SubRip, pe aceeași cronologie, doar când creația are subtitrări.
  • credits.txt — mențiunile pentru materialele de stoc, doar când sunt folosite.

Dezarhivează-l și păstrează dosarul întreg (căile către media sunt relative). Deschide project.fcpxml în Final Cut Pro (File → Import → XML) sau în DaVinci Resolve (File → Import → Timeline); în Premiere Pro folosește File → Import și alege project.xml; orice alt editor importă project.edl, apoi adaugă captions.srt ca pistă de subtitrări. CapCut pentru desktop poate importa fișierele media. Răspunsul StudioExport listează fișierele de proiect în files.

curl "https://ai.codai.ro/v1/studio/creatives/$ID/export.fcpxml?format=16x9" \
  -H "Authorization: Bearer $CODAI_API_KEY"
# 202 { "id": "<id export>", "object": "studio.export", "status": "queued", ... }

curl https://ai.codai.ro/v1/studio/exports/$EXPORT_ID \
  -H "Authorization: Bearer $CODAI_API_KEY"

Implicit, format este primul format al documentului de 1920×1080, 1080×1920 sau 1080×1080, iar seq este versiunea curentă. Corpul răspunsului 202 este un StudioExport cu câmpurile id, object (studio.export), status, creative_id, seq, format, fps, filename, url, expires_at, bytes, rendered_seconds, cost_micro_usd, warnings, error și created_at. Interoghează GET /v1/studio/exports/{id} (răspunde mereu 200 cât timp exportul e al tău) până când status devine succeeded — atunci url este un link de descărcare semnat, valabil o oră (fiecare interogare semnează unul nou) — sau failed, cu error.code și error.message. Asset-urile audio lipsă sau care nu sunt gata sunt sărite și apar în warnings. Erori: 400 unsupported_kind (creația nu e video), 400 unsupported_format, 400 too_long (peste 180 s), 404 (creație sau seq necunoscut), 429 too_many_renders (exporturile se numără împreună cu randările la limita de 3 lucrări în curs).

const queued = await codai.creatives.exportFcpxml(c.id, { format: '16x9' });
const done = await codai.exports.wait(queued.id); // interoghează la 3 s, cel mult 15 min
if (done.status === 'succeeded') console.log(done.filename, done.url, done.expires_at, done.cost_micro_usd);
else console.error(done.error?.code, done.error?.message);
queued = client.creatives.export_fcpxml(c["id"], format="16x9")
done = client.exports.wait(queued["id"])  # interoghează la 3 s, cel mult 15 min
if done["status"] == "succeeded":
    print(done["filename"], done["url"], done["expires_at"], done["cost_micro_usd"])

Preț. Segmentele sunt aceleași pe care le folosesc randările finale: scenele deja randate (de un export anterior sau de o randare finală) se refolosesc gratuit. Se facturează doar secundele randate acum, la prețul randării finale, o singură dată, după ce exportul a reușit (rendered_seconds, cost_micro_usd); un export făcut numai din segmente din cache nu costă nimic. Interogarea este gratuită.

Export Lottie

Folosește o creație video pe web sau într-o aplicație ca animație vectorială ușoară: GET /v1/studio/creatives/{id}/export.lottie o exportă în format Lottie, sincron și gratuit — fără randare, fără coadă.

  • container=json (implicit) — un singur JSON Bodymovin cu imaginile încorporate ca data URL. Rulează în lottie-web, în playerele Lottie pentru Android și iOS și în importatoarele pentru After Effects.
  • container=dotlottie — o arhivă dotLottie v2 (.lottie: manifest.json, a/<format>.json, i/<asset>.<ext>), mai mică, pentru playerele dotLottie.
  • formats=all — toate formatele documentului (dotLottie: câte o animație pe format; JSON: o arhivă zip cu fișierele JSON). Fără el, format alege unul (implicit formatul implicit al documentului); dacă le trimiți pe amândouă primești 400 invalid_request.
  • seq — versiunea (implicit cea curentă).
# fișierul însuși, direct pe disc
curl -OJ "https://ai.codai.ro/v1/studio/creatives/$ID/export.lottie?format=16x9&download=1" \
  -H "Authorization: Bearer $CODAI_API_KEY"

# toate formatele într-o singură arhivă dotLottie, ca obiect export cu URL semnat
curl "https://ai.codai.ro/v1/studio/creatives/$ID/export.lottie?container=dotlottie&formats=all" \
  -H "Authorization: Bearer $CODAI_API_KEY"
# 200 { "id": "<id export>", "object": "studio.export", "kind": "lottie", "container": "dotlottie",
#       "status": "succeeded", "format": "all", "url": "https://…", "cost_micro_usd": "0", "warnings": [...] }

Fără download, răspunsul este un StudioExport (același obiect ca la exportul FCPXML), deja succeeded: url este un link de descărcare semnat, valabil o oră, rendered_seconds este 0, iar cost_micro_usd 0; îl poți citi din nou cu GET /v1/studio/exports/{id}. Cu download=1, răspunsul este fișierul însuși (application/json sau application/zip, content-disposition: attachment) și nu se salvează nimic. Numele fișierelor: <slug>-<format>.lottie.json (JSON), <slug>-<format|all>.lottie (dotLottie), <slug>-lottie.zip (JSON, toate formatele).

Fidelitate. Lottie este un format vectorial, așa că nu tot ce poate arăta un video încape în el. warnings enumeră ce a fost aproximat sau lăsat deoparte:

Se exportăforme (dreptunghi, elipsă, linie), imagini (cover / contain, punct focal, decupare, colțuri rotunjite), text (fonturi de sistem), liste (ca text), subtitrări, grupuri, fundaluri (culoare, gradient liniar, imagine), toate presetările de animație și animațiile cu cadre-cheie, tranzițiile dintre scene, câte un marcaj pe scenă
Se aproximeazăapariția cuvânt cu cuvânt, evidențierea progresivă, stilul subtitrărilor, stilurile de text mixte în același nod, animația opacității unui grup
Nu se exportănodurile video, grafic, bloc HTML, tabel, grilă de celule și audio; imaginile peste bugetul de încorporare de 8 MB; corecția de culoare (grade) a unei imagini (avertismentul grade_not_exported)

Proveniență. Fiecare fișier spune că a fost făcut cu AI: meta din JSON conține g: "Kodwen Studio", tc: "AI-generated", ai_generated: true și custom: { ai_generated: true, generator: "codai-studio", doc_hash }, iar fiecare răspuns 2xx are antetele x-codai-ai-generated: true și x-codai-generator: codai/codai-studio. Erori: 400 unsupported_kind (creația nu e video), 400 invalid_request, 404 (creație sau seq necunoscut), 413 too_large (fișierul ar depăși 20 MB).

const e = await codai.creatives.exportLottie(c.id, { container: 'dotlottie', formats: 'all' });
console.log(e.filename, e.url, e.warnings);
const file = await codai.creatives.downloadLottie(c.id, { format: '16x9' }); // { bytes, filename, contentType }
e = client.creatives.export_lottie(c["id"], container="dotlottie", formats="all")
print(e["filename"], e["url"], e["warnings"])
f = client.creatives.download_lottie(c["id"], format="16x9")  # {"bytes", "filename", "content_type"}

Preț. Gratuit: fără randare, fără rând de consum, fără plafon de cheltuieli.

Documente (prezentare, document, foaie de calcul)

Documentele Studio — prezentările (PPTX), documentele (DOCX) și foile de calcul (XLSX) — pot fi editate și ele ca o creație. Creația păstrează același conținut ca spec-ul documentului, sub formă de noduri cu tip pe care le editezi prin operații, le previzualizezi live și le generezi înapoi într-un fișier.

Reprezentarea.

  • Prezentare — câte o scenă pe slide (s1, s2, …), cu aranjarea slide-ului în layout (title, section, bullets, two_column, image, chart, table, quote, stats, timeline, closing) și notele pentru vorbitor în notes, pe scenă. Nodurile sunt așezate pe formatul 16:9 (1920×1080) sau 4:3 (1440×1080), cu id-uri precum s3-title, s3-bullets, s3-left-heading sau s3-stat2-value. Listele cu marcatori devin un nod list, tabelele un nod table, graficele un nod chart, cele două coloane câte un group, iar fiecare indicator și fiecare punct din cronologie câte un group.
  • Document — câte o scenă pe pagină (p1, p2, …; o pagină nouă oriunde spec-ul avea un page_break). Fiecare pagină conține un singur group cu rolul flow, ai cărui copii sunt blocurile în ordine (b0, b1, …): titluri, paragrafe, liste, tabele, imagini, grafice și citate.
  • Foaie de calcul — câte o scenă pe foaie (sh1, …, cu numele foii), cu un nod cell_grid (columns, rows, freezeHeader, autofilter, totals, conditional) plus câte un nod chart pentru fiecare grafic, cu celula lui de ancorare în anchor.

Fiecare nod are un rol semantic în role: title, subtitle, heading1–heading4, paragraph, quote, attribution, caption, contact, footer, flow, column-left, column-right, column-heading, stat, stat-value, stat-label, stat-delta, timeline-item, timeline-date, timeline-label și timeline-detail. Generarea citește doar rolurile, tipurile de nod și aranjările scenelor — niciodată id-urile —, așa că poți redenumi, muta și restiliza nodurile liber. Fragmentele de text pot fi code, pot avea un link în link (https) sau o notă de subsol în footnote (DOCX); imaginile primesc alt și caption; paragrafele pot fi aliniate justify. Culorile și fonturile trimit la token-urile temei (color.bg, color.fg, color.accent, font.heading, font.body), iar meta.document păstrează setările documentului (tema, limba, raportul de aspect, subsolul, mărimea paginii, orientarea, antetul, numerotarea paginilor, cuprinsul).

Dintr-un document. POST /v1/studio/documents/{id}/creative (fără corp) convertește unul dintre documentele tale. Este idempotent: primul apel creează creația (201, existing: false) și o leagă de document (documentul raportează apoi creative_id); apelurile următoare întorc aceeași creație (200, existing: true). Corpul răspunsului este creația obișnuită plus document_id și existing. Erori: 404 (document necunoscut), 409 document_spec_missing (nu există spec stocat), 400 invalid_spec, 413 too_large. Gratuit.

curl -X POST https://ai.codai.ro/v1/studio/documents/$DOC_ID/creative \
  -H "Authorization: Bearer $CODAI_API_KEY"
# 201 { "id": "<id creație>", "object": "studio.creative", "kind": "deck", "head_seq": 1, ..., "document_id": "…", "existing": false }

Editare și previzualizare. Folosești outline, find, subtree și operațiile ca la orice creație. GET …/compiled previzualizează acum live și creațiile de tip prezentare, document și foaie de calcul: slide-urile, paginile sau foile apar una sub alta într-o singură pagină HTML (duration_ms: 0, plus kind în răspuns), iar fiecare scenă și fiecare nod pot fi adresate prin id, deci un clic în previzualizare selectează nodul.

Generare. POST /v1/studio/creatives/{id}/build cu { "seq"?, "preview"?, "filename"? } convertește creația (implicit versiunea curentă) înapoi într-un spec de document și generează fișierul PPTX, DOCX sau XLSX pe aceeași cale ca POST /v1/studio/documents. Fiecare generare este o versiune nouă a aceluiași document (based_on = cea mai nouă generare, deci version crește) și reține creative_id și creative_seq. Nodurile pe care un document nu le poate păstra (forme libere, blocuri HTML, videoclipuri, texte suplimentare) sunt sărite și apar în warnings; o scenă adăugată fără layout primește unul dedus din nodurile ei. Erori: 400 not_supported_for_kind (creații video sau imagine), 400 invalid_spec (spec-ul convertit nu trece validarea) și orice eroare de generare a POST /v1/studio/documents.

curl https://ai.codai.ro/v1/studio/creatives/$ID/build \
  -H "Authorization: Bearer $CODAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "preview": true }'
# 200 { "object": "studio.creative_build", "creative_id": "…", "seq": 4, "warnings": [], "document": { "version": 2, "file": { "url": "…" }, ... } }
const c = await codai.creatives.fromDocument(docId); // c.existing spune dacă era deja legată
const { ids } = await codai.creatives.find(c.id, { type: 'list' }); // listele cu marcatori
await codai.creatives.edit(c.id, {
  baseSeq: c.head_seq,
  ops: [{ op: 'updateScene', target: 's2', patch: { notes: 'Începe cu marja din T3.' } }],
});
const built = await codai.creatives.build(c.id, { preview: true });
console.log(built.document.version, built.document.file.url, built.warnings);
c = client.creatives.from_document(doc_id)
client.creatives.edit(
    c["id"],
    base_seq=c["head_seq"],
    ops=[{"op": "updateScene", "target": "s2", "patch": {"notes": "Începe cu marja din T3."}}],
)
built = client.creatives.build(c["id"], preview=True)
print(built["document"]["version"], built["document"]["file"]["url"], built["warnings"])

Preț. Conversia, editarea, previzualizarea (compiled) și generarea sunt gratuite. Costă doar preview: true: previzualizarea documentului se facturează ca POST /v1/studio/documents/{id}/previews (0,002 $ pe pagină, minimum 0,01 $).

Producții (brief → rezultat gata)

POST /v1/studio/productions transformă un brief într-un rezultat gata, pe server. kind alege ce se construiește (implicit video): un singur apel de plan limitat (codai::production-storyboard), imaginile sau muzica necesare sub o singură rezervare din portofel, o creație editabilă, apoi rezultatul din generatoarele obișnuite. Interoghezi GET /v1/studio/productions/{id} până când status devine succeeded; producțiile non-video întorc kind, output_asset_id și outputs (active Studio cu un role, url semnat pentru 1 h). starter, voice, narration, quality, music, aspect_ratio și duration_s se aplică doar la video (duration_s, 15–60, este durata țintă în secunde: storyboard-ul și vocea sunt gândite pentru ea, iar scenele se lungesc până o ating; fără ea un video durează circa 15–30 s); audio folosește Lyria 3 Clip și nu este disponibil pentru cheile cu rezidența datelor în UE. Bugetul (implicit 3 $, maxim 20 $) plafonează toată producția.

kindCe conține planulRezultat (outputs[].role → format)C2PA
videoStoryboard pe un șablon (texte, o imagine, o replică de voce, o mișcare de cameră și o tranziție pe scenă)Randare MP4 (render_id) la calitatea cerută în quality, verificare vizuală + o reparareDa
imageTitlu, subtitlu, prompt de imagine, format (1:1, 4:5 sau 9:16)image → PNGDa
deckTitlu + 4–8 slide-uri (marcatori, imagine opțională), 16:9document → PPTXNu (marcaj în proprietățile documentului)
docTitlu + 4–8 secțiuni (paragraf, marcatori, imagine opțională), A4document → DOCXNu (marcaj în proprietățile documentului)
sheetTitlu + coloane + 5–20 rânduri, o singură foaiedocument → XLSXNu (marcaj în proprietățile documentului)
carousel4–8 slide-uri (titlu, text, imagine opțională), 4:5pages → ZIP cu PNG-uri, cover → PNGCoperta da; ZIP-ul nu (XMP în fiecare PNG)
printAfiș sau flyer: titlu, text, prompt de imagine, A4 sau A5pdf → PDFNu (marcaj în PDF Info)
audioJingle sau cântec: titlu, gen, prompt, versuri opționale, ≤ 60 saudio → MP3Da

Video (kind: "video")

Apelul de storyboard scrie textele, câte un prompt de imagine și câte o replică de voce pe scenă. Tot el alege, pentru fiecare scenă, o mișcare de cameră pentru imaginea de fundal (zoom-in, zoom-out, pan-left, pan-right sau static) și tranziția de intrare în scenă (orice tip de tranziție între scene). Fiecare scenă își primește imaginea și replica, sub voce se adaugă un fundal muzical, rezultatul se salvează ca o creație editabilă și se pune în coadă o randare. Când randarea e gata, verificarea vizuală poate corecta textul o dată; randarea de reparare folosește același quality.

CâmpSemnificație
qualityfinal (implicit): rezoluție completă la 30 fps. draft: 540p la 15 fps, la jumătate din prețul randării
musictrue (implicit): adaugă un fundal muzical instrumental (codai-music), generat dintr-un prompt de muzică scris de storyboard și așezat sub voce, la un nivel mai scăzut. false: fără muzică

Fundalul muzical este omis, cu o intrare în warnings, acolo unde muzica nu e disponibilă (gateway-urile cu rezidența datelor în UE; cheile care nu au voie să folosească codai-music) sau când generarea lui eșuează; producția reușește oricum. Se facturează la prețul obișnuit codai-music, în limita bugetului producției. Răspunsul unei producții video conține quality și music (true când s-a adăugat efectiv un fundal muzical).

curl https://ai.codai.ro/v1/studio/productions \
  -H "Authorization: Bearer $CODAI_API_KEY" -H "Idempotency-Key: promo-001" \
  -H "Content-Type: application/json" \
  -d '{"brief":"Promo de 20 s pentru reducerea de toamnă: 30% la toată cafeaua boabe, comandă online","language":"ro",
       "aspect_ratio":"9:16","quality":"final","music":true}'
# 202 { "id": "…", "object": "studio.production", "status": "running", "phase": "render",
#       "quality": "final", "music": true, "warnings": [], ... }

SDK-uri: productions.create({ brief, quality: 'draft', music: false }) (Python productions.create(brief, quality="draft", music=False)); unealta de agent creative_produce (quality, music).

Verificare înainte de randarea finală (review)

review: true adaugă o oprire înainte de randarea finală. Producția scrie storyboard-ul, face imaginile, vocea și muzica, randează o previzualizare și apoi așteaptă în etapa review (status rămâne running). Fără review nu se schimbă nimic: producția randează imediat, ca până acum.

curl https://ai.codai.ro/v1/studio/productions \
  -H "Authorization: Bearer $CODAI_API_KEY" -H "Idempotency-Key: promo-002" \
  -H "Content-Type: application/json" \
  -d '{"brief":"Promo de 20 s pentru reducerea de toamnă: 30% la toată cafeaua boabe, comandă online","language":"ro",
       "aspect_ratio":"9:16","review":true}'
# 202 { "id": "…", "status": "running", "phase": "assets",
#       "review": { "state": "preparing", "animatic_render_id": null, "stills": [], "since": null, "expires_at": null }, ... }

Interoghezi GET /v1/studio/productions/{id} până când phase devine review. Obiectul review conține atunci ce este de verificat:

CâmpSemnificație
statepreparing cât timp se fac imaginile și previzualizarea, waiting cât așteaptă răspunsul tău, apoi approved, cancelled sau expired
animatic_render_idPrevizualizarea, o randare obișnuită: o citești cu GET /v1/studio/renders/{id} pentru MP4 și imaginea de copertă. null când previzualizarea a eșuat; producția așteaptă oricum, doar cu imaginile
stillsCâte o intrare pe scenă: scene, asset_id (imaginea generată, null când a eșuat) și narration (replica de voce)
since, expires_atCând a început așteptarea și când expiră (după 7 zile)

Cât timp producția așteaptă poți edita creația ei (creative_id) ca pe oricare alta; modificările sunt randate când aprobi. Apoi dai răspunsul:

curl https://ai.codai.ro/v1/studio/productions/<id>/review \
  -H "Authorization: Bearer $CODAI_API_KEY" -H "Content-Type: application/json" \
  -d '{"decision":"approve"}'
# 200 { "id": "…", "status": "running", "phase": "render", "review": { "state": "approved", ... }, ... }
  • approve pornește randarea finală la calitatea cerută în quality, facturată ca orice randare. Verificarea de după randare rulează ca de obicei.
  • cancel încheie producția cu status: "failed" și error: "cancelled". Creația rămâne în biblioteca ta.
  • Dacă nu vine niciun răspuns în 7 zile, producția se încheie la fel, cu error: "gate_expired".
  • 402 insufficient_funds la approve înseamnă că portofelul nu acoperă randarea finală. Producția rămâne în așteptare, deci poți alimenta portofelul și aproba din nou.
  • 409 not_in_review înseamnă că producția nu așteaptă un răspuns. Dacă trimiți din nou aceeași decizie după ce a fost aplicată, primești 200.

Costuri: cât timp o producție așteaptă nu se rezervă și nu se facturează nimic. Imaginile, vocea și muzica se facturează când sunt făcute, ca la orice producție, iar previzualizarea se facturează ca o randare de probă (draft). render_id și first_render_id nu indică niciodată previzualizarea.

SDK-uri: productions.create({ brief, review: true }), apoi productions.review(id, { decision: 'approve' }) (Python productions.create(brief, review=True), apoi productions.review(id, decision="approve")). productions.wait() și productions.run() se întorc când producția ajunge să aștepte răspunsul. Unealta de agent creative_produce: review pornește o producție cu verificare, iar production_id împreună cu decision dă răspunsul.

Compoziție liberă (director)

director alege cine așază videoclipul în pagină. starter (implicit) este comportamentul descris mai sus: modelul completează unul dintre șabloanele video incluse. Cu free modelul scrie singur toată compoziția: lista de cadre, locul fiecărui text pe grilă, mărimile de literă și intrarea fiecărui text, pe același sistem de design pentru mișcare ca șabloanele. Rezultatul este o creație editabilă ca oricare alta. free nu se poate combina cu starter (id-ul unui șablon): răspunsul este 400. Răspunsul conține director.

curl https://ai.codai.ro/v1/studio/productions \
  -H "Authorization: Bearer $CODAI_API_KEY" -H "Content-Type: application/json" \
  -d '{"brief":"Promo de 20 s pentru reducerea de toamnă: 30% la toată cafeaua boabe, comandă online","language":"ro",
       "director":"free"}'
# 202 { "id": "…", "status": "running", "director": "free", "starter": null, ... }

Runde de critică (critique_rounds)

Fără acest câmp, verificarea de după randare este un singur apel vizual pe cadre fixe, care poate corecta textul o dată. critique_rounds: 1 sau 2 o înlocuiește cu o buclă de critică de cel mult atâtea runde: din randare se iau mostre (cadre fixe plus cadre de început, în care se vede mișcarea), o critică o notează după o listă scurtă de criterii scrise pentru brief, iar schimbările ei (text, așezare, sincronizare, animație) sunt aplicate și randate din nou. Fiecare rundă poate adăuga la cost o randare și un apel de model, în limita bugetului producției.

curl https://ai.codai.ro/v1/studio/productions \
  -H "Authorization: Bearer $CODAI_API_KEY" -H "Content-Type: application/json" \
  -d '{"brief":"Promo de 20 s pentru reducerea de toamnă: 30% la toată cafeaua boabe, comandă online","language":"ro",
       "critique_rounds":2}'
# mai târziu, GET /v1/studio/productions/<id>:
# { "status": "succeeded", "phase": "ready",
#   "critique": { "max_rounds": 2, "rounds": [
#     { "round": 1, "verdict": "revise", "score": 62, "issues": ["…"], "ops": 5, "reverted": false },
#     { "round": 2, "verdict": "accept", "score": 81, "issues": [], "ops": 0, "reverted": false } ] }, ... }

score merge de la 0 la 100, iar ops numără schimbările aplicate în runda respectivă. reverted: true înseamnă că schimbările rundei anterioare au înrăutățit videoclipul: au fost anulate și se livrează randarea de dinainte. qa își păstrează forma obișnuită (issues din ultima rundă, fixes ca număr total de schimbări aplicate). Scorurile sunt părerea criticii despre această randare; nu promit cum se va comporta videoclipul la public.

Toate cele trei opțiuni se aplică doar la kind: "video" (400 cu alt tip) și se pot combina. SDK-uri: productions.create({ brief, director: 'free', critique_rounds: 2 }) (Python productions.create(brief, director="free", critique_rounds=2)); unealta de agent creative_produce (director, critique_rounds).

Montaj din materialele încărcate (kind: "cut")

kind: "cut" transformă materialele tale încărcate într-un video montat. asset_ids conține 1–10 dintre activele tale gata, video, audio și imagini (cel puțin unul video sau audio, cel mult 60 de minute de material în total; imaginile devin b-roll și referințe). Etapele sunt ingest → music → plan → broll → assemble → render: fiecare sursă e analizată (transcriere, cadre, ritm, fețe), se adaugă un fundal muzical, un apel de planificare alege segmentele (cod determinist alege cadrele, niciodată în mijlocul unui cuvânt), ezitările, pauzele lungi și dublele mai slabe sunt tăiate, b-roll-ul vine din filmările tale, apoi din stoc, apoi din imagini generate (fotografiile tale ca referințe, cu o mișcare Ken Burns; Veo doar cu broll.generated: "veo"), se adaugă subtitrări și reîncadrări animate pentru fiecare format și se pune în coadă o randare de probă a formatului default_format. Rezultatul e o creație editabilă nouă cu formatele 9x16, 1x1 și 16x9 (sau cele pe care le ceri).

curl https://ai.codai.ro/v1/studio/productions \
  -H "Authorization: Bearer $CODAI_API_KEY" -H "Idempotency-Key: cut-001" \
  -H "Content-Type: application/json" \
  -d '{"kind":"cut","brief":"Teaser de vlog de 45 s pentru Instagram, păstrează gluma cu cafeaua","language":"ro",
       "asset_ids":["<id activ video>","<id activ foto>"],"target_duration_s":45,"pacing":"punchy",
       "music":{"source":"lyria"},"broll":{"own":true,"stock":true,"generated":"images"}}'
# 202 { "id": "…", "object": "studio.production", "kind": "cut", "status": "running", "phase": "ingest",
#       "ingest": { "total": 1, "done": 0, "failed": 0 }, "ingest_quote_micro_usd": "…", ... }

Interoghezi GET /v1/studio/productions/{id} (o sursă lungă poate dura aproape o oră; procesul de fundal o termină și dacă nu întreabă nimeni). Fiecare decizie automată e înregistrată cu motivul ei: GET /v1/studio/productions/{id}/cut întoarce decisions (filler, silence, retake, hook, broll, zoom, reframe, music, script_gap; multicam: angle, split, sync), grupurile de duble clusters cu fiecare variantă și cea aleasă, script_gaps (cu script) și stats. Schimbi oricare dintre ele gratuit cu o revizie — fără apel de model, nu se facturează nimic:

curl https://ai.codai.ro/v1/studio/productions/<id>/cut/revise \
  -H "Authorization: Bearer $CODAI_API_KEY" -H "Content-Type: application/json" \
  -d '{"disabled":["filler:f3","+zoom:2"],"takes":{"c1":"s7"},"pacing":"calm","base_seq":2}'
# 200 { "creative_id": "…", "seq": 3, "branch": "main", "decisions": [...], "stats": {...}, "warnings": [] }

Id-ul unei decizii în disabled o dezactivează, +<id> o reactivează (cumulativ între revizii). Versiunea nouă ajunge pe ramura montajului cât timp nimeni nu a editat creația, altfel pe o ramură nouă cut-<n>, ca modificările tale să nu fie niciodată suprascrise. Prețuri: apelul de planificare e taxat pe tokeni (codai::production-cut-plan, cache de prompt activ), fundalul Lyria Pro, imaginile b-roll și randarea de probă la prețurile obișnuite, toate plafonate de budget_micro_usd (implicit 3 $, maxim 20 $); analiza surselor neanalizate încă se facturează pe fiecare activ și e arătată dinainte în ingest_quote_micro_usd. SDK-uri: productions.create({ kind: 'cut', … }), productions.cut(id), productions.reviseCut(id, body) (Python productions.create_cut, cut, revise_cut).

Editări prin prompt

Spune în cuvinte ce vrei, în loc să alegi id-uri de decizii: prompt pe aceeași revizie. Cererile de durată, energie, subtitrări, b-roll și încadrare se aplică gratuit, fără apel de model („fă-l de 60 s”, „make it 30 s”, „mai dinamic” → stilul Fast social, „mai calm” → Minimal, „fără subtitrări”, „fără b-roll”, „pătrat” / „vertical” / „orizontal”). Cererile despre conținut — „doar partea despre preț”, „începe cu întrebarea despre preț”, „scoate introducerea” — costă un apel de model (ofertă 0,15 $, de obicei mai puțin; din alocarea planului, altfel din portofel, 402 insufficient_funds când nu ajunge), care răspunde cu propozițiile de păstrat. Nu se scrie nimic când răspunsul e inutilizabil (502 revise_failed).

curl https://ai.codai.ro/v1/studio/productions/<id>/cut/revise \
  -H "Authorization: Bearer $CODAI_API_KEY" -H "Content-Type: application/json" \
  -d '{"prompt":"doar partea despre preț, cam 45 s","base_seq":3}'
# 200 { "creative_id": "…", "seq": 4, "branch": "main", "decisions": [...], "stats": {...}, "warnings": [],
#       "prompt": { "path": "model", "understood": ["target 45 s", "story edit"],
#                   "explanation": "Am păstrat cele patru propoziții despre preț.", "unmet": null, "cost_micro_usd": 61000 } }

Aceleași modificări se pot trimite direct, mereu gratuit: target_s (15–600; se scot întâi propozițiile cele mai puțin importante — incomplete, cea mai slabă dublă, cele mai scurte, cele de la final — niciodată deschiderea sau încheierea; null revine la ținta producției), plan_delta (keep_only, drop, hook, order cu id-uri de propoziții din GET …/cut; null anulează toate editările de poveste), default_format și broll. Unealta agentului: creative_cut_revise cu prompt.

Înregistrări cu mai multe camere

Un podcast sau un interviu filmat cu mai multe camere e un singur montaj: pui fiecare cameră în asset_ids și în multicam.angles (2–4). Camera master_audio (implicit primul unghi) dă sunetul și povestea — doar ea e transcrisă și planificată; celelalte camere sunt doar imagine: un unghi încă neanalizat e analizat fără recunoașterea vorbirii, deci costă mai puțin (încarcă celelalte camere cu ingest: false ca să beneficiezi de asta; un unghi deja analizat e refolosit așa cum e). Fiecare unghi se sincronizează cu master-ul după sunet, gratuit: întâi o corelație a anvelopelor de volum pe care analiza le păstrează deja, apoi o trecere GCC-PHAT la nivel de eșantion pe audio, cu corecția derivei de ceas (camerele obișnuite derivă 10–100 ppm; o diferență 29,97/30 fps înseamnă ~1000 ppm). Montajul urmează apoi reguli simple pe replicile din master: prim-planul vorbitorului puțin înaintea primului cuvânt, niciun cadru mai scurt decât minimul ritmului, planul larg când vorbesc mai mulți deodată, ecran împărțit (stânga/dreapta în 16:9, sus/jos în 9:16) la un schimb rapid de replici, un scurt plan de reacție în monologurile lungi. Vorbitorii sunt asociați prim-planurilor după camera care îi aude cel mai tare, iar planul larg după fețele din cadru.

curl https://ai.codai.ro/v1/studio/productions \
  -H "Authorization: Bearer $CODAI_API_KEY" -H "Content-Type: application/json" \
  -d '{"kind":"cut","brief":"Clip de 60 s cu discuția despre prețuri","asset_ids":["<larg>","<cam-ana>","<cam-mihai>"],
       "multicam":{"angles":["<larg>","<cam-ana>","<cam-mihai>"],"master_audio":"<larg>"}}'

Fiecare cadru e o decizie (angle:<n>, split:<n>; dacă oprești una rămâne cadrul anterior) și fiecare sincronizare o intrare sync:<asset> cu metoda, deriva și încrederea (sub 30 % montajul avertizează sync_low_confidence:<asset>); GET …/cut listează și camerele multicam. Fixezi gratuit un cadru cu revizia "angles": {"angle:3": "<cam-ana>"} ("wide", "split" sau null ca să scoți fixarea). Când sincronizarea sau asocierea automată greșește, trimite offsets_ms (resursă → ms master minus ms unghi), speaker_angles (S1 → resursă) sau wide_angle; "split": false dezactivează ecranul împărțit. SDK-uri: multicam la crearea montajului, angles la reviseCut / revise_cut; unelte de agent creative_produce (multicam) și creative_cut_revise (angles).

Variante notate

POST /v1/studio/productions/<id>/cut/variants propune montaje alternative ale unui montaj terminat și le notează. Un apel de model propune n variante (implicit 3, maximum 5), fiecare cu alt unghi — strongest_hook (deschide cu cea mai puternică afirmație sau întrebare de sine stătătoare), story (context, idee, concluzie), punchy (cea mai scurtă versiune care încă transmite mesajul), topic (un singur subiect) — iar un al doilea apel notează fiecare variantă și montajul tău curent de la 0 la 99 pe patru axe: Hook (primele secunde îl fac pe privitor să rămână), Flow (o idee clară, fără începuturi din mijlocul propoziției, cu o concluzie), Value (util pentru publicul din brief) și Fit (durată, ritmul vorbirii și tempo pentru format). Fiecare axă combină 60 % evaluatorul cu 40 % trăsături măsurate; totalul e 0,35 Hook + 0,25 Flow + 0,25 Value + 0,15 Fit, iar lista vine de la cea mai bună, fiecare notă cu un motiv pe axă, axa cea mai slabă și un fix_hint. Volumul și controversa nu sunt niciodată răsplătite. Notele sunt predicții, nu garanții.

curl https://ai.codai.ro/v1/studio/productions/<id>/cut/variants \
  -H "Authorization: Bearer $CODAI_API_KEY" -H "Content-Type: application/json" -d '{"n":3}'
# 200 { "object": "studio.production.cut_variants", "base_seq": 4, "stale": false, "judged": true,
#       "variants": [ { "id": "v2", "angle": "strongest_hook", "title": "Întâi întrebarea despre preț", "output_ms": 42100,
#                       "plan_delta": { "keep_only": ["s7", "s8", "s12"], "hook": "s7" },
#                       "score": { "total": 81, "hook": 90, "flow": 75, "value": 80, "fit": 60, "source": "blend",
#                                  "weakest_axis": "fit", "fix_hint": "fă-l de 35 s", "reasons": { ... } } }, ... ],
#       "current": { "output_ms": 61000, "score": { "total": 58, ... } }, "cost_micro_usd": 41000, "warnings": [] }

Ambele apeluri sunt taxate pe tokeni (codai::production-cut-variants, codai::production-cut-score, cache de prompt activ), fiecare cotat și rezervat separat (întâi din alocarea planului, apoi din portofel): lipsa fondurilor înainte de primul apel e un 402 și nu rulează nimic; înainte de evaluator variantele păstrează nota măsurată gratuită (source: "heuristic", avertismentul judge_insufficient_funds). "judge": false sare peste evaluator. Fiecare variantă e un plan_delta la nivel de propoziție, asamblat gratuit, iar rezultatul se salvează pe producție: GET …/cut/variants îl citește din nou gratuit (404 no_variants înainte de primul apel; stale: true după ce montajul s-a schimbat). POST …/cut/select {variant, base_seq?} scrie o variantă ca versiune nouă, gratuit, după aceleași reguli de ramură ca o revizie; anulezi cu plan_delta: null la revizie. Ca să ridici axa cea mai slabă trimite fix_hint ca prompt la …/cut/revise. SDK-uri: productions.cutVariants(id, { n }), retrieveCutVariants(id), selectCutVariant(id, { variant }) (Python cut_variants, retrieve_cut_variants, select_cut_variant); unelte de agent creative_cut_variants și creative_cut_select.

Hook A/B

POST /v1/studio/productions/<id>/cut/hooks propune 2–3 alte propoziții din înregistrare care ar putea deschide același montaj: corpul rămâne neschimbat, se schimbă doar deschiderea. Clasamentul e gratuit și măsurat — o întrebare, o cifră sau o afirmație la început, scurtă și apropiată de brief — iar deschiderea folosită acum e mereu în listă, cu current: true. "judge": true adaugă un apel de model plătit (codai::production-cut-hooks, cotat la 60.000 µUSD, întâi din alocarea planului, apoi din portofel) care le reordonează și scrie câte un why de un rând; lipsa fondurilor păstrează clasamentul gratuit (avertismentul judge_insufficient_funds). Rezultatul se salvează: GET …/cut/hooks îl citește din nou gratuit (404 no_hooks înainte de primul apel, stale: true după ce montajul s-a schimbat). Notele sunt predicții, nu garanții.

curl https://ai.codai.ro/v1/studio/productions/<id>/cut/hooks \
  -H "Authorization: Bearer $CODAI_API_KEY" -H "Content-Type: application/json" -d '{"n":3,"judge":false}'
# 200 { "object": "studio.production.cut_hooks", "base_seq": 4, "stale": false, "judged": false,
#       "hooks": [ { "id": "h1", "sentence": "s7", "text": "De ce să plătești de două ori aceeași factură?", "duration_ms": 2100,
#                    "score": 86, "reasons": ["question", "brief"], "why": null, "current": false }, ... ],
#       "cost_micro_usd": 0, "warnings": [] }

Aplici o deschidere cu POST …/cut/revise {"hook": "s7"} (gratuit; "hook": null scoate deschiderea) sau randezi toate deschiderile pentru un test A/B cu Randează tot, mai jos.

Durate presetate

GET /v1/studio/productions/<id>/cut/lengths reasamblează gratuit montajul la 15, 30, 60 și 90 de secunde și max (180 s, limita YouTube Shorts / Instagram Reels). Se scot întâi propozițiile cele mai puțin importante; deschiderea, hook-ul și încheierea rămân. Fiecare preset are output_ms real, propozițiile păstrate, o notă măsurată 0–99, fits_material (materialul e mai scurt decât presetul), trimmed și over_caps — platformele a căror limită de durată o depășește: Shorts și Reels 3:00, X 2:20 (fără Premium), TikTok 10:00 (încărcare prin API), LinkedIn 15:00. Aceeași listă se calculează și pentru montajul curent.

curl https://ai.codai.ro/v1/studio/productions/<id>/cut/lengths -H "Authorization: Bearer $CODAI_API_KEY"
# 200 { "object": "studio.production.cut_lengths", "seq": 4,
#       "current": { "output_ms": 201000, "preset": null, "over_caps": ["shorts", "reels", "x"] },
#       "presets": [ { "id": "30", "target_s": 30, "output_ms": 29400, "sentences": 7, "score": 71,
#                      "fits_material": false, "over_caps": [], "trimmed": true }, ... ] }

Aplici unul cu POST …/cut/revise {"target_s": 30} (gratuit; max = 180).

Ghid de atenție (euristic)

GET /v1/studio/productions/<id>/cut/attention (gratuit) întoarce o curbă pe secundă, 0–1, a montajului curent, construită doar din semnale măsurate — ritmul vorbirii, energia vocii și schimbarea imaginii (tăieturi, b-roll, zoom-uri) — cu semnalele însele, media și fiecare scădere de cel puțin 2 s. Fiecare scădere are motivul probabil (reason: silence, low_energy, slow_speech, static_visual) și, când există, o reparare dintr-un clic (fix) al cărei corp revise îl trimiți ca atare la …/cut/revise (tai pauza, adaugi b-roll sau un zoom, ritm mai alert, scoți propoziția). E o euristică, nu o predicție a retenției (heuristic: true): nu există dovezi publicate și evaluate că astfel de indicatori prezic retenția publicului, deci etichetează-l așa în orice interfață.

curl https://ai.codai.ro/v1/studio/productions/<id>/cut/attention -H "Authorization: Bearer $CODAI_API_KEY"
# 200 { "object": "studio.production.cut_attention", "heuristic": true, "step_s": 1, "points": [0.82, 0.77, ...],
#       "signals": { "speech": [...], "energy": [...], "visual": [...] }, "mean": 0.64,
#       "dips": [ { "start_s": 18, "end_s": 21.5, "reason": "silence", "sentence": null,
#                   "fix": { "action": "cut_pause", "revise": { "disabled": ["+silence:12"] } } } ] }

Chip-uri de subiect

POST /v1/studio/productions/<id>/cut/topics rulează un apel de model plătit (codai::production-cut-topics, cotat la 60.000 µUSD, din alocarea planului) care împarte înregistrarea în subiecte etichetate — label sub 40 de caractere, un kind (intro, problem, solution, demo, pricing, story, tips, qa, outro, other), propozițiile, început și sfârșit — și le salvează. {"refresh": false} întoarce gratuit subiectele salvate; GET …/cut/topics le citește mereu gratuit (404 no_topics înainte de primul apel; 502 topics_failed când modelul nu întoarce nimic utilizabil).

curl https://ai.codai.ro/v1/studio/productions/<id>/cut/topics \
  -H "Authorization: Bearer $CODAI_API_KEY" -H "Content-Type: application/json" -d '{}'
# 200 { "object": "studio.production.cut_topics",
#       "topics": [ { "id": "t1", "label": "De ce sunt respinse facturile", "kind": "problem", "sentences": ["s1", "s2", "s3"],
#                     "start_ms": 0, "end_ms": 24100 }, { "id": "t2", "label": "Prețuri", "kind": "pricing", ... } ],
#       "cost_micro_usd": 3800, "warnings": [] }

Un chip e o revizie gratuită: POST …/cut/revise {"topics": ["t1", "t3"]} păstrează doar acele subiecte (hook-ul rămâne dacă e în interiorul lor); {"topics": []} aduce înapoi toate subiectele. Aceleași subiecte devin capitolele podcastului.

Randează tot

POST /v1/studio/productions/<id>/cut/renders {"kind": "hooks" | "lengths", "ids"?, "quality"?} transformă fiecare variantă într-o creație separată și pornește câte o randare, taxată la prețurile obișnuite de randare (draft 0,005 $/s, final 0,01 $/s). hooks folosește id-urile propozițiilor de deschidere salvate (implicit toate), lengths id-urile preseturilor (implicit 15, 30 și 60). Rulează cel mult 3 randări odată, deci un lot care ar depăși limita e refuzat din start (429 too_many_renders), iar portofelul trebuie să acopere tot lotul înainte să se scrie ceva (402). Idempotency-Key reia primul răspuns.

curl https://ai.codai.ro/v1/studio/productions/<id>/cut/renders \
  -H "Authorization: Bearer $CODAI_API_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" -d '{"kind":"lengths","ids":["15","30","60"],"quality":"draft"}'
# 200 { "object": "studio.production.cut_renders", "kind": "lengths", "quality": "draft", "quote_micro_usd": 525000,
#       "renders": [ { "id": "15", "label": "15 s", "creative_id": "…", "render_id": "…", "output_ms": 15200 }, ... ] }

Instagram Trial Reels e singurul mod nativ de a testa A/B variante video; YouTube Test & Compare acoperă doar titluri și miniaturi, nu videoclipul în sine.

Pachet de podcast

POST /v1/studio/productions/<id>/cut/podcast împachetează o randare terminată a montajului ca episod de podcast, în fundal, ca un export FCPXML: răspunsul e un 202 Export (kind: "podcast") plus spotify_upload_url și chapters; interoghează GET /v1/studio/exports/<export_id> până la succeeded și descarcă url (<slug>-podcast.zip). Arhiva conține:

  • episode.mp3 — loudness -16 LUFS, vârf real -1 dBTP, etichete ID3v2, coperta și capitolele CHAP/CTOC (Apple Podcasts le citește); mp3 {"channels": 1 | 2, "bitrate_k": 128 | 192}, implicit stereo 192 kbit/s;
  • cover.jpg — 3000×3000, un cadru din montaj cu titlul (Apple acceptă pătrat RGB de 1400–3000 px);
  • notes.md — titlu, descriere cu capitole MM:SS, note de episod și cuvinte-cheie;
  • chapters.txt când există capitole și episode.mp4 (randarea; include_video: false îl lasă afară).

Capitolele vin din subiectele salvate (întâi rulează un apel de subiecte când nu există și notes e activ) și respectă regulile platformelor: cel puțin 3, primul la 00:00, la cel puțin 30 s distanță, titluri sub 40 de caractere — altfel nu există capitole (avertismentul chapters_too_few). Spotify nu are API de încărcare: încarcă de mână episode.mp4 (podcast video, H.264 16:9 cu AAC) sau episode.mp3 în Spotify for Creators și lipește descrierea din notes.md — Spotify construiește capitolele doar din marcajele de timp din descriere.

curl https://ai.codai.ro/v1/studio/productions/<id>/cut/podcast \
  -H "Authorization: Bearer $CODAI_API_KEY" -H "Content-Type: application/json" -d '{"notes":true,"mp3":{"channels":1,"bitrate_k":128}}'
# 202 { "id": "…", "object": "studio.export", "kind": "podcast", "status": "queued", "filename": "sfaturi-facturi-podcast.zip",
#       "files": ["episode.mp3", "cover.jpg", "notes.md", "chapters.txt", "episode.mp4"],
#       "spotify_upload_url": "https://creators.spotify.com/pod/login", "chapters": 4, ... }

Costuri: jobul de împachetare e 20.000 µUSD fix (rezervat, taxat când pornește); notes: true (implicit) adaugă un apel pentru note (codai::production-cut-notes, cotat la 30.000 µUSD) — false scrie gratuit note simple. 409 production_not_ready, no_render sau render_not_ready când nu există nimic terminat de împachetat.

Curățarea vocii

Producțiile de montaj curăță gratuit vorbirea: voice_cleanup din cererea de creare e auto (implicit), on sau off. auto măsoară zgomotul de fond dintre cuvinte pentru fiecare sursă și dozează trecerea: -60 dBFS sau mai încet = nimic (deja curat ca pentru difuzare), între -60 și -45 dBFS = o trecere ușoară (intensitate 0,4), peste -45 dBFS = o trecere completă (intensitate 1,0); on curăță mereu (cel puțin trecerea ușoară), off niciodată. Curățarea (denoise DeepFilterNet3, de-esser, compresie, loudness) rulează în etapa opțională clean, între b-roll și asamblare, iar fiecare sursă curățată e o decizie voice:<asset> pe care o poți opri ca pe oricare alta.

POST …/cut/revise {"voice_cleanup": false} oprește vocea curățată; true o pornește la loc — pe un montaj făcut fără surse curățate pornește un job gratuit în fundal, iar răspunsul conține voice_cleanup_job. Interoghează-l cu GET /v1/studio/productions/<job_id> (kind: "cut_clean", phase clean → ready, sources cu floor_db, strength, status și clean_asset_id); când reușește, montajul primește încă o versiune cu vocea curățată.

SDK-uri și unelte de agent (valul 3)

TypeScript: productions.cutHooks(id, { n, judge }), retrieveCutHooks(id), cutLengths(id), cutAttention(id), cutTopics(id, { refresh }), retrieveCutTopics(id), cutRenders(id, { kind, ids, quality }), cutPodcast(id, { notes, mp3 }) (apoi exports.wait) și retrieveCleanJob(id); reviseCut primește hook, topics, drop și voice_cleanup. Python: cut_hooks, retrieve_cut_hooks, cut_lengths, cut_attention, cut_topics, retrieve_cut_topics, cut_renders, cut_podcast; revise_cut(hook=…, topics=…, drop=…, voice_cleanup=…) și create_cut(voice_cleanup=…). Unelte de agent: creative_cut_hooks, creative_cut_lengths, creative_cut_attention, creative_cut_topics, creative_cut_render_all, creative_cut_podcast (extensia VS Code salvează arhiva în .codai/studio/), plus hook / topics / drop / voice_cleanup la creative_cut_revise și voice_cleanup la creative_produce.

Stiluri de editare

Un stil de editare este un set de setări de montaj cu nume: pacing (calm, balanced, punchy), captions (false sau un preset cu max_words_per_line), zoom (off, light, normal, heavy), broll (density none / light / normal / dense, stock true sau false, generated none sau images) și reframe (slow, default, fast). Șase sunt predefinite — podcast-clip, talking-head, tutorial, fast-social, cinematic, minimal — iar GET /v1/studio/edit-styles?lang=en|ro le întoarce cu nume traduse, lângă stilurile tale (adaugă gallery=1 ca să primești și prima pagină din galerie în același apel). Gestionarea stilurilor e gratuită.

  • Stilurile tale: POST /v1/studio/edit-styles {name, description?, knobs} salvează un stil privat (cel mult 50, altfel 409 too_many_styles); PATCH /v1/studio/edit-styles/{id} îl modifică, iar DELETE îl șterge. Orice modificare face din nou privat un stil trimis în galerie.
  • Galerie: POST /v1/studio/edit-styles/{id}/submit trimite în galerie unul dintre stilurile tale. Se trimit doar numele, descrierea și setările, cu numele tău de utilizator, iar Kodwen îl verifică mai întâi: statusul e pending („În așteptarea verificării”) până îl aprobă un moderator (10 trimiteri pe zi). GET /v1/studio/edit-styles/gallery?limit=&cursor= listează stilurile aprobate, cele mai noi primele, cu next_cursor; POST /v1/studio/edit-styles/{id}/copy pune o copie privată în stilurile tale (copied_from arată sursa).
  • Raportare și moderare: stilurile din galerie sunt trimise de alți utilizatori și verificate de Kodwen înainte să apară. Raportează un stil care încalcă regulile cu POST /v1/studio/edit-styles/{id}/report {reason, details?} (spam, harassment, hate, sexual, violence, illegal, ip, other; o raportare pe utilizator, la 5 raportări stilul e ascuns până la verificare). Moderatorii folosesc GET /v1/studio/edit-styles/moderation?status= și POST /v1/studio/edit-styles/{id}/moderate {action: approve|reject|remove} (doar super_admin).

Aplică stilul unui montaj terminat cu un id predefinit sau un id numeric de stil. Se aplică imediat și gratuit: setările se scriu ca versiune nouă, după aceleași reguli de ramură ca o revizie.

curl https://ai.codai.ro/v1/studio/productions/<id>/cut/restyle \
  -H "Authorization: Bearer $CODAI_API_KEY" -H "Content-Type: application/json" \
  -d '{"style":"fast-social","base_seq":3}'
# 200 { "creative_id": "…", "seq": 4, "branch": "main", "decisions": [...], "stats": {...}, "warnings": [],
#       "style": "builtin:fast-social", "broll_slots_needed": 6,
#       "broll_job": { "id": "<id job>", "kind": "cut_restyle", "status": "running", "phase": "broll",
#                      "slots_total": 3, "slots_done": 0, "quote_micro_usd": "…", ... } }

B-roll-ul în plus se caută în fundal; imaginile generate se taxează la cost. Când stilul vrea mai mult b-roll decât are montajul, broll_job umple sloturile lipsă din filmările tale, apoi din stoc, apoi cu imagini generate (doar când stilul le permite). Inserturile proprii și din stoc sunt gratuite; imaginile generate sunt acoperite de o singură rezervare din portofel (quote_micro_usd) și se taxează doar ce s-a cheltuit (spent_micro_usd). Interoghezi GET /v1/studio/productions/<id job> până când status e succeeded (a scris încă o versiune, seq) sau failed; un restyle mai nou anulează jobul în curs și eliberează rezervarea. Trimite "fetch_broll": false ca să aplici doar setările; când rezervarea eșuează din lipsă de fonduri, setările se aplică totuși și warnings conține broll_insufficient_funds. GET …/cut arată apoi stilul în overrides.style, cu overrides.pacing_params, overrides.reframe și overrides.broll_limit. SDK-uri: editStyles.list / create / update / delete / submit / gallery / copy / report, productions.restyleCut(id, { style }), productions.retrieveRestyleJob(jobId) (Python edit_styles.*, productions.restyle_cut(id, style), productions.retrieve(job_id)); unelte de agent creative_edit_styles și creative_cut_restyle.

B-roll din stoc

GET /v1/studio/stock/search?q=…&kind=video|photo caută în stocul fără redevențe (întâi Pexels, apoi Pixabay ca rezervă; elementele Pixabay marcate ca generate cu AI sunt eliminate), iar POST /v1/studio/stock/import {provider, id, kind} copiază un element în activele tale, cu licența înregistrată (meta.stock). Ambele sunt gratuite (600 de căutări și 200 de importuri pe utilizator pe zi); 503 stock_unavailable când niciun furnizor nu e configurat, 429 stock_rate_limited când sunt limitați. Montajul folosește aceeași cale pentru b-roll-ul din stoc.

Reguli de atribuire: afișează credit pentru fiecare element (de exemplu „Video by Jane Doe on Pexels”, cu legătură la page_url) oriunde îl arăți și păstrează vizibil credit-ul activului importat acolo unde publici lucrarea — Kodwen îl scrie în creditele randărilor, în paginile partajate și în exporturi. Licențele permit folosirea într-o lucrare nouă, inclusiv comercial, dar nu redistribuirea separată a fișierelor brute, un serviciu de stoc concurent, copierea în masă sau antrenarea de modele; preview_url și files[].url sunt doar pentru afișare temporară. Oamenii, mărcile și locurile din imagini pot necesita propriul tău acord pentru reclame. SDK-uri: stock.search({ q }), stock.import({ provider, id, kind }) (Python stock.search, stock.import_).

Publicare pe YouTube

Publicarea directă este în pregătire: până când Kodwen o activează, GET /v1/studio/connections răspunde enabled: false, iar conectarea, ciorna și publicarea răspund 404 not_found.

Conectezi o dată un canal YouTube, apoi încarci un randament video terminat cu câmpurile pe care le aprobi. Publicarea este gratuită; încărcarea intră în limitele YouTube (Kodwen permite cel mult 100 de încărcări pe zi pe canal).

  • Conectare: POST /v1/studio/connections/youtube/start întoarce un authorize_url Google cu un state și un code_verifier PKCE; după ce persoana aprobă, trimiți code din redirecționare la POST /v1/studio/connections/youtube/exchange {code, state, code_verifier} în cel mult 10 minute. Hub-ul face asta pentru tine în Studio → Conexiuni. Kodwen cere doar permisiunea de încărcare și păstrează token-urile criptate cu o cheie din Cloud KMS. GET /v1/studio/connections listează canalele (enabled arată dacă publicarea e activă); DELETE /v1/studio/connections/{id} revocă accesul la Google, șterge token-urile și anulează încărcările nepornite — videoclipurile deja pe YouTube rămân acolo.
  • Ciornă: POST /v1/studio/publish/draft {render_id, title, description?, hashtags?, language?} completează titlul, descrierea, etichetele și declarația de conținut AI din randament, fără să încarce nimic. Vizibilitatea și „creat pentru copii” nu sunt niciodată precompletate: le alege persoana.
  • Încărcare: POST /v1/studio/publish {render_id, connection_id, title, description?, tags?, privacy, made_for_kids, contains_synthetic_media, publish_at?, category_id?, language?, client_request_id?} răspunde 202 cu o publicare. Câmpurile sunt verificate ca la YouTube: titlu de cel mult 100 de caractere, descriere de cel mult 5000 de octeți, fără < sau >, etichete de cel mult 500 de caractere în total, publish_at doar cu privacy: "private". Randamentele făcute cu AI generativ trebuie să păstreze contains_synthetic_media: true (doar cele care doar editează filmările tale îl pot pune pe false). Trimite client_request_id ca reîncercările să fie sigure.
  • Stare: GET /v1/studio/publish/{id} întoarce phase (queued, uploading, processing, done, failed, cancelled), progress, linkul videoclipului și o error lizibilă; POST /v1/studio/publish/{id}/cancel funcționează până începe încărcarea.

SDK-uri: publishing.listConnections / startConnection / exchangeConnection / deleteConnection / draft / create / retrieve / cancel (Python publishing.list_connections, …); unelte de agent creative_publish_draft (pregătește doar o ciornă — persoana o confirmă în aplicație) și creative_publish_status.

Cronologiile devin creații

POST /v1/studio/timelines funcționează ca înainte și acum salvează aceeași cronologie și ca o creație editabilă: corpul 202 are creative_id (sau null dacă copia nu a putut fi făcută — randarea nu este niciodată afectată).

Partajare, revizuire și remix

O creație poate fi partajată printr-un link de vizualizare public, doar pentru citire (oricine are o cheie API o poate remixa în propriul cont, cu resursele copiate), sau printr-un link de revizuire, unde persoane fără cont lasă comentarii ancorate pe un nod și pe un moment, iar aprobatorii aprobă sau cer modificări; o aprobare fixează versiunea. Totul este gratuit — vezi Partajare, revizuire și remix.

Hub (intern)

Hub-ul apelează aceleași rute sub /internal/studio/creatives…, /internal/studio/brand-kits…, /internal/studio/templates…, /internal/studio/exports… și /internal/studio/edit-styles… (plus POST /internal/studio/productions/:id/cut/restyle), cu un token de serviciu și un user_id; corpurile și erorile sunt identice. Kiturile de brand sunt oglindite complet: POST /internal/studio/brand-kits, PATCH /internal/studio/brand-kits/:id, GET /internal/studio/brand-kits/:id/dtcg, POST /internal/studio/brand-kits/extract (cu url sau logo_asset_id) și POST /internal/studio/creatives/:id/restyle. Sunt oglindite și subtitrările, platformele, corecturile și panoul de ajustare: POST /internal/studio/creatives/:id/captions, GET /internal/studio/creatives/:id/captions.srt și .vtt, POST /internal/studio/creatives/:id/adapt, POST /internal/studio/creatives/:id/lint/fix și GET /internal/studio/creatives/:id/controls. Este oglindită și localizarea: GET /internal/studio/creatives/:id/locales, POST /internal/studio/creatives/:id/localize și POST /internal/studio/creatives/:id/locales/:locale/fork; loturile se află sub /internal/studio/batches… (vezi Loturi); linkurile de partajare, revizuirile și remixul se află sub /internal/studio/creatives/:id/shares…, /internal/studio/creatives/:id/review… și /internal/studio/shares/:token/remix (vezi Partajare, revizuire și remix).

SDK-uri

const c = await codai.creatives.create({ kind: 'video', title: 'Teaser de lansare' });
const r = await codai.creatives.edit(c.id, {
  baseSeq: c.head_seq,
  ops: [{ op: 'setMeta', title: 'Teaser de lansare v2' }],
});
const job = await codai.creatives.render(c.id, { quality: 'draft' });

let frames = await codai.creatives.frame(c.id, { t_ms: [0, 1500] });
while (frames.status === 'running') {
  await new Promise((r) => setTimeout(r, 2000));
  frames = await codai.creatives.frameJob(c.id, frames.frame_job_id);
}
const edit = await codai.creatives.comment(c.id, { t_ms: 1500, comment: 'Fă titlul roșu', base_seq: 2 });
const ab = await codai.creatives.variants(c.id, { base_seq: edit.head_seq, count: 3, focus: ['hook', 'cta'] });
await codai.creatives.pickVariant(c.id, ab.variants[0].creative_id, { base_seq: edit.head_seq });

const kit = await codai.brandKits.extract({ url: 'https://acme.example/' });
const branded = await codai.creatives.create({ kind: 'image', brand_kit_id: kit.id });
c = client.creatives.create({"kind": "video", "title": "Teaser de lansare"})
client.creatives.edit(c["id"], base_seq=c["head_seq"], ops=[{"op": "setMeta", "title": "v2"}])
job = client.creatives.render(c["id"], {"quality": "draft"})
frames = client.creatives.frame(c["id"], t_ms=[0, 1500], exact=True)
if frames["status"] == "running":
    frames = client.creatives.frame_job(c["id"], frames["frame_job_id"])  # interoghează până la "succeeded"
edit = client.creatives.comment(c["id"], t_ms=1500, comment="Fă titlul roșu", base_seq=2)
ab = client.creatives.variants(c["id"], base_seq=edit["head_seq"], count=3, focus=["hook", "cta"])
client.creatives.pick_variant(c["id"], ab["variants"][0]["creative_id"], base_seq=edit["head_seq"])

Pe această pagină