Point d'arrivée du billet
Tickets
Créer, mettre à jour, exporter et supprimer des tickets d'assistance. Comprend l'historique des statuts, les attributions par administrateur, les rendez-vous CRM associés, les pièces jointes et les demandes de modification.
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}Points de terminaison
| Méthode | Point de terminaison | Portée | Description |
|---|---|---|---|
| GET | /Export/JSON/tickets | tickets:read | Récupérer tous les tickets (pagination) |
| GET | /Export/JSON/ticket/{id} | tickets:read | Un ticket individuel avec l'historique des statuts et les rendez-vous associés |
| GET | /Export/JSON/ticket_attachments/{id} | tickets:read | Lister les pièces jointes d'un ticket |
| GET | /Export/JSON/ticket_attachment/{id}?file=name | tickets:read | Télécharger un fichier individuel au format binaire |
| POST | /Import/JSON/tickets | tickets:write | Créer un nouveau ticket (INSERT) — avec commentaire et pièces jointes (facultatif) |
| PUT | /Import/JSON/tickets | tickets:write | Mettre à jour un ticket existant (UPDATE) — avec, en option, un commentaire, une réponse publique et des pièces jointes |
| PUT | /Import/JSON/ticket_claims | tickets:write | Créer, prolonger, reprendre ou clôturer une demande de modification NOUVEAU v2.118.0 |
| DELETE | /Delete/JSON/ticket/{id} | tickets:delete | Supprimer un ticket avec suppression en cascade |
Export : paramètres de requête
| Paramètres | Description |
|---|---|
?status={id} | Filtrer par ID de statut |
?priority={id} | Filtrer par ID de priorité |
?department={id} | Filtrer par ID de service |
?customer_id={id} | Tickets d'un client spécifique |
?admin_id={id} | Tickets attribués à un administrateur |
?type={type} | Filtrer par type de ticket |
?date_from=&date_to= | Filtre par période |
?last_modified=AAAA-MM-JJ | Modifié depuis la date |
?language=fr | Filtre par langue |
?limit=50&offset=0 | Pagination (par défaut : 50, max. : 250) |
Exportation : champs de réponse
| Champ | Type | Description |
|---|---|---|
ticket_id | entier | ID du ticket |
ticket_link_id | chaîne de caractères | ID de lien alphanumérique à 14 caractères |
ticket_subject | chaîne | Objet du ticket |
ticket_status | chaîne | Statut actuel |
priorité_du_ticket | chaîne | Priorité |
department_id | entier | ID du service |
department_name | chaîne de caractères | Nom du service |
historique_statut[] | tableau | Conversation complète avec commentaires |
assigned_admins[] | tableau | Administrateurs affectés |
followers[] | tableau | Abonnés au ticket |
linked_appointments[] | tableau | NOUVEAU v2.11.0 : rendez-vous CRM associés (ID, début, fin, titre, lieu, statut) |
claim | objet|null | NOUVEAU v2.118.0 : Réclamation de traitement active avec admin_id, admin_name, label, claimed_at, expires_at et expires_in_seconds; null si personne ne revendique le ticket |
edit_lock | object|null | NOUVEAU v2.118.0 : le ticket est actuellement ouvert dans le backend : admin_id, admin_name, locked_at (valable 300 secondes à compter de la dernière activité sur le masque) ; sinon null |
Import POST : créer un ticket
Champs obligatoires et champs facultatifs lors de la création d’un nouveau ticket.
Champs obligatoires
ticket_subject— Objetticket_customers_email— E-mail du clientticket_customers_name— Nom du client
Champs facultatifs
ticket_type— Type de ticket (par défaut : client)ticket_status_id— ID du statut (validé)ticket_priority_id— ID de priorité (validé)ticket_department_id— ID du service (validé)ticket_customers_id— ID du clientticket_language_id— ID de la langueticket_comments— Premier commentaire sous forme de texte (toujours interne)admin_id— ID administrateur pour le commentaire (0 = système)attachments[]— NOUVEAU v2.60.0 : pièces jointes (nom+contenu_base64) ; nécessite la présence deticket_commentsassigned_admins[]— Tableau contenant les identifiants des administrateurs
Exemple : créer 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]
}]
}Import PUT : mettre à jour un ticket
Champs modifiables et système de commentaires.
ticket_internal_comment=1). Depuis la version 2.25.0, une réponse visible par le client nécessite l'indicateur explicite is_public_reply: true et un admin_id valide > 0 ; l'envoi d'e-mails nécessite en outre notify_customer: true. Ces deux indicateurs sont vérifiés de manière strictement booléenne — « true » ou 1 ne sont pas pris en compte.| Champ | Type | Description |
|---|---|---|
ticket_id | entier | Obligatoire. Identifiant du ticket à mettre à jour |
ticket_subject | chaîne de caractères | Modifier l'objet |
type_de_ticket | chaîne | Modifier le type |
ticket_status_id | entier | Modifier le statut (sera validé) |
ticket_priority_id | entier | Modifier la priorité (sera validé) |
ticket_department_id | entier | Modifier le service (sera validé) |
date_de_masquage_du_ticket | date-heure | Masquer le ticket jusqu'au |
ticket_login_required | 0/1 | Connexion requise |
ticket_allow_learning | 0/1 | Autoriser l'apprentissage par IA |
ticket_comments | chaîne | Texte du commentaire — crée une nouvelle entrée dans l'historique |
admin_id | entier | Auteur du commentaire (0 = système ; doit être > 0 en cas de réponse publique) |
is_public_reply | booléen | v2.25.0 : true = réponse visible par le client au lieu d'une note interne |
notify_customer | booléen | v2.25.0 : true = e-mail envoyé au client (uniquement en combinaison avec is_public_reply) |
append_signature | booléen | v2.27.0 : ajouter la signature de l'administrateur à une réponse publique (activée par défaut ; désactivation uniquement via « false ») |
attachments[] | tableau | NOUVEAU v2.60.0 : pièces jointes (name + content_base64) ; nécessite la présence de ticket_comments |
assigned_admins[] | tableau | Remplacer les affectations d'administrateurs |
Format des commentaires
« ticket_comments » : « Note interne via l'API »,
« admin_id » : 1ticket_comments correspond au texte du commentaire, admin_id à son auteur (0 = système, >0 = ID d'administrateur, validé).
Exemple : mise à jour d'un ticket avec un commentaire
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]
}]
}Suppression : suppression en cascade
Lors de la suppression d'un ticket, toutes les données associées sont automatiquement supprimées :
Ordre de la suppression en cascade
ticket_status_history— Commentaires/historiqueticket_to_admins— Attributions aux administrateursticket_to_followers— Abonnésxocrm_appointments_relationships— Liens avec les rendez-vous (les rendez-vous sont conservés)xocrm_followers— Abonnés CRM- Fichiers sur le disque —
upload/ticket/{link_id}/ ticket_ticket— Le ticket lui-même
Exemple
DELETE /Delete/JSON/ticket/42
Authorization: Bearer {token}Réponse
{
"success": true,
"deleted_id": 42,
"details": {
"ticket_id": 42,
"ticket_subject": "Gelöschtes Ticket",
"ticket_link_id": "8i1c5O8g7w8b5c"
}
}Rendez-vous CRM associés v2.11.0
Dans l'exportation, chaque ticket contient un tableau linked_appointments[] avec les rendez-vous CRM associés :
« 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": ""
}
]Les rendez-vous sont associés via xocrm_appointments_relationships (target_type = ticket_id). La suppression d'un ticket supprime uniquement le lien, et non le rendez-vous lui-même.
Corrigé dans la v2.118.1 : l'appel de ` linked_appointments ` vers ` POST` / `PUT /Import/JSON/tickets ` générait en plus l'avertissement « UNKNOWN_FIELD », bien que les rendez-vous aient été créés. L'avertissement a été supprimé ; le traitement reste inchangé.
Points de terminaison des pièces jointes
Les pièces jointes sont identifiées par l'ID du ticket :
Extrait : 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"
}
]
}Téléchargement : GET /ticket_attachment/{id}?file=screenshot.png
Téléchargement binaire avec un type de contenu (Content-Type) et une disposition de contenu (Content-Disposition) corrects : en-tête « attachment ». Protection contre le parcours de chemin via la validation basename() + realpath().
Téléversement : attachments[] via POST/PUT NOUVEAU v2.60.0
Jusqu’à la v2.59.0, les pièces jointes étaient uniquement en lecture — elles étaient exclusivement ajoutées via le formulaire du backend. Un ticket créé via l’API restait donc sans pièce jointe (PDF, .eml, capture d’écran). Depuis la v2.60.0, la requêtePOST/PUT /Import/JSON/tickets accepte le champ optionnel attachments[]:
« ticket_comments » : « Pièce jointe à la réclamation »,
« admin_id » : 1,
« attachments » : [
{
"name": "rechnung.pdf",
"content_base64": "JVBERi0xLjQKJcfs…"
}
]le champ « ticket_comments » dans la même requête, aucune entrée n'est créée à laquelle le fichier pourrait être associé — il est alors rejeté avec un message d'erreur au lieu d'être simplement ignoré.| Champ | Type | Description |
|---|---|---|
name | chaîne | Obligatoire. Nom du fichier, extension comprise ; l'extension doit figurer dans la liste autorisée (règle identique à celle du téléchargement vers le backend) |
content_base64 | chaîne | Obligatoire. Contenu du fichier encodé en Base64 ; un encodage Base64 non valide est rejeté et n'est pas enregistré |
Le stockage s'effectue selon le même mécanisme que le téléchargement vers le backend : même liste blanche d'extensions, même convention de nommage et déduplication par hachage pour éviter les doublons. Le nom de fichier effectivement attribué figure, pour chaque enregistrement, dans la réponse sous « attachments ». La limite maximale par fichier correspond au point de terminaison /media(XOPORT_MEDIA_MAX_FILESIZE, par défaut 10 Mo).
Les entrées erronées (extension non autorisée, Base64 non valide, taille dépassée) génèrent une erreur par fichier; la mise à jour du ticket elle-même reste réussie. Si is_public_reply : true est associé à notify_customer : true, les fichiers sont envoyés avec l’e-mail du client — ils sont enregistrés avant l’envoi.
Réclamation de traitement NOUVEAU v2.118.0
Une revendication indique qu’une personne travaille actuellement sur un ticket, par exemple un agent IA mandaté par un collaborateur. Les autres intervenants le voient dans l’exportation et dans le backend (liste des tickets et vue du ticket) et ne commencent pas à travailler en parallèle. La revendication est une indication, pas un verrouillage : aucun accès en écriture ne la vérifie, le ticket reste modifiable par tous.
PUT /Import/JSON/ticket_claims
{
"type": "ticket_claims",
"data": [
{ "ticket_id": 33999, "admin_id": 12, "state": "claimed", "ttl_minutes": 60, "label": "KI-Agent" }
]
}| Champ | Type | Obligatoire | Description |
|---|---|---|---|
ticket_id | entier | oui | ID d'un ticket existant |
admin_id | entier | oui | Administrateur existant pour le compte duquel on travaille. Une revendication désigne toujours une personne. |
state | chaîne de caractères | oui | claimed crée ou prolonge la revendication ; « released » y met fin. Il n’y a pas de valeur par défaut : le point de fin définit un état et ne bascule jamais. |
ttl_minutes | entier | non | Durée en minutes, valeur par défaut 60, comprise entre 5 et 240. Une prolongation ne raccourcit jamais une réclamation en cours. |
label | chaîne | non | Identifiant de la source, par exemple KI-Agent; 32 caractères maximum, le code HTML est supprimé. Le backend affiche « Nom (Label) ». |
force | booléen | non | Uniquement JSON :true/false. true reprend la revendication d’un autre administrateur ou la libère. |
Résultat par enregistrement
records[].status indique le résultat, records[].claim l'état après l'appel :
| status | Signification |
|---|---|
claimed | Nouvelle revendication créée |
refreshed | Réclamation propre prolongée ; claimed_at est conservé |
taken_over | Réclamation d’un autre administrateur reprise avec force: true; previous_claim contient l’ancienne |
released | réclamation a pris fin ; previous_claim contient l'ancienne |
unchanged | state: released Envoyé, mais aucune revendication n’est active |
conflict | Un autre administrateur détient la revendication. Rien n’est écrit ; claim indique le détenteur et errors[] la déclaration. L’enregistrement est considéré comme failed. |
Réponse (HTTP 200) lorsqu’un autre administrateur envoie une requête alors que Max Mustermann détient la revendication :
{
"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
}
}
]
}Validation automatique
- Réponse publique au client, via l’API (
PUT /Import/JSON/tickets) ou dans le backend. Peu importe qui répond. - Passage à un statut « Fermé ». Quiconque enregistre à nouveau un ticket déjà fermé ne libère pas de réclamation.
- Expiration de la durée de validité.
Une note interne ne libère pas la revendication. Les actions « Définir » et « Libérer » ne modifient ni ticket_date_last_modified ni l’historique et ne déclenchent aucun événement ; le tri et l’exportation delta (?last_modified=) ne sont pas affectés. Il n’y a qu’une seule revendication par ticket : parmi deux demandes simultanées, une seule reçoit conflict.
claim d’un autre administrateur ou un edit_lock, cela signifie que quelqu’un est en train de travailler sur ce ticket. Mettez-vous d’accord avant d’envoyer force: true.À partir de la version 4.9.73 de la boutique. Si le tableau ticket_claims n’existe pas encore, le point de terminaison renvoie un code HTTP 503 TICKET_CLAIMS_UNAVAILABLE; la boutique le crée lors de la prochaine synchronisation du schéma (à l’ouverture d’une page xoCRM ou du HealthCheck).
Portées
| Portée | Description |
|---|---|
tickets:read | Exporter les tickets et les pièces jointes |
tickets:write | Créer et mettre à jour des tickets, rédiger des commentaires et ajouter des pièces jointes, ouvrir et clôturer des demandes de modification |
tickets:delete | Supprimer des tickets (suppression en cascade) |
Gérer les tickets via l'API
Créez un client OAuth2 avec les scopes tickets:read, tickets:write et tickets:delete.