Passa al contenuto principale

Esempi di utilizzo

Questa sezione raccoglie esempi pratici di integrazione di Askme Desk all’interno di applicazioni e script esterni, utilizzando le API REST esposte dal sistema tramite il prefisso /rest.

Gli esempi mostrano scenari ricorrenti di utilizzo, tra cui:

  • autenticazione OAuth 2.0 tramite Keycloak
  • creazione e gestione di richieste di I livello
  • creazione e avanzamento di ticket di II livello
  • interrogazione di statistiche SLA
  • importazione massiva di richieste da file CSV

Esempio 1: client Python per richieste e ticket

Il seguente esempio mostra un client Python per l’invocazione delle principali API REST di Askme Desk, con gestione automatica del token OAuth 2.0 ottenuto tramite client credentials flow.

import requests
from datetime import datetime, timedelta
from typing import Optional


class DeskClient:
"""Client per le API REST di Askme Desk con gestione automatica del token OAuth 2.0."""

def __init__(self, base_url: str, client_id: str, client_secret: str, keycloak_url: str):
self.base_url = base_url.rstrip("/") # es. https://desk.example.com/rest
self.client_id = client_id
self.client_secret = client_secret
self.keycloak_url = keycloak_url
self.access_token: Optional[str] = None
self.token_expiry: Optional[datetime] = None

def _get_token(self) -> str:
"""Ottiene un nuovo token OAuth 2.0 da Keycloak."""
url = f"{self.keycloak_url}/protocol/openid-connect/token"
payload = {
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret,
}
response = requests.post(url, data=payload)
response.raise_for_status()
data = response.json()
self.access_token = data["access_token"]
self.token_expiry = datetime.utcnow() + timedelta(seconds=data["expires_in"] - 60)
return self.access_token

def _headers(self) -> dict:
"""Restituisce gli header HTTP con un token valido."""
if self.access_token is None or datetime.utcnow() >= self.token_expiry:
self._get_token()
return {
"Authorization": f"Bearer {self.access_token}",
"Content-Type": "application/json",
}

# ── Richieste (I livello) ──

def crea_richiesta(self, id_servizio: int, id_tipo_richiesta: int,
oggetto: str, descrizione: str = "", **kwargs) -> dict:
"""Crea una nuova richiesta di I livello."""
payload = {
"idServizio": id_servizio,
"idTipoRichiesta": id_tipo_richiesta,
"oggetto": oggetto,
"descrizione": descrizione,
**kwargs,
}
resp = requests.post(
f"{self.base_url}/richieste/creazione-richiesta",
json=payload,
headers=self._headers(),
)
resp.raise_for_status()
return resp.json()

def lista_richieste(self, filtri: dict = None) -> dict:
"""Recupera l’elenco delle richieste con filtri opzionali."""
resp = requests.get(
f"{self.base_url}/richieste/",
json=filtri or {},
headers=self._headers(),
)
resp.raise_for_status()
return resp.json()

def dettaglio_richiesta(self, id_richiesta: int) -> dict:
"""Recupera il dettaglio completo di una richiesta."""
resp = requests.get(
f"{self.base_url}/richieste/{id_richiesta}",
headers=self._headers(),
)
resp.raise_for_status()
return resp.json()

def assegna_richiesta(self, id_richiesta: int,
id_assegnatore: int, id_assegnatario: int,
note: str = "") -> dict:
"""Assegna una richiesta a un operatore."""
payload = {
"idRichiesta": id_richiesta,
"idUtenteAssegnatore": id_assegnatore,
"idUtenteAssegnatario": id_assegnatario,
"noteAss": note,
}
resp = requests.post(
f"{self.base_url}/richieste/assegnazione-richiesta",
json=payload,
headers=self._headers(),
)
resp.raise_for_status()
return resp.json()

def presa_in_carico(self, id_richiesta: int, id_utente: int,
note: str = "") -> dict:
"""Esegue la presa in carico di una richiesta."""
payload = {
"idRichiesta": id_richiesta,
"idUtente": id_utente,
"note": note,
}
resp = requests.post(
f"{self.base_url}/richieste/presa-in-carico",
json=payload,
headers=self._headers(),
)
resp.raise_for_status()
return resp.json()

def chiudi_richiesta(self, id_richiesta: int, id_utente: int,
note: str = "") -> dict:
"""Chiude una richiesta."""
payload = {
"idRichiesta": id_richiesta,
"idUtente": id_utente,
"note": note,
}
resp = requests.post(
f"{self.base_url}/richieste/chiusura",
json=payload,
headers=self._headers(),
)
resp.raise_for_status()
return resp.json()

# ── Ticket (II livello) ──

def crea_ticket(self, id_richiesta: int, oggetto: str,
id_priorita: int = None, **kwargs) -> dict:
"""Crea un ticket di II livello associato a una richiesta."""
payload = {
"idRichiesta": id_richiesta,
"oggetto": oggetto,
**kwargs,
}
if id_priorita:
payload["idPriorita"] = id_priorita

resp = requests.post(
f"{self.base_url}/ticket/creazione-ticket",
json=payload,
headers=self._headers(),
)
resp.raise_for_status()
return resp.json()

def dettaglio_ticket(self, id_ticket: int) -> dict:
"""Recupera il dettaglio completo di un ticket."""
resp = requests.get(
f"{self.base_url}/ticket/{id_ticket}",
headers=self._headers(),
)
resp.raise_for_status()
return resp.json()

def avanza_ticket(self, id_ticket: int, id_azione: int,
note: str = "", durata: int = 0) -> dict:
"""Avanza un ticket allo step successivo del workflow."""
payload = {
"idTicket": id_ticket,
"idAzione": id_azione,
"note": note,
"durata": durata,
}
resp = requests.post(
f"{self.base_url}/ticket/avanzamento-ticket",
json=payload,
headers=self._headers(),
)
resp.raise_for_status()
return resp.json()

def assegna_ticket(self, id_ticket: int,
id_assegnatore: int, id_assegnatario: int,
note: str = "") -> dict:
"""Assegna un ticket a un operatore di II livello."""
payload = {
"idTicket": id_ticket,
"idUtenteAssegnatore": id_assegnatore,
"idUtenteAssegnatario": id_assegnatario,
"noteAss": note,
}
resp = requests.post(
f"{self.base_url}/ticket/assegnazione-ticket",
json=payload,
headers=self._headers(),
)
resp.raise_for_status()
return resp.json()


# ── Esempio di utilizzo ──

client = DeskClient(
base_url="https://desk.example.com/rest",
client_id="my-integration",
client_secret="secret",
keycloak_url="https://keycloak.example.com/realms/askme",
)

# 1. Creazione di una richiesta di I livello
info = client.crea_richiesta(
id_servizio=1,
id_tipo_richiesta=5,
oggetto="Problema di login",
descrizione="L'utente non riesce ad accedere al portale aziendale",
idUrgenza=2,
codiceCanale="WEB",
)
print(f"Richiesta creata: ID {info['idRichiesta']}")

# 2. Recupero del dettaglio
dettaglio = client.dettaglio_richiesta(info["idRichiesta"])
print(f"Oggetto: {dettaglio['oggetto']}")
print(f"Stato: {dettaglio['statoRichiesta']}")
print(f"Servizio: {dettaglio['servizio']['nome']}")

# 3. Assegnazione della richiesta
ris = client.assegna_richiesta(
id_richiesta=info["idRichiesta"],
id_assegnatore=100,
id_assegnatario=200,
note="Assegnata per competenza tecnica",
)
print(f"Assegnazione: {ris['esitoAssegnazione']}")

# 4. Presa in carico
ris = client.presa_in_carico(
id_richiesta=info["idRichiesta"],
id_utente=200,
)
print(f"Presa in carico: {ris['esitoPresaInCarico']}")

# 5. Creazione di un ticket di II livello
ticket = client.crea_ticket(
id_richiesta=info["idRichiesta"],
oggetto="Analisi tecnica - Problema login",
id_priorita=3,
descrizione="Approfondimento tecnico richiesto",
)
id_ticket = ticket["ticket"]["idTicket"]
print(f"Ticket creato: {ticket['ticket']['protocollo']}")

# 6. Avanzamento del ticket nel workflow
ris = client.avanza_ticket(
id_ticket=id_ticket,
id_azione=10,
note="Analisi completata, problema risolto",
durata=120,
)
print(f"Ticket avanzato allo step {ris['idStep']}")

# 7. Chiusura della richiesta
ris = client.chiudi_richiesta(
id_richiesta=info["idRichiesta"],
id_utente=200,
note="Problema risolto con reset credenziali",
)
print(f"Chiusura: {ris['esitoChiusuraRichiesta']}")

Descrizione del flusso

L’esempio mostra un flusso applicativo completo:

  1. autenticazione automatica tramite OAuth 2.0
  2. creazione di una richiesta di I livello
  3. consultazione del dettaglio della richiesta
  4. assegnazione a un operatore
  5. presa in carico
  6. creazione di un ticket collegato
  7. avanzamento del ticket nel workflow
  8. chiusura della richiesta

Esempio 2: client JavaScript / Node.js

L’esempio seguente presenta un client JavaScript per ambiente Node.js basato su axios, con gestione del token OAuth 2.0 e invocazione delle API principali di richieste e ticket.

const axios = require("axios");

class DeskClient {
constructor({ baseUrl, clientId, clientSecret, keycloakUrl }) {
this.baseUrl = baseUrl.replace(/\/$/, "");
this.clientId = clientId;
this.clientSecret = clientSecret;
this.keycloakUrl = keycloakUrl;
this.accessToken = null;
this.tokenExpiry = null;
}

async _getToken() {
const url = `${this.keycloakUrl}/protocol/openid-connect/token`;
const params = new URLSearchParams({
grant_type: "client_credentials",
client_id: this.clientId,
client_secret: this.clientSecret,
});

const { data } = await axios.post(url, params.toString(), {
headers: { "Content-Type": "application/x-www-form-urlencoded" },
});

this.accessToken = data.access_token;
this.tokenExpiry = Date.now() + (data.expires_in - 60) * 1000;
return this.accessToken;
}

async _headers() {
if (!this.accessToken || Date.now() >= this.tokenExpiry) {
await this._getToken();
}

return {
Authorization: `Bearer ${this.accessToken}`,
"Content-Type": "application/json",
};
}

// ── Richieste ──

async creaRichiesta(payload) {
const headers = await this._headers();
const { data } = await axios.post(
`${this.baseUrl}/richieste/creazione-richiesta`,
payload,
{ headers }
);
return data;
}

async listaRichieste(filtri = {}) {
const headers = await this._headers();
const { data } = await axios.get(`${this.baseUrl}/richieste/`, {
headers,
data: filtri,
});
return data;
}

async dettaglioRichiesta(idRichiesta) {
const headers = await this._headers();
const { data } = await axios.get(
`${this.baseUrl}/richieste/${idRichiesta}`,
{ headers }
);
return data;
}

async assegnaRichiesta(idRichiesta, idAssegnatore, idAssegnatario, note = "") {
const headers = await this._headers();
const { data } = await axios.post(
`${this.baseUrl}/richieste/assegnazione-richiesta`,
{
idRichiesta,
idUtenteAssegnatore: idAssegnatore,
idUtenteAssegnatario: idAssegnatario,
noteAss: note
},
{ headers }
);
return data;
}

async presaInCarico(idRichiesta, idUtente, note = "") {
const headers = await this._headers();
const { data } = await axios.post(
`${this.baseUrl}/richieste/presa-in-carico`,
{ idRichiesta, idUtente, note },
{ headers }
);
return data;
}

async chiudiRichiesta(idRichiesta, idUtente, note = "") {
const headers = await this._headers();
const { data } = await axios.post(
`${this.baseUrl}/richieste/chiusura`,
{ idRichiesta, idUtente, note },
{ headers }
);
return data;
}

// ── Ticket ──

async creaTicket(payload) {
const headers = await this._headers();
const { data } = await axios.post(
`${this.baseUrl}/ticket/creazione-ticket`,
payload,
{ headers }
);
return data;
}

async dettaglioTicket(idTicket) {
const headers = await this._headers();
const { data } = await axios.get(
`${this.baseUrl}/ticket/${idTicket}`,
{ headers }
);
return data;
}

async avanzaTicket(idTicket, idAzione, note = "", durata = 0) {
const headers = await this._headers();
const { data } = await axios.post(
`${this.baseUrl}/ticket/avanzamento-ticket`,
{ idTicket, idAzione, note, durata },
{ headers }
);
return data;
}
}

// ── Esempio di utilizzo ──

(async () => {
const client = new DeskClient({
baseUrl: "https://desk.example.com/rest",
clientId: "my-integration",
clientSecret: "secret",
keycloakUrl: "https://keycloak.example.com/realms/askme",
});

try {
// 1. Creazione richiesta
const info = await client.creaRichiesta({
idServizio: 1,
idTipoRichiesta: 5,
oggetto: "Problema di login",
descrizione: "L'utente non riesce ad accedere al portale",
idUrgenza: 2,
codiceCanale: "WEB",
});
console.log(`Richiesta creata: ${info.idRichiesta}`);

// 2. Recupero dettaglio richiesta
const dettaglio = await client.dettaglioRichiesta(info.idRichiesta);
console.log(`Stato: ${dettaglio.statoRichiesta}`);
console.log(`Servizio: ${dettaglio.servizio.nome}`);

// 3. Presa in carico
const pc = await client.presaInCarico(info.idRichiesta, 200);
console.log(`Presa in carico: ${pc.esitoPresaInCarico}`);

// 4. Creazione ticket di II livello
const ticket = await client.creaTicket({
idRichiesta: info.idRichiesta,
oggetto: "Analisi tecnica - Problema login",
idPriorita: 3,
});
console.log(`Ticket: ${ticket.ticket.protocollo}`);

// 5. Avanzamento ticket
const av = await client.avanzaTicket(
ticket.ticket.idTicket,
10,
"Analisi completata",
60
);
console.log(`Step: ${av.idStep}`);

// 6. Chiusura richiesta
const ch = await client.chiudiRichiesta(
info.idRichiesta,
200,
"Risolto"
);
console.log(`Chiusura: ${ch.esitoChiusuraRichiesta}`);
} catch (error) {
console.error("Errore:", error.response?.data || error.message);
}
})();

Descrizione del flusso

Anche in questo caso viene mostrato un processo end-to-end che comprende:

  • autenticazione
  • creazione della richiesta
  • lettura del dettaglio
  • presa in carico
  • creazione del ticket
  • avanzamento del ticket
  • chiusura della richiesta

Esempio 3: script Bash per il monitoraggio SLA

Il seguente script Bash consente di interrogare alcuni endpoint statistici di Askme Desk per ottenere informazioni sintetiche sullo stato delle SLA di richieste e ticket.

#!/bin/bash
# Monitoraggio SLA richieste e ticket tramite API Askme Desk

BASE_URL="https://desk.example.com/rest"
KEYCLOAK_URL="https://keycloak.example.com/realms/askme/protocol/openid-connect"
CLIENT_ID="my-integration"
CLIENT_SECRET="secret"

# Ottenimento del token OAuth 2.0
TOKEN=$(curl -s -X POST "$KEYCLOAK_URL/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=$CLIENT_ID" \
-d "client_secret=$CLIENT_SECRET" | jq -r '.access_token')

if [ "$TOKEN" = "null" ] || [ -z "$TOKEN" ]; then
echo "Errore: impossibile ottenere il token"
exit 1
fi

echo "=== STATISTICHE SLA RICHIESTE ==="

echo ""
echo "--- SLA presa in carico ---"
curl -s -X GET "$BASE_URL/statistics/slaRichiestePc" \
-H "Authorization: Bearer $TOKEN" | jq '.'

echo ""
echo "--- SLA chiusura ---"
curl -s -X GET "$BASE_URL/statistics/slaRichiesteCh" \
-H "Authorization: Bearer $TOKEN" | jq '.'

echo ""
echo "=== STATISTICHE SLA TICKET ==="

echo ""
echo "--- SLA ticket ---"
curl -s -X GET "$BASE_URL/statistics/slaTicket" \
-H "Authorization: Bearer $TOKEN" | jq '.'

echo ""
echo "--- Tempo residuo SLA ticket ---"
curl -s -X GET "$BASE_URL/statistics/slaTempoResiduoTicket" \
-H "Authorization: Bearer $TOKEN" | jq '.'

echo ""
echo "=== STATISTICHE GENERALI ==="

echo ""
echo "--- Riepilogo richieste fornitore ---"
curl -s -X GET "$BASE_URL/statistics/riepilogoRichiesteFornitore" \
-H "Authorization: Bearer $TOKEN" | jq '.'

Note operative

Questo script può essere utilizzato, ad esempio:

  • come comando di controllo manuale
  • all’interno di job schedulati
  • in pipeline di monitoraggio o integrazione con strumenti esterni

Per l’esecuzione sono richiesti curl e jq.


Esempio 4: creazione massiva di richieste da CSV (Python)

L’esempio seguente mostra come importare richieste di I livello da un file CSV utilizzando il client Python presentato nell’esempio precedente.

import csv
from desk_client import DeskClient # Classe definita nell'Esempio 1

def importa_richieste_da_csv(file_path: str):
"""Importa richieste di I livello da un file CSV."""

client = DeskClient(
base_url="https://desk.example.com/rest",
client_id="my-integration",
client_secret="secret",
keycloak_url="https://keycloak.example.com/realms/askme",
)

risultati = {"ok": 0, "errori": 0}

with open(file_path, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
try:
info = client.crea_richiesta(
id_servizio=int(row["id_servizio"]),
id_tipo_richiesta=int(row["id_tipo_richiesta"]),
oggetto=row["oggetto"],
descrizione=row.get("descrizione", ""),
idUrgenza=int(row.get("id_urgenza", 1)),
codiceCanale=row.get("canale", "MAIL"),
)
print(f"OK - Richiesta {info['idRichiesta']}: {row['oggetto']}")
risultati["ok"] += 1
except Exception as e:
print(f"ERRORE - {row['oggetto']}: {e}")
risultati["errori"] += 1

print(
f"\nImportazione completata: {risultati['ok']} ok, {risultati['errori']} errori"
)

# File CSV atteso con colonne:
# id_servizio, id_tipo_richiesta, oggetto, descrizione, id_urgenza, canale
importa_richieste_da_csv("richieste.csv")

Formato del file CSV

Il file di input deve contenere almeno le seguenti colonne:

  • id_servizio
  • id_tipo_richiesta
  • oggetto

Possono inoltre essere presenti campi opzionali, come:

  • descrizione
  • id_urgenza
  • canale

Troubleshooting

Di seguito sono riportati alcuni dei casi più comuni riscontrabili in fase di integrazione.

Errore 401 Unauthorized

Possibili cause:

  • token OAuth 2.0 assente, scaduto o non valido
  • client_id o client_secret errati
  • realm Keycloak non corretto

Verifiche consigliate:

  • controllare la configurazione del client OAuth
  • verificare l’endpoint di autenticazione
  • rinnovare il token prima della scadenza

Errore 403 Forbidden

Possibili cause:

  • l’utente o il client non dispongono dei permessi necessari
  • i ruoli applicativi non consentono l’operazione richiesta

Verifiche consigliate:

  • controllare i ruoli assegnati nel profilo utente Askme Desk
  • verificare eventuali restrizioni lato applicativo o lato IAM

Risposta con campo esito* = false

In questo caso la chiamata HTTP è andata a buon fine, ma l’operazione applicativa non è stata eseguita correttamente.

Cause ricorrenti:

  • richiesta già chiusa
  • ticket senza transizioni disponibili
  • conflitto di assegnazione
  • dati di input non coerenti con lo stato corrente

Verifiche consigliate:

  • controllare il contenuto del campo message
  • verificare lo stato attuale dell’entità prima di ripetere l’operazione

Errore in fase di creazione richiesta

Una delle cause più frequenti è la configurazione non valida della terna.

Verificare che la combinazione:

  • Servizio
  • Asset
  • Tipo Richiesta

sia configurata, attiva e utilizzabile.

A questo scopo è possibile interrogare l’endpoint:

GET /terne/lista-terne

Upload allegati non riuscito

Per allegare file a richieste o ticket è necessario seguire il flusso corretto di caricamento:

  1. ottenere un uploadId tramite GET /upload/upload-id
  2. caricare il file con POST /upload/, passando file e uploadId
  3. utilizzare l’uploadId nel campo idUpload della richiesta o del ticket

Il mancato rispetto di questa sequenza impedisce l’associazione corretta dell’allegato.


Best practice

Per una integrazione più robusta e stabile si consiglia di adottare le seguenti pratiche.

1. Gestione preventiva del token

Rinnovare il token poco prima della scadenza evita errori durante chiamate lunghe o sequenze di operazioni ravvicinate.

2. Verifica preventiva della terna

Prima di creare richieste o ticket, verificare che la combinazione di classificazione sia valida e disponibile.

3. Utilizzo degli endpoint massivi

Per operazioni su più entità è preferibile utilizzare gli endpoint dedicati alle elaborazioni massive, in modo da ridurre il numero di chiamate e migliorare l’efficienza.

4. Controllo esplicito dell’esito applicativo

Uno status HTTP 200 OK non garantisce da solo il buon esito logico dell’operazione. È quindi necessario verificare sempre i campi esito* restituiti dal servizio.

5. Gestione corretta degli allegati

Per l’upload di documenti è necessario seguire la sequenza completa:

uploadId → caricamento file → utilizzo di idUpload nell’operazione finale

6. Utilizzo degli endpoint di esportazione

Gli endpoint CSV risultano particolarmente utili per attività di reporting, analisi esterne e riconciliazione dei dati.