App esterne
Collega la tua applicazione dentro IntelligenceBox: compare nel menu laterale del cliente e si apre al suo posto, già autenticata come la persona che l’ha aperta. L’apertura ti consegna la sua chiave API, con cui parli con la Box a suo nome — la tua app vede esattamente quello che vede lei, e niente di più.
Per chi è questa pagina
Per chi sviluppa l’applicazione esterna. Il cliente la collega dalla sua Box in Automazioni → App esterna e da lì ti consegna un solo valore: l’indirizzo di verifica, che è lo stesso per ogni cliente. Non c’è nessun segreto da configurare: la chiave ti arriva insieme all’apertura dell’app.Come funziona#
Quattro passaggi. I primi due li fa la Box, gli altri due la tua applicazione.
- 1
L’utente apre la tua app dal menu
La Box conia una chiave API per quella persona e chiede al servizio di autenticazione un ticket monouso, valido 60 secondi, legato a quella chiave. - 2
La Box apre il tuo percorso SSO in un iframe
Riceverai una POST sul percorso che hai indicato in configurazione, conticket,apiKeyeapiBaseUrlnel corpo. Mai in query string: un ticket in un URL resterebbe in cronologia, referrer e log. - 3
Riscatti il ticket dal tuo server
Una POST a/api/integrations/embed/exchangecon il ticket, usando come bearer laapiKeyappena ricevuta. Ti risponde con chi è l’utente. - 4
Apri la sessione e usi la chiave
Crei la tua sessione applicativa come faresti con qualsiasi SSO, e conservi la chiave API per parlare con la Box — subito o più tardi, anche quando l’utente non è collegato.
Riscattare il ticket#
/api/integrations/embed/exchangepubblicoConsuma il ticket di apertura e restituisce l’identità della persona che ha aperto l’app.
Chiamata da server a server, mai dal browser. Come bearer usi la apiKey arrivata con l’apertura: il ticket è legato a quella chiave, quindi chi avesse intercettato il solo ticket non può riscattarlo.
# The key is the one that arrived in the same launch POST.
curl -X POST AUTH_URL/api/integrations/embed/exchange \
-H "Authorization: Bearer $API_KEY_FROM_THE_LAUNCH" \
-H "Content-Type: application/json" \
-d '{ "ticket": "THE_TICKET_FROM_THE_LAUNCH" }'Corpo della richiesta
AuthorizationstringobbligatorioBearer <apiKey>— la chiave ricevuta nella stessa POST di apertura. Non c’è nessun segreto da configurare in anticipo.
ticketstringobbligatorio- Il valore del campo
ticketche hai ricevuto sul percorso SSO. È monouso: la seconda chiamata con lo stesso ticket fallisce.
Risposta
{
"user": {
"id": "cmlwjuy7e0000fiyvsdbo6mya",
"email": "laura@example.com",
"name": "Laura Bianchi"
},
"embed": { "key": "portale", "name": "Portale" },
"apiKey": {
"key": "boxe_9f3c…",
"persistent": true,
"issuedNow": false,
"header": "x-api-key"
},
"api": { "baseUrl": "https://box.example.com/api/public" }
}userobject- Chi ha aperto l’app: id, email e nome come li conosce il servizio di autenticazione.
embedobject- La chiave dell’app, così puoi verificare di essere stato aperto come quello che credi.
apiKey.keystring- Non è nella risposta: l’hai già ricevuta con l’apertura. Mettila in
x-api-keysu ogni chiamata all’API della Box. api.baseUrlstringapiBaseUrlarriva con l’apertura ed è la radice a cui rivolgere le chiamate: reverse proxy, tunnel e Box in LAN hanno indirizzi diversi. Confrontalo con quello che conosci per quel cliente prima di usarlo — arriva da una POST che chiunque può forgiare.
200Ticket valido: identità nella risposta.400Manca il ticket o la chiave.401Ticket scaduto, già usato, oppure chiave sbagliata.403L’account non è abilitato ad aprire questa app.500Il servizio di autenticazione non è raggiungibile, oppure le app esterne non sono configurate su questa Box.
La chiave API#
Una chiave per persona e per app installata. Non è una chiave di servizio: è la persona che ha aperto la tua applicazione.
Dura nel tempo
Uno scambio successivo per lo stesso utente restituisce la stessa chiave. Riaprire l’app non invalida quella che stai già usando, quindi puoi conservarla e lavorare anche quando l’utente non è collegato — sincronizzazioni notturne, webhook, promemoria.
Vede quello che vede l’utente
La chiave non è un accesso amministrativo: ogni chiamata è filtrata sulla visibilità di quella persona, e sullo spazio di lavoro da cui l’app è stata aperta. Quello spazio è legato alla chiave: mandare un header x-organization-id diverso non la sposta altrove.
Come muore
La chiave smette di funzionare, subito, quando:
- il cliente revoca gli accessi dalla pagina di configurazione;
- l’app viene disinstallata dalla Box;
- l’app viene messa in pausa.
Dopo una revoca, la chiave nuova arriva con la prima riapertura dell’app: non c’è niente da riconfigurare. Gestisci un 401 rimandando l’utente al lancio, non ritentando la stessa chiave.
Usare la chiave#
Da qui in avanti è la normale API pubblica: stessa autenticazione, stessi endpoint. La cosa più utile con cui iniziare è una notifica, perché arriva alla persona anche quando la tua app non è aperta.
curl -X POST BOX_URL/api/public/notifications \
-H "x-api-key: boxe_9f3c…" \
-H "Content-Type: application/json" \
-d '{
"title": "Pratica 4821 approvata",
"description": "Il documento e pronto per la firma.",
"type": "success",
"icon": "FileCheck",
"name": "Portale"
}'Il resto della superficie
Cartelle, documenti, ricerca semantica, assistenti e conversazioni funzionano allo stesso modo con questa chiave. ChiediGET /api/public/capabilities per l’elenco aggiornato di ciò che la Box del cliente espone.Link, file, stampa e finestre di dialogo#
Nell’app Intelligence Box la tua pagina gira in un iframe di una webview nativa, non in un browser. Alcune cose che nel browser vanno da sole qui non fanno nulla: per quelle chiedi all’app, con una piccola libreria.
Link verso un altro sito, target="_blank", window.open
L’app li apre nel browser di sistema. Per aprire un indirizzo da codice usa openExternal(url).
Download con <a download>
Il file finisce in Download senza chiedere dove. Con saveFile l’utente sceglie dove salvarlo.
window.print()
Non fa nulla. Genera un PDF e passalo a openFile: si apre nell’applicazione predefinita (Anteprima su Mac), da cui si stampa. Oppure salvalo con saveFile.
alert(), confirm(), prompt()
Non mostrano nulla: confirm risponde sempre false, prompt sempre null. Usa le versioni della libreria: sono asincrone e mostrano la finestra dell’app.
navigator.clipboard.writeText
Funziona: non serve la libreria.
Includi la libreria
Un solo file, senza dipendenze: /sdk/embed-v1.js di questo sito espone window.IntelligenceBox (con un bundler CommonJS, require restituisce lo stesso oggetto). I tipi TypeScript sono accanto, in /sdk/embed-v1.d.ts. Puoi anche copiarlo nel tuo progetto: una modifica incompatibile arriverà come v2, in un file nuovo.
<script src="https://www.intelligencebox.it/sdk/embed-v1.js"></script>
<script>
// "Excel / CSV": the user chooses where the file goes.
exportButton.addEventListener("click", async () => {
const csv = new Blob([rows.join("\n")], { type: "text/csv" });
await IntelligenceBox.saveFile({ fileName: "dashboard.csv", data: csv });
});
// "PDF / print": produce the PDF, then open it in the default viewer.
printButton.addEventListener("click", async () => {
const pdf = await fetch("/dashboard.pdf").then((r) => r.blob());
await IntelligenceBox.openFile({ fileName: "dashboard.pdf", data: pdf });
});
// Instead of window.confirm / window.prompt.
deleteButton.addEventListener("click", async () => {
if (await IntelligenceBox.confirm("Delete this record?", { destructive: true })) remove();
});
linkButton.addEventListener("click", async () => {
const url = await IntelligenceBox.prompt("Link URL", "https://");
if (url !== null) insertLink(url);
});
</script>Funzioni
openExternal(url)Promise<void>- Apre un indirizzo http(s), mailto: o tel: fuori dall’app.
saveFile({ fileName, data, mimeType? })Promise<boolean>- Mostra «Salva con nome» e scrive il file; false se l’utente annulla. data è un Blob, un File, un ArrayBuffer, un typed array o testo; puoi anche passare direttamente un File.
openFile({ fileName, data })Promise<void>- Apre un documento nell’applicazione predefinita del computer: un PDF da stampare, un foglio in Excel. Solo PDF, testo, CSV/TSV, RTF, Office (docx, xlsx, pptx), OpenDocument e immagini. Non disponibile su telefono.
alert(message)Promise<void>- Un avviso con il pulsante OK.
confirm(message, { confirmLabel?, cancelLabel?, destructive? })Promise<boolean>- Una conferma; destructive colora il pulsante come un’azione distruttiva.
prompt(message, defaultValue?, { placeholder?, confirmLabel?, cancelLabel? })Promise<string | null>- Una domanda con una casella di testo; null se l’utente annulla.
connectTools(path)Promise<{ connected, tools } | null>- Porta nella chat gli strumenti del server MCP della tua app, a questo percorso del suo dominio, per la persona che la sta usando, con il suo accesso all’app: nessuna configurazione in Integrazioni. Chiamalo dopo l’accesso; un collegamento esistente resta com’è. null fuori dall’app.
connect({ timeout? })Promise<Client | null>- L’app intorno alla pagina, con platform, locale, capabilities e can(nome); null in un browser normale. Le funzioni sopra lo chiamano da sole.
Le finestre di dialogo hanno sempre come titolo il nome della tua app, come un browser scrive il sito sopra un confirm: la pagina sceglie solo il testo e le etichette dei pulsanti.
Lo stesso codice nel browser
Fuori dall’app — una scheda del browser, un altro sito che incorpora la tua pagina — ogni funzione usa il browser: openExternal apre una nuova scheda, saveFile scarica il file, openFile lo apre in una scheda, alert, confirm e prompt sono quelli nativi.
Limiti e sicurezza
- File fino a 100 MB.
- Testo delle finestre fino a 4.000 caratteri, solo testo semplice; etichette dei pulsanti fino a 40.
- Una finestra alla volta, fino a 5 in coda. Se la pagina dell’iframe cambia, quelle in attesa si chiudono come annullate.
- openExternal: al massimo 5 indirizzi ogni 10 secondi.
- La libreria parla solo con la pagina dell’app, e l’app risponde solo all’iframe e all’origine della tua installazione. Non passa identità né chiavi: non mandarci mai la apiKey.
Errori
Una richiesta rifiutata rigetta la promessa con IntelligenceBoxError, il cui code è unsupported (funzione non disponibile qui, o un tipo di file che openFile non apre), invalid (argomenti sbagliati), busy (troppe richieste in attesa) o failed (non è riuscito, e l’utente ha già visto l’avviso).
Regole di sicurezza#
Poche, ma non sono negoziabili.
La chiave non tocca mai il browser
Arriva in una POST al tuo server e lì deve restare. Se finisce in un bundle JavaScript, chiunque può agire per conto dell’utente.
Nemmeno la chiave API
Trattala come la password dell’utente: sta sul tuo backend, e le chiamate alla Box partono da lì. Un frontend che la espone la regala a chi apre gli strumenti di sviluppo.
Riscatta il ticket una volta sola
È monouso per costruzione. Se la tua pagina SSO fa doppio rendering o prefetch, il secondo tentativo fallisce: riscatta lato server, non in un effect del client.
Non fidarti dell’iframe per l’identità
L’unica identità autorevole è quella che torna dallo scambio. Qualsiasi cosa arrivi via postMessage o query string oltre al ticket va considerata suggerimento, non prova.
Quando non funziona#
401 allo scambio, ticket appena ricevuto
Il ticket è già stato consumato (doppio rendering della pagina SSO) oppure il segreto client non corrisponde più: chiedi al cliente se l’ha ruotato.
403 allo scambio
L’account non soddisfa i requisiti che il cliente ha impostato sull’app — dominio email, verifica dell’indirizzo, brand.
401 su una chiamata API che prima andava
Segreto ruotato, app in pausa o disinstallata. Riparti dal lancio.
L’app non compare nel menu del cliente
È una questione di visibilità, decisa nella sua pagina di configurazione: chi non la vede non può neanche aprirla.
Una voce di menu non risponde al clic, su Mac
WebKit — Safari e l’app su macOS — non dà il focus a un pulsante quando lo premi. Un menu che si chiude su blur senza relatedTarget sparisce prima del clic. L’app lo compensa per le app incorporate, ma in Safari il menu resta rotto: chiudilo su un clic fuori dal menu.