{"openapi":"3.1.0","info":{"title":"AgileVolto","summary":"Persone digitali sovrane: da una foto e un testo, un video che parla.","description":"\n**AgileVolto** è una persona digitale sovrana: gira sulle tue GPU, in Europa,\nsenza token di terzi. Da una **foto** e da un **testo** produce un `video/mp4`\ncon la persona che parla.\n\n### Come si comincia\n1. fatti dare una chiave dal **portale sviluppatori**;\n2. `GET /v1/chiave` — la prova più economica che funziona;\n3. `POST /v1/riferimenti` — carica la foto, ricevi un `id`;\n4. `POST /v1/sessioni` — lega foto, voce e lingua, ricevi un `id`;\n5. `POST /v1/sessioni/{id}/parla` — mandi il testo, ti torna il video;\n6. `DELETE /v1/sessioni/{id}` — chiudi, e i file spariscono dal disco.\n\nGli esempi `curl` copiabili stanno su **[/inizia](/inizia)**.\n\n### Le regole\n- Ogni chiamata sotto `/v1` vuole l'intestazione `X-API-Key`. Senza → **401**.\n  `GET /salute` è **aperta**: serve a monitorare il servizio, non a usarlo.\n- Ogni chiave ha un tetto di **turni di lavoro al giorno** (azzeramento a\n  mezzanotte UTC). Non sono chiamate: un `parla` vale 1 turno, un caricamento 3,\n  uno sguardo 0,2, e ogni `GET` **zero**. Il listino per intero si legge\n  dall'API, in `GET /v1/chiave` → `listino_turni`. Superato → **429** con\n  `Retry-After` e, nel corpo, quanto costava il gesto rifiutato. Le risposte\n  portano `X-Quota-Giorno`, `X-Quota-Usate`, `X-Quota-Residue` e `X-Quota-Peso`\n  (quanto ha consumato questa chiamata, zero compreso).\n- Le rotte del **round 3** (`/conversa`, `/vede`) rispondono **503** dicendo\n  quale round le porterà: preferiamo un rifiuto onesto a una risposta finta.\n- Il **volto resta da noi**: i riferimenti restano su disco locale, non vengono\n  inviati a nessun servizio esterno, e `DELETE` li cancella dichiarando quanti\n  file ha tolto.\n\n### Rivendere il volto: l'accreditamento come officina\nIl volto si **rivende** — mettere il volto nel proprio prodotto e aprire un conto\nper ogni cliente proprio — ma solo da un conto **accreditato come officina**\n(decisione del titolare di AgileTav del 21/10/2026). Non è un permesso che si\nchiede coniando una chiave: `permessi: [\"rivende\"]` su\n`POST /v1/conti/{id}/chiavi` risponde **422 `rivende_non_si_conia`** con\nentrambe le credenziali, e il silenzio (`permessi` assente) concede i **cinque**\nche si comprano — `lettura, parla, conversa, vede, clonazione` — mai il sesto.\n\nIl processo, per intero:\n1. **chi chiede** — il conto che vuole rivendere, scrivendo a AgileTav con l'`id`\n   del proprio conto (`c_…`) e l'uso che farà del volto;\n2. **cosa si verifica** — che il conto esista, sia in regola con la propria\n   licenza, e venda un **prodotto proprio** in cui il volto entra;\n3. **chi concede** — la **regia di AgileTav**, su decisione del titolare: conia\n   la chiave col permesso `rivende` e col **riferimento della decisione** scritto\n   in archivio, nella nota della chiave. Nessuna rotta HTTP la conia;\n4. **chi revoca** — la stessa regia, **revocando quella chiave**: l'accreditamento\n   è la vita della chiave, non una riga a parte. Revocata, l'officina non apre\n   più conti; i conti che ha aperto restano suoi, coi loro token;\n5. **la traccia** — il riferimento della decisione nella nota della chiave, e\n   **ogni** gesto da rivenditore (`POST /v1/conti`, `POST /v1/conti/{id}/catena`)\n   nel registro d'uso del servizio, come tutte le altre chiamate.\n\nCon la chiave dell'officina si apre un conto (`POST /v1/conti`) e si guida la\ncatena **solo per i conti che ha aperto lei**; la chiave coniata per il cliente\nè sempre del cliente e **non eredita** `rivende` — un cliente di chi rivende non\napre conti a sua volta.\n","version":"2.7-round2"},"paths":{"/v1/chiave":{"get":{"tags":["chiave"],"summary":"Chi sono e quanta quota mi resta","description":"La prova più economica che la chiave funziona: nessun file, nessun video.\n\nNon è nel contratto §8.1 — è un'aggiunta di comodo, ed è la prima chiamata\ndella pagina «Inizia in 5 minuti».\n\nDal 5/10/2026 porta anche `licenza`: sotto quale licenza sta questa chiave,\ncon il tetto, l'usato e il resto **della licenza**. Vale `null` in due casi\n— chiave senza tenant, e chiave **orfana** (un tenant di cui la licenza non\nesiste) — spiegati in `licenza_della_chiave` qui sopra. Nessun campo di\nprima e' stato toccato: si aggiunge soltanto, perche' adp legge questa\nrisposta oggi.\n\nDal 6/10/2026 porta anche l'UNITA' in cui quella quota si conta\n(BIGLIETTO 49): `listino_turni` — letto da `chiavi.PESO_DELLE_ROTTE`, una\nvoce per ogni rotta che pesa — `listino_turni_nota` e `azzeramento_il`,\nl'istante in cui la quota riparte. AGGIUNTI 3, CAMBIATI 0, SPARITI 0,\ncontati da `test_la_chiave_dice_la_sua_licenza.py`.\n\nDal 12/10/2026 porta anche `quota_minuti` e `minuti_oggi` (D-337, A349): il\nQUARTO tetto, in minuti di video di oggi, e il contatore contro cui si\nmisura. Vale `-1` su ogni chiave — il tetto e' scritto e SPENTO — e nessuna\nintestazione nuova lo ripete su ogni risposta.\n\nDal 9/10/2026 porta anche `conto_del_tenant` (D-227, misura di adp): i\nminuti di volto di TUTTE le chiavi dell'organizzazione, col pezzo di questa\nchiave accanto in `di_questa_chiave`. `minuti_video` qui sopra e' per\nCHIAVE e non e' stato toccato: e' il soggetto del numero che cambia, e una\nfattura di tenant riconciliata contro un conto di chiave confronta un totale\ncon un suo pezzo. AGGIUNTO 1, CAMBIATI 0, SPARITI 0, contati dalle celle.\n\nDal 20/10/2026 porta anche `permessi_dichiarati` (D-437, misura di adp), e\nnello stesso giorno `permessi` ha cambiato LETTORE: dice gli EFFETTIVI,\nquelli che la guardia concede davvero, e non più la lista grezza\ndell'archivio. I due campi accanto sono la differenza fra ciò che una\nchiave DICE di sé e ciò che ha scritto in archivio: `permessi_dichiarati`\nvale `[]` quando chi l'ha coniata non ha detto niente, e `permessi`\nelenca allora i CINQUE che il silenzio concede — `rivende`, che è il\nmestiere di chi vende, non arriva mai dal silenzio. AGGIUNTO 1, CAMBIATI 0,\nSPARITI 0, contati dalle due liste del censimento di questa rotta.\n\nDal 21/10/2026 `prefisso` dice la forma MASCHERATA (rilievo di adp, POSTA\n12:33Z punto 6): portava i primi 12 caratteri della chiave, cioè `atv_` più\nOTTO dei trentadue casuali, dentro un referto di autodiagnosi fatto per\nessere copiato in un biglietto. La regola di quanto se ne possa mostrare è\nin `chiavi.mascherata` ed è di otto caratteri: questo campo la smentiva\nstando accanto al campo che la applica. Il nome resta — adp ne ha misurato\nzero lettori e lo dichiara eliminabile, ma un campo si toglie col giro dei\ndue clienti (A54), non da soli. AGGIUNTI 0, **CAMBIATO 1 valore**\n(`prefisso`, da 12 caratteri a 8), SPARITI 0: nessun campo nuovo, nessuno\nrimosso, il tipo è lo stesso `str`.","operationId":"chi_sono_v1_chiave_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"chiave mancante, sconosciuta o revocata","content":{"application/json":{"example":{"errore":"chiave_mancante","spiegazione":"manca l'intestazione X-API-Key: ogni chiamata sotto /v1 la vuole.","intestazione_attesa":"X-API-Key","documentazione":"/inizia"}}}},"429":{"description":"quota giornaliera esaurita (vedi `Retry-After`)","content":{"application/json":{"example":{"errore":"quota_esaurita","spiegazione":"questa chiave ha consumato 200 chiamate su 200 previste per oggi","quota_giorno":200,"usate_oggi":200,"riprova_fra_secondi":43200}}}},"403":{"description":"la chiave e' valida ma **non puo' fare questa chiamata adesso**, e `errore` dice quale dei motivi: `chiave_sospesa` (si riattiva, non si riconia), `licenza_sospesa` e `licenza_chiusa` (il tenant, non la chiave: la prima si riapre, la seconda no), `origine_non_ammessa` (questa chiave accetta solo certe origini). Non e' un 401: la chiave esiste ed e' tua","content":{"application/json":{"example":{"errore":"chiave_sospesa","spiegazione":"questa chiave e' sospesa: esiste, e' tua, e torna a funzionare quando la riattivi dal conto.","chiave":"atv_…"}}}}},"security":[{"X-API-Key":[]}]}},"/v1/persone":{"get":{"tags":["persone"],"summary":"Le persone digitali pronte all'uso","description":"Le persone che puoi aprire subito, senza caricare nessuna foto.\n\nL'`id` di una di queste righe si passa a `POST /v1/sessioni` nel campo\n`persona`, **senza** `riferimento`: la sessione nasce sul volto della\nscheda, con la sua voce e il suo tono.\n\nNessuna delle tre esiste. I nomi sono inventati, le voci sono sintetiche e i\nvolti sono **ritratti fotorealistici generati da un modello**: sembrano\npersone, e non sono nessuno. Non è una promessa da credere sulla parola —\nogni volto porta la sua **provenienza** (modello, licenza, passi, seme,\nprompt, sha256 del file) in `banco/dataset/persone/provenienza.json`, e il\nprompt che li ha generati parla di un mestiere e di una luce: nessun nome,\nnessuna somiglianza cercata, nessuna età, nessuna etnia. `GET /salute` →\n`volti_delle_persone` dice, volto per volto, se quello in servizio è il\nritratto di prodotto o il ripiego disegnato.","operationId":"elenca_persone_v1_persone_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogoPersone"}}}},"401":{"description":"chiave mancante, sconosciuta o revocata","content":{"application/json":{"example":{"errore":"chiave_mancante","spiegazione":"manca l'intestazione X-API-Key: ogni chiamata sotto /v1 la vuole.","intestazione_attesa":"X-API-Key","documentazione":"/inizia"}}}},"429":{"description":"quota giornaliera esaurita (vedi `Retry-After`)","content":{"application/json":{"example":{"errore":"quota_esaurita","spiegazione":"questa chiave ha consumato 200 chiamate su 200 previste per oggi","quota_giorno":200,"usate_oggi":200,"riprova_fra_secondi":43200}}}},"403":{"description":"la chiave e' valida ma **non puo' fare questa chiamata adesso**, e `errore` dice quale dei motivi: `chiave_sospesa` (si riattiva, non si riconia), `licenza_sospesa` e `licenza_chiusa` (il tenant, non la chiave: la prima si riapre, la seconda no), `origine_non_ammessa` (questa chiave accetta solo certe origini). Non e' un 401: la chiave esiste ed e' tua","content":{"application/json":{"example":{"errore":"chiave_sospesa","spiegazione":"questa chiave e' sospesa: esiste, e' tua, e torna a funzionare quando la riattivi dal conto.","chiave":"atv_…"}}}},"503":{"description":"il registro delle persone digitali (`motori/persone`) non si carica su questa macchina: `errore` è `registro_persone_non_leggibile`. Le sessioni con un riferimento tuo continuano a funzionare, quelle da persona no","content":{"application/json":{"example":{"errore":"registro_persone_non_leggibile","spiegazione":"il registro delle persone digitali (motori/persone) non si carica su questa macchina: le sessioni con un riferimento tuo continuano a funzionare, quelle da persona no"}}}}},"security":[{"X-API-Key":[]}]}},"/v1/persone/{identificativo}/volto":{"get":{"tags":["persone"],"summary":"Il volto della persona (PNG 1024×1024, ritratto inventato)","description":"Il PNG del volto, servito da qui.\n\nSta sotto `/v1` come tutto il resto, quindi **vuole la chiave**: la guardia\nnon fa eccezioni di percorso, e una porta aperta in mezzo a un'API chiusa è\nuna porta che nessuno si ricorda di aver lasciato aperta. Per mostrarlo in\nuna pagina si scarica con `fetch` passando `X-API-Key` e si dà all'`img` un\nobject URL — un `<img src>` diretto non può mandare intestazioni.\n\nIl file è quello del registro e **non** si consuma: le sessioni ne fanno una\ncopia propria (vedi `POST /v1/sessioni`), così `DELETE /v1/sessioni/{id}`\ncancella la copia e la persona resta.","operationId":"volto_della_persona_v1_persone__identificativo__volto_get","security":[{"X-API-Key":[]}],"parameters":[{"name":"identificativo","in":"path","required":true,"schema":{"type":"string","title":"Identificativo"}}],"responses":{"200":{"description":"il PNG del volto","content":{"image/png":{}}},"401":{"description":"chiave mancante, sconosciuta o revocata","content":{"application/json":{"example":{"errore":"chiave_mancante","spiegazione":"manca l'intestazione X-API-Key: ogni chiamata sotto /v1 la vuole.","intestazione_attesa":"X-API-Key","documentazione":"/inizia"}}}},"429":{"description":"quota giornaliera esaurita (vedi `Retry-After`)","content":{"application/json":{"example":{"errore":"quota_esaurita","spiegazione":"questa chiave ha consumato 200 chiamate su 200 previste per oggi","quota_giorno":200,"usate_oggi":200,"riprova_fra_secondi":43200}}}},"403":{"description":"la chiave e' valida ma **non puo' fare questa chiamata adesso**, e `errore` dice quale dei motivi: `chiave_sospesa` (si riattiva, non si riconia), `licenza_sospesa` e `licenza_chiusa` (il tenant, non la chiave: la prima si riapre, la seconda no), `origine_non_ammessa` (questa chiave accetta solo certe origini). Non e' un 401: la chiave esiste ed e' tua","content":{"application/json":{"example":{"errore":"chiave_sospesa","spiegazione":"questa chiave e' sospesa: esiste, e' tua, e torna a funzionare quando la riattivi dal conto.","chiave":"atv_…"}}}},"404":{"description":"nessuna persona con quell'id"},"503":{"description":"la persona c'è ma il suo volto **non è su questa macchina** (`volto_non_disponibile`), oppure il registro delle persone non si carica (`registro_persone_non_leggibile`)"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/persone/{identificativo}/attesa":{"get":{"tags":["persone"],"summary":"Il girato d'attesa della persona (mp4 muto, per il palco)","description":"Il girato da mettere sul palco **mentre non parla**.\n\nPerché esiste (8/9/2026). La stanza, fra una risposta e l'altra, teneva un\n**ritratto che respira**: un'immagine ferma con sopra un po' di movimento\ndisegnato. Da quando la regia del buffer aspetta a far partire il video —\nparte solo quando il buffer regge, altrimenti sul palco resta la base viva —\nquella base si guarda per secondi interi, e un ritratto che respira si vede\nper quello che è. Il girato sorgente della persona, invece, è la **stessa\nripresa** da cui nascono le labbra: la testa si muove davvero, e il passaggio\nfra attesa e risposta non si nota.\n\nÈ lo **stesso file** che la sessione usa come riferimento\n(`registro.percorso_sorgente`, la sorgente attiva di questa macchina): non\nuna seconda copia da tenere allineata, e nemmeno un secondo criterio di\nscelta. Se un domani il catalogo tornasse al ritratto, questa rotta\nrisponderebbe 404 e la stanza tornerebbe al ritratto senza che nessuno\ndebba ricordarsi di cambiarla.\n\n**Dal 9/9/2026 quel file è la BASE VIVA** (`<id>.base-viva.mp4`), non il\ngirato delle card: l'uso è «videochat», e in quel mestiere la persona\n**ascolta** — bocca chiusa, testa calma, montaggio avanti e indietro senza\ncucitura. Il girato delle card resta alle card, dove una persona che parla è\nquello che serve. Il perché, e i numeri: `docs/BASE_VIVA.md`.\n\n**Muto per contratto**: il girato a diffusione nasce senza audio, e la\nstanza lo mette in `loop` avanti e indietro. Se un file avesse una traccia\naudio, il `<video>` dell'attesa la ignora — è `muted` nella pagina — ma qui\nnon si toglie niente: si serve il file com'è.\n\n`Cache-Control` lungo perché è un file **immutabile**: cambia solo quando si\nrigenera il catalogo, e allora cambia anche il suo nome nel registro.\n\nCome `/volto`, sta sotto `/v1` e quindi **vuole la chiave** (o il token del\nconto, §1.4): una porta aperta in mezzo a un'API chiusa è una porta che\nnessuno si ricorda di aver lasciato aperta.","operationId":"attesa_della_persona_v1_persone__identificativo__attesa_get","security":[{"X-API-Key":[]}],"parameters":[{"name":"identificativo","in":"path","required":true,"schema":{"type":"string","title":"Identificativo"}}],"responses":{"200":{"description":"il girato sorgente, muto","content":{"video/mp4":{}}},"401":{"description":"chiave mancante, sconosciuta o revocata","content":{"application/json":{"example":{"errore":"chiave_mancante","spiegazione":"manca l'intestazione X-API-Key: ogni chiamata sotto /v1 la vuole.","intestazione_attesa":"X-API-Key","documentazione":"/inizia"}}}},"429":{"description":"quota giornaliera esaurita (vedi `Retry-After`)","content":{"application/json":{"example":{"errore":"quota_esaurita","spiegazione":"questa chiave ha consumato 200 chiamate su 200 previste per oggi","quota_giorno":200,"usate_oggi":200,"riprova_fra_secondi":43200}}}},"403":{"description":"la chiave e' valida ma **non puo' fare questa chiamata adesso**, e `errore` dice quale dei motivi: `chiave_sospesa` (si riattiva, non si riconia), `licenza_sospesa` e `licenza_chiusa` (il tenant, non la chiave: la prima si riapre, la seconda no), `origine_non_ammessa` (questa chiave accetta solo certe origini). Non e' un 401: la chiave esiste ed e' tua","content":{"application/json":{"example":{"errore":"chiave_sospesa","spiegazione":"questa chiave e' sospesa: esiste, e' tua, e torna a funzionare quando la riattivi dal conto.","chiave":"atv_…"}}}},"404":{"description":"nessuna persona con quell'id, o nessun girato per la sorgente attiva su questa macchina"},"503":{"description":"il registro delle persone digitali (`motori/persone`) non si carica su questa macchina: `errore` è `registro_persone_non_leggibile`. Le sessioni con un riferimento tuo continuano a funzionare, quelle da persona no","content":{"application/json":{"example":{"errore":"registro_persone_non_leggibile","spiegazione":"il registro delle persone digitali (motori/persone) non si carica su questa macchina: le sessioni con un riferimento tuo continuano a funzionare, quelle da persona no"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/riferimenti":{"get":{"tags":["riferimenti"],"summary":"I tuoi riferimenti su disco, e chi li usa","description":"Che cosa c'è su questo disco a tuo nome, riga per riga.\n\nPer ogni riferimento: tipo, byte, secondi, quando è stato caricato, le\nsessioni **aperte** che lo usano (finché ce n'è una non si cancella), se un\navatar salvato lo rivendica, e — se è **orfano** — quando la potatura lo\ntoglierà da sola. Nata l'8/9/2026 perché la regia aveva caricato dei video,\nchiuso le sessioni, e non aveva nessun modo di sapere che cosa fosse rimasto.\n\nCon la chiave API si vedono i riferimenti di **quella** chiave; con il token\ndel conto quelli di **tutte** le chiavi del conto.","operationId":"elenca_riferimenti_v1_riferimenti_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ElencoRiferimenti"}}}},"401":{"description":"chiave mancante, sconosciuta o revocata","content":{"application/json":{"example":{"errore":"chiave_mancante","spiegazione":"manca l'intestazione X-API-Key: ogni chiamata sotto /v1 la vuole.","intestazione_attesa":"X-API-Key","documentazione":"/inizia"}}}},"429":{"description":"quota giornaliera esaurita (vedi `Retry-After`)","content":{"application/json":{"example":{"errore":"quota_esaurita","spiegazione":"questa chiave ha consumato 200 chiamate su 200 previste per oggi","quota_giorno":200,"usate_oggi":200,"riprova_fra_secondi":43200}}}},"403":{"description":"la chiave e' valida ma **non puo' fare questa chiamata adesso**, e `errore` dice quale dei motivi: `chiave_sospesa` (si riattiva, non si riconia), `licenza_sospesa` e `licenza_chiusa` (il tenant, non la chiave: la prima si riapre, la seconda no), `origine_non_ammessa` (questa chiave accetta solo certe origini). Non e' un 401: la chiave esiste ed e' tua","content":{"application/json":{"example":{"errore":"chiave_sospesa","spiegazione":"questa chiave e' sospesa: esiste, e' tua, e torna a funzionare quando la riattivi dal conto.","chiave":"atv_…"}}}}},"security":[{"X-API-Key":[]}]},"post":{"tags":["riferimenti"],"summary":"Carica una foto, un video o un campione di voce","description":"Il file resta **solo su disco locale**, in una cartella della tua chiave.\n\nNon viene inviato a nessun servizio esterno, non entra in nessun modello e\nnon è visibile ad altre chiavi (brief §7: il volto resta da noi). Sparisce\nquando chiudi la sessione che lo usa, quando lo cancelli tu\n(`DELETE /v1/riferimenti/{id}`), oppure da solo dopo\n`AGILETAV_RIFERIMENTI_ORFANI_ORE` ore senza una sessione aperta che lo usi\n(la **potatura degli orfani**, dichiarata: `GET /v1/riferimenti` dice quando).\n\n**Tre tipi, una rotta sola.** Il campione di voce viaggia come la foto,\nperché è la stessa cosa: materiale di una persona vera, di proprietà di una\nchiave sola, con la stessa scadenza e la stessa cancellazione. Il `tipo` non\nlo decide l'estensione da sola — un `.webm` registrato dal browser può\ncontenere solo la voce, e allora è `audio`, non `video`: si guarda **dentro**\nil file (ffmpeg), perché su questa distinzione poi si decide se una sessione\npuò clonare una voce.","operationId":"carica_riferimento_v1_riferimenti_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_carica_riferimento_v1_riferimenti_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RiferimentoCaricato"}}}},"401":{"description":"chiave mancante, sconosciuta o revocata","content":{"application/json":{"example":{"errore":"chiave_mancante","spiegazione":"manca l'intestazione X-API-Key: ogni chiamata sotto /v1 la vuole.","intestazione_attesa":"X-API-Key","documentazione":"/inizia"}}}},"429":{"description":"quota giornaliera esaurita (vedi `Retry-After`)","content":{"application/json":{"example":{"errore":"quota_esaurita","spiegazione":"questa chiave ha consumato 200 chiamate su 200 previste per oggi","quota_giorno":200,"usate_oggi":200,"riprova_fra_secondi":43200}}}},"403":{"description":"questa chiamata vuole il permesso **`clonazione`** e la chiave non ce l'ha (`permesso_mancante`): la risposta porta `permesso_richiesto` e `permessi_della_chiave`, cosi' si vede la differenza senza indovinare. Un permesso si aggiunge o si toglie **sulla chiave che hai** (il valore non cambia e non c'e' da riconiare niente): si chiede dal conto. Lo stesso 403 lo danno anche `chiave_sospesa`, `licenza_sospesa`, `licenza_chiusa` e `origine_non_ammessa`, che non dipendono da questa rotta","content":{"application/json":{"example":{"errore":"permesso_mancante","permesso_richiesto":"clonazione","permessi_della_chiave":["lettura"],"chiave":"atv_…"}}}},"503":{"description":"lo strumento che guarda dentro al file non è pronto su questa macchina (`strumento_non_pronto`): non è un problema del tuo file, e riprovare più tardi ha senso"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"X-API-Key":[]}]}},"/v1/riferimenti/{identificativo}":{"get":{"tags":["riferimenti"],"summary":"Un riferimento solo: c'è ancora? chi lo usa? quando scade?","description":"La riga di `GET /v1/riferimenti` per un id solo.\n\nPerché è nata (8/9/2026, verdetto della tornata 3, punto «T-405»). Chi\ncancella un riferimento con `DELETE /v1/riferimenti/{id}` e poi vuole\n**verificare** che sia sparito faceva la cosa ovvia — una `GET` sullo stesso\nindirizzo — e riceveva **405 Method Not Allowed**: il percorso esisteva solo\nper il `DELETE`. Un 405 dice «quel verbo qui non si usa» e non dice niente\nsul riferimento, quindi la verifica di una cancellazione, che è il momento in\ncui un cliente ha più bisogno di una risposta chiara, era l'unico posto del\nservizio dove il servizio taceva. Adesso la stessa `GET` risponde **404**, e\nprima della cancellazione risponde con la riga.\n\nNon è una rotta nuova nel senso dei dati: legge lo **stesso** elenco di\n`GET /v1/riferimenti` (`elenca_riferimenti` dell'archivio) e ne prende una\nriga. Due strade per calcolare «chi usa questo file» sarebbero due posti in\ncui sbagliare, e un giorno direbbero cose diverse.\n\nUn riferimento di un'altra chiave e uno inesistente ricevono lo **stesso**\n404 del `DELETE`: dalla differenza si imparerebbe quali id esistono.","operationId":"leggi_riferimento_v1_riferimenti__identificativo__get","security":[{"X-API-Key":[]}],"parameters":[{"name":"identificativo","in":"path","required":true,"schema":{"type":"string","title":"Identificativo"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RiferimentoInElenco"}}}},"401":{"description":"chiave mancante, sconosciuta o revocata","content":{"application/json":{"example":{"errore":"chiave_mancante","spiegazione":"manca l'intestazione X-API-Key: ogni chiamata sotto /v1 la vuole.","intestazione_attesa":"X-API-Key","documentazione":"/inizia"}}}},"429":{"description":"quota giornaliera esaurita (vedi `Retry-After`)","content":{"application/json":{"example":{"errore":"quota_esaurita","spiegazione":"questa chiave ha consumato 200 chiamate su 200 previste per oggi","quota_giorno":200,"usate_oggi":200,"riprova_fra_secondi":43200}}}},"403":{"description":"la chiave e' valida ma **non puo' fare questa chiamata adesso**, e `errore` dice quale dei motivi: `chiave_sospesa` (si riattiva, non si riconia), `licenza_sospesa` e `licenza_chiusa` (il tenant, non la chiave: la prima si riapre, la seconda no), `origine_non_ammessa` (questa chiave accetta solo certe origini). Non e' un 401: la chiave esiste ed e' tua","content":{"application/json":{"example":{"errore":"chiave_sospesa","spiegazione":"questa chiave e' sospesa: esiste, e' tua, e torna a funzionare quando la riattivi dal conto.","chiave":"atv_…"}}}},"404":{"description":"nessun riferimento con quell'id per questa chiave (o per questo conto, col token) — ed è la stessa risposta che si riceve **dopo** averlo cancellato"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["riferimenti"],"summary":"Cancella un riferimento dal disco, subito","description":"«Il materiale resta lì e si cancella su richiesta» (brief §7): questa è la\nrichiesta, per un riferimento che nessuna sessione ha portato via.\n\nToglie il file, il fotogramma di identità che gli sta accanto e ciò che il\ncaveau delle voci teneva per lui, e dice **quanti file e quanti byte** — non\n«fatto», che non si può verificare. Lo stesso codice della chiusura di una\nsessione (`api/sessioni.elimina_riferimento`).\n\nUn riferimento di un'altra chiave e uno inesistente ricevono lo **stesso**\n404: dalla differenza si imparerebbe quali id esistono. Uno ancora usato da\nuna sessione aperta risponde **409** con le sessioni: si chiudono quelle\n(`DELETE /v1/sessioni/{id}` lo porta via con sé). Uno rivendicato da un\navatar salvato risponde 409: si cancella l'avatar\n(`DELETE /v1/conti/{conto}/avatar/{avatar_id}`), che porta via anche lui.","operationId":"cancella_riferimento_v1_riferimenti__identificativo__delete","security":[{"X-API-Key":[]}],"parameters":[{"name":"identificativo","in":"path","required":true,"schema":{"type":"string","title":"Identificativo"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RiferimentoCancellato"}}}},"401":{"description":"chiave mancante, sconosciuta o revocata","content":{"application/json":{"example":{"errore":"chiave_mancante","spiegazione":"manca l'intestazione X-API-Key: ogni chiamata sotto /v1 la vuole.","intestazione_attesa":"X-API-Key","documentazione":"/inizia"}}}},"429":{"description":"quota giornaliera esaurita (vedi `Retry-After`)","content":{"application/json":{"example":{"errore":"quota_esaurita","spiegazione":"questa chiave ha consumato 200 chiamate su 200 previste per oggi","quota_giorno":200,"usate_oggi":200,"riprova_fra_secondi":43200}}}},"403":{"description":"la chiave e' valida ma **non puo' fare questa chiamata adesso**, e `errore` dice quale dei motivi: `chiave_sospesa` (si riattiva, non si riconia), `licenza_sospesa` e `licenza_chiusa` (il tenant, non la chiave: la prima si riapre, la seconda no), `origine_non_ammessa` (questa chiave accetta solo certe origini). Non e' un 401: la chiave esiste ed e' tua","content":{"application/json":{"example":{"errore":"chiave_sospesa","spiegazione":"questa chiave e' sospesa: esiste, e' tua, e torna a funzionare quando la riattivi dal conto.","chiave":"atv_…"}}}},"404":{"description":"nessun riferimento con quell'id per questa chiave (o per questo conto, col token)"},"409":{"description":"in uso da una sessione aperta, o di un avatar salvato: si chiude quella, o si cancella quello"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/sessioni":{"post":{"tags":["sessioni"],"summary":"Apri una sessione avatar","description":"Lega un volto a una voce e a una lingua. Non consuma GPU: è un registro.\n\nIl volto può arrivare da due parti, e sono l'una l'alternativa dell'altra:\nun `riferimento` che hai caricato tu, oppure una `persona` del nostro\nregistro (`GET /v1/persone`), che porta con sé anche voce, lingua, ruolo e\ntono. La persona **non** consuma il PNG del registro: la sessione ne riceve\nuna copia sua, e quando la chiudi sparisce quella — non la persona.\n\n**La voce clonata è un'ambizione, non una condizione** (6/9/2026). Con un\n`campione_voce`, su una macchina che ha il motore `clona` la sessione parla\ncon la voce di chi ha dato il campione; su una che non ce l'ha — il caso\nnormale di un host CPU — la sessione si apre **lo stesso** con la voce di\ncatalogo, `voce_clonata_da` vale `null` e la `nota` dice cos'è successo. Il\ncampione resta legato alla sessione anche se oggi non lo si usa, così dove\nil motore c'è la stessa sessione parla con la voce clonata senza ricaricare\nniente. Chi non può accettare quel ripiego lo dichiara con\n`clonazione: \"obbligatoria\"` e riceve 503 `catena_non_completa`.","operationId":"apri_sessione_v1_sessioni_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NuovaSessione"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessioneCreata"}}}},"401":{"description":"chiave mancante, sconosciuta o revocata","content":{"application/json":{"example":{"errore":"chiave_mancante","spiegazione":"manca l'intestazione X-API-Key: ogni chiamata sotto /v1 la vuole.","intestazione_attesa":"X-API-Key","documentazione":"/inizia"}}}},"429":{"description":"quota giornaliera esaurita (vedi `Retry-After`)","content":{"application/json":{"example":{"errore":"quota_esaurita","spiegazione":"questa chiave ha consumato 200 chiamate su 200 previste per oggi","quota_giorno":200,"usate_oggi":200,"riprova_fra_secondi":43200}}}},"403":{"description":"la chiave e' valida ma **non puo' fare questa chiamata adesso**, e `errore` dice quale dei motivi: `chiave_sospesa` (si riattiva, non si riconia), `licenza_sospesa` e `licenza_chiusa` (il tenant, non la chiave: la prima si riapre, la seconda no), `origine_non_ammessa` (questa chiave accetta solo certe origini). Non e' un 401: la chiave esiste ed e' tua. **E su questa rotta anche la persona**: una chiave della singola persona apre sessioni solo su quella persona, e su un'altra — o su un `riferimento`, che e' per definizione un altro volto — risponde `persona_non_tua`, con `persona_della_chiave` e `persona_chiesta` accanto. Si guarda il **corpo** della richiesta, non il percorso, quindi non lo si vede dalle altre rotte","content":{"application/json":{"example":{"errore":"chiave_sospesa","spiegazione":"questa chiave e' sospesa: esiste, e' tua, e torna a funzionare quando la riattivi dal conto.","chiave":"atv_…"}}}},"404":{"description":"il volto che hai chiesto non c'e': una `persona` che non e' in catalogo e non e' un tuo avatar (`persona_sconosciuta`, con `persone_disponibili`), un `avatar` che non e' di questo conto (`avatar_sconosciuto`), o un `riferimento` che questa chiave non ha caricato (`riferimento_sconosciuto`). **Un avatar di un altro conto e un avatar inesistente ricevono lo STESSO 404**: dalla differenza si imparerebbe quali avatar esistono"},"409":{"description":"due tuoi avatar portano quel `nome` e non si indovina quale aprire (`nome_ambiguo`, con `avatar_con_quel_nome`): il nome non e' un identificativo, e prendere il piu' recente aprirebbe un volto che nessuno ha chiesto sembrando funzionare. Si passa l'`id` nel campo `avatar`"},"410":{"description":"l'`avatar` e' in archivio ma i suoi file non sono piu' **su questa macchina** (`avatar_senza_file`): si cancella con `DELETE /v1/conti/{conto}/avatar/{avatar}` e si rifa'. Non e' un caso raro: con il motore su un pod elastico che si accende e si spegne e' lo scenario normale del trasloco"},"503":{"description":"la sessione non si apre perché manca un pezzo **su questa macchina**: un anello della catena non è in servizio (`catena_non_completa`, con `motori_mancanti`, `perche` e **fra quanto riprovare**: `riprova_fra_secondi` nel corpo e `Retry-After` nell'intestazione, D-445 — i due campi li porta SOLO questo codice), il volto della persona non è qui (`volto_non_disponibile`), o il registro delle persone non si carica (`registro_persone_non_leggibile`)"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"X-API-Key":[]}]}},"/v1/sessioni/{identificativo}":{"get":{"tags":["sessioni"],"summary":"Stato e tempi della sessione","description":"Come sta questa sessione e quanto ci ha messo l'ultima volta che ha parlato.\n\nÈ la rotta che permette a chi ci pilota da fuori di sapere **senza chiedere\nun altro video** se la persona è pronta, quante volte ha parlato e dove\nfinisce il tempo: quanto la voce, quanto il volto, quanto il totale. Del\nriferimento si dice l'`id` e se il file c'è ancora — **mai il percorso su\ndisco**: dov'è il volto sulla nostra macchina non è affare di nessuno\n(brief §7).\n\nUna sessione di **un'altra chiave** dà 404, non 403: è la stessa regola dei\nriferimenti, chi bussa non deve nemmeno sapere che esiste. Una sessione\n**chiusa** dà anch'essa 404 — il `DELETE` cancella per davvero e non resta\nniente da leggere — ma al suo proprietario si dice che è stato lui a\nchiuderla e quando, invece di fingere che non sia mai esistita.","operationId":"leggi_sessione_v1_sessioni__identificativo__get","security":[{"X-API-Key":[]}],"parameters":[{"name":"identificativo","in":"path","required":true,"schema":{"type":"string","title":"Identificativo"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessioneLetta"}}}},"401":{"description":"chiave mancante, sconosciuta o revocata","content":{"application/json":{"example":{"errore":"chiave_mancante","spiegazione":"manca l'intestazione X-API-Key: ogni chiamata sotto /v1 la vuole.","intestazione_attesa":"X-API-Key","documentazione":"/inizia"}}}},"429":{"description":"quota giornaliera esaurita (vedi `Retry-After`)","content":{"application/json":{"example":{"errore":"quota_esaurita","spiegazione":"questa chiave ha consumato 200 chiamate su 200 previste per oggi","quota_giorno":200,"usate_oggi":200,"riprova_fra_secondi":43200}}}},"403":{"description":"la chiave e' valida ma **non puo' fare questa chiamata adesso**, e `errore` dice quale dei motivi: `chiave_sospesa` (si riattiva, non si riconia), `licenza_sospesa` e `licenza_chiusa` (il tenant, non la chiave: la prima si riapre, la seconda no), `origine_non_ammessa` (questa chiave accetta solo certe origini). Non e' un 401: la chiave esiste ed e' tua","content":{"application/json":{"example":{"errore":"chiave_sospesa","spiegazione":"questa chiave e' sospesa: esiste, e' tua, e torna a funzionare quando la riattivi dal conto.","chiave":"atv_…"}}}},"404":{"description":"sessione sconosciuta, di un'altra chiave, oppure chiusa"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["sessioni"],"summary":"Chiudi la sessione e cancella i riferimenti","description":"Chiude e **cancella i file dal disco**, dicendo quanti ne ha tolti.\n\nIl volto resta da noi (brief §7) vale in entrambe le direzioni: non esce di\nqui, e quando il cliente dice che ha finito non resta nemmeno qui.","operationId":"chiudi_v1_sessioni__identificativo__delete","security":[{"X-API-Key":[]}],"parameters":[{"name":"identificativo","in":"path","required":true,"schema":{"type":"string","title":"Identificativo"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessioneChiusa"}}}},"401":{"description":"chiave mancante, sconosciuta o revocata","content":{"application/json":{"example":{"errore":"chiave_mancante","spiegazione":"manca l'intestazione X-API-Key: ogni chiamata sotto /v1 la vuole.","intestazione_attesa":"X-API-Key","documentazione":"/inizia"}}}},"429":{"description":"quota giornaliera esaurita (vedi `Retry-After`)","content":{"application/json":{"example":{"errore":"quota_esaurita","spiegazione":"questa chiave ha consumato 200 chiamate su 200 previste per oggi","quota_giorno":200,"usate_oggi":200,"riprova_fra_secondi":43200}}}},"403":{"description":"la chiave e' valida ma **non puo' fare questa chiamata adesso**, e `errore` dice quale dei motivi: `chiave_sospesa` (si riattiva, non si riconia), `licenza_sospesa` e `licenza_chiusa` (il tenant, non la chiave: la prima si riapre, la seconda no), `origine_non_ammessa` (questa chiave accetta solo certe origini). Non e' un 401: la chiave esiste ed e' tua","content":{"application/json":{"example":{"errore":"chiave_sospesa","spiegazione":"questa chiave e' sospesa: esiste, e' tua, e torna a funzionare quando la riattivi dal conto.","chiave":"atv_…"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/sessioni/{identificativo}/attesa":{"get":{"tags":["sessioni"],"summary":"Il girato d'attesa di questa sessione (la base viva del cliente)","description":"Che cosa mettere sul palco **mentre la persona ascolta**.\n\nLo specchio di `GET /v1/persone/{id}/attesa` per il volto che il cliente si\nè portato. Il catalogo ha il suo girato d'attesa — la stessa ripresa da cui\nnascono le labbra — e da ieri la stanza lo mette in loop invece del ritratto\nche respira. Il cliente non aveva niente: la sua ripresa è una persona che\n**parla**, e metterla in loop dietro un'attesa muta si vede subito.\n\nQui esce la sua **base viva** (`motori/volto/base_viva.py`): il tratto a\nbocca chiusa e testa calma più lungo del suo video, montato avanti e\nindietro. È lo **stesso file** da cui nascono le pose del suo lip-sync — non\nuna seconda copia da tenere allineata — quindi il passaggio fra attesa e\nrisposta è lo stesso viso, ripreso una volta.\n\n**Muto per costruzione** e in risoluzione nativa. `Cache-Control` privata:\nè il volto di una persona reale, e un proxy non deve tenerne una copia.\n\nIl cliente che **parla dal primo all'ultimo secondo** riceve 200 lo stesso\n(9/9/2026, verifica della regia): la base nasce dal **tratto con meno\nbocca** — la finestra a testa calma con l'apertura mediana più bassa — e\nl'intestazione `X-Base-Viva-Ripiego` lo dichiara, come lo dichiarano il\nreferto e `GET /v1/sessioni/{id}`. Fino a stamattina qui c'era un 404\nonesto e sul palco restava il ritratto fermo: onesto e inutile.\n\n**«Non ancora» non è «non esiste»** (10/9/2026, tornata 6 punto 4). La base\nviva si monta al caricamento, su un filo che nessuno aspetta: chi apre la\nsessione un secondo dopo aver caricato — cioè chiunque, perché è il percorso\ndella sala — arrivava qui mentre ffmpeg stava ancora scrivendo, e riceveva un\n**404**. Un 404 è definitivo: la stanza smetteva di chiedere e sul palco\nrestava il ritratto fermo per tutta la seduta, mentre la base viva era pronta\nda un pezzo. Adesso quel caso è un **503 `attesa_in_preparazione`** con\n`Retry-After`, la stessa lingua con cui questa API dice «occupato» ovunque, e\nche la stanza già sa riprovare.\n\nIl 404 resta, ed è di tre specie — tutte «non ce l'ha e non potrà averla»,\ntutte scritte nella risposta: la sessione è di catalogo (chiedi\n`GET /v1/persone/{id}/attesa`), il cliente ha caricato una **foto sola**\n(volto senza base viva: è la strada 2, generarla, e non è costruita), oppure\ndal suo video non si è potuta montare nessuna base — nemmeno col ripiego, e\nil montaggio è **finito** con quell'esito. Chi chiama deve saper ripiegare\nsul ritratto, che è il comportamento di prima del 9/9/2026.","operationId":"attesa_della_sessione_v1_sessioni__identificativo__attesa_get","security":[{"X-API-Key":[]}],"parameters":[{"name":"identificativo","in":"path","required":true,"schema":{"type":"string","title":"Identificativo"}}],"responses":{"200":{"description":"la base viva, mp4 muto in loop","content":{"video/mp4":{}}},"401":{"description":"chiave mancante, sconosciuta o revocata","content":{"application/json":{"example":{"errore":"chiave_mancante","spiegazione":"manca l'intestazione X-API-Key: ogni chiamata sotto /v1 la vuole.","intestazione_attesa":"X-API-Key","documentazione":"/inizia"}}}},"429":{"description":"quota giornaliera esaurita (vedi `Retry-After`)","content":{"application/json":{"example":{"errore":"quota_esaurita","spiegazione":"questa chiave ha consumato 200 chiamate su 200 previste per oggi","quota_giorno":200,"usate_oggi":200,"riprova_fra_secondi":43200}}}},"403":{"description":"la chiave e' valida ma **non puo' fare questa chiamata adesso**, e `errore` dice quale dei motivi: `chiave_sospesa` (si riattiva, non si riconia), `licenza_sospesa` e `licenza_chiusa` (il tenant, non la chiave: la prima si riapre, la seconda no), `origine_non_ammessa` (questa chiave accetta solo certe origini). Non e' un 401: la chiave esiste ed e' tua","content":{"application/json":{"example":{"errore":"chiave_sospesa","spiegazione":"questa chiave e' sospesa: esiste, e' tua, e torna a funzionare quando la riattivi dal conto.","chiave":"atv_…"}}}},"404":{"description":"sessione sconosciuta o chiusa; oppure questa sessione non ha una base viva **e non potrà averla** (catalogo, foto sola, montaggio impossibile), e la risposta dice perché"},"503":{"description":"la base viva si sta montando adesso: `Retry-After` dice fra quanto richiederla. Non è un rifiuto"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/sessioni/{identificativo}/parla":{"post":{"tags":["sessioni"],"summary":"Fai parlare la persona: testo (o audio) → video/mp4","description":"Usa il riferimento e la lingua della sessione. Stessa catena di `POST /parla`.\n\n**A spezzoni di predefinito**, quando la domanda è `testo`: la risposta esce\nmentre si genera e il cliente comincia a vedere dopo la **prima frase**, non\ndopo l'ultima. Con un `audio` già pronto no, e il perché è nel corpo della\nrotta: non c'è un testo da spezzare.\n\nEmette sul flusso eventi (`GET /v1/sessioni/{id}/eventi`) un `parla` quando\ncomincia e un `fine` quando il video è pronto — oppure un `errore`.","operationId":"parla_in_sessione_v1_sessioni__identificativo__parla_post","security":[{"X-API-Key":[]}],"parameters":[{"name":"identificativo","in":"path","required":true,"schema":{"type":"string","title":"Identificativo"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_parla_in_sessione_v1_sessioni__identificativo__parla_post"}}}},"responses":{"200":{"description":"il video, con le misure nelle intestazioni `X-…`","content":{"video/mp4":{}}},"401":{"description":"chiave mancante, sconosciuta o revocata","content":{"application/json":{"example":{"errore":"chiave_mancante","spiegazione":"manca l'intestazione X-API-Key: ogni chiamata sotto /v1 la vuole.","intestazione_attesa":"X-API-Key","documentazione":"/inizia"}}}},"429":{"description":"quota giornaliera esaurita (vedi `Retry-After`)","content":{"application/json":{"example":{"errore":"quota_esaurita","spiegazione":"questa chiave ha consumato 200 chiamate su 200 previste per oggi","quota_giorno":200,"usate_oggi":200,"riprova_fra_secondi":43200}}}},"403":{"description":"questa chiamata vuole il permesso **`parla`** e la chiave non ce l'ha (`permesso_mancante`): la risposta porta `permesso_richiesto` e `permessi_della_chiave`, cosi' si vede la differenza senza indovinare. Un permesso si aggiunge o si toglie **sulla chiave che hai** (il valore non cambia e non c'e' da riconiare niente): si chiede dal conto. Lo stesso 403 lo danno anche `chiave_sospesa`, `licenza_sospesa`, `licenza_chiusa` e `origine_non_ammessa`, che non dipendono da questa rotta","content":{"application/json":{"example":{"errore":"permesso_mancante","permesso_richiesto":"parla","permessi_della_chiave":["lettura"],"chiave":"atv_…"}}}},"503":{"description":"il turno non si può servire **adesso**, e si dice perché: la scheda video sta servendo un altro turno (`servo_occupato`, con `Retry-After` e `riprova_fra_secondi`), la sessione si sta ancora preparando (`sessione_in_preparazione`), oppure un motore non è in servizio su questa macchina (`catena_non_completa`, `motore_non_pronto`, `motore_<nome>_non_in_servizio`). È il caso **normale** quando il motore sta su un pod elastico: un cliente lo gestisce riprovando. Dal 9/10/2026 anche `video_vuoto`: il flusso a spezzoni si è chiuso senza un solo byte, e un mp4 di lunghezza zero non si consegna (D-231)"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/sessioni/{identificativo}/conversa":{"post":{"tags":["sessioni"],"summary":"Domanda a voce → video con la risposta (ascolto → cervello → voce → volto)","description":"La domanda a voce, la risposta parlata dal volto della sessione.\n\nÈ **la stessa catena** della scorciatoia `POST /conversa` del banco\n(`motori/conversa.py`): sarebbe un difetto se le due potessero comportarsi\ndiversamente. Quello che aggiunge la sessione è tutto ciò che sta intorno —\nil riferimento non si ricarica a ogni turno, la lingua è quella dichiarata\nall'apertura, la quota si consuma, il turno finisce nel registro d'uso e\nnegli eventi, e **l'ultimo `/vede` viene usato qui**.\n\nSe l'ultimo sguardo di `/vede` è ancora fresco e non è già stato consumato,\nfinisce nelle istruzioni del cervello e l'intestazione `X-Contesto-Visivo`\nlo dichiara. Viene usato **una volta sola**: il brief dice «la risposta\nsuccessiva», al singolare.\n\n**`X-Ascolto-Dal-Vivo: <turno>`** (facoltativa). Se il turno è già stato\nascoltato in flusso su `WS /v1/sessioni/{id}/ascolto-dal-vivo`, questa\nintestazione lo dichiara e la trascrizione non si rifà: sono 371-434 ms in\nmeno sul cammino critico (misura del 30/9/2026). L'audio si manda **lo\nstesso**, ed è quello che prova — confrontando le durate — che il testo sia\ndi questo turno. Se qualcosa non torna si trascrive l'audio come sempre e il\nturno riesce lo stesso: il motivo finisce nel diario, mai in un errore al\ncliente. `X-Motore-Ascolto` dice da quale delle due strade è arrivato il\ntesto, e `X-Tempo-Ascolto-Ms` quanto la catena ha davvero aspettato.","operationId":"conversa_in_sessione_v1_sessioni__identificativo__conversa_post","security":[{"X-API-Key":[]}],"parameters":[{"name":"identificativo","in":"path","required":true,"schema":{"type":"string","title":"Identificativo"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_conversa_in_sessione_v1_sessioni__identificativo__conversa_post"}}}},"responses":{"200":{"description":"il video della risposta, con le misure dei quattro anelli nelle intestazioni `X-…`","content":{"video/mp4":{}}},"401":{"description":"chiave mancante, sconosciuta o revocata","content":{"application/json":{"example":{"errore":"chiave_mancante","spiegazione":"manca l'intestazione X-API-Key: ogni chiamata sotto /v1 la vuole.","intestazione_attesa":"X-API-Key","documentazione":"/inizia"}}}},"429":{"description":"quota giornaliera esaurita (vedi `Retry-After`)","content":{"application/json":{"example":{"errore":"quota_esaurita","spiegazione":"questa chiave ha consumato 200 chiamate su 200 previste per oggi","quota_giorno":200,"usate_oggi":200,"riprova_fra_secondi":43200}}}},"403":{"description":"questa chiamata vuole il permesso **`conversa`** e la chiave non ce l'ha (`permesso_mancante`): la risposta porta `permesso_richiesto` e `permessi_della_chiave`, cosi' si vede la differenza senza indovinare. Un permesso si aggiunge o si toglie **sulla chiave che hai** (il valore non cambia e non c'e' da riconiare niente): si chiede dal conto. Lo stesso 403 lo danno anche `chiave_sospesa`, `licenza_sospesa`, `licenza_chiusa` e `origine_non_ammessa`, che non dipendono da questa rotta","content":{"application/json":{"example":{"errore":"permesso_mancante","permesso_richiesto":"conversa","permessi_della_chiave":["lettura"],"chiave":"atv_…"}}}},"503":{"description":"il turno non si può servire **adesso**, e `errore` dice quale dei casi. **Un posto preso**: la scheda video sta servendo un altro turno (`servo_occupato`, con `Retry-After`, `riprova_fra_secondi`, `in_coda` e `tetto_turni_contemporanei`; il semaforo si legge PRIMA del gesto in `GET /salute` → `coda`, e questa rotta è fra le `rotte_che_prendono_un_posto`: col tetto a 2 e quattro sessioni insieme due clienti su quattro lo ricevono). **La sessione non è pronta**: `sessione_in_preparazione`. **Un motore non è in servizio su questa macchina**: `catena_non_completa` (con `motori_mancanti` e `perche`), `motore_non_pronto`, `catena_non_caricabile`, `catena_interrotta`. I primi due si riprovano col numero che la risposta porta, gli altri no finché il motore non torna. Dal 21/10/2026 i nomi sono scritti qui: il CODICE c'era dal 4/10 (D-060) e i NOMI no, e un cliente che riprova un `servo_occupato` come se fosse un motore spento sbaglia il gesto (T12). Dal 9/10/2026 anche `video_vuoto`: il flusso a spezzoni si è chiuso senza un solo byte, e un mp4 di lunghezza zero non si consegna (D-231)"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/sessioni/{identificativo}/interrompi":{"post":{"tags":["sessioni"],"summary":"Ferma la risposta in corso (barge-in)","description":"Chi ascolta ha ricominciato a parlare: la risposta in corso non serve più.\n\nServe alla stanza che conversa **a microfono aperto**: quando il rilevatore\ndi voce sente che l'utente ha ripreso la parola mentre la persona digitale\nsta ancora rispondendo, il browser annulla la richiesta (`AbortController`)\n**e** chiama questa rotta. Le due cose sono complementari e nessuna delle\ndue è di troppo:\n\n- l'annullamento ferma il video **da chi guarda**, subito;\n- questa rotta ferma la generazione **da chi genera**, senza dipendere da\n  quando l'interprete si accorge che il flusso è stato abbandonato.\n\nNon è un errore non avere niente da fermare: chi interrompe due volte, o\ninterrompe una risposta appena finita, riceve **200** con\n`interrotta: false`. Un 404 lì racconterebbe un guasto che non c'è.\n\nLa sessione **resta aperta** e utilizzabile: il turno interrotto risulta nel\nregistro con i pezzi che aveva fatto in tempo a fare, e il turno successivo\nriparte normalmente. Per chiudere davvero c'è `DELETE /v1/sessioni/{id}`.\n\nVale sulla modalità **a spezzoni** (§3.7), che è il predefinito: è l'unica\nin cui, mentre il client scarica, la macchina sta ancora generando. Con\nl'mp4 intero il video esiste già tutto quando parte il primo byte, e\ninterrompere non risparmierebbe niente — quindi la risposta lo dice invece\ndi fingere di aver fermato qualcosa.","operationId":"interrompi_in_sessione_v1_sessioni__identificativo__interrompi_post","security":[{"X-API-Key":[]}],"parameters":[{"name":"identificativo","in":"path","required":true,"schema":{"type":"string","title":"Identificativo"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TurnoInterrotto"}}}},"401":{"description":"chiave mancante, sconosciuta o revocata","content":{"application/json":{"example":{"errore":"chiave_mancante","spiegazione":"manca l'intestazione X-API-Key: ogni chiamata sotto /v1 la vuole.","intestazione_attesa":"X-API-Key","documentazione":"/inizia"}}}},"429":{"description":"quota giornaliera esaurita (vedi `Retry-After`)","content":{"application/json":{"example":{"errore":"quota_esaurita","spiegazione":"questa chiave ha consumato 200 chiamate su 200 previste per oggi","quota_giorno":200,"usate_oggi":200,"riprova_fra_secondi":43200}}}},"403":{"description":"la chiave e' valida ma **non puo' fare questa chiamata adesso**, e `errore` dice quale dei motivi: `chiave_sospesa` (si riattiva, non si riconia), `licenza_sospesa` e `licenza_chiusa` (il tenant, non la chiave: la prima si riapre, la seconda no), `origine_non_ammessa` (questa chiave accetta solo certe origini). Non e' un 401: la chiave esiste ed e' tua","content":{"application/json":{"example":{"errore":"chiave_sospesa","spiegazione":"questa chiave e' sospesa: esiste, e' tua, e torna a funzionare quando la riattivi dal conto.","chiave":"atv_…"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/sessioni/{identificativo}/vede":{"post":{"tags":["sessioni"],"summary":"Fotogramma della webcam → descrizione breve e non identificante","description":"L'avatar guarda chi gli parla, e se ne ricorda per la risposta dopo (brief §7).\n\n**Il fotogramma non tocca il disco.** Non c'è cartella di lavoro e non c'è\nfile temporaneo da cancellare: i byte del multipart vanno in memoria e\nfiniscono direttamente in `motori.vista.descrivi_da_byte()`, che è la via\nche il motore stesso indica per questa rotta. È il motivo per cui non si usa\n`descrivi(percorso)`, che vorrebbe un file: la garanzia più solida che\nun'immagine non resti su disco è non averla mai scritta.\n\n**Della descrizione non si fida nessuno.** Quello che torna è già passato\ndal filtro di non identificazione (`motori/vista/compito.py`): niente età,\ngenere, etnia, salute, emozioni, aspetto, nomi, né il testo dei documenti\ninquadrati. In archivio resta solo quella frase — e sparisce con il `DELETE`\ndella sessione.\n\nNel **registro d'uso** finisce la chiamata (rotta, esito, millisecondi,\nbyte), come per ogni altra rotta di `/v1`: mai il fotogramma, mai la\ndescrizione.","operationId":"vede_in_sessione_v1_sessioni__identificativo__vede_post","security":[{"X-API-Key":[]}],"parameters":[{"name":"identificativo","in":"path","required":true,"schema":{"type":"string","title":"Identificativo"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_vede_in_sessione_v1_sessioni__identificativo__vede_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SguardoDellaSessione"}}}},"401":{"description":"chiave mancante, sconosciuta o revocata","content":{"application/json":{"example":{"errore":"chiave_mancante","spiegazione":"manca l'intestazione X-API-Key: ogni chiamata sotto /v1 la vuole.","intestazione_attesa":"X-API-Key","documentazione":"/inizia"}}}},"429":{"description":"quota giornaliera esaurita (vedi `Retry-After`)","content":{"application/json":{"example":{"errore":"quota_esaurita","spiegazione":"questa chiave ha consumato 200 chiamate su 200 previste per oggi","quota_giorno":200,"usate_oggi":200,"riprova_fra_secondi":43200}}}},"403":{"description":"questa chiamata vuole il permesso **`vede`** e la chiave non ce l'ha (`permesso_mancante`): la risposta porta `permesso_richiesto` e `permessi_della_chiave`, cosi' si vede la differenza senza indovinare. Un permesso si aggiunge o si toglie **sulla chiave che hai** (il valore non cambia e non c'e' da riconiare niente): si chiede dal conto. Lo stesso 403 lo danno anche `chiave_sospesa`, `licenza_sospesa`, `licenza_chiusa` e `origine_non_ammessa`, che non dipendono da questa rotta","content":{"application/json":{"example":{"errore":"permesso_mancante","permesso_richiesto":"vede","permessi_della_chiave":["lettura"],"chiave":"atv_…"}}}},"503":{"description":"la vista non può rispondere **adesso**, e `errore` dice quale dei tre casi: `vista_non_pronta` (i pesi non ci sono, il fotogramma non si decodifica, oppure il motore è sparito fra il controllo e la chiamata), `catena_non_completa` (la vista non è in servizio su questa macchina: `motori_mancanti` dice quale), e `servo_occupato` — che è il caso che questa voce taceva fino al 21/10/2026 (T12): quando `vista` è delegata a un servo remoto, e quel servo sta servendo un altro turno, anche l'occhio riceve il rifiuto educato con `Retry-After` e `riprova_fra_secondi`. Questa rotta NON prende un posto del servizio, quindi il semaforo di `GET /salute` → `coda` non lo prevede: la riga che la nomina è `rotte_che_possono_dire_servo_occupato`"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/sessioni/{identificativo}/eventi":{"get":{"tags":["sessioni"],"summary":"Flusso eventi della sessione (SSE)","description":"Server-Sent Events. Quattro tipi, nessuno di più:\n\n- **`pronto`** — arriva **due volte** e le due dicono cose diverse: la prima\n  «la sessione è aperta e accetta `/parla`», la seconda — con\n  `volto_pronto: true` e la `preparazione` per esteso — «il volto è\n  preparato sul pod e la voce clonata è calda: il primo turno parte come il\n  secondo». Chi ha fretta può parlare già dopo la prima e il turno aspetterà\n  la preparazione in corso (dicendolo in `x-attesa-preparazione-ms`); chi\n  vuole i tempi della giuria abilita il microfono sulla seconda;\n- **`parla`** — un turno è cominciato (`turno`, `caratteri`, `lingua`);\n- **`fine`** — il turno è finito (`ms`, `byte`, `secondi`), oppure la\n  sessione è stata chiusa (`motivo: sessione_chiusa`) e il flusso termina;\n- **`errore`** — quel turno è fallito, con il motivo.\n\nChi si collega a metà riceve prima lo **storico** (fino a 200 eventi), poi\nil seguito: con `?da=<ordinale>` o l'intestazione `Last-Event-ID` si riparte\nda dove si era rimasti. Ogni 15 s passa un commento `: battito` che tiene\nviva la connessione attraverso i proxy.","operationId":"eventi_sessione_v1_sessioni__identificativo__eventi_get","security":[{"X-API-Key":[]}],"parameters":[{"name":"identificativo","in":"path","required":true,"schema":{"type":"string","title":"Identificativo"}},{"name":"da","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Da"}}],"responses":{"200":{"description":"flusso `text/event-stream` con gli eventi `pronto`, `parla`, `fine`, `errore`","content":{"text/event-stream":{"example":"id: 1\nevent: pronto\ndata: {\"sessione\":\"s_1a2b3c\"}\n\n"}}},"401":{"description":"chiave mancante, sconosciuta o revocata","content":{"application/json":{"example":{"errore":"chiave_mancante","spiegazione":"manca l'intestazione X-API-Key: ogni chiamata sotto /v1 la vuole.","intestazione_attesa":"X-API-Key","documentazione":"/inizia"}}}},"429":{"description":"quota giornaliera esaurita (vedi `Retry-After`)","content":{"application/json":{"example":{"errore":"quota_esaurita","spiegazione":"questa chiave ha consumato 200 chiamate su 200 previste per oggi","quota_giorno":200,"usate_oggi":200,"riprova_fra_secondi":43200}}}},"403":{"description":"la chiave e' valida ma **non puo' fare questa chiamata adesso**, e `errore` dice quale dei motivi: `chiave_sospesa` (si riattiva, non si riconia), `licenza_sospesa` e `licenza_chiusa` (il tenant, non la chiave: la prima si riapre, la seconda no), `origine_non_ammessa` (questa chiave accetta solo certe origini). Non e' un 401: la chiave esiste ed e' tua","content":{"application/json":{"example":{"errore":"chiave_sospesa","spiegazione":"questa chiave e' sospesa: esiste, e' tua, e torna a funzionare quando la riattivi dal conto.","chiave":"atv_…"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/conti/accesso":{"post":{"tags":["conti (self-service)"],"summary":"Entra nel tuo conto (nome utente e password)","description":"L'ingresso del percorso di giuria: si **entra**, non ci si iscrive.\n\nRestituisce un `token_conto` nuovo, che è la stessa credenziale della\nregistrazione self-service (`X-Conto-Token`): da qui in poi la galleria, gli\navatar e gli esempi si aprono con quello. I token precedenti restano validi —\nentrare da una seconda scheda non deve buttare fuori la prima.\n\nL'utente **demo** della giuria esiste solo se il servizio ha in ambiente la\nvariabile `AGILETAV_UTENTE_DEMO_PASSWORD`: nel codice non c'è nessuna\npassword. Se l'utente demo c'è o no lo dice `GET /salute` — che si guarda\n**dal box**, perché sul sito pubblico è chiusa (`infra/nginx-agiletav.conf`).","operationId":"accesso_v1_conti_accesso_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Accesso"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessoRiuscito"}}}},"401":{"description":"utente sconosciuto o password errata"},"429":{"description":"troppi tentativi da questo indirizzo"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/conti/{conto_id}/galleria":{"get":{"tags":["conti (self-service)"],"summary":"La galleria del conto: le persone del prodotto e i tuoi avatar","description":"Quello che il titolare vede appena entrato: le nostre produzioni e le sue.\n\nLe tre persone digitali del prodotto — volto fotorealistico, nome, ruolo,\ntono, voce, ritratto e video di presentazione — e, sotto, gli avatar che ha\nsalvato lui. `video_pronto` dice, persona per persona, se il video di\npresentazione è davvero su questa macchina: un'anteprima che non parte è\npeggio di un'anteprima che si sa non esserci.","operationId":"galleria_v1_conti__conto_id__galleria_get","parameters":[{"name":"conto_id","in":"path","required":true,"schema":{"type":"string","title":"Conto Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"token del conto mancante o errato"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/persone/{identificativo}/presentazione":{"get":{"tags":["conti (self-service)","persone"],"summary":"Il video di presentazione di una persona (mp4)","description":"Quindici secondi in cui la persona si presenta, con il lip-sync vero.\n\nSi apre con la chiave (`X-API-Key`) **o** con il token del conto\n(`X-Conto-Token`), perché serve alla galleria di chi è appena entrato e non\nha ancora coniato una chiave.\n\nDall'8/9/2026 la card servita è quella **a diffusione** — il girato\nWan2.2-S2V rifinito, passato sotto MuseTalk — perché è quella che le tre\nschede dichiarano. La card della catena di prima non è sparita: si chiede\ncon `?sorgente=attuale`, ed è così che si guardano le due una accanto\nall'altra senza scaricare il repository. Quale delle due si è ottenuta lo\ndice l'intestazione `X-Volto-Sorgente`, come su `/parla` e `/conversa`.\n\nSe il video non è su questa macchina la risposta è **404 con il motivo**, non\nun file vuoto: i video si producono con il motore del volto\n(`banco/risultati/persone/<id>.mp4`) e su una macchina senza pesi non ci\nsono. Meglio dirlo che far partire un lettore su niente.","operationId":"presentazione_v1_persone__identificativo__presentazione_get","security":[{"X-API-Key":[]}],"parameters":[{"name":"identificativo","in":"path","required":true,"schema":{"type":"string","title":"Identificativo"}},{"name":"sorgente","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sorgente"}}],"responses":{"200":{"description":"il video di presentazione","content":{"video/mp4":{}}},"401":{"description":"serve una credenziale: la chiave API (X-API-Key) oppure il token del conto (X-Conto-Token)"},"403":{"description":"la chiave e' valida ma non puo' chiamare adesso: `chiave_sospesa`, `licenza_sospesa`, `licenza_chiusa` o `origine_non_ammessa`. Non e' un 401: la chiave esiste ed e' tua"},"404":{"description":"la persona non c'è, o il video non è su questa macchina"},"503":{"description":"il registro delle persone non si carica su questa macchina (`registro_persone_non_leggibile`): la gemella della vetrina (`/v1/conti/persone/{id}/presentazione`) lo dichiarava già, questa no (D-060)"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/conti/persone/{identificativo}/presentazione":{"get":{"tags":["conti (self-service)","persone"],"summary":"Il video di presentazione in vetrina (mp4, senza chiave)","description":"La card di catalogo, **senza chiave**: è la merce esposta in vetrina.\n\nSorella di `GET /v1/conti/persone/{id}/volto` (api/v1.py, `vetrina_volto`) e\ncon lo stesso identico ragionamento, che qui vale se possibile di più: il\nritratto fermo dice come è fatta la persona, la card dice **che cosa\ncompri** — che parla, che ha una voce, che le labbra seguono l'audio. Fino\nall'8/9 la landing la nascondeva dietro l'accesso, e chi apriva il sito\ndoveva credere sulla parola a una fotografia. F19 del lascito dei tre\neliminati (9/9/2026): «galleria con video prima del login».\n\nPerché è accettabile che sia aperta, e dove si ferma l'apertura:\n  · la persona è **inventata** — volto generato, nome di fantasia, voce\n    sintetica — quindi in questi quindici secondi non c'è niente di nessuno;\n  · è un **file già prodotto**, servito da disco: chi entra qui non apre\n    sessioni, non fa girare la GPU e non consuma la quota di nessuno.\n    Il magazzino resta chiuso: `POST /v1/sessioni` e `/parla` vogliono la\n    chiave come sempre.\n\n**Solo le persone del catalogo.** Il registro (`motori/persone`) contiene le\ntre di serie e nient'altro: gli avatar salvati dai clienti stanno in\n`api/conti.py`, non passano di qui e non hanno una card in vetrina.\n\nSempre la card a **diffusione** (quella che le schede dichiarano): la\nvetrina non prende `?sorgente=`, perché il confronto fra le due catene è\nlavoro da chi ha una chiave, non da chi sta guardando il negozio.","operationId":"vetrina_presentazione_v1_conti_persone__identificativo__presentazione_get","parameters":[{"name":"identificativo","in":"path","required":true,"schema":{"type":"string","title":"Identificativo"}}],"responses":{"200":{"description":"la card di catalogo della persona","content":{"video/mp4":{}}},"404":{"description":"la persona non c'è, o la card non è su questa macchina"},"503":{"description":"il registro delle persone non è leggibile"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/conti/{conto_id}/avatar":{"post":{"tags":["conti (self-service)"],"summary":"Salva un avatar nel conto (resta oltre la seduta)","description":"Da «materiale di una sessione» a «una persona dell'utente».\n\nLa sessione dura quanto la seduta e alla chiusura cancella i suoi file;\nl'avatar salvato no: la sua foto (o il suo video) e il suo campione di voce\nrestano su questo disco finché l'avatar esiste — la scadenza dei riferimenti\nli salta apposta (`api/sessioni.proteggi_con`). È ciò che permette al\ntitolare di chiudere il browser, tornare il giorno dopo e ritrovare la sua\nfaccia in galleria.\n\nDopo, l'avatar si apre come una persona: `POST /v1/sessioni` con\n`{\"avatar\": \"av_…\"}`, con la chiave API o con il token del conto.","operationId":"salva_avatar_v1_conti__conto_id__avatar_post","parameters":[{"name":"conto_id","in":"path","required":true,"schema":{"type":"string","title":"Conto Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NuovoAvatar"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AvatarSalvato"}}}},"401":{"description":"token del conto mancante o errato"},"404":{"description":"il riferimento non è di questo conto"},"422":{"description":"nome non valido"}}},"get":{"tags":["conti (self-service)"],"summary":"I tuoi avatar salvati","description":"L'elenco, dal più recente. Vuoto è una risposta buona: non ne hai ancora.","operationId":"elenca_avatar_v1_conti__conto_id__avatar_get","parameters":[{"name":"conto_id","in":"path","required":true,"schema":{"type":"string","title":"Conto Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"token del conto mancante o errato"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/conti/{conto_id}/avatar/{avatar_id}":{"delete":{"tags":["conti (self-service)"],"summary":"Cancella un avatar e i suoi file","description":"Cancella l'avatar **e il materiale della persona vera** che c'era sotto.\n\n«Il materiale resta lì e si cancella su richiesta» (percorso di giuria,\nvincoli): questa è la richiesta. La risposta dice **quanti file** sono spariti\ndal disco e quanti byte sono stati liberati — non «fatto», che non si può\nverificare. Un file ancora usato da una sessione aperta non si cancella e lo\nsi dichiara: sparirà alla chiusura di quella sessione.","operationId":"cancella_avatar_v1_conti__conto_id__avatar__avatar_id__delete","parameters":[{"name":"conto_id","in":"path","required":true,"schema":{"type":"string","title":"Conto Id"}},{"name":"avatar_id","in":"path","required":true,"schema":{"type":"string","title":"Avatar Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"token del conto mancante o errato"},"404":{"description":"nessun avatar con quell'id in questo conto"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/conti/{conto_id}/chiavi":{"get":{"tags":["conti (self-service)"],"summary":"Le chiavi API di questo conto (mascherate, con stato e uso)","description":"Quali chiavi ha coniato questo conto, come stanno e quanto hanno lavorato.\n\nIl **valore** non c'è e non ci sarà mai: in archivio ne resta solo\nl'impronta, e la chiave si vede una volta sola, quando nasce. Qui c'è tutto\nil resto — il nome che le hai dato, il **tipo** (ambiente o persona), la\npersona associata, la forma mascherata (`atv_6df1…`), lo stato, quando è\nstata creata, l'ultimo uso, le chiamate e i minuti di video, la quota\nresidua e l'`avviso` quando sta per finire.","operationId":"elenca_chiavi_v1_conti__conto_id__chiavi_get","parameters":[{"name":"conto_id","in":"path","required":true,"schema":{"type":"string","title":"Conto Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"token del conto mancante o errato"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["conti (self-service)"],"summary":"Conia la TUA chiave API — di ambiente o della singola persona","description":"Una chiave API vera, intestata all'email del conto. **Due livelli.**\n\n- **Ambiente** (predefinito, e la chiave di sempre): apre tutto ciò che il\n  conto ha comprato.\n- **Persona** (`{\"tipo\": \"persona\", \"persona\": \"…\"}`): vale **solo** per\n  quella persona — una del catalogo che hai comprato, o un tuo avatar.\n  `GET /v1/persone` mostra solo lei, le sessioni si aprono solo su di lei, e\n  una sessione su un'altra persona riceve **403**. Quota e registro d'uso\n  sono suoi, e si revoca senza toccare le altre.\n\nI due campi viaggiano **insieme**: `persona` senza `tipo: persona` è un\n**422 `persona_senza_tipo`** e non una chiave di ambiente in silenzio —\nl'archivio azzera la persona di una chiave di ambiente, quindi quella\nchiave aprirebbe tutto il conto mentre tu ne hai chiesta una sola (D-207).\n\nFacoltativi, e validi per entrambi i livelli: `nome` (per ritrovarla in\nelenco), `scadenza_giorni`, `permessi` (`lettura`, `parla`, `conversa`,\n`vede`, `clonazione`) e `origini` (da dove può essere usata).\n\nLa chiave nasce nello **stesso archivio** che l'API già legge\n(`dati/chiavi.sqlite3`, via `api/chiavi.py`) e funziona subito. Si mostra\n**una volta sola**: dopo questa risposta il valore non esiste più da\nnessuna parte, e in elenco resta la forma mascherata `atv_6df1…`. Senza\ncorpo il comportamento è **identico a prima**: chiave di ambiente, nessuna\nrestrizione. Se il conto non ha ancora un acquisto valido, si riceve un\n**402**: la chiave è ciò che si è comprato, non un dato che si prende senza\npassare dalla cassa.\n\n**Due credenziali**, come su `…/acquisto` (12/10/2026, D-319): il token del\nconto, oppure la chiave `rivende` di chi ha aperto questo conto. La chiave\nconiata è sempre del **cliente** e non eredita `rivende`: un cliente di chi\nrivende non apre conti a sua volta.","operationId":"conia_chiave_v1_conti__conto_id__chiavi_post","parameters":[{"name":"conto_id","in":"path","required":true,"schema":{"type":"string","title":"Conto Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/NuovaChiave"},{"type":"null"}],"title":"Corpo"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChiaveConiata"}}}},"401":{"description":"token del conto mancante o errato; in alternativa una chiave API col permesso `rivende`, se è quella che ha aperto il conto"},"402":{"description":"nessun acquisto valido: prima si acquista"},"403":{"description":"la chiave API presentata non ha il permesso `rivende`, oppure non è lei che ha aperto questo conto (`conto_non_aperto_da_questa_chiave`)"},"404":{"description":"la persona chiesta non è di questo conto"},"422":{"description":"tipo, permessi o persona non validi, oppure `persona` senza `tipo: persona` (`persona_senza_tipo`): una chiave più larga di quella chiesta non si conia in silenzio. `permessi: [\"rivende\"]` è un `rivende_non_si_conia`: questa rotta conia la chiave del CLIENTE (D-438). Se accanto a `rivende` c'è anche un nome che non esiste, il rifiuto è `permessi_non_validi` — il nome sbagliato parla per primo — ma nomina **tutti e due** i motivi: `permessi_sconosciuti` e `permesso_rifiutato: rivende`, così il divieto non si scopre al secondo tentativo (D-464, rilievo di adp)"}},"security":[{},{"X-API-Key":[]}]}},"/v1/conti/{conto_id}/chiavi/{chiave_id}/uso":{"get":{"tags":["conti (self-service)"],"summary":"Il registro d'uso di UNA chiave: ora, percorso, esito","description":"Che cosa ha fatto questa chiave, riga per riga, dalla più recente.\n\nServe a due domande che si fanno sempre insieme: «questa chiave la usa\nancora qualcuno?» e «da dove viene quel 403?». Non c'è il contenuto delle\nchiamate — non lo conserviamo — ma c'è quando, che cosa e com'è finita.","operationId":"uso_della_chiave_v1_conti__conto_id__chiavi__chiave_id__uso_get","parameters":[{"name":"conto_id","in":"path","required":true,"schema":{"type":"string","title":"Conto Id"}},{"name":"chiave_id","in":"path","required":true,"schema":{"type":"string","title":"Chiave Id"}},{"name":"righe","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Righe"}},{"name":"giorni","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Giorni"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"token del conto mancante o errato"},"404":{"description":"nessuna chiave con quell'id in questo conto"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/conti/{conto_id}/chiavi/{chiave_id}/sospendi":{"post":{"tags":["conti (self-service)"],"summary":"Sospendi una tua chiave (403, e si riattiva)","description":"Spegne una chiave **senza bruciarla**.\n\nÈ la mossa che serve quando una chiave «forse» è finita dove non doveva:\nda qui in poi risponde **403 «chiave sospesa»** e non 401, perché non è\nsparita — è tua, e la riaccendi con «Riattiva» quando hai capito.","operationId":"sospendi_chiave_v1_conti__conto_id__chiavi__chiave_id__sospendi_post","parameters":[{"name":"conto_id","in":"path","required":true,"schema":{"type":"string","title":"Conto Id"}},{"name":"chiave_id","in":"path","required":true,"schema":{"type":"string","title":"Chiave Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"token del conto mancante o errato"},"404":{"description":"nessuna chiave con quell'id in questo conto"},"409":{"description":"la chiave è revocata: non si sospende"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/conti/{conto_id}/chiavi/{chiave_id}/riattiva":{"post":{"tags":["conti (self-service)"],"summary":"Riattiva una tua chiave sospesa","description":"Riaccende una chiave sospesa. Su una revocata non si torna indietro.","operationId":"riattiva_chiave_v1_conti__conto_id__chiavi__chiave_id__riattiva_post","parameters":[{"name":"conto_id","in":"path","required":true,"schema":{"type":"string","title":"Conto Id"}},{"name":"chiave_id","in":"path","required":true,"schema":{"type":"string","title":"Chiave Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"token del conto mancante o errato"},"404":{"description":"nessuna chiave con quell'id in questo conto"},"409":{"description":"la chiave è revocata: non si riattiva"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/conti/{conto_id}/chiavi/{chiave_id}/ruota":{"post":{"tags":["conti (self-service)"],"summary":"Ruota una chiave: la nuova subito, la vecchia per una finestra","description":"Cambia la chiave **senza fermare niente**.\n\nLa nuova nasce con gli stessi poteri della vecchia — tipo, persona, quota,\npermessi, origini, scadenza — e si vede **una volta sola**, adesso. La\nvecchia continua a funzionare per la finestra che scegli (di solito 24 ore):\nè il tempo di sostituirla nelle configurazioni. Poi si spegne da sola, e da\nlì in poi risponde **401**.\n\nSono due chiavi diverse, quindi due quote e due registri d'uso — e i\nriferimenti caricati dalla vecchia restano della vecchia: si ricaricano con\nla nuova, come si farebbe con qualunque credenziale sostituita.","operationId":"ruota_chiave_v1_conti__conto_id__chiavi__chiave_id__ruota_post","parameters":[{"name":"conto_id","in":"path","required":true,"schema":{"type":"string","title":"Conto Id"}},{"name":"chiave_id","in":"path","required":true,"schema":{"type":"string","title":"Chiave Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/Rotazione"},{"type":"null"}],"title":"Corpo"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"token del conto mancante o errato"},"404":{"description":"nessuna chiave con quell'id in questo conto"},"409":{"description":"la chiave è revocata: non si ruota"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/conti/{conto_id}/chiavi/{chiave_id}":{"delete":{"tags":["conti (self-service)"],"summary":"Revoca una tua chiave API","description":"Chi conia una chiave deve poterla **spegnere**, dal suo conto.\n\nFino al 6/9 la revoca era solo dell'amministratore, dal portale: chi provava\nil prodotto accumulava chiavi che non poteva togliere, e una chiave che non\nsi può revocare è una chiave che resta valida per sempre. Si revoca solo una\nchiave **di questo conto** (il legame è `chiavi_coniate`), e la revoca è\ndefinitiva: una chiave revocata non si riattiva, se ne conia un'altra.","operationId":"revoca_chiave_v1_conti__conto_id__chiavi__chiave_id__delete","parameters":[{"name":"conto_id","in":"path","required":true,"schema":{"type":"string","title":"Conto Id"}},{"name":"chiave_id","in":"path","required":true,"schema":{"type":"string","title":"Chiave Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"token del conto mancante o errato"},"404":{"description":"nessuna chiave con quell'id in questo conto"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/conti/{conto_id}/esempi":{"get":{"tags":["conti (self-service)"],"summary":"Esempi pronti per usare l'API dalla tua applicazione","description":"Sei passi in curl, Python e JavaScript, già compilati con questo indirizzo.\n\nÈ la sezione **API** dell'area conto: quella su cui si gioca il confronto con\nTavus, perché «se non si riesce a usare da fuori, la prova è persa». La\nchiave non compare: al suo posto c'è il segnaposto, e la chiave vera si conia\n(una volta sola) con `POST /v1/conti/{id}/chiavi`.","operationId":"esempi_del_conto_v1_conti__conto_id__esempi_get","parameters":[{"name":"conto_id","in":"path","required":true,"schema":{"type":"string","title":"Conto Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"token del conto mancante o errato"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/conti":{"post":{"tags":["conti (self-service)"],"summary":"Apri un conto per un cliente (gesto di rivendita)","description":"Apre un conto **per un cliente**, e restituisce il suo token.\n\n**Vuole una chiave col permesso `rivende`** (8/10/2026, D-194, ordine del\ntitolare A134): aprire un conto non è un gesto che si fa da sé. Prima di\noggi questa rotta nasceva senza nessuna credenziale, e il solo lucchetto\ndella catena della vendita era il fronte pubblico, che la rifiutava per nome\n(404 `non_esposta`, D-096). Misurato: con quattro richieste e il codice promo\nscritto negli esempi di questo stesso contratto, chiunque otteneva una\nchiave viva con tutti e cinque i permessi. Aprirla «per adp» col matcher di\nun percorso non si poteva: un percorso non sa chi chiama.\n\n**Un conto per cliente** (decisione della regia, 8/10): chi rivende apre un\nconto per ogni cliente proprio, così tetti, revoca e misura sono di quel\ncliente e non condivisi. Per questo il `token_conto` **torna al chiamante**\nin questa risposta: è lui che poi acquista e conia la chiave per il cliente.\n\nIl primo passo: un conto, con email e nome. Nessun amministratore.\n\nRestituisce il `token_conto` **una volta sola**: è la credenziale con cui si\ncompra e si conia la chiave. Come la chiave API, in archivio ne resta solo\nl'impronta — se lo perdi, ti registri di nuovo.\n\n**Un campo che non è qui si rifiuta col suo nome** (D-158): il conto che ti\navrei dato non sarebbe stato quello che hai chiesto, e la risposta non te\nl'avrebbe detto.\n\n⚠️ Le due righe che seguivano qui dicevano «non c'è nessun 401 da rispettare\n— la rotta è aperta — quindi il controllo del corpo è la prima riga», e\ndall'8/10 sarebbero FALSE in entrambe le metà: il 401 c'è, e il controllo del\ncorpo non è più la prima riga perché la guardia viene prima. L'ordine vero è\nquello: **401 senza chiave, 403 senza il permesso, 422 sul corpo** — un\nerrore di scrittura non si scopre prima di avere il diritto di scrivere.","operationId":"registra_conto_v1_conti_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NuovoConto"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContoCreato"}}}},"401":{"description":"manca la chiave API: aprire un conto è un gesto di rivendita"},"403":{"description":"la chiave non ha il permesso `rivende`"},"422":{"description":"email o nome non validi"}},"security":[{"X-API-Key":[]}]}},"/v1/conti/{conto_id}/acquisto":{"post":{"tags":["conti (self-service)"],"summary":"Acquista una persona con un codice promo (nessun pagamento reale)","description":"Il codice promo trasforma la scelta in un acquisto valido, a prezzo zero.\n\nSul sito non c'è nessun pagamento: è il promo a concludere l'acquisto. Senza\nun codice valido si riceve un **402** con la spiegazione — non un acquisto\nfinto a metà.\n\n**Due credenziali** (12/10/2026, D-319). Il token del conto\n(`X-Conto-Token`) è quella del cliente, nella sua area, e non cambia. In\nalternativa, chi ha **aperto** questo conto — il rivenditore, con la chiave\ncol permesso `rivende` di `POST /v1/conti` — può presentare la propria\nchiave in `X-API-Key` e guidare la catena per il suo cliente senza\nconservarne il token. Solo per i conti che ha aperto lui: su un conto di un\naltro rivenditore la risposta è **403\n`conto_non_aperto_da_questa_chiave`**, la stessa che si riceve per un conto\nche non esiste.","operationId":"acquista_v1_conti__conto_id__acquisto_post","parameters":[{"name":"conto_id","in":"path","required":true,"schema":{"type":"string","title":"Conto Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Acquisto"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcquistoRegistrato"}}}},"401":{"description":"token del conto mancante o errato; in alternativa una chiave API col permesso `rivende`, se è quella che ha aperto il conto"},"402":{"description":"codice promo mancante o non valido: senza, l'acquisto non si conclude"},"403":{"description":"la chiave API presentata non ha il permesso `rivende`, oppure non è lei che ha aperto questo conto (`conto_non_aperto_da_questa_chiave`)"},"404":{"description":"nessuna persona con quell'id"},"503":{"description":"il registro delle persone digitali (`motori/persone`) non si carica su questa macchina: `errore` è `registro_persone_non_leggibile`. Le sessioni con un riferimento tuo continuano a funzionare, quelle da persona no","content":{"application/json":{"example":{"errore":"registro_persone_non_leggibile","spiegazione":"il registro delle persone digitali (motori/persone) non si carica su questa macchina: le sessioni con un riferimento tuo continuano a funzionare, quelle da persona no"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{},{"X-API-Key":[]}]}},"/v1/conti/persone":{"get":{"tags":["conti (self-service)"],"summary":"Le persone acquistabili (vetrina del negozio, senza chiave)","description":"L'elenco delle persone che si possono acquistare, **senza chiave**.\n\nÈ la stessa lista di `GET /v1/persone`, ma serve al percorso self-service:\nchi sta per registrarsi deve poter vedere COSA compra prima di avere una\nchiave. I nomi sono inventati e i volti pure (vedi `GET /v1/persone`),\nquindi mostrarli qui non svela nulla di nessuno.\n\n**Il `volto_url` è cambiato il 6/9/2026, ed è una decisione, non una\nsvista.** Prima questa rotta non lo dava affatto, e la pagina del negozio\ndisegnava un volto sintetico per conto suo: era difendibile finché il volto\ndi prodotto *era* quel disegno. Oggi non lo è più — il volto è un ritratto\nfotorealistico, ed è la cosa che si compra: un negozio che mostra un\nsurrogato al posto della merce inganna il cliente, e lo inganna proprio sul\npunto che il titolare ha chiesto di sistemare.\n\nQuindi il ritratto si serve **da qui**, sotto `/v1/conti/…`, che è l'area\ndel negozio ed è aperta per costruzione (`api/guardia.py`). La rotta a\nchiave `GET /v1/persone/{id}/volto` **non si apre**: chi ha la chiave passa\ndi là come sempre. Aprire la vetrina non è aprire il magazzino.","operationId":"vetrina_persone_v1_conti_persone_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VetrinaPersone"}}}},"503":{"description":"il registro delle persone non è leggibile"}}}},"/v1/conti/persone/{identificativo}/volto":{"get":{"tags":["conti (self-service)"],"summary":"Il ritratto della persona in vetrina (PNG, senza chiave)","description":"Il ritratto che si vede nel negozio, **senza chiave**.\n\nServe lo stesso file di `GET /v1/persone/{id}/volto`, e la differenza non è\ntecnica ma di porta: quella sta sotto la guardia e serve a chi **usa** il\nprodotto, questa sta nel negozio e serve a chi lo sta **guardando**. Chi\nentra qui non può fare nient'altro: la vetrina non apre sessioni, non\ngenera video e non consuma quota.\n\nPerché è accettabile che sia aperta: è il ritratto di una persona che non\nesiste, inventato da un modello, ed è la merce esposta. Se ci fosse qualcosa\nda proteggere in questo PNG, il problema non sarebbe la rotta — sarebbe il\nPNG.\n\nUn `<img src=\"/v1/conti/persone/…/volto\">` funziona e basta: nessuna\nintestazione da mandare, quindi la pagina del negozio non ha bisogno di\nJavaScript per mostrare una faccia.","operationId":"vetrina_volto_v1_conti_persone__identificativo__volto_get","parameters":[{"name":"identificativo","in":"path","required":true,"schema":{"type":"string","title":"Identificativo"}}],"responses":{"200":{"description":"il PNG del ritratto","content":{"image/png":{}}},"404":{"description":"nessuna persona con quell'id"},"503":{"description":"il volto non è su questa macchina"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/licenze/richiesta":{"post":{"tags":["licenze"],"summary":"Chiedi una licenza: è la porta dietro al «prezzo su richiesta»","description":"Deposita una richiesta di licenza. Aperta, perché chi chiede non ha una chiave.\n\nAccetta sia JSON sia un modulo HTML (`application/x-www-form-urlencoded`).\nCol modulo risponde **303 verso `/licenza-ricevuta.html`**, così la pagina\nfunziona anche con JavaScript spento: senza quel ramo, chi non ha JS si\ntroverebbe davanti una risposta JSON, che per un cliente è una porta in\nfaccia. Col JSON risponde 201 e il riferimento, e la landing lo mostra senza\ncambiare pagina.","operationId":"richiedi_una_licenza_licenze_richiesta_post","responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"503":{"description":"la richiesta non si è potuta depositare: il deposito è pieno (`deposito_pieno`) o non è scrivibile (`deposito_non_scrivibile`). È un «non adesso», non un «no»: il modulo della landing lo può dire e invitare a riprovare"}},"x-fronte-pubblico":{"stato":"chiusa","codice":404,"errore":"non_esposta","fronte":"api.agiletav.agile.software","decisione":"D-096","spiegazione":"chiusa sul fronte pubblico api.agiletav.agile.software: risponde 404 «non_esposta» perche' consuma GPU senza chiave API (D-096). L'operazione esiste e funziona, ma la si chiede alla regia di AgileTav."}}},"/salute":{"get":{"tags":["salute"],"summary":"Stato reale dei motori e dell'archivio chiavi","description":"`stato` è «ok» solo se la catena del round 1 funziona davvero.\n\nNon dipende dall'archivio delle chiavi: un servizio senza chiavi è chiuso,\nnon malato. Lo stato dell'archivio si legge sotto `api.chiavi`, e serve a\ncapire in un colpo d'occhio perché `/v1` risponde 401.\n\n**E non dipende dal pod.** Questa rotta è quella che l'healthcheck del\ncontainer interroga ogni 5 secondi: se aspettasse un motore che gira\naltrove, un pod appeso basterebbe a far dichiarare malato — e in altre\nconfigurazioni a far riavviare — un servizio che sta benissimo. Tutto ciò\nche qui dentro riguarda il pod si legge da una memoria aggiornata da un filo\ndi sfondo (`_stato_remoto`).","operationId":"salute_salute_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/inizia":{"get":{"tags":["salute"],"summary":"«Inizia in 5 minuti»: esempi curl veri","description":"La pagina di partenza per uno sviluppatore: sei comandi, in italiano.\n\nÈ servita dall'API stessa perché la documentazione deve essere raggiungibile\ndove è raggiungibile il servizio: se arrivi all'API, sei già arrivato alla\nguida. Nessuna dipendenza esterna, nessun CDN.","operationId":"inizia_inizia_get","responses":{"200":{"description":"Successful Response"}}}},"/parla":{"post":{"tags":["banco"],"summary":"Scorciatoia del banco: foto + testo → video/mp4, senza sessione","description":"Foto + testo (o audio) → mp4 con audio e volto che parla.\n\nResta **senza chiave** perché è la porta che usa il banco di prova della\nregia (brief §3). Chi costruisce un prodotto usa `/v1`, che ha sessioni,\nquote e registro d'uso.\n\nSe arriva `audio` invece di `testo`, al round 1 quell'audio si usa **così\ncom'è**: non c'è ascolto e non c'è cervello, quelli sono il round 3, e\nfingere sarebbe peggio che dirlo.","operationId":"parla_parla_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_parla_parla_post"}}},"required":true},"responses":{"200":{"description":"Successful Response"},"503":{"description":"il video non si può fare **adesso**, e si dice perché: la scheda video sta servendo un altro turno (`servo_occupato`, con `Retry-After` e `riprova_fra_secondi`), oppure un motore non è in servizio su questa macchina (`motore_<nome>_non_in_servizio`, `motore_non_pronto`). Sul fronte del volto il motore sta su un pod elastico che si accende e si spegne: questo è il caso **normale**, e un cliente lo gestisce riprovando. Fino al 4/10/2026 il contratto dichiarava solo 200 e 422 (D-060). Dal 9/10/2026 anche `video_vuoto`: il flusso a spezzoni si è chiuso senza un solo byte, e un mp4 di lunghezza zero non si consegna (D-231)"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-fronte-pubblico":{"stato":"chiusa","codice":404,"errore":"non_esposta","fronte":"api.agiletav.agile.software","decisione":"D-096","spiegazione":"chiusa sul fronte pubblico api.agiletav.agile.software: risponde 404 «non_esposta» perche' consuma GPU senza chiave API (D-096). L'operazione esiste e funziona, ma la si chiede alla regia di AgileTav."}}},"/conversa":{"post":{"summary":" Conversa","description":"Foto + domanda a voce → mp4 con la risposta parlata.","operationId":"_conversa_conversa_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body__conversa_conversa_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"503":{"description":"la catena si è interrotta a metà (`catena_interrotta`, con `verifica` che dice quali anelli mancano) o un motore non è in servizio su questa macchina (`motore_non_pronto`). Sul fronte del volto il motore sta su un pod elastico: è il caso **normale**, e si gestisce riprovando. Fino al 4/10/2026 il contratto dichiarava solo 200 e 422 (D-060)"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-fronte-pubblico":{"stato":"chiusa","codice":404,"errore":"non_esposta","fronte":"api.agiletav.agile.software","decisione":"D-096","spiegazione":"chiusa sul fronte pubblico api.agiletav.agile.software: risponde 404 «non_esposta» perche' consuma GPU senza chiave API (D-096). L'operazione esiste e funziona, ma la si chiede alla regia di AgileTav."}}}},"components":{"schemas":{"Accesso":{"properties":{"utente":{"type":"string","title":"Utente","description":"il nome utente, oppure l'email del conto","examples":["demo"]},"password":{"type":"string","title":"Password","description":"la password del conto","examples":["…"]}},"type":"object","required":["utente","password"],"title":"Accesso","description":"Il corpo di `POST /v1/conti/accesso`."},"AccessoRiuscito":{"properties":{"conto_id":{"type":"string","title":"Conto Id","examples":["c_1a2b3c4d5e6f"]},"token_conto":{"type":"string","title":"Token Conto","description":"la credenziale delle rotte del conto (intestazione X-Conto-Token)","examples":["ct_XXXXXXXXXXXXXXXXXXXXXXXX"]},"nome":{"type":"string","title":"Nome","examples":["Giuria AgileVolto"]},"email":{"type":"string","title":"Email","examples":["demo@agile.software"]},"prossimo_passo":{"type":"string","title":"Prossimo Passo"}},"type":"object","required":["conto_id","token_conto","nome","email","prossimo_passo"],"title":"AccessoRiuscito"},"Acquisto":{"properties":{"persona":{"type":"string","title":"Persona","description":"l'`id` di una persona della vetrina GET /v1/conti/persone (si apre senza chiave: chi compra non ne ha ancora una)","examples":["nadia-orvieti"]},"codice_promo":{"type":"string","title":"Codice Promo","description":"un codice promo valido: sul sito non si paga con denaro, è il promo che conclude l'acquisto","examples":["AGILEVOLTO"]}},"type":"object","required":["persona","codice_promo"],"title":"Acquisto","description":"Il corpo di `POST /v1/conti/{id}/acquisto`."},"AcquistoRegistrato":{"properties":{"acquisto_id":{"type":"string","title":"Acquisto Id","examples":["a_1a2b3c4d5e6f"]},"persona":{"type":"string","title":"Persona","examples":["nadia-orvieti"]},"codice_promo":{"type":"string","title":"Codice Promo","examples":["AGILEVOLTO"]},"sconto_percento":{"type":"integer","title":"Sconto Percento","examples":[100]},"prezzo_base":{"type":"integer","title":"Prezzo Base","description":"prezzo di listino, in centesimi di euro","examples":[4900]},"prezzo_pagato":{"type":"integer","title":"Prezzo Pagato","description":"quanto si è «pagato», in centesimi; 0 col promo che azzera","examples":[0]},"creato_il":{"type":"string","title":"Creato Il"},"prossimo_passo":{"type":"string","title":"Prossimo Passo"}},"type":"object","required":["acquisto_id","persona","codice_promo","sconto_percento","prezzo_base","prezzo_pagato","creato_il","prossimo_passo"],"title":"AcquistoRegistrato"},"AvatarSalvato":{"properties":{"avatar_id":{"type":"string","title":"Avatar Id","examples":["av_1a2b3c4d5e6f"]},"nome":{"type":"string","title":"Nome","examples":["Il mio avatar"]},"creato_il":{"type":"string","title":"Creato Il"},"riferimento":{"type":"string","title":"Riferimento"},"campione_voce":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campione Voce"},"ruolo":{"type":"string","title":"Ruolo"},"tono":{"type":"string","title":"Tono"},"contesto":{"type":"string","title":"Contesto"},"come_si_apre":{"additionalProperties":true,"type":"object","title":"Come Si Apre"},"nota":{"type":"string","title":"Nota"}},"type":"object","required":["avatar_id","nome","creato_il","riferimento","ruolo","tono","contesto","come_si_apre","nota"],"title":"AvatarSalvato"},"Body__conversa_conversa_post":{"properties":{"riferimento":{"type":"string","format":"binary","title":"Riferimento","description":"foto del volto (png/jpg)"},"audio":{"type":"string","format":"binary","title":"Audio","description":"la domanda, a voce (wav/mp3)"},"persona":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Persona","description":"JSON: nome, ruolo, tono, non_fare, lingua"},"storia":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Storia","description":"JSON: i turni precedenti"},"lingua":{"type":"string","title":"Lingua","default":"it"},"px":{"type":"integer","title":"Px","default":1024},"max_secondi":{"type":"integer","title":"Max Secondi","default":10},"spezzoni":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Spezzoni","description":"genera a spezzoni e servi mentre generi; assente = il predefinito del servizio, oggi ACCESO (AGILETAV_SPEZZONI=0 lo spegne)"}},"type":"object","required":["riferimento","audio"],"title":"Body__conversa_conversa_post"},"Body_carica_riferimento_v1_riferimenti_post":{"properties":{"file":{"type":"string","format":"binary","title":"File","description":"foto (png/jpg/webp/bmp), video (mp4/mov/webm/mkv) o campione di voce (wav/mp3/m4a/ogg/webm di sola voce)"}},"type":"object","required":["file"],"title":"Body_carica_riferimento_v1_riferimenti_post"},"Body_conversa_in_sessione_v1_sessioni__identificativo__conversa_post":{"properties":{"audio":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Audio","description":"la domanda, a voce (wav/mp3/m4a/ogg/flac/aac). Obbligatorio."},"px":{"type":"integer","title":"Px","description":"lato del video in pixel","default":1024},"max_secondi":{"type":"integer","title":"Max Secondi","description":"durata minima concessa al video; se la risposta dura di più il video si allunga, invece di tagliare la frase a metà","default":10},"spezzoni":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Spezzoni","description":"genera a spezzoni e servi mentre generi; assente = il predefinito del servizio, oggi ACCESO (AGILETAV_SPEZZONI=0 lo spegne)"}},"type":"object","title":"Body_conversa_in_sessione_v1_sessioni__identificativo__conversa_post"},"Body_parla_in_sessione_v1_sessioni__identificativo__parla_post":{"properties":{"testo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Testo","description":"cosa deve dire","examples":["Buongiorno, sono Carla."]},"audio":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Audio","description":"in alternativa al testo: un audio già pronto"},"px":{"type":"integer","title":"Px","description":"lato del video in pixel","default":1024},"max_secondi":{"type":"integer","title":"Max Secondi","description":"tetto alla durata del video","default":10},"spezzoni":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Spezzoni","description":"genera a spezzoni e servi mentre generi; assente = il predefinito del servizio, oggi ACCESO (AGILETAV_SPEZZONI=0 lo spegne). Vale solo con `testo`: con un `audio` già pronto non c'è un testo da spezzare in frasi, e la risposta esce come mp4 intero"}},"type":"object","title":"Body_parla_in_sessione_v1_sessioni__identificativo__parla_post"},"Body_parla_parla_post":{"properties":{"riferimento":{"type":"string","format":"binary","title":"Riferimento","description":"foto del volto (png/jpg)"},"testo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Testo","description":"cosa deve dire"},"audio":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Audio","description":"in alternativa al testo: un audio già pronto"},"lingua":{"type":"string","title":"Lingua","default":"it"},"px":{"type":"integer","title":"Px","default":1024},"max_secondi":{"type":"integer","title":"Max Secondi","default":10},"spezzoni":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Spezzoni","description":"genera a spezzoni e servi mentre generi; assente = il predefinito del servizio, oggi ACCESO (AGILETAV_SPEZZONI=0 lo spegne)"}},"type":"object","required":["riferimento"],"title":"Body_parla_parla_post"},"Body_vede_in_sessione_v1_sessioni__identificativo__vede_post":{"properties":{"fotogramma":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Fotogramma","description":"un fotogramma della webcam dell'interlocutore (jpg o png). Obbligatorio."},"domanda":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Domanda","description":"la curiosità dell'avatar («mi sta mostrando qualcosa?»), non una domanda dell'utente: orienta lo sguardo"},"max_parole":{"type":"integer","title":"Max Parole","description":"tetto alla lunghezza della frase","default":40}},"type":"object","title":"Body_vede_in_sessione_v1_sessioni__identificativo__vede_post"},"CatalogoPersone":{"properties":{"persone":{"items":{"$ref":"#/components/schemas/PersonaInCatalogo"},"type":"array","title":"Persone"}},"type":"object","required":["persone"],"title":"CatalogoPersone"},"ChiaveConiata":{"properties":{"chiave":{"type":"string","title":"Chiave","description":"la chiave API, mostrata UNA sola volta","examples":["atv_XXXXXXXXXXXXXXXXXXXXXXXX"]},"chiave_id":{"type":"string","title":"Chiave Id","examples":["k_1a2b3c4d5e6f"]},"sviluppatore":{"type":"string","title":"Sviluppatore","description":"a chi è intestata: l'email del conto","examples":["prova@example.it"]},"quota_giorno":{"type":"integer","title":"Quota Giorno","description":"turni di lavoro al giorno inclusi (= `quota_assegnata`)","examples":[200]},"quota_richiesta":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Quota Richiesta","description":"quanto hai chiesto con `quota_giorno`; `null` se non l'hai chiesto"},"quota_assegnata":{"type":"integer","title":"Quota Assegnata","description":"quanto ha davvero la chiave","examples":[200]},"tetto":{"type":"integer","title":"Tetto","description":"il tetto self-service (`AGILETAV_QUOTA_MASSIMA`): sopra, si conia al tetto e lo si dichiara","examples":[1000]},"uso":{"type":"string","title":"Uso","description":"`programmatore` oppure `sito` (percorso umano)","default":"programmatore","examples":["programmatore"]},"tetto_conto_turni":{"type":"integer","title":"Tetto Conto Turni","description":"il tetto del CONTO in turni al giorno, sommato su tutte le sue chiavi (`AGILETAV_QUOTA_CONTO_TURNI`): è il limite vero, qualunque quota abbia la singola chiave","default":0,"examples":[500]},"nota":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nota","description":"c'è quando la quota assegnata non è quella chiesta, e dice perché"},"prova_subito":{"type":"string","title":"Prova Subito","description":"un esempio pronto da eseguire"},"nome":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nome","description":"il nome che le hai dato"},"tipo":{"type":"string","title":"Tipo","description":"`ambiente` oppure `persona`","default":"ambiente"},"persona":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Persona","description":"la persona che apre, se è di persona"},"mascherata":{"type":"string","title":"Mascherata","description":"come si vedrà da qui in avanti: `atv_6df1…`","default":""},"permessi":{"items":{"type":"string"},"type":"array","title":"Permessi"},"origini":{"items":{"type":"string"},"type":"array","title":"Origini"},"scade_il":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scade Il","description":"ISO-8601 UTC, se ha una scadenza"},"mostrata_una_volta":{"type":"boolean","title":"Mostrata Una Volta","description":"sempre vero: dopo questa risposta il valore non esiste più da nessuna parte","default":true},"ambito":{"type":"string","title":"Ambito","description":"sempre `produzione` per una chiave coniata da un conto: `prova` è solo della passe-partout adottata dentro il servizio","examples":["produzione"]}},"type":"object","required":["chiave","chiave_id","sviluppatore","quota_giorno","quota_assegnata","tetto","prova_subito","ambito"],"title":"ChiaveConiata"},"ContoCreato":{"properties":{"conto_id":{"type":"string","title":"Conto Id","examples":["c_1a2b3c4d5e6f"]},"email":{"type":"string","title":"Email"},"nome":{"type":"string","title":"Nome"},"token_conto":{"type":"string","title":"Token Conto","description":"il token del conto, mostrato UNA sola volta: conservalo, serve a comprare e a coniare la chiave","examples":["ct_XXXXXXXXXXXXXXXXXXXXXXXX"]},"creato_il":{"type":"string","title":"Creato Il"},"prossimo_passo":{"type":"string","title":"Prossimo Passo"}},"type":"object","required":["conto_id","email","nome","token_conto","creato_il","prossimo_passo"],"title":"ContoCreato"},"ElencoRiferimenti":{"properties":{"riferimenti":{"items":{"$ref":"#/components/schemas/RiferimentoInElenco"},"type":"array","title":"Riferimenti"},"orfani_scadono_dopo_ore":{"type":"number","title":"Orfani Scadono Dopo Ore","description":"`AGILETAV_RIFERIMENTI_ORFANI_ORE`: dopo quante ore senza una sessione aperta un riferimento si cancella da solo (0 = mai)"},"sostituisce":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sostituisce","description":"l'`id` della chiave che QUESTA ha sostituito ruotando, quando l'elenco è vuoto: i riferimenti sono rimasti alla vecchia e si ricaricano con la nuova. Facoltativo — una chiave che non è nata da una rotazione non lo riceve affatto, e del predecessore si dice il numero dei riferimenti, mai gli id (6/10/2026, D-140)","examples":["k_1a2b3c4d5e6f"]},"nota":{"type":"string","title":"Nota"}},"type":"object","required":["riferimenti","orfani_scadono_dopo_ore","nota"],"title":"ElencoRiferimenti"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"NuovaChiave":{"properties":{"nome":{"anyOf":[{"type":"string","maxLength":60},{"type":"null"}],"title":"Nome","description":"il nome che dai tu alla chiave, per ritrovarla in elenco","examples":["produzione"]},"tipo":{"type":"string","title":"Tipo","description":"`ambiente` apre tutto ciò che il conto ha comprato; `persona` vale solo per la persona indicata in `persona`","default":"ambiente","examples":["persona"]},"persona":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Persona","description":"l'`id` della persona (del catalogo, comprata) o dell'avatar (`av_…`). Obbligatorio con `tipo: persona`","examples":["nadia-orvieti"]},"scadenza_giorni":{"anyOf":[{"type":"number","maximum":3650.0,"exclusiveMinimum":0.0},{"type":"null"}],"title":"Scadenza Giorni","description":"scadenza facoltativa, in giorni: dopo, la chiave risponde 401","examples":[30]},"permessi":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Permessi","description":"che cosa può fare, fra `lettura`, `parla`, `conversa`, `vede`, `clonazione`. **Assente o vuoto = questi cinque**, cioè tutto ciò che un conto compra. Esiste un sesto permesso, `rivende` (aprire un conto per un cliente proprio): non si eredita dal silenzio e non si conia da qui — scriverlo in questo campo è un **422 `rivende_non_si_conia`**, con nessuna delle due credenziali. Per rivendere il volto si chiede l'**accreditamento come officina**: il processo e' nella descrizione del servizio, sotto «rivendere il volto» (20/10/2026 D-437 e D-438, misure di adp; 21/10/2026 A716, decisione del titolare)","examples":[["lettura","parla"]]},"origini":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Origini","description":"da dove può essere usata: origini (`https://mio.sito`) o indirizzi IP. Assente o vuoto = da ovunque","examples":[["https://mio.sito"]]},"quota_giorno":{"anyOf":[{"type":"integer","maximum":10000000.0,"minimum":1.0},{"type":"null"}],"title":"Quota Giorno","description":"quanti **turni di lavoro** al giorno vuoi per questa chiave (un turno = una risposta parlata; le GET di stato non contano). Assente = la predefinita del servizio. Sopra il **tetto self-service** la chiave nasce AL tetto e la risposta lo dice (`quota_richiesta`, `quota_assegnata`, `tetto`, `nota`): non si assegna in silenzio meno di quanto chiesto","examples":[500]},"uso":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Uso","description":"a che cosa serve questa chiave, e quindi quanto è alto il suo tetto. `programmatore` (predefinito) è la chiave di sempre, quella che si integra in un programma. `sito` è la chiave che la pagina del conto conia da sola per chi è entrato e vuole soltanto parlare: nasce con il tetto del CONTO, perché la quota di chi guarda non è quella del programmatore. In entrambi i casi il tetto del conto resta il limite vero, e si somma su tutte le chiavi del conto. **Assente** vuol dire «non l'ho detto»: allora si guarda il `nome`, perché è l'unico segno che la pagina manda oggi","examples":["sito"]}},"additionalProperties":false,"type":"object","title":"NuovaChiave","description":"Il corpo — **facoltativo** — di `POST /v1/conti/{id}/chiavi`.\n\nSenza corpo si ottiene esattamente la chiave di prima: di **ambiente**,\nsenza scadenza, senza restrizioni. È il motivo per cui ogni campo ha un\npredefinito: chi chiamava questa rotta ieri deve continuare a ricevere la\nstessa cosa oggi.\n\n**Un campo che non è qui non si ingoia in silenzio** (7/10/2026). Il\npredefinito di pydantic è *scartare* ciò che non riconosce: chi mandava\n`{\"ambito\": \"prova\"}` riceveva **200** e una chiave di `produzione`, senza\nuna parola — misurato, ed è il modo più veloce per far credere che un campo\nesista. Qui gli extra si **tengono** (`extra: allow`) perché la rotta possa\nrifiutarli **col loro nome** e dire chi li scrive davvero\n(`campo_sconosciuto`, 422). Lo schema dichiara `additionalProperties:\nfalse`, che è la verità del servizio: non è un campo in più, è un rifiuto."},"NuovaSessione":{"properties":{"persona":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Persona","description":"l'`id` di una persona di GET /v1/persone (es. `nadia-orvieti`), **o il `nome` di un tuo avatar** (es. `Carla Maria`: le righe con `proprio: true` di GET /v1/persone — due tuoi avatar con lo stesso nome danno `409 nome_ambiguo`, e lì si passa il loro `id` nel campo `avatar`), oppure — se passi anche un `riferimento` — un nome libero tuo, che serve solo a ritrovare la sessione nei log","examples":["nadia-orvieti"]},"riferimento":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Riferimento","description":"l'`id` tornato da POST /v1/riferimenti. Facoltativo **solo** se `persona` è l'id di una persona nostra: in quel caso il volto è quello della scheda","examples":["r_9f3a1c2b4d5e"]},"sorgente":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sorgente","description":"da quale file nasce il volto vivo della **persona di catalogo**: `attuale` (il ritratto, ed è il predefinito), `vivo` (il girato di 5 s generato dal ritratto) o `diffusione` (il girato generato dall'audio con Wan2.2-S2V). Se il file chiesto non è su questa macchina si scende alla sorgente sotto e lo si **dichiara** nella `nota` della risposta, invece di far finta di niente. Non ha effetto quando passi un `riferimento` tuo: quello è il tuo file.","examples":["diffusione"]},"voce":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Voce","description":"nome della voce; se manca si usa quella della persona scelta, o quella predefinita del servizio","examples":["valentina"]},"lingua":{"type":"string","title":"Lingua","description":"lingua del parlato. Al round 1 il motore parla solo italiano.","default":"it","examples":["it"]},"campione_voce":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campione Voce","description":"l'`id` di un riferimento **audio** (POST /v1/riferimenti con un wav/webm/mp4 di voce): la persona parlerà con **quella** voce invece che con una di catalogo. Se non lo passi e il `riferimento` è un video che contiene audio, si usa l'audio del video — e la risposta lo dichiara in `voce_clonata_da`","examples":["r_7c1e2b9a4d3f"]},"clonazione":{"type":"string","title":"Clonazione","description":"che cosa fare se il motore `clona` **non** è in servizio su questa macchina. `se_possibile` (predefinito): la sessione si apre lo stesso con la voce di catalogo, `voce_clonata_da` vale `null`, il campione resta legato alla sessione e la `nota` dice cos'è successo — la clonazione è un'ambizione, non una condizione. `obbligatoria`: senza motore la sessione **non** si apre e arriva 503 `catena_non_completa`","default":"se_possibile","examples":["se_possibile"]},"voce_scelta":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Voce Scelta","description":"**quale voce**, quando nella registrazione ce n'è più d'una (l'intervistatore, una domanda fuori campo, chi sta di fianco). È l'indice dentro `voci.voci[]` della risposta di `POST /v1/riferimenti`: `0` è quella che il servizio sceglierebbe da solo — quella di chi è in camera e muove le labbra, o in mancanza di video quella che parla di più. Passalo solo per **correggerci**: senza, si usa il criterio, e la risposta dichiara comunque in `voce_isolata` che cosa è stato scelto e perché","examples":[0]},"etichetta":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Etichetta","description":"**una tua etichetta, per ritrovare questa sessione nei TUOI log** (dal 13/10/2026, misura di adp). E' una stringa tua: la ripetiamo in questa risposta, in `GET /v1/sessioni/{id}` e nei nostri diari, e non la interpretiamo — non sceglie niente e non ha valori ammessi. Serve perche' `riferimento` **non** e' un'etichetta libera: e' l'`id` di un file che hai caricato, e un identificativo inventato li' dentro riceve `404 riferimento_sconosciuto` senza aprire niente. Tetto 64 caratteri, su una riga sola (i caratteri di controllo sono `422 etichetta_non_valida`: finirebbero nei nostri diari)","examples":["favore-pixelschool-7"]},"voce_indice":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Voce Indice","description":"**lo stesso di `voce_scelta`**, con il nome che usa il referto dell'isolamento (`voci.cambiabile.campi`). I due campi sono un campo solo: passane uno; passarli tutti e due con valori diversi è un 422. Esiste perché il nome che il cliente legge nel referto deve essere un nome che l'API accetta — un rimedio dichiarato e non accettato è peggio di nessun rimedio","examples":[0]}},"type":"object","title":"NuovaSessione","description":"Il corpo di `POST /v1/sessioni`.\n\nDue modi di aprire una sessione, e uno solo dei due campi è obbligatorio:\n\n- **la tua faccia**: `riferimento` = l'`id` di `POST /v1/riferimenti`. Qui\n  `persona` è quello che è sempre stato, un nome libero per ritrovarti nei\n  log;\n- **una persona nostra**: `persona` = l'`id` di una scheda di\n  `GET /v1/persone`, e niente `riferimento`. Volto, voce, lingua e tono\n  vengono dalla scheda, e non devi procurarti nessuna foto — che è poi il\n  punto: il volto di una persona vera qui dentro non ci deve entrare;\n- **un tuo avatar, chiamato per NOME** (dal 7/10/2026, D-172): `persona` =\n  il `nome` di un avatar tuo, e niente `riferimento`. È la stessa sessione\n  che apriresti con `avatar` = il suo `av_…`, per chi parte da una scheda e\n  un `av_…` non ce l'ha in mano. Il catalogo vince (un tuo avatar chiamato\n  `nadia-orvieti` non copre la persona di catalogo), il nome si cerca solo\n  fra i **tuoi** avatar, e se due dei tuoi portano lo stesso nome la\n  risposta è `409 nome_ambiguo` con i loro id: quale aprire non si indovina.","examples":[{"persona":"nadia-orvieti"},{"lingua":"it","persona":"Carla","riferimento":"r_9f3a1c2b4d5e","voce":"valentina"},{"campione_voce":"r_7c1e2b9a4d3f","persona":"ospite della giuria","riferimento":"r_9f3a1c2b4d5e"},{"campione_voce":"r_7c1e2b9a4d3f","clonazione":"obbligatoria","persona":"ospite della giuria","riferimento":"r_9f3a1c2b4d5e"},{"campione_voce":"r_7c1e2b9a4d3f","persona":"ospite della giuria","riferimento":"r_9f3a1c2b4d5e","voce_scelta":1}]},"NuovoAvatar":{"properties":{"nome":{"type":"string","title":"Nome","description":"il nome con cui ritrovarlo in galleria","examples":["Il mio avatar"]},"riferimento":{"type":"string","title":"Riferimento","description":"l'`id` di un riferimento (foto o video) già caricato con POST /v1/riferimenti","examples":["r_1a2b3c4d5e6f"]},"campione_voce":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campione Voce","description":"facoltativo: l'`id` di un riferimento audio, per la voce","examples":["r_0f1e2d3c4b5a"]},"ruolo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ruolo","description":"chi è, in una riga: finisce nelle istruzioni del cervello. Se manca: «l'avatar digitale di <nome>»","examples":["il responsabile commerciale di una piccola impresa"]},"tono":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tono","description":"come parla. Se manca, un tono predefinito in prima persona, calmo e cortese"},"contesto":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contesto","description":"fatti che l'avatar conosce e non deve contraddire. Se manca, il fatto che è la persona digitale di chi l'ha creato"}},"additionalProperties":false,"type":"object","required":["nome","riferimento"],"title":"NuovoAvatar","description":"Il corpo di `POST /v1/conti/{id}/avatar`.\n\n**E non si accetta altro** (7/10/2026, D-158). Qui il campo che arriva per\nsbaglio è `voce` — o `lingua`, o `sorgente`: sono campi veri di\n`POST /v1/sessioni`, e chi ha appena aperto una sessione li ha sotto gli\nocchi. Prima di oggi `{\"nome\", \"riferimento\", \"voce\": \"...\"}` tornava\n**200** con un avatar la cui voce non era quella, e la risposta non poteva\nsmentirlo perché `AvatarSalvato` la voce non la nomina: l'avatar porta solo\nun `campione_voce`. La voce si sceglie **aprendo la sessione**, e un avatar\nche credi di aver fatto parlare in inglese parla in italiano al primo\n`/parla`."},"NuovoConto":{"properties":{"email":{"type":"string","title":"Email","description":"l'email del conto; un indirizzo inventato va bene, purché abbia la forma giusta","examples":["prova@example.it"]},"nome":{"type":"string","title":"Nome","description":"il nome di chi si registra","examples":["Prova Rossi"]}},"additionalProperties":false,"type":"object","required":["email","nome"],"title":"NuovoConto","description":"Il corpo di `POST /v1/conti`: bastano un'email e un nome.\n\n**E non si accetta altro** (7/10/2026, D-158). Il campo che un programmatore\nmanda qui per primo è `password`, perché `POST /v1/conti/accesso` ne chiede\nuna e perché registrarsi, ovunque, vuol dire sceglierne una. Prima di oggi\narrivava **200**: il conto nasceva con `password_hash` a NULL, la risposta\nnon nominava la password, e il guasto si scopriva al primo accesso — un\n**401** su una credenziale che il cliente giurava di aver scelto. Un segreto\nscartato in silenzio è il peggiore dei campi ingoiati: chi l'ha scritto crede\nche esista da qualche parte."},"PersonaInCatalogo":{"properties":{"id":{"type":"string","title":"Id","description":"da passare a POST /v1/sessioni","examples":["nadia-orvieti"]},"nome":{"type":"string","title":"Nome","examples":["Nadia Orvieti"]},"ruolo":{"type":"string","title":"Ruolo","examples":["l'addetta all'accoglienza di uno studio professionale"]},"lingua":{"type":"string","title":"Lingua","examples":["it"]},"tono":{"type":"string","title":"Tono","description":"come parla: registro, lunghezza delle risposte, cosa non fa. Arriva al cervello così com'è"},"voce":{"type":"string","title":"Voce","description":"quale voce di catalogo interpreta questa persona","examples":["valentina"]},"volto_url":{"type":"string","title":"Volto Url","description":"il PNG del volto, servito da questa stessa API (chiave come tutto /v1)","examples":["/v1/persone/nadia-orvieti/volto"]},"sorgente":{"type":"string","title":"Sorgente","description":"da quale catena nasce il volto vivo di questa persona: `diffusione` (girato Wan2.2-S2V), `vivo` (girato di 5 s) o `attuale` (il ritratto fermo). Si può chiedere l'altra con `sorgente` in POST /v1/sessioni","default":"attuale","examples":["diffusione"]},"sorgente_volto":{"type":"string","title":"Sorgente Volto","description":"da che cosa nasce il volto, nei nomi del contratto: `diffusione` (girato Wan2.2-S2V), `video` (una ripresa), `foto` (un ritratto fermo). È `sorgente` tradotta: `vivo` e `cliente` sono `video`, `attuale` è `foto`","default":"foto","examples":["diffusione"]},"px":{"type":"integer","title":"Px","description":"il lato del video con cui questa persona risponde se non chiedi altro (`px` in POST /parla lo cambia per quel turno). Il pod può declassarlo su una ripresa stretta, e allora la risposta lo dichiara con `X-Px` e `X-Volto-Px-Chiesto`","default":1024,"examples":[1024]}},"type":"object","required":["id","nome","ruolo","lingua","tono","voce","volto_url"],"title":"PersonaInCatalogo","description":"Una persona digitale pronta all'uso: la riga di `GET /v1/persone`."},"PersonaInVetrina":{"properties":{"id":{"type":"string","title":"Id","examples":["nadia-orvieti"]},"nome":{"type":"string","title":"Nome","examples":["Nadia Orvieti"]},"ruolo":{"type":"string","title":"Ruolo","examples":["l'addetta all'accoglienza di uno studio professionale"]},"lingua":{"type":"string","title":"Lingua","examples":["it"]},"tono":{"type":"string","title":"Tono"},"voce":{"type":"string","title":"Voce","examples":["it_IT-serena-medium"]},"volto_url":{"type":"string","title":"Volto Url","examples":["/v1/conti/persone/nadia-orvieti/volto"]}},"type":"object","required":["id","nome","ruolo","lingua","tono","voce","volto_url"],"title":"PersonaInVetrina","description":"Una persona acquistabile: la riga di `GET /v1/conti/persone`."},"RiferimentoCancellato":{"properties":{"id":{"type":"string","title":"Id"},"cancellato":{"type":"boolean","title":"Cancellato"},"file_rimossi":{"type":"integer","title":"File Rimossi","description":"quanti file sono spariti dal disco: il file, il fotogramma di identità e ciò che il caveau delle voci teneva per lui"},"byte_liberati":{"type":"integer","title":"Byte Liberati"},"spiegazione":{"type":"string","title":"Spiegazione"}},"type":"object","required":["id","cancellato","file_rimossi","byte_liberati","spiegazione"],"title":"RiferimentoCancellato"},"RiferimentoCaricato":{"properties":{"id":{"type":"string","title":"Id","examples":["r_9f3a1c2b4d5e"]},"tipo":{"type":"string","title":"Tipo","description":"`foto`, `video` oppure `audio` (il campione di voce). Non lo decide l'estensione da sola: un `.webm` registrato dal browser è `audio` se dentro non c'è nemmeno un fotogramma","examples":["foto"]},"byte":{"type":"integer","title":"Byte"},"sha256":{"type":"string","title":"Sha256","description":"impronta del file, per verificare di aver caricato ciò che credi"},"secondi":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Secondi","description":"quanti secondi di parlato ci sono dentro: valorizzato per `audio` e per i `video` che hanno una traccia audio, `null` per le foto e per i video muti","examples":[28.4]},"voci":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Voci","description":"il **referto dell'isolamento**: quante voci ci sono dentro, quale è del soggetto e con che criterio (`labbra` = quella di chi è in camera, `durata` = quella che parla di più), quanti secondi ciascuna, e i tratti in cui parla. `null` per le foto e quando il motore `isola` non è in servizio. L'indice di `voci[]` è quello che si passa a `POST /v1/sessioni` come `voce_scelta`","examples":[{"correlazione_labbra":0.81,"criterio":"labbra","frase":"Ho trovato 2 voci: uso la voce 1 (23 s, è quella di chi è in camera). Le altre: 6 s.","secondi_per_voce":[23.4,6.1],"voce_scelta":0,"voci_trovate":2}]},"nota":{"type":"string","title":"Nota"}},"type":"object","required":["id","tipo","byte","sha256","nota"],"title":"RiferimentoCaricato"},"RiferimentoDellaSessione":{"properties":{"id":{"type":"string","title":"Id","examples":["r_9f3a1c2b4d5e"]},"tipo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tipo","description":"`foto` oppure `video`","examples":["foto"]},"byte":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Byte"},"presente":{"type":"boolean","title":"Presente","description":"se il file è ancora su questo disco; diventa `false` quando scade o viene cancellato"}},"type":"object","required":["id","presente"],"title":"RiferimentoDellaSessione","description":"Il riferimento visto da chi legge la sessione: l'`id`, mai il percorso."},"RiferimentoInElenco":{"properties":{"id":{"type":"string","title":"Id","examples":["r_9f3a1c2b4d5e"]},"chiave_id":{"type":"string","title":"Chiave Id","description":"di quale chiave è: col token del conto se ne vedono più d'una","examples":["k_1a2b3c4d5e6f"]},"tipo":{"type":"string","title":"Tipo","examples":["video"]},"byte":{"type":"integer","title":"Byte"},"secondi":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Secondi"},"nome":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nome","description":"il nome del file caricato"},"creato_il":{"type":"string","title":"Creato Il"},"presente":{"type":"boolean","title":"Presente","description":"se il file è ancora su questo disco"},"sessioni_aperte":{"items":{"type":"string"},"type":"array","title":"Sessioni Aperte","description":"le sessioni aperte che lo usano: finché ce n'è una non si cancella"},"sessioni_chiuse":{"type":"integer","title":"Sessioni Chiuse","description":"quante sessioni chiuse lo hanno usato","default":0},"di_un_avatar":{"type":"boolean","title":"Di Un Avatar","description":"`true` se un avatar salvato lo rivendica: non scade, e si cancella con l'avatar","default":false},"orfano":{"type":"boolean","title":"Orfano","description":"nessuna sessione aperta e nessun avatar: scadrà da solo a `scade_il`"},"scade_il":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scade Il","description":"quando la potatura lo toglierà, se è orfano (ISO-8601 UTC)"}},"type":"object","required":["id","chiave_id","tipo","byte","creato_il","presente","orfano"],"title":"RiferimentoInElenco","description":"Una riga di `GET /v1/riferimenti`: cosa c'è su disco e chi lo sta usando."},"Rotazione":{"properties":{"finestra_ore":{"type":"number","maximum":720.0,"minimum":0.0,"title":"Finestra Ore","description":"per quante ore la chiave VECCHIA resta valida accanto alla nuova. 0 = si spegne subito; 24 è il predefinito","default":24.0,"examples":[24]}},"type":"object","title":"Rotazione","description":"Il corpo (facoltativo) di `POST …/chiavi/{k}/ruota`."},"SessioneChiusa":{"properties":{"id":{"type":"string","title":"Id"},"chiusa":{"type":"boolean","title":"Chiusa"},"chiusa_il":{"type":"string","title":"Chiusa Il"},"turni":{"type":"integer","title":"Turni"},"riferimenti_rimossi":{"type":"integer","title":"Riferimenti Rimossi","description":"quanti file sono stati cancellati dal disco: il volto, il campione di voce e i file del profilo vocale"},"byte_liberati":{"type":"integer","title":"Byte Liberati"},"voce_clonata_cancellata":{"type":"boolean","title":"Voce Clonata Cancellata","description":"`true` se questa sessione aveva una voce clonata e il suo profilo è stato cancellato","default":false},"file_del_profilo_voce":{"type":"integer","title":"File Del Profilo Voce","description":"quanti dei file rimossi erano del profilo di voce","default":0},"riferimenti_trattenuti":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Riferimenti Trattenuti","description":"riferimenti NON cancellati perché un'altra sessione aperta li usa, con il motivo"},"profilo_voce_condiviso_con":{"items":{"type":"string"},"type":"array","title":"Profilo Voce Condiviso Con","description":"le sessioni aperte che usano ancora lo stesso profilo di voce (una riaperta dal conto sullo stesso avatar): finché ce n'è una il profilo resta, e `voce_clonata_cancellata` è `false`"},"spiegazione":{"type":"string","title":"Spiegazione"}},"type":"object","required":["id","chiusa","chiusa_il","turni","riferimenti_rimossi","byte_liberati","spiegazione"],"title":"SessioneChiusa"},"SessioneCreata":{"properties":{"id":{"type":"string","title":"Id","examples":["s_1a2b3c4d5e6f"]},"persona":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Persona"},"persona_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Persona Id","description":"l'`id` della persona del registro, se la sessione è nata da una scheda invece che da una foto tua","examples":["nadia-orvieti"]},"riferimento":{"type":"string","title":"Riferimento"},"etichetta":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Etichetta","description":"l'etichetta che hai passato, ripetuta come l'abbiamo accettata (i soli spazi ai bordi via); `null` se non ne hai passata una","examples":["favore-pixelschool-7"]},"voce":{"type":"string","title":"Voce","description":"`clonata` quando la sessione parla con la voce di chi ha dato il campione; altrimenti il nome della voce di catalogo","examples":["clonata"]},"voce_base":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Voce Base","description":"la voce di catalogo che sta sotto: con una voce clonata è quella che pronuncia l'italiano prima che il timbro diventi quello della persona","examples":["valentina"]},"voce_clonata_da":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Voce Clonata Da","description":"`campione` se hai caricato un audio a parte, `video` se la voce è stata presa dall'audio del video del volto, `null` se la voce non è clonata","examples":["campione"]},"voce_isolata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Voce Isolata","description":"che cosa è stato **isolato** dal campione: `voci_trovate` quante ce n'erano, `voce_scelta` quale si è presa (l'indice da passare a `voce_scelta`), `secondi` quanti secondi puliti sono usciti, `criterio` come si è scelto (`labbra` = chi è in camera, `durata` = chi parla di più, `unica` = ce n'era una sola), `scelta_da` se ha scelto il criterio o l'hai scelta tu, e `frase` la stessa cosa in italiano. `null` quando la voce non è clonata","examples":[{"criterio":"labbra","scelta_da":"criterio","secondi":23.4,"voce_scelta":0,"voci_trovate":2}]},"campione_voce":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campione Voce","description":"l'`id` del riferimento audio da cui è nato il profilo di voce; `DELETE` della sessione cancella anche quello"},"sorgente_volto":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sorgente Volto","description":"da quale file nasce il volto: `attuale` (il ritratto), `vivo` (il girato di 5 s) o `diffusione` (il girato generato dall'audio) per una persona di catalogo — quella **ottenuta**, non quella chiesta; `cliente` per un riferimento o un avatar caricato. Le risposte di /parla e /conversa la ripetono in `X-Volto-Sorgente`","examples":["diffusione"]},"lingua":{"type":"string","title":"Lingua"},"stato":{"type":"string","title":"Stato","examples":["aperta"]},"creata_il":{"type":"string","title":"Creata Il"},"pronto":{"type":"boolean","title":"Pronto","description":"`true` quando il volto è già preparato sul pod a tutti i px che le sessioni usano **e** la voce clonata è calda: il primo turno costa quanto il secondo. `false` non è un rifiuto — si può parlare lo stesso, e il turno aspetta la preparazione in corso invece di rifarla — ma è il momento in cui una pagina onesta mostra «preparo il volto…» e tiene spento il microfono","default":true,"examples":[false]},"preparazione":{"anyOf":[{"$ref":"#/components/schemas/StatoPreparazione"},{"type":"null"}],"description":"che cosa manca, e quanto ci vuole di stima. `GET /v1/sessioni/{id}` lo ripete aggiornato, e l'evento SSE `pronto` con `volto_pronto: true` lo annuncia da sé"},"nota":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nota","description":"`null` quando non c'è niente da segnalare. Vale una frase quando il servizio ha dovuto scostarsi da ciò che la scheda della persona dichiara — oggi può succedere sulla voce, perché il servizio ne carica una sola all'avvio — e dice cosa è cambiato e perché"}},"type":"object","required":["id","riferimento","voce","lingua","stato","creata_il"],"title":"SessioneCreata"},"SessioneLetta":{"properties":{"id":{"type":"string","title":"Id","examples":["s_1a2b3c4d5e6f"]},"creata_il":{"type":"string","title":"Creata Il","examples":["2026-09-05T07:11:02.004+00:00"]},"persona":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Persona"},"persona_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Persona Id","description":"l'`id` della persona di `GET /v1/persone` che questa sessione interpreta; `null` se il volto l'hai portato tu","examples":["nadia-orvieti"]},"voce":{"type":"string","title":"Voce","description":"`clonata` se la sessione parla con la voce di chi ha dato il campione","examples":["clonata"]},"voce_base":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Voce Base","description":"la voce di catalogo sotto a quella clonata","examples":["valentina"]},"voce_clonata_da":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Voce Clonata Da","description":"`campione`, `video`, oppure `null` se la voce non è clonata"},"campione_voce":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campione Voce","description":"l'`id` del riferimento audio da cui è nato il profilo di voce"},"sorgente_volto":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sorgente Volto","description":"`attuale`, `vivo` o `diffusione` (persona di catalogo: la sorgente ottenuta), `cliente` (riferimento o avatar tuo); `null` sulle sessioni aperte prima dell'8/9/2026"},"lingua":{"type":"string","title":"Lingua"},"etichetta":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Etichetta","description":"l'etichetta del chiamante passata all'apertura; `null` sulle sessioni che non ne hanno una e su quelle aperte prima del 13/10/2026","examples":["favore-pixelschool-7"]},"riferimento":{"$ref":"#/components/schemas/RiferimentoDellaSessione"},"stato":{"type":"string","title":"Stato","description":"`creata` (non ha ancora CONSEGNATO video) oppure `ha_parlato`. Dal 6/10/2026 un turno rifiutato (503) non lo muove: lo muove solo un video uscito","examples":["ha_parlato"]},"turni":{"type":"integer","title":"Turni","description":"quante chiamate a /parla o /conversa ha CHIESTO questa sessione, riuscite o no"},"turni_riusciti":{"type":"integer","title":"Turni Riusciti","description":"quante di quelle chiamate hanno davvero consegnato del video. Minore di `turni` quando il servizio ha rifiutato (503 `motore_non_pronto`, `servo_occupato`) o il turno si è troncato: è il numero da leggere per sapere quanti video hai ricevuto. 0 sulle sessioni aperte prima del 6/10/2026 che avevano già parlato","default":0,"examples":[1]},"secondi_video_totali":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Secondi Video Totali","description":"i secondi di volto dichiarati da **tutti** i turni di questa sessione, sommati — il numero che si riconcilia. `ultimo_parla` qui sotto tiene solo l'ULTIMO turno, quindi fino al 9/10/2026 chi interrogava la sessione dopo il terzo turno aveva perso i secondi del primo e del secondo, e non per un guasto: per contratto. **`null` non è zero:** vale `null` sulle sessioni che avevano già consegnato video prima del 9/10/2026 (il totale non è misurabile per quelle, e uno zero direbbe «non ha prodotto secondi»), e vale `0.0` su una sessione che davvero non ha ancora consegnato niente. Un turno che non dichiara i suoi secondi non somma: il suo numero resta `null` anche in `ultimo_parla`","examples":[8.0]},"ultimo_parla":{"anyOf":[{"$ref":"#/components/schemas/TempiDellUltimoParla"},{"type":"null"}],"description":"`null` finché la sessione non ha parlato"},"ultimo_sguardo":{"anyOf":[{"$ref":"#/components/schemas/SguardoDellaSessione"},{"type":"null"}],"description":"`null` finché nessuno ha mandato un fotogramma a `/vede`; poi l'ultima descrizione, con l'indicazione di quale turno l'ha già usata"},"pronto":{"type":"boolean","title":"Pronto","description":"il volto è sul pod e la voce clonata è calda: da qui in poi il primo turno costa quanto il secondo. È la rotta da interrogare in ciclo mentre si mostra «preparo il volto…»","default":true,"examples":[true]},"preparazione":{"anyOf":[{"$ref":"#/components/schemas/StatoPreparazione"},{"type":"null"}],"description":"che cosa manca ancora, e quanto ci vuole di stima"}},"type":"object","required":["id","creata_il","voce","lingua","riferimento","stato","turni"],"title":"SessioneLetta","description":"Il corpo di `GET /v1/sessioni/{id}`."},"SguardoDellaSessione":{"properties":{"descrizione":{"type":"string","title":"Descrizione","description":"una frase breve, fattuale e **non identificante**: mai età, genere, etnia, salute, emozioni, aspetto o nomi","examples":["una persona tiene un foglio davanti alla webcam"]},"ms":{"type":"integer","title":"Ms","description":"quanto ci ha messo la vista","examples":[812]},"modello":{"type":"string","title":"Modello","examples":["agilevista/qwen3-vl-2b"]},"motore":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Motore","description":"quale dei due motori della vista ha risposto: `vlm` (il modello che descrive la scena) oppure `leggero` (il rilevatore su CPU: conta le persone, vede un foglio mostrato e la luce, non descrive e non legge nulla). `null` sulle sessioni scritte prima del 6/9/2026","examples":["leggero"]},"confidenza":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Confidenza","examples":[0.71]},"filtrato":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Filtrato","description":"se il filtro di non identificazione ha dovuto intervenire"},"quando":{"type":"string","title":"Quando","examples":["2026-09-05T07:12:31.004+00:00"]},"usato_al_turno":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Usato Al Turno","description":"`null` finché nessuna risposta l'ha usata; poi il numero del turno che l'ha consumata"}},"type":"object","required":["descrizione","ms","modello","quando"],"title":"SguardoDellaSessione","description":"L'ultima descrizione di `POST /vede`, quella che il cervello userà dopo."},"StatoPreparazione":{"properties":{"stato":{"type":"string","title":"Stato","description":"`pronto`, `in_corso`, `da_fare` oppure `non_serve` (motori in casa: qui non c'è nessun conto fisso da anticipare). Vale `pronto` anche quando il riscaldamento della voce non è riuscito: lo dice `voce.stato`","examples":["in_corso"]},"ms_stimati":{"type":"integer","title":"Ms Stimati","description":"quanto manca, **stimato** dall'ultima preparazione misurata su questa macchina (o dalla stima di serie finché non ce n'è una). Non è una promessa: chi lo mostra lo dica","examples":[9200]},"ms_restanti":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ms Restanti","description":"quanto **manca** adesso, in millisecondi: lo stesso numero di `ms_stimati` col nome che dice la verità. Scende a ogni lettura","examples":[9200]},"ms_totali":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ms Totali","description":"quanto durava tutta la preparazione, in millisecondi: con `ms_restanti` fa la barra di avanzamento senza che la pagina debba ricordarsi il punto di partenza","examples":[14000]},"volto":{"additionalProperties":true,"type":"object","title":"Volto","description":"`stato`, i px chiesti e quali sono già sul pod (`px_pronti`/`px_in_corso`/`px_da_fare`): «pronto» al px sbagliato non è pronto, ed è costato 17,9 s di primo byte l'8/9"},"voce":{"additionalProperties":true,"type":"object","title":"Voce","description":"`stato` e `ms` del riscaldamento del clonatore. `non_serve` quando non c'è niente da anticipare; **`pronta_senza_riscaldamento`** quando il regalo non è riuscito (scheda occupata, per esempio): la sessione è utilizzabile lo stesso e la `spiegazione` dice quanto costerà in più il primo turno. Un riscaldamento che non riesce non tiene mai una sessione fuori servizio (§7.1)"},"chi_prepara":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chi Prepara","description":"chi sta portando avanti il conto fisso ADESSO: `volto`, `voce`, `volto+voce`, oppure **`null`** = nessuno. `null` con `stato: da_fare` non è un guasto e non è un passo indietro: vuol dire che non c'è niente da aspettare e il primo turno preparerà il volto da sé (`X-Attesa-Preparazione-Ms` lo dichiara)","examples":["voce"]},"si_puo_parlare":{"type":"boolean","title":"Si Puo Parlare","description":"`true` quando un turno (`/parla`, `/conversa`) parte **adesso**: non è la stessa cosa di `pronto`. Con `pronto: false` si può quasi sempre parlare lo stesso — il turno aspetta la preparazione in corso invece di rifarla, e il conto esce in `X-Attesa-Preparazione-Ms`. Vale `false` in un caso solo: il **volto** `in_corso` sul pod (`preparazione.volto.stato`), dove il turno riceve `503 sessione_in_preparazione` con `Retry-After` — rifarla sarebbe una seconda copia dello stesso riferimento sulla stessa GPU. Il riscaldamento della **voce** non lo mette mai a `false`: si paga dentro il turno. Chi deve decidere se accettare un turno legga QUESTO campo e non la prosa di `spiegazione`","default":true,"examples":[true]},"base_viva":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Base Viva","description":"che cosa sta sul palco mentre la persona ascolta. `stato`: `pronta` (montata dal video del cliente: la serve `GET /v1/sessioni/{id}/attesa`), `catalogo` (il girato d'attesa è della persona, `GET /v1/persone/{id}/attesa`), `assente` (foto sola: volto senza base viva), `ripiego` (nemmeno il tratto con meno bocca si è potuto prendere: testa che scatta dal primo all'ultimo secondo), `preparazione` (si sta montando adesso: l'attesa risponde 503 con `Retry-After`, non 404). Con `secondi` la durata del loop e `secondi_pose` quanti ne diventano pose. `ripiego: \"tratto con meno bocca\"` quando la base c'è ma nasce dal parlato meno aperto invece che dal silenzio: è una base viva vera, di qualità dichiarata più bassa","examples":[{"pronta":true,"secondi":24.0,"secondi_pose":2.0,"stato":"pronta"}]},"spiegazione":{"type":"string","title":"Spiegazione"}},"type":"object","required":["stato","ms_stimati","volto","voce","spiegazione"],"title":"StatoPreparazione","description":"`preparazione` di una sessione: che cosa manca prima del primo turno.\n\nDue conti fissi, che corrono **insieme** e si dichiarano separati perché si\ncurano in modi diversi: il **volto** (contorno + latente VAE sul pod, 4,3 s\nper un ritratto, 5,7-9,2 s per un girato) e la **voce clonata** (il\nclonatore residente scarica il modello dopo 300 s di quiete: ~2,2 s per\nricaricarlo, più la costruzione del prompt del campione)."},"TempiDellUltimoParla":{"properties":{"turno":{"type":"integer","title":"Turno","description":"quale turno della sessione","examples":[1]},"quando":{"type":"string","title":"Quando","examples":["2026-09-05T07:12:44.918+00:00"]},"rotta":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rotta","description":"quale rotta ha prodotto il turno: `parla` oppure `conversa`","examples":["parla"]},"ms":{"additionalProperties":{"type":"integer"},"type":"object","title":"Ms","description":"millisecondi per anello (`voce`, `volto`, e quando la domanda arriva a voce anche `ascolto` e `cervello`) più `totale`. Gli anelli non attraversati non compaiono. Con `spezzoni: true` ci sono anche `primo_byte` e `primo_frammento`, che nella modalità intera non vorrebbero dire niente.","examples":[{"totale":2777,"voce":1779,"volto":400}]},"spezzoni":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Spezzoni","description":"`true` se il turno è stato **generato a spezzoni e servito mentre si generava**. In quel caso i totali non stanno nelle intestazioni della risposta — sono partite prima del video — ma qui e nell'evento `fine`."},"secondi_video":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Secondi Video","examples":[1.219]},"byte_video":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Byte Video","examples":[25373]},"tempo_reale":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Tempo Reale","description":"secondi di audio per ogni secondo di generazione: `1.0` vuol dire che la persona si genera alla stessa velocità con cui la si ascolta, sopra 1 più in fretta. `null` su un turno troncato, dove il rapporto non misurerebbe niente","examples":[0.78]},"secondi_audio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Secondi Audio","description":"i secondi detti — il numeratore di `tempo_reale`","examples":[11.8]},"secondi_generazione":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Secondi Generazione","description":"i secondi spesi dal POST all'ultimo byte, la preparazione compresa — il denominatore di `tempo_reale`, e lo stesso `time_total` che misura chi cronometra da fuori","examples":[15.1]},"motore_ascolto":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Motore Ascolto","examples":["faster-whisper/small"]},"motore_cervello":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Motore Cervello","examples":["qwen2.5-1.5b-instruct-q4_k_m"]},"motore_voce":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Motore Voce","examples":["piper/it_IT-serena-medium"]},"motore_volto":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Motore Volto","examples":["agilevolto-r1"]},"contesto_visivo":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Contesto Visivo","description":"se la risposta ha tenuto conto dell'ultimo `/vede`"},"sorgente":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sorgente","description":"`sintesi` se la domanda era testo, `fornito` se era audio riprodotto così com'è, `conversazione` se la risposta l'ha scritta il cervello (`/conversa`)","examples":["sintesi"]},"completo":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Completo","description":"`true` se tutti gli spezzoni previsti sono stati generati e serviti. `false` = risposta troncata (interruzione, stacco del client, o guasto a metà)"},"spezzoni_previsti":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Spezzoni Previsti","examples":[4]},"spezzoni_fatti":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Spezzoni Fatti","examples":[4]},"spezzone_mancato":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Spezzone Mancato","description":"il primo spezzone che non è stato servito, quando il turno è uscito monco"}},"type":"object","required":["turno","quando","ms"],"title":"TempiDellUltimoParla","description":"Le misure dell'ultimo turno di parlato, anello per anello."},"TurnoInterrotto":{"properties":{"sessione":{"type":"string","title":"Sessione","examples":["2785b83f"]},"interrotta":{"type":"boolean","title":"Interrotta","description":"`true` se c'era una risposta in corso e adesso è ferma"},"turno":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Turno","description":"quale turno è stato fermato. `null` quando non ce n'era nessuno"},"spiegazione":{"type":"string","title":"Spiegazione","description":"che cosa è successo, con parole intere"}},"type":"object","required":["sessione","interrotta","spiegazione"],"title":"TurnoInterrotto"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VetrinaPersone":{"properties":{"persone":{"items":{"$ref":"#/components/schemas/PersonaInVetrina"},"type":"array","title":"Persone"}},"type":"object","required":["persone"],"title":"VetrinaPersone"}},"securitySchemes":{"X-API-Key":{"type":"apiKey","description":"La chiave del tuo conto: si conia dall'area conto (/conto.html, scheda «Sviluppatori») e si vede una volta sola. Obbligatoria su tutto /v1.","in":"header","name":"X-API-Key"}}},"tags":[{"name":"salute","description":"Stato reale del servizio. **Senza chiave.**"},{"name":"banco","description":"Le scorciatoie del brief §3: una chiamata, un video, nessuna sessione. **Senza chiave.**"},{"name":"chiave","description":"Chi sei e quanta quota ti resta."},{"name":"riferimenti","description":"Le foto (o i video) di partenza. Restano **solo su disco locale**."},{"name":"sessioni","description":"Il ciclo di vita di una persona che parla: apri, parla, ascolta gli eventi, chiudi."}]}