Point d'arrivée du billet

v2.118.1 · CRUD complet · Tickets, pièces jointes et réclamations

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éthodePoint de terminaisonPortéeDescription
GET/Export/JSON/ticketstickets:readRécupérer tous les tickets (pagination)
GET/Export/JSON/ticket/{id}tickets:readUn ticket individuel avec l'historique des statuts et les rendez-vous associés
GET/Export/JSON/ticket_attachments/{id}tickets:readLister les pièces jointes d'un ticket
GET/Export/JSON/ticket_attachment/{id}?file=nametickets:readTélécharger un fichier individuel au format binaire
POST/Import/JSON/ticketstickets:writeCréer un nouveau ticket (INSERT) — avec commentaire et pièces jointes (facultatif)
PUT/Import/JSON/ticketstickets:writeMettre à jour un ticket existant (UPDATE) — avec, en option, un commentaire, une réponse publique et des pièces jointes
PUT/Import/JSON/ticket_claimstickets:writeCréer, prolonger, reprendre ou clôturer une demande de modification NOUVEAU v2.118.0
DELETE/Delete/JSON/ticket/{id}tickets:deleteSupprimer un ticket avec suppression en cascade

Export : paramètres de requête

ParamètresDescription
?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-JJModifié depuis la date
?language=frFiltre par langue
?limit=50&offset=0Pagination (par défaut : 50, max. : 250)

Exportation : champs de réponse

ChampTypeDescription
ticket_identierID du ticket
ticket_link_idchaîne de caractèresID de lien alphanumérique à 14 caractères
ticket_subjectchaîneObjet du ticket
ticket_statuschaîneStatut actuel
priorité_du_ticketchaînePriorité
department_identierID du service
department_namechaîne de caractèresNom du service
historique_statut[]tableauConversation complète avec commentaires
assigned_admins[]tableauAdministrateurs affectés
followers[]tableauAbonnés au ticket
linked_appointments[]tableauNOUVEAU v2.11.0 : rendez-vous CRM associés (ID, début, fin, titre, lieu, statut)
claimobjet|nullNOUVEAU 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_lockobject|nullNOUVEAU 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 — Objet
  • ticket_customers_email — E-mail du client
  • ticket_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 client
  • ticket_language_id — ID de la langue
  • ticket_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 de ticket_comments
  • assigned_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.

Important : par défaut, les commentaires sont internes (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.
ChampTypeDescription
ticket_identierObligatoire. Identifiant du ticket à mettre à jour
ticket_subjectchaîne de caractèresModifier l'objet
type_de_ticketchaîneModifier le type
ticket_status_identierModifier le statut (sera validé)
ticket_priority_identierModifier la priorité (sera validé)
ticket_department_identierModifier le service (sera validé)
date_de_masquage_du_ticketdate-heureMasquer le ticket jusqu'au
ticket_login_required0/1Connexion requise
ticket_allow_learning0/1Autoriser l'apprentissage par IA
ticket_commentschaîneTexte du commentaire — crée une nouvelle entrée dans l'historique
admin_identierAuteur du commentaire (0 = système ; doit être > 0 en cas de réponse publique)
is_public_replybooléenv2.25.0 : true = réponse visible par le client au lieu d'une note interne
notify_customerbooléenv2.25.0 : true = e-mail envoyé au client (uniquement en combinaison avec is_public_reply)
append_signaturebooléenv2.27.0 : ajouter la signature de l'administrateur à une réponse publique (activée par défaut ; désactivation uniquement via « false »)
attachments[]tableauNOUVEAU v2.60.0 : pièces jointes (name + content_base64) ; nécessite la présence de ticket_comments
assigned_admins[]tableauRemplacer les affectations d'administrateurs

Format des commentaires

« ticket_comments » : « Note interne via l'API »,
« admin_id » : 1

ticket_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

  1. ticket_status_history — Commentaires/historique
  2. ticket_to_admins — Attributions aux administrateurs
  3. ticket_to_followers — Abonnés
  4. xocrm_appointments_relationships — Liens avec les rendez-vous (les rendez-vous sont conservés)
  5. xocrm_followers — Abonnés CRM
  6. Fichiers sur le disque — upload/ticket/{link_id}/
  7. 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…"
  } 
]
Les pièces jointes sont associées à une entrée de l'historique, et non au ticket. Sans 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é.
ChampTypeDescription
namechaîneObligatoire. 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_base64chaîneObligatoire. 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" }
  ]
}
ChampTypeObligatoireDescription
ticket_identierouiID d'un ticket existant
admin_identierouiAdministrateur existant pour le compte duquel on travaille. Une revendication désigne toujours une personne.
statechaîne de caractèresouiclaimed 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_minutesentiernonDurée en minutes, valeur par défaut 60, comprise entre 5 et 240. Une prolongation ne raccourcit jamais une réclamation en cours.
labelchaînenonIdentifiant de la source, par exemple KI-Agent; 32 caractères maximum, le code HTML est supprimé. Le backend affiche « Nom (Label) ».
forcebooléennonUniquement 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 :

statusSignification
claimedNouvelle revendication créée
refreshedRéclamation propre prolongée ; claimed_at est conservé
taken_overRéclamation d’un autre administrateur reprise avec force: true; previous_claim contient l’ancienne
releasedréclamation a pris fin ; previous_claim contient l'ancienne
unchangedstate: released Envoyé, mais aucune revendication n’est active
conflictUn 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.

À vérifier avant de procéder : si l’export mentionne un 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éeDescription
tickets:readExporter les tickets et les pièces jointes
tickets:writeCré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:deleteSupprimer 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.

Créer un client OAuth2