API key, non sessione
La chiave viaggia in Authorization: Bearer o in X-Persona-Key. Mai nella querystring, mai in un cookie.
Per sviluppatori
Una persona digitale è un servizio governabile via API key: la interroghi da /v1, con scope e quota dichiarati sulla chiave. Nessun browser, nessun cookie, nessun CSRF — l'autenticazione è la chiave stessa.
Il modello
Il tuo programma parla con la persona del tuo cliente usando la sua chiave. L'ordine dei passi è sempre lo stesso e non è negoziabile: riconosci la chiave → autorizza prima di eseguire → esegui → registra il consumo.
La chiave viaggia in Authorization: Bearer o in X-Persona-Key. Mai nella querystring, mai in un cookie.
Gli scope sono ciò che è stato acquistato. Non esiste una chiamata che li estenda: se serve di più, si compra di più.
Ogni operazione registra il consumo dopo l'esecuzione, con i valori veri. Un turno non servito non si paga.
La persona ricorda l'interlocutore attraverso i canali: la chat di oggi e la telefonata di domani sono la stessa conversazione.
La chiave
Non c'è un bottone «genera una chiave di prova». Una chiave nasce con l'acquisto di una persona digitale (o di un singolo componente sovrano), perché scope e quote sono ciò che è stato comprato: non esistono prima dell'ordine.
Dal configuratore si compone la persona — cervello, organi, canali — e si arriva al checkout. In alternativa si compra un componente standalone (per esempio la sola voce): riceve una chiave sua, con gli scope del solo organo.
Per una trattativa, un profilo di conformità particolare o un motore su misura, si passa dal modulo di richiesta: non emette una chiave, apre un contatto.
Il provisioning crea il tenant della persona e, nella stessa transazione, la riga della chiave con gli scopes e le quote dell'ordine. Il nome che porta è letteralmente «chiave di consegna — ordine riferimento»: serve a ritrovarla nell'elenco.
La consegna è un messaggio di posta al cliente dell'ordine. Il valore in chiaro non viene salvato da nessuna parte: il database conserva l'impronta SHA-256 e il prefisso (i primi 16 caratteri), che basta a riconoscerla in un elenco e non basta a indovinarla.
Mettila subito dove la tieni — una variabile d'ambiente, un gestore di segreti. Se la perdi non si recupera: si rigenera.
Nell'area riservata, alla voce delle tue persone, c'è «rigenera». La chiave vecchia viene revocata prima che la nuova nasca: non esiste un istante in cui valgono entrambe, quindi la rigenerazione è un'interruzione di servizio — breve, ma va programmata.
Anche la nuova arriva per email. Se la posta non parte, la risposta lo dice: la vecchia è comunque morta.
/v1 emette, ruota o revoca chiavi. Le 45 rotte del contratto usano la chiave, non la amministrano: la rigenerazione richiede una sessione dell'area riservata, quindi non si automatizza da programma.chiave_scaduta non si vedrà mai. Ruotare è una scelta tua, non un promemoria nostro.Questo elenco è scritto perché un'assenza dichiarata costa meno di un flusso raccontato e poi non trovato. Quando uno di questi passi esisterà, cambierà questa pagina.
Quickstart
Tre chiamate: chi è la persona, quanto le resta, e come le si parla. La chiave sta in una variabile d'ambiente — mai incollata nel codice.
GET /v1/personaLa carta d'identità: scope acquistati, quote residue, vincoli e l'asse di sovranità. È la prima chiamata che si fa, ed è la più economica che esista qui: non carica file e non accende una GPU. Non è gratis però — conta come una richiesta sulla quota richieste_giorno, come ogni altra.
È anche l'unica che non chiede uno scope particolare: se la tua chiave è attiva, questa risponde.
adp_pk_ più 40-64 caratteri) ma non esiste in nessun database: copiandola otterrai 401 chiave_non_valida. Metti la tua, quella arrivata per email.
# la chiave è nell'ambiente, non nel sorgente
export ADP_KEY="adp_pk_QUESTA-CHIAVE-E-FINTA-non-funziona-0000000000"
curl https://adp.agile.software/v1/persona \
-H "Authorization: Bearer $ADP_KEY"
{
"persona": { "agent_key": "giulia", "slug": "giulia-back-office", "stadio": "operativa" },
"scopes": ["channels.chat", "memory.read", "organs.voce"],
"quote": {
"richieste_giorno": { "tetto": 2000, "usato": 137, "restante": 1863, "finestra": "giorno" }
},
"vincoli": { "sovranita": { "valore": "ue", "livello": "duro" } },
"sovranita": "ue",
"cervello": { "base": "…", "veloce": "…", "ammessi": [] },
"skills": []
}
GET /v1/consumiIl consumo, da solo: la chiamata che si mette in un cruscotto per sapere se aspettare o comprare di più.
curl https://adp.agile.software/v1/consumi \
-H "X-Persona-Key: $ADP_KEY"
{
"quote": {
"richieste_giorno": { "tetto": 2000, "usato": 137, "restante": 1863, "finestra": "giorno" },
"minuti_voce": { "tetto": 300, "usato": 12, "restante": 288, "finestra": "mese" }
},
"momento": "2026-01-01T12:00:00.000Z"
}
POST /v1/messaggi/inviaIl tuo sistema scrive, la persona risponde. La memoria dell'interlocutore è unica attraverso i canali. Autorizza prima, esegue, poi registra il costo reale del turno.
curl https://adp.agile.software/v1/messaggi/invia \
-H "Authorization: Bearer $ADP_KEY" \
-H "Content-Type: application/json" \
-d '{
"canale": "chat",
"mittente": "cliente-482",
"nome": "Marco Rossi",
"testo": "Quando scade la mia pratica?"
}'
Un corpo malformato risponde 400 con { "errore": ... }; quota esaurita risponde con tetto, usato e finestra, così il tuo codice sa se ritentare. Il cervello non disponibile è un 503 — e quel turno non si paga.
Nella risposta c'è anche verificaNumerica, ed è nuovo dal 18/9/2026: ogni numero che la persona scrive viene passato a un motore di calcolo deterministico, e quello che non si è potuto verificare esce dichiarato. esito vale pulito, marcato (c'è almeno un numero non verificato, ed è in nonVerificati) o sbagliato (un calcolo falso, con dentro quanto fa davvero). La risposta arriva comunque: sta al tuo sistema mostrare l'avviso accanto al testo, mai dentro — se si confonde con ciò che la persona afferma, non serve a nulla.
Quando va storto
Il corpo di un errore ha sempre la stessa forma: { "errore": "<codice>", "dettaglio": … }. Il codice è una parola stabile — è su quella che si scrive la gestione, non sul testo.
| Stato | Codice | Che cos'è, e che cosa fare |
|---|---|---|
| 401 | chiave_non_valida |
Chiave assente, malformata, sconosciuta, revocata, o persona archiviata: i casi sono indistinguibili di proposito, perché chi prova chiavi a caso non deve imparare nulla dalla risposta. Non ritentare: controlla l'header, e se la chiave è stata rigenerata prendi quella nuova. |
| 401 | chiave_scaduta |
La chiave è vera ma oltre la sua data: te lo diciamo perché tu non vada a cercare un guasto che non c'è. Porta scaduta_il. Lo stato resta 401 apposta — la tua gestione degli errori non cambia, cambia solo la parola. Oggi non lo vedrai: le chiavi nascono senza scadenza. |
| 423 | persona_sospesa |
La chiave è giusta, ma la persona è stata sospesa dall'area riservata: non risponde su nessun condotto. Qui il perche si dice, perché chi ha la chiave giusta è il proprietario. Si risolve riprendendola dall'area, e riparte esattamente dallo stadio in cui era. |
| 429 | quota_esaurita |
La quota acquistata è finita. Il dettaglio porta quota, tetto, usato e finestra, e l'header Retry-After dice i secondi che mancano perché il consumo più vecchio esca dalla finestra. Aspetta quei secondi, oppure compra di più. |
| Stato | Codice | Che cos'è, e che cosa fare |
|---|---|---|
| 403 | scope_mancante |
La chiamata esiste, ma il suo scope non è fra quelli acquistati. Il dettaglio dice serve e hai: non è un'informazione utile a un estraneo, e a te serve. Nessuna chiamata estende uno scope — si compra. |
| 402 | quota_non_acquistata |
Per quell'operazione non c'è una quota, oppure è a zero. Una quota non dichiarata non vuol dire illimitata: vuol dire non comprata, e qui si fallisce chiusi. |
| 403 | vietato_dal_profilo |
Il profilo di conformità della persona vieta quella famiglia di operazioni — per esempio il profilo minori sulle chiamate di voce. Non si aggira con uno scope comprato. |
| 429 | troppe_richieste |
È un 429 diverso: non la quota, la frequenza. Il freno è per indirizzo IP, con finestra di un minuto (240 richieste sulle rotte di lettura, 60 su quelle che fanno lavoro). La risposta porta bucket e gli header standard RateLimit. Distingui i due 429 sul campo errore, mai sullo stato. |
Il contratto a macchina dichiara per ogni rotta 401, 423 e 429. I tre della seconda tabella li restituisce il codice ma il contratto non li elenca ancora: sono qui perché gestirli ti evita un guasto, non perché siano documentati là.
La superficie
/v1Sono ventidue, e sono tutte qui. Ogni chiamata è governata dallo scope della chiave: quello che la persona non ha comprato risponde 403 scope_mancante. Lo scope indicato accanto a ogni rotta è quello che il codice pretende davvero.
/v1/personeforgia una persona nuova: nasce in «staging», l'ordine la promuoveforgia.persone/v1/personele persone che hai forgiato e non ancora comprato. L’elenco è tagliato a 200 righe e non c’è paginazione: la risposta porta totale, inQuestaPagina e troncato, così sai se ne esistono altrenessuno scope/v1/persone/:agentKey/promuovida «staging» a «produzione»: vuole un ordine pagato della tua organizzazioneforgia.persone/v1/persone/:agentKey/metodola checklist di nascita: quali dei 6 pezzi del metodo questa persona ha già (il libro, i fatti, le domande, le tabelle, gli strumenti, il collaudo) e dove si scrivono quelli che mancanonessuno scope/v1/persone/:agentKey/profilocorregge nome, ruolo, carattere, carattere della voce, caratteristiche: tocca solo i campi che mandi, e agent_key non cambia maiforgia.persone/v1/personaidentità, scope, quotenessuno scope/v1/consumiquote residuenessuno scope/v1/messaggi/inviaun turno di conversazionechannels.chat/v1/capacitache cosa questa persona può fare su un canale, su quali canali, e l'elenco chiuso degli esiti di ogni capacità: si legge prima di scrivere il proprio switchnessuno scope/v1/canale/arrivoè arrivato un messaggio: la persona capisce, decide e risponde — oppure dice perché no. Lo stesso uid due volte dà una rispostail canale della richiesta/v1/canale/scrivila persona scrive per prima: la chiave di idempotenza logica la decidi tu, consenso e soppressione li impone il trasportoil canale della richiesta/v1/canale/passa-a-umanopassa a un umano: il referente viene dal forge, mai cablato nel canaleil canale della richiesta/v1/canale/consegna/:idè partita davvero? risposto vuol dire accodato, e accodato non è consegnatonessuno scope/v1/canale/fermatila persona si ferma e lo dice: il rifiuto entra nel registro e torna la frase da dire. Non consuma quota — un rifiuto non si fatturanessuno scope/v1/memoria/:interlocutorelo storico di un interlocutorememory.read/v1/memoria/:interlocutore/dimenticadiritto all'oblio (art. 17)memory.write/v1/memoria/:interlocutore/collegastessa persona, canale nuovo: il codice arriva sul canale NUOVOmemory.write/v1/memoria/:interlocutore/collega/confermail codice, digitato sul canale di primamemory.write/v1/voce/sintetizzatesto → audioorgans.voce/v1/voce/trascriviaudio → testoorgans.orecchie/v1/voce/turnoaudio → audio: un turno interoorgans.orecchie + channels.chat + organs.voce/v1/videochat/apriuna sessione con volto e vocechannels.videochat/v1/videochat/chiudichiude la stanza e addebita i minuti verichannels.videochat/v1/atelier/schedauna scheda di esercizi (PDF vero)mani.atelier/v1/atelier/documentoun documento (markdown) nel cassettomani.atelier/v1/atelieril cassetto dei materiali, tagliato a 50 righe e senza paginazione: la risposta porta totale, inQuestaPagina e troncatomani.atelier/v1/atelier/:idscarica un materialemani.atelier/v1/agendala persona PROPONE un impegno (mai nei giorni vietati)mani.agenda/v1/agenda/:id/statoconcordare o disdiremani.agenda/v1/agendai prossimi impegnimani.agenda/v1/calendario/aggregatoil calendario AGGREGATO dell'aula per un partner: istanti e capienza, nessun identificativo di studentemani.agenda/v1/bibliotecaassorbe un libro posseduto (il DRM si respinge, mai si aggira)organs.biblioteca/v1/biblioteca/cercacerca per testo o per PAGINA («pagina 184»)organs.biblioteca/v1/bibliotecalo zaino dei volumiorgans.biblioteca/v1/biblioteca/:idtoglie un volume dallo zainoorgans.biblioteca/v1/conoscenzacarica una fonte della tua conoscenza (corpo GREZZO, metadati in query): nasce in bozza, non fa fedeconoscenza.scrivi/v1/conoscenzail catalogo delle fonti, con la loro certificazione e la modalita del firewallconoscenza.leggi/v1/conoscenza/urlcarica da un indirizzo: solo https pubblico, ogni salto rigiudicato, tipi e byte col tettoconoscenza.scrivi/v1/conoscenza/:id/certificada qui fa fede: chi firma e la chiave, e servono validita e AMBITOconoscenza.scrivi/v1/conoscenza/:id/revocatoglie la fede da subito; i brani restano, la revoca e un fattoconoscenza.scrivi/v1/conoscenza/:idcancella la fonte e i suoi brani (resta la riga di audit)conoscenza.scrivi/v1/conoscenza/cercache cosa sa la persona su una domanda: brani, punteggi e soglia, senza farla parlareconoscenza.leggi/v1/conoscenza/reindicizzarifa i vettori (indicizzazione fallita, o modello cambiato). Idempotenteconoscenza.scrivi/v1/conoscenza/verdettiil referto del firewall: quanti NO, quanti SI, e per quale motivoconoscenza.leggi/v1/mani/operaopera un'applicazione col contratto a verbi (scope-dati obbligatorio; default = prova)mani.operate_appAttenzione a un'asimmetria che costa tempo: gli scope qui sopra sono quelli che governano le rotte, e non coincidono uno a uno con le voci del listino. Comprare il solo cervello dà lo scope converse, che nessuna di queste trentasette rotte controlla: per far rispondere /v1/messaggi/invia serve channels.chat. La GET /v1/persona ti dice sempre quali scope hai davvero in mano — è il motivo per cui è la prima chiamata da fare.
Una persona sospesa dall'area riservata risponde 423 persona_sospesa su ogni endpoint: chi ha la chiave giusta ha diritto al motivo — la ripresa dall'area la rimette esattamente dov'era.
Il diritto all'oblio è un endpoint: il tuo cliente è titolare del trattamento dei suoi interlocutori, e la cancellazione cancella davvero — non azzera campi. La risposta elenca anche le registrazioni che devi distruggere tu.
Il contratto
Questa pagina è la spiegazione. La verità formale sta in un file OpenAPI 3.0.3, con le 45 rotte, il metodo, il percorso, il permesso di ognuna e le tre risposte d'errore del contratto.
docs/openapi-v1.yaml
Il server dichiarato è il percorso relativo /v1: nessun host è cablato dentro il contratto, perché l'host lo decide il rilascio. La sicurezza è dichiarata in due modi alternativi per la stessa chiave — Authorization: Bearer oppure l'header X-Persona-Key: usa quello che il tuo client gestisce meglio.
Non si modifica a mano. Il file è generato dal codice delle rotte, quindi non può descrivere una rotta che non esiste né dimenticarne una che è stata aggiunta. Se un giorno divergesse dal comportamento, il difetto è nel codice, non nella specifica.
Due cose che è giusto sapere prima di puntarci uno strumento: il contratto non è pubblicato su HTTP — sta nel repository del progetto, e te lo passiamo insieme alla chiave — e non dichiara gli schemi dei corpi di richiesta e risposta, lasciati aperti di proposito. Per generare un client tipizzato oggi non basta: per sapere che cosa esiste, che cosa pretende e come rifiuta, sì.
Sovranità
Ogni cosa vendibile porta con sé la risposta a due domande indipendenti. La GET /v1/persona le restituisce entrambe: chi integra non deve chiederle a nessuno.
esecuzione: nostra (motori sovrani self-host), ue (provider in Unione Europea) o extra-ue. Il profilo di conformità della persona può vincolarlo — e allora una scelta fuori regione viene rifiutata, non silenziosamente accettata.
dati: sovrani (restano nel nostro perimetro) o condivisi (passano dal provider). Sono due assi separati: un calcolo in UE non implica dati sovrani, e il sistema non lascia confondere le due cose.
Né provider né voice_id si accettano dal corpo della richiesta: la voce e il volto della persona sono quelli che ha comprato, risolti dal Lettore Unico. Un parametro dal chiamante non la farà parlare con una voce che non è la sua, sul nostro conto.
Motori sovrani
I motori self-host sono prodotti a sé, con revisione semantica e stato di rilascio (sviluppo / beta / attiva / deprecata / ritirata). "L'ultima" è sempre l'ultima attiva, mai una beta. Girano su hardware in UE, on-demand, con licenza in affitto (e, in prospettiva, perpetua).
La sintesi vocale sovrana: testo dentro, audio fuori. È l'organo voce di default, con dati sovrani.
La trascrizione sovrana (STT): audio dentro, testo fuori. È l'organo orecchie di default.
La visione sovrana su GPU: legge documenti e immagini. È l'organo occhi di default.
Accanto ai motori sovrani restano i provider esterni (ElevenLabs, Deepgram, Tavus…): stesso organo, asse dei dati diverso. Il catalogo vendibile, con codice, revisione attiva e i due assi, è pubblico su GET /api/servizi.
Distinta base
Una persona digitale è una distinta base di componenti sovrani: cervello, voce, orecchie, occhi, volto, mani, canali. Ognuno è un prodotto con un part number stabile — un progressivo che non si riusa mai, come nei sistemi maturi.
Puoi comprare un singolo componente sovrano e usarlo via API, senza assumere una persona intera: una voce o delle orecchie diventano un servizio a sé, con la loro chiave e la loro quota. La distinta AS-CONFIGURED nasce dal configuratore e arriva intatta fino all'assunzione.
Gli organi, come li vende il listino
Questi sono i nomi con cui gli organi si comprano. Gli scope che aprono le rotte di /v1 stanno accanto a ogni endpoint, e non sono gli stessi: la fonte di verità su che cosa hai in mano è sempre GET /v1/persona.
Multi-canale
Chat, email, WhatsApp, telefono e videochiamata sono scope sulla chiave. Il campo canale del messaggio dice da dove arriva l'interlocutore; la memoria resta unica quando cambia.
channels.chatchatchannels.emailemailchannels.waWhatsAppchannels.telefonovoce, richiede orecchiechannels.videochatvolto + voceDi questi, quelli che oggi aprono una rotta di /v1 sono due: channels.chat (che governa /v1/messaggi/invia, qualunque sia il valore del campo canale) e channels.videochat. Email, WhatsApp e telefono si comprano e si configurano, ma non hanno una rotta dedicata nel contratto: passano di lì.
Chiedi una API key per la persona del tuo cliente e integrala nel tuo software. Scope e quota li decide il contratto, non il codice.