1Una chiave
Un amministratore la crea in Impostazioni → Integrazioni. Compare una volta sola: conserviamo solo la sua impronta, quindi non possiamo mostrarla di nuovo nemmeno noi.
cst_live_xxxxxxxxxxxxxxxxxxxxxx_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
Alla creazione si sceglie anche quanto può fare, e i valori predefiniti sono i più stretti:
- Permessi — spuntati uno per uno. Una chiave nuova nasce in sola lettura.
- Moduli raggiungibili — vuoto significa tutti. Restringerli accorcia l’elenco di strumenti che l’integrazione vede.
- Azioni distruttive — spenta. Senza quella spunta la chiave non vede nemmeno gli strumenti che eliminano.
- Chiamate al minuto e tetto giornaliero, indirizzi ammessi (singoli o blocchi CIDR), scadenza facoltativa.
2Un endpoint
Ogni operazione si chiama nello stesso modo: POST /api/v1/tools/<nome>, con gli argomenti nel corpo.
curl -X POST https://api-custodo.xdevapp.it/api/v1/tools/documents_search \
-H "Authorization: Bearer cst_live_…" \
-H "Content-Type: application/json" \
-d '{"query":"fattura acme","limit":10}'
Per sapere cosa quella chiave può chiamare:
curl https://api-custodo.xdevapp.it/api/v1/tools \
-H "Authorization: Bearer cst_live_…"
Il riferimento completo — un’operazione per strumento, con lo schema esatto degli argomenti — è pubblicato in OpenAPI 3.1 e non richiede autenticazione, perché va letto prima di avere una chiave:
GET https://api-custodo.xdevapp.it/api/v1/openapi.json
La stessa cosa da leggere, generata da quel documento — oppure scaricala come collection già pronta per Postman o Insomnia.
3Come leggere la risposta
Ogni chiamata torna con un codice HTTP e, quando è 200, con un corpo che dice se lo strumento ha fatto o ha rifiutato. Le due cose sono diverse e vanno gestite diversamente.
| Esito | Significa | Cosa fare |
|---|---|---|
200 is_error: false | Fatto. La risposta è in data.result. | — |
200 is_error: true | Lo strumento ha girato e ha rifiutato: documento sotto blocco legale, offerta già chiusa, campo non valido. | Correggi gli argomenti. Ritentare identico non serve. |
| 401 | La chiave non è valida, è stata revocata o è scaduta. | Controlla la chiave. |
| 403 | La chiamata non è avvenuta: modulo non attivo, permesso mancante, livello insufficiente, oppure strumento distruttivo e chiave senza quel consenso. Il campo error.code dice quale. | Serve un amministratore. |
| 404 | Quel nome di strumento non esiste in questa versione. | Controlla l’elenco. |
| 429 | Minuto o giornata esauriti per quella chiave. | Attendi Retry-After secondi. |
I codici in error.code sono stabili: fai i controlli su quelli, non sul messaggio.
4Oppure MCP, se dall’altra parte c’è un assistente
Le stesse operazioni sono raggiungibili sul protocollo MCP — quello che Claude e ChatGPT già parlano — allo stesso indirizzo per tutti:
POST https://api-custodo.xdevapp.it/mcp
Funziona con la stessa chiave API come Bearer, oppure con OAuth 2.1 (registrazione dinamica del client, PKCE obbligatorio, schermata di consenso in cui la persona spunta cosa concedere). I metadati sono dove il protocollo li cerca: /.well-known/oauth-protected-resource/mcp.
5I webhook, per sapere quando succede qualcosa
Il contrario delle chiamate: quando in Custodo succede qualcosa, chiamiamo noi il tuo sistema. Si configurano in Integrazioni → Webhook: un indirizzo https e gli eventi che vuoi (nessuna spunta = tutti).
Ogni consegna porta la firma:
POST /il-tuo-hook HTTP/1.1
X-Custodo-Event: documents.document.ready
X-Custodo-Delivery: 6f1e…
X-Custodo-Signature: t=1788280000,v1=<hmac-sha256 esadecimale>
Content-Type: application/json
{"id":"6f1e…","event":"documents.document.ready","tenant_id":"…","created_at":"…","data":{…}}
Verificarla
// Node
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(secret, rawBody, header) {
const parts = new Map(header.split(',').map((p) => p.split('=')));
const t = Number(parts.get('t'));
// Rifiuta quello che ha più di cinque minuti: il momento è dentro la firma
// proprio perché una consegna intercettata non sia riutilizzabile domani.
if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(parts.get('v1') ?? '');
return a.length === b.length && timingSafeEqual(a, b);
}
Firma il corpo grezzo, prima di qualunque parsing: un JSON riserializzato non produce gli stessi byte.
Se il tuo server non risponde
Riproviamo con attese crescenti — 3 secondi, 9, 27, 81, 243 — e poi ci fermiamo. Ogni tentativo lascia una riga nel registro delle consegne, con il codice di risposta e le prime righe di quello che è tornato indietro, e da lì puoi riprovare a mano.
https, e mai verso reti private o indirizzi di servizio interni. Il controllo si ripete a ogni consegna e non solo quando salvi l’indirizzo: un nome che stamattina risolveva a un indirizzo pubblico può risolvere altrove nel pomeriggio.6Scaricare la collection
Tre file, tutti generati dallo stesso documento OpenAPI che il server pubblica — con le operazioni disponibili al momento della generazione. Imposta apiKey con la tua chiave e sostituisci gli argomenti di esempio con i valori del tuo ambiente prima di inviare una richiesta.
Collection Postman
Una cartella per gruppo, una richiesta per operazione, con un corpo di esempio già compilato dai campi obbligatori. Le variabili baseUrl e apiKey sono in testa alla collection.
Documento OpenAPI
Lo schema esatto di ogni argomento, così come il server lo valida. È quello che serve a generare un client, non a leggerlo: openapi-generator, oapi-codegen e simili lo mangiano direttamente.
Riferimento da leggere
La stessa cosa in pagina: ogni operazione con permesso richiesto, livello minimo e tabella degli argomenti.
Apri il riferimentohttps://api-custodo.xdevapp.it/api/v1/openapi.json, pubblico e senza chiave — e non a questi file: la copia qui esiste perché il browser possa salvarla e perché la CORS dell’API non nomina questo sito.7I limiti, detti prima
- Una chiave non supera la persona per cui è stata creata: se domani le togli un permesso, la chiave lo perde alla chiamata successiva, senza rotazioni.
- Firmare non è chiamabile. Firmare è un atto di volontà di una persona fisica; non esiste un’operazione che firmi al posto suo.
- Creare chiavi non è chiamabile da una chiave: una credenziale che può allargarsi da sola è esattamente la cosa che questo modello di permessi esiste per impedire.
- Un interruttore generale per fermare tutto, per organizzazione, e il registro di ogni chiamata — con esito, durata e credenziale — che nessuno può modificare.
Parliamone
Se stai valutando un’integrazione e vuoi capire se regge il tuo caso, scrivici da questa pagina: ti risponde chi l’ha costruito.