Punto di arrivo dei biglietti

v2.118.1 · CRUD completo · Ticket, allegati e richieste di risarcimento

Ticket

Creare, aggiornare, esportare ed eliminare ticket di assistenza. Include cronologia degli stati, assegnazioni amministrative, appuntamenti CRM collegati, allegati e richieste di modifica.

GET    /Export/JSON/tickets
GET    /Export/JSON/ticket/{id}
POST   /Import/JSON/tickets
PUT    /Import/JSON/tickets
PUT    /Import/JSON/ticket_claims
DELETE /Delete/JSON/ticket/{id}

Endpoint

MetodoEndpointAmbitoDescrizione
GET/Export/JSON/ticketstickets:readRecupera tutti i ticket (paginati)
GET/Export/JSON/ticket/{id}tickets:readSingolo ticket con cronologia dello stato e appuntamenti associati
GET/Export/JSON/ticket_attachments/{id}tickets:readElenca gli allegati di un ticket
GET/Export/JSON/ticket_attachment/{id}?file=nametickets:readScaricare un singolo file in formato binario
POST/Import/JSON/ticketstickets:writeCrea un nuovo ticket (INSERT) — facoltativamente con commento e allegati
PUT/Import/JSON/ticketstickets:writeAggiorna ticket esistente (UPDATE) — facoltativamente con commento, risposta pubblica e allegati
PUT/Import/JSON/ticket_claimstickets:writeImpostare, prorogare, acquisire da un altro amministratore o terminare una richiesta di modifica NOVITÀ v2.118.0
DELETE/Delete/JSON/ticket/{id}tickets:deleteEliminare un ticket con Cascade-Delete

Esportazione: parametri di query

ParametroDescrizione
?status={id}Filtra in base all'ID dello stato
?priority={id}Filtra per ID priorità
?department={id}Filtra per ID reparto
?customer_id={id}Ticket di un determinato cliente
?admin_id={id}Ticket assegnati a un amministratore
?type={type}Filtra per tipo di ticket
?date_from=&date_to=Filtro per periodo
?last_modified=AAAA-MM-GGModificato a partire dal data
?language=deFiltro per lingua
?limit=50&offset=0Impaginazione (Predefinito: 50, Massimo: 250)

Esportazione: campi della risposta

CampoTipoDescrizione
ticket_idinteroID ticket
ticket_link_idstringaID link alfanumerico di 14 caratteri
ticket_subjectstringaOggetto del ticket
ticket_statusstringaStato attuale
priorità_ticketstringaPriorità
department_idnumero interoID reparto
department_namestringaNome del reparto
cronologia_stato[]arrayConversazione completa con commenti
amministratori_assegnati[]arrayAmministratori assegnati
followers[]arraySeguaci del ticket
linked_appointments[]arrayNOVITÀ v2.11.0: Appuntamenti CRM collegati (ID, inizio, fine, titolo, luogo, stato)
claimoggetto|nullNOVITÀ v2.118.0: Richiesta di elaborazione attiva con admin_id, admin_name, label, claimed_at, expires_at e expires_in_seconds; null se nessuno ha rivendicato il ticket
edit_lockobject|nullNOVITÀ v2.118.0: il ticket è attualmente aperto nel backend: admin_id, admin_name, locked_at (valido per 300 secondi dall'ultima attività nella maschera); altrimenti null

Importazione POST: creazione di un ticket

Campi obbligatori e facoltativi durante la creazione di un nuovo ticket.

Campi obbligatori

  • ticket_subject — Oggetto
  • ticket_customers_email — E-mail del cliente
  • ticket_customers_name — Nome del cliente

Campi facoltativi

  • ticket_type — Tipo di ticket (predefinito: cliente)
  • ticket_status_id — ID stato (viene convalidato)
  • ticket_priority_id — ID priorità (viene convalidato)
  • ticket_department_id — ID reparto (viene convalidato)
  • ticket_customers_id — ID cliente
  • ticket_language_id — ID della lingua
  • ticket_comments — Primo commento come testo (sempre interno)
  • admin_id — ID amministratore per il commento (0 = sistema)
  • attachments[] — NOVITÀ v2.60.0: allegati (nome + contenuto_base64); richiede la presenza di ticket_comments
  • assigned_admins[] — Array con gli ID amministratore

Esempio: Creazione di un ticket

POST /Import/JSON/tickets
Authorization: Bearer {token}
Content-Type: application/json

{
  "type": "tickets",
  "data": [{
    "ticket_subject": "API-Test-Ticket",
    "ticket_customers_email": "kunde@example.com",
    "ticket_customers_name": "Max Mustermann",
    "ticket_priority_id": 2,
    "ticket_department_id": 1,
    "ticket_comments": "Erstellt via xoPort API",
    "admin_id": 0,
    "attachments": [
      { "name": "bestellung.pdf", "content_base64": "JVBERi0xLjQKJcfs…" }
    ],
    "assigned_admins": [1, 3]
  }]
}

Importazione PUT: aggiornare un ticket

Campi modificabili e sistema di commenti.

Importante: per impostazione predefinita, i commenti sono interni (ticket_internal_comment=1). A partire dalla versione 2.25.0, una risposta visibile al cliente richiede l'esplicito flag is_public_reply: true e un admin_id valido > 0; l'invio dell'e-mail richiede inoltre notify_customer: true. Entrambi i flag vengono verificati rigorosamente come valori booleani — "true" o 1 non sono validi.
CampoTipoDescrizione
ticket_idinteroObbligatorio. ID del ticket da aggiornare
ticket_subjectstringaModifica oggetto
tipo_ticketstringaModifica tipo
id_stato_ticketnumero interoModifica stato (soggetto a convalida)
ticket_priority_idinteroModifica priorità (soggetto a convalida)
id_reparto_ticketinteroModifica reparto (soggetto a convalida)
data_nascondimento_ticketdatetimeNascondi ticket fino al
ticket_login_required0/1Accesso richiesto
ticket_allow_learning0/1Consenti l'apprendimento tramite IA
ticket_commentsstringTesto del commento — crea una nuova voce nella cronologia
admin_idinteroAutore del commento (0 = sistema; in caso di risposta pubblica deve essere > 0)
is_public_replybooleanov2.25.0: true = risposta visibile al cliente anziché nota interna
notify_customerbooleanov2.25.0: true = e-mail al cliente (solo in combinazione con is_public_reply)
append_signaturebooleanov2.27.0: allega la firma dell'amministratore a una risposta pubblica (impostazione predefinita: attiva; disattivazione solo tramite false)
attachments[]arrayNOVITÀ v2.60.0: allegati (name + content_base64); richiede ticket_comments
assigned_admins[]arraySostituisci le assegnazioni degli amministratori

Formato dei commenti

"ticket_comments": "Nota interna tramite API",
"admin_id": 1

ticket_comments è il testo del commento, admin_id è il suo autore (0 = sistema, >0 = ID amministratore, viene convalidato).

Esempio: aggiornamento di un ticket con commento

PUT /Import/JSON/tickets
Authorization: Bearer {token}
Content-Type: application/json

{
  "type": "tickets",
  "data": [{
    "ticket_id": 42,
    "ticket_status_id": 3,
    "ticket_priority_id": 1,
    "ticket_comments": "Status auf Erledigt gesetzt",
    "admin_id": 1,
    "assigned_admins": [1, 5]
  }]
}

Eliminazione: eliminazione a cascata

Quando si elimina un ticket, vengono automaticamente rimossi tutti i dati ad esso collegati:

Ordine di cascata

  1. ticket_status_history — Commenti/Cronologia
  2. ticket_to_admins — Assegnazioni agli amministratori
  3. ticket_to_followers — Follower
  4. xocrm_appointments_relationships — Collegamenti agli appuntamenti (gli appuntamenti vengono mantenuti)
  5. xocrm_followers — Follower CRM
  6. File su disco — upload/ticket/{link_id}/
  7. ticket_ticket — Il ticket stesso

Esempio

DELETE /Delete/JSON/ticket/42
Authorization: Bearer {token}

Risposta

{
  "success": true,
  "deleted_id": 42,
  "details": {
    "ticket_id": 42,
    "ticket_subject": "Gelöschtes Ticket",
    "ticket_link_id": "8i1c5O8g7w8b5c"
  }
}

Appuntamenti CRM collegati v2.11.0

Nell'esportazione, ogni ticket contiene un array linked_appointments[] con gli appuntamenti CRM collegati:

"linked_appointments": [
 {
    "appointment_id": 15,
    "startdate": "2026-04-14 10:00:00",
    "enddate": "2026-04-14 11:00:00",
    "allday": 0,
    "done": 0,
    "status_id": 1,
    "title": "Rückruf Kunde",
    "description": "Bezüglich offener Rückfrage",
    "location": ""
  } 
]

Gli appuntamenti vengono collegati tramite xocrm_appointments_relationships (target_type = ticket_id). L'eliminazione di un ticket rimuove solo il collegamento, non l'appuntamento stesso.

Corretto nella v2.118.1: linked_appointments su POST/PUT /Import/JSON/tickets generava anche l’avviso UNKNOWN_FIELD, sebbene gli appuntamenti fossero stati creati. L’avviso non viene più visualizzato; l’elaborazione rimane invariata.


Endpoint degli allegati

Gli allegati vengono identificati tramite l’ID del ticket:

Listing: GET /ticket_attachments/{id}

{
  "success": true,
  "ticket_id": 42,
  "ticket_link_id": "8i1c5O8g7w8b5c",
  "files": [
    {
      "name": "screenshot.png",
      "size": 245760,
      "mime_type": "image/png",
      "last_modified": "2026-03-15 14:30:22"
    }
  ]
}

Download: GET /ticket_attachment/{id}?file=screenshot.png

Download di file binari con Content-Type e Content-Disposition corretti : header "attachment". Protezione dal path traversal tramite validazione basename() + realpath().

Caricamento: attachments[] su POST/PUT NOVITÀ v2.60.0

Fino alla v2.59.0 gli allegati erano solo in lettura — venivano caricati esclusivamente tramite il modulo del backend. Un ticket creato tramite API rimaneva quindi privo di allegati (PDF, .eml, screenshot). A partire dalla v2.60.0,POST/PUT /Import/JSON/tickets accetta il campo opzionale attachments[]:

"ticket_comments": "Allegato relativo al reclamo",
"admin_id": 1,
"attachments": [
 {
    "name": "rechnung.pdf",
    "content_base64": "JVBERi0xLjQKJcfs…"
  } 
]
Gli allegati sono associati a una voce della cronologia, non al ticket. Senza ticket_comments nella stessa richiesta, non viene creata alcuna voce a cui il file possa essere associato — in tal caso, il file viene rifiutato con un messaggio di errore anziché essere semplicemente scartato.
CampoTipoDescrizione
namestringObbligatorio. Nome del file, estensione inclusa; l'estensione deve essere presente nell'elenco consentito (regola identica a quella per il caricamento dal backend)
content_base64stringaObbligatorio. Contenuto del file codificato in Base64; il codice Base64 non valido viene rifiutato e non salvato

L'archiviazione avviene tramite lo stesso meccanismo del caricamento sul backend: stessa lista di estensioni consentite, convenzione di denominazione __ e deduplicazione tramite hash per evitare il doppio salvataggio. Il nome del file effettivamente assegnato è riportato nella risposta alla voce attachments per ogni record. Il limite massimo per ogni file corrisponde all’endpoint /media(XOPORT_MEDIA_MAX_FILESIZE, valore predefinito 10 MB).

Le voci errate (estensione non consentita, Base64 non valido, superamento della dimensione massima) generano un errore per ogni file; l’aggiornamento del ticket stesso va comunque a buon fine. Se is_public_reply: true è impostato insieme a notify_customer: true, i file vengono inviati tramite l’e-mail del cliente — vengono scritti prima dell’invio.

Claim di elaborazione NUOVO v2.118.0

Un claim indica che qualcuno sta attualmente lavorando su un ticket, ad esempio un agente IA per conto di un collaboratore. Gli altri responsabili lo vedono nell’esportazione e nel backend (elenco ticket e vista ticket) e non iniziano a lavorarci in parallelo. La richiesta di elaborazione è un’indicazione, non un blocco: non è protetta da accesso in scrittura, il ticket rimane modificabile da tutti.

PUT /Import/JSON/ticket_claims
{
  "type": "ticket_claims",
  "data": [
    { "ticket_id": 33999, "admin_id": 12, "state": "claimed", "ttl_minutes": 60, "label": "KI-Agent" }
  ]
}
CampoTipoObbligatorioDescrizione
ticket_idinterosìID di un ticket esistente
admin_idnumero interosìAmministratore esistente per conto del quale si opera. Un claim indica sempre una persona.
statestringasìclaimed imposta o rinnova il claim, mentre released lo termina. Non esiste un valore predefinito: l'endpoint imposta uno stato e non cambia mai.
ttl_minutesinteronoDurata in minuti, valore predefinito 60, compresa tra 5 e 240. Una proroga non riduce mai la durata di un claim in corso.
labelstringanoIdentificativo della fonte, ad es. KI-Agent; massimo 32 caratteri, l'HTML viene rimosso. Il backend mostra «Nome (Etichetta)».
forcebooleanonoSolo JSON:true/false. true acquisisce o rilascia il claim di un altro amministratore.

Risultato per ogni record

records[].status indica il risultato, records[].claim lo stato dopo la chiamata:

statusSignificato
claimedNuova richiesta creata
refreshedClaim proprio prorogato; claimed_at rimane invariato
taken_overClaim di un altro amministratore acquisito con force: true; previous_claim contiene il valore precedente
releasedrevocato; previous_claim contiene quello precedente
unchangedstate: released inviato, ma nessun claim attivo
conflictUn altro amministratore detiene il claim. Non viene scritto nulla, claim indica il detentore e errors[] la dichiarazione. Il record viene conteggiato come failed.

Risposta (HTTP 200) se un altro amministratore effettua una richiesta mentre Max Mustermann detiene il claim:

{
  "success": false,
  "outcome": "failed",
  "api_version": "2.118.1",
  "type": "ticket_claims",
  "stats": { "total": 1, "claimed": 0, "refreshed": 0, "taken_over": 0, "released": 0, "unchanged": 0, "failed": 1 },
  "errors": [
    { "record": 1, "error": "Ticket 33999 is claimed by Max Mustermann (admin_id 12) until 2026-09-17 14:05:10. Send force=true to take it over." }
  ],
  "timestamp": "2026-09-17 13:20:02",
  "records": [
    {
      "index": 1,
      "ticket_id": 33999,
      "status": "conflict",
      "claim": {
        "admin_id": 12,
        "admin_name": "Max Mustermann",
        "label": "KI-Agent",
        "claimed_at": "2026-09-17 13:05:10",
        "expires_at": "2026-09-17 14:05:10",
        "expires_in_seconds": 2708
      }
    }
  ]
}

Rilascio automatico

  • Risposta pubblica al cliente, tramite API (PUT /Import/JSON/tickets) o nel backend. Non importa chi risponde.
  • Passaggio a uno stato di chiusura. Chi salva nuovamente un ticket già chiuso non rilascia alcun claim.
  • Scadenza del periodo di validità.

Una nota interna non sblocca il claim. Le operazioni di impostazione e sblocco non modificano né ticket_date_last_modified né la cronologia e non attivano eventi; l’ordinamento e l’esportazione delta (?last_modified=) rimangono inalterati. Per ogni ticket esiste al massimo un claim: su due richieste simultanee, solo una riceve conflict.

Da verificare prima dell’elaborazione: se l’esportazione indica un claim di un altro amministratore o un edit_lock, significa che qualcuno sta già lavorando sul ticket. Coordinatevi prima di inviare force: true.

A partire dalla versione 4.9.73 dello Shop. Se la tabella ticket_claims non è ancora presente, l’endpoint risponde con un codice HTTP 503 TICKET_CLAIMS_UNAVAILABLE; lo Shop la creerà al prossimo allineamento dello schema (all’apertura di una pagina xoCRM o dell’HealthCheck).


Ambiti

AmbitoDescrizione
tickets:readEsportazione di ticket e allegati
tickets:writeCreare e aggiornare ticket, scrivere commenti e allegare file, impostare e chiudere richieste di modifica
tickets:deleteEliminare ticket (a cascata)

Gestire i ticket tramite API

Creare un client OAuth2 con gli ambiti tickets:read, tickets:write e tickets:delete.

Creare un client OAuth2