Spécifications

Généralités

SAINet fournit une API d’accès qui se base sur les standards du marché (REST-JSON). Cette interface permet à un logiciel externe de s’interfacer avec SAINet afin de récupérer différentes données selon ses besoins.

La communication set fait exclusivement à travers le protocole HTTPS et tous les échanges sont donc chiffrés.

Le point d’accès (endpoint) est https://<host>/api/v1.

Lorsque le serveur SAINet n’est accessible que depuis l’intranet, l’API est publiée par la WebUI : le point d’accès est alors https://<hôte WebUI>/api/v1. La WebUI relaie la requête de manière transparente (l’authentification est celle du client de l’API), mais refuse (erreur HTTP 400) tout chemin contenant %2F, %2E, %5C, \ ou un segment . ou ...

Prérequis

Afin de pouvoir utiliser l’API, il est nécessaire de disposer d’un compte utilisateur actif dans SAINet avec les accès nécessaire aux différentes tâches permettant d’accéder aux données souhaitées.

Info:

Il est recommandé que les comptes utilisés pour utiliser l’API soient de type “système” en SYS02.

Les différents exemples sont de requêtes sont fait au moyen de la commande cURL.

Activation

Par défaut, l’accès à l’API est désactivé. Un administrateur SAINet doit l’activer en SYS22 (variable system.api_enabled).

Lorsque l’API n’est pas active, SAINet renverra systématiquement une erreur HTTP 403.

Terminologie

Les termes ci-dessous font références à certains concepts de SAINet qui sont utilisés dans l’API.

Nom Description
Tâche Une tâche correspond à un écran. Un code de tâche est composé de 5 caractères (uniquement majuscules et chiffres, par exemple: ADR02 ou SYSUS). Chaque tâche a un type qui lui est associé (voir ci-dessous).
Saisie simple C’est une tâche permettant la gestion (création,modification,suppression) d’un enregistrement quelconque (généralement 1 table en base de données).
Saisie double C’est une tâche permettant la gestion d’un enregistrement complexe (2 ou plusieurs tables en base de données).
Saisie dossier C’est une tâche permettant la gestion d’un dossier. Un dossier se compose de plusieurs champs dynamiques, regroupés dans des pages.
Vue Affichage d’une liste d’enregistrements.
Liste Impression d’une liste d’enregistements. Ce type de tâche renvoie un zip qui peut contenir plusieurs fichiers (html) et autres resources (images).
Traitement Effectue un traitement ne nécessitant pas de retour particulier (si ce n’est un message d’information).

Tâches simples vs longues

Les tâches de vue, liste et traitement peuvent être simples ou longues. Les tâches simples n’ont généralement pas de traitement particulier et un temps de réponse faible.

Les tâches longues comprennent souvent des traitements complexes et peuvent facilement durer plusieurs minutes, ce qui peut poser des problèmes de connexion. Pour ces cas-là, l’API permet de lancer le processus, d’en vérifier la progression puis de récupérer le résultat une fois terminé (polling).

Limitations

Type de tâche Code Exemples Support
Saisie simple B SYS02, CGE02, SAL03 Accès en lecture, création, modification et suppression
Saisie dossier R ADR02, SAL04, CRM06 Accès en lecture et suppression (en-tête), ainsi que lecture, création, modification et suppression (pages)
Saisie double F SYS01, SAL21, STO30 Accès en lecture, création, modification et suppression
Vue simple V SYSUS, SALPA, CRMEV Accès en lecture
Vue longue W GTPLC, GTPEV Accès en lecture
Traitement simple T SYS87, SYS46 Supporté
Traitement long U SYS88, SAL24 Supporté
Liste simple L SYS12 Supporté
Liste longue M SYS34, SAL25 Supporté

Spécifications

Format

Les échanges de données se font au format JSON. Les réponses JSON sont compressées. Il est possible d’utiliser un JSON Beautifier afin d’avoir une visualisation de la hiérarchie des objets.

Les réponses sont encodées en UTF-8.

Types

Les formats ci-dessous sont utilisés dans les échanges JSON:

Type Format Exemple
String "Ceci est une chaîne de caractère"
Date yyyy-MM-dd'T'HH:mm:ssZ "2018-05-03T09:00:00-0100"
Boolean true ou false
Liste Array [ "value1", "value2" ]
Combo Objet { "key":"3", "value":"Libellé" }
Integer 42
Double 1337.8523

Les valeurs null ne sont pas sérialisées et donc absentes des objets dans les réponses retournées.

Les dates ont toujours le format complet (avec les heures et le TimeZone), même pour les champs pour lesquels ces données ne sont pas utilisées.

Objets

Les objets retournés contiennent les informations présentes dans l’écran de la tâche.

La clé spéciale $recordId représente l’identifiant unique permettant d’accéder directement à l’enregistrement.

Note:

Cette clé n’est retournée que dans le cadre des requêtes permettant d’accéder aux enregistrements.

Pour les tâches de saisie simple (B) dont la clé primaire est composée d’un seul champ, $recordId contient la valeur brute de cette clé (comportement historique, inchangé). Pour celles dont la clé est composée de plusieurs champs, $recordId contient un jeton préfixé par $k, suivi de chaque valeur de la clé séparée par $ (par exemple $k$1$1$2018-06-01) : il ne doit jamais être construit manuellement, uniquement réutilisé tel que retourné par le serveur.

Attention:

Lorsque $recordId est inséré dans le chemin d’une URL (par exemple /api/v1/<tâche>/<recordId>), il doit être percent-encodé par le consommateur de l’API, car il peut contenir des caractères invalides dans un chemin d’URL (par exemple un espace au sein d’une valeur de type date). Une valeur de clé contenant elle-même un / peut toutefois être problématique même une fois percent-encodée (%2F), car certains serveurs redécoupent le chemin avant de le décoder. Via la WebUI, un chemin contenant %2F est même refusé (HTTP 400), ainsi que %2E, %5C, \ ou un segment . ou ...

Paramètre autoAcknowledge

Paramètres Type Description
autoAcknowledge Boolean Active (true, valeur par défaut) ou désactive (false) la réponse implicite aux questions interactives à issue unique. Valeurs acceptées: true, 1, false, 0 (insensible à la casse). Paramètre optionnel.

Certains traitements posent, en cours d’exécution, une question interactive à l’utilisateur (message de statut ONGOING) avant de pouvoir continuer. Par défaut (autoAcknowledge=true, ou paramètre absent), l’API y répond implicitement lorsque la question n’a qu’une seule issue possible:

  • une question à options OK-CANCEL reçoit la réponse OK;
  • une question à options CUSTOM-CANCEL avec un seul bouton personnalisé reçoit ce bouton;

et l’appel est rejoué de manière transparente avec cette réponse, de manière invisible pour le client. Si ce rejeu lève à son tour une nouvelle question à issue unique, elle est acquittée de la même manière: toutes les questions implicites successives rencontrées au cours d’un même appel sont ainsi acquittées, et non plus une seule. Les questions à plusieurs issues possibles (YES-NO, YES-NO-CANCEL, ou CUSTOM/CUSTOM-CANCEL avec plusieurs boutons) sont renvoyées telles quelles au client, sauf si le client y répond lui-même explicitement au moyen de la clé $responses. Ce rejeu implicite emprunte le même mécanisme que celui décrit ci-dessous pour $responses: la tentative interrompue est intégralement annulée (rollback) avant que l’appel entier ne soit rejoué depuis le début avec cette réponse déjà appliquée; vu du client, il ne reçoit qu’une seule réponse HTTP, comme si l’appel n’avait connu aucune interruption.

En passant autoAcknowledge=false, toute question ONGOING est renvoyée telle quelle au client, y compris celles qui auraient normalement une réponse implicite. Dans ce cas, la transaction est annulée (rollback): aucune donnée n’est écrite.

Note:

Ce paramètre n’a d’effet que sur les chemins d’écriture: création, modification et suppression d’un enregistrement, création, modification et suppression d’une page de dossier, ainsi que la validation d’un enregistrement. Il est accepté (et validé) sur les autres tâches, sans y avoir aucun effet.

Attention:

Une valeur qui n’est ni true/1 ni false/0 (par exemple autoAcknowledge=maybe) est rejetée par une erreur métier (code HTTP 406).

Exemple de désactivation lors d’une suppression:

curl --insecure --silent \
     -H "Authorization: Bearer <bearer_token>" \
     -X DELETE \
     'https://<host>/api/v1/<taskId>/<recordId>?autoAcknowledge=false'

Clé $responses

Paramètres Type Description
$responses Array Liste de réponses explicites aux questions interactives (ONGOING) rencontrées durant l’appel. Chaque entrée est un objet {"messageId": "<CODE>", "responseId": "<REPONSE>"}. Portée dans le corps JSON de la requête, au niveau racine (pour une tâche à double niveau, à côté de header et lines, jamais à l’intérieur de ceux-ci). Clé optionnelle.

En complément d’autoAcknowledge, la clé $responses permet au client de répondre lui-même à une question interactive, y compris celles à plusieurs issues (YES-NO, YES-NO-CANCEL, CUSTOM/CUSTOM-CANCEL à plusieurs boutons) qu’autoAcknowledge ne peut pas acquitter implicitement. Chaque réponse est appariée au message qui l’a levée par son messageId, comparé de manière exacte (sensible à la casse) au code du message ONGOING reçu (voir erreur métier) : c’est ce même code qui doit être utilisé comme messageId dans $responses. Les valeurs acceptées pour responseId dépendent du type de la question posée:

Type de question Valeurs acceptées pour responseId
OK-CANCEL OK
YES-NO, YES-NO-CANCEL YES ou NO
CUSTOM, CUSTOM-CANCEL un des identifiants de bouton personnalisé de la question
Attention:

Aucune valeur CANCEL n’est jamais acceptée. Pour annuler une question (et donc l’appel), il suffit de ne fournir aucune réponse pour son messageId: la question est alors renvoyée telle quelle et la transaction est annulée.

Note:

Le cycle d’intégration naturel est donc: un premier appel sans $responses qui se heurte à une question ONGOING, dont l’erreur 422 porte le code (à utiliser comme messageId), le requestType de la question et la liste exhaustive responses des responseId acceptables (voir erreur métier) - puis un second appel rejouant la requête avec l’entrée $responses correspondante.

L’ordre de résolution d’une question ONGOING est le suivant: 1) une réponse explicite est cherchée dans $responses parmi les entrées non encore consommées pour le messageId concerné; 2) à défaut, une réponse implicite est appliquée si autoAcknowledge est actif et que la question n’a qu’une seule issue possible; 3) à défaut, la question est renvoyée telle quelle au client.

Note:

Un messageId présent dans $responses mais jamais demandé pendant l’appel est ignoré silencieusement, sans erreur: un client peut ainsi fournir défensivement les mêmes réponses à chaque appel, même pour des questions qui ne sont posées que sous certaines conditions.

$responses est une file ordonnée: ses entrées sont consommées dans l’ordre du tableau, et chaque entrée répond à une seule occurrence de la question portant ce messageId - la première occurrence qui n’a pas encore reçu de réponse. Pour une question donnée, l’appariement retient la première entrée non encore consommée du tableau dont le messageId correspond: une entrée d’un autre messageId, croisée en chemin dans le tableau, est simplement ignorée pour cette question et ne bloque jamais l’accès à une entrée suivante qui correspond. Si la même question est levée une nouvelle fois plus tard dans le traitement (par exemple par une autre ligne d’une tâche à double niveau) et qu’il ne reste plus aucune entrée disponible pour son messageId, cette occurrence-là est renvoyée telle quelle au client, exactement comme si aucune réponse n’avait été fournie pour elle. Une entrée qui n’a été consommée par aucune occurrence à la fin de l’appel est ignorée sans erreur (voir le tip ci-dessus).

Exemple: une tâche à double niveau dont deux lignes lèvent chacune, indépendamment, la même question LIGNE_REMISE_EXCESSIVE (code fictif, donné à titre d’illustration ; question de type YES-NO, « la remise dépasse le maximum autorisé, confirmer la ligne? »), avec deux réponses différentes:

curl --insecure --silent \
     -H "Authorization: Bearer <bearer_token>" \
     --json '{
       "lines": [
         { "$recordId": "$k$D1000$20200001$1", "SUB_REMISE": 50.0 },
         { "$recordId": "$k$D1000$20200001$2", "SUB_REMISE": 80.0 }
       ],
       "$responses": [
         {"messageId": "LIGNE_REMISE_EXCESSIVE", "responseId": "YES"},
         {"messageId": "LIGNE_REMISE_EXCESSIVE", "responseId": "NO"}
       ]
     }'
     -X PATCH
     'https://<host>/api/v1/STO30/$k$D1000$20200001$0'

La question levée par la première ligne consomme la première entrée (YES: remise acceptée telle quelle), celle levée par la seconde ligne consomme la seconde entrée (NO: remise refusée, la ligne est traitée en conséquence). N’envoyer qu’une seule entrée LIGNE_REMISE_EXCESSIVE ferait échouer l’appel dès la seconde occurrence, faute d’entrée restante à lui appliquer.

Attention:

Sont rejetées par une erreur métier (code HTTP 406), transaction annulée: une entrée de $responses mal formée (pas un objet, messageId ou responseId absent ou vide, ou clé supplémentaire inconnue), ainsi qu’un responseId qui n’est pas accepté par la question effectivement levée (voir le tableau ci-dessus). Un messageId présent plusieurs fois dans $responses n’est en revanche pas une erreur: chaque occurrence est une entrée distincte de la file, consommée séparément, comme décrit ci-dessus.

Chaque réponse consommée provoque, côté serveur, l’annulation complète de la transaction de la tentative en cours puis la ré-exécution intégrale de la requête depuis le début (nouveaux descripteurs, nouveaux contrôles de sécurité), avec l’ensemble des réponses déjà décidées lors des tentatives précédentes réappliquées automatiquement. Vu du client, l’appel reste atomique: il ne reçoit qu’une seule réponse HTTP pour l’ensemble du mécanisme, et aucune écriture d’une tentative interrompue par une question n’est conservée en base. Le nombre de questions pouvant être répondues au cours d’un même appel est plafonné; le dépasser est rejeté par une erreur métier (code HTTP 406).

Attention:

Un effet de bord qui échappe à la transaction de base de données (envoi d’un courriel, appel à un service externe, écriture d’un fichier, etc.) situé sur un chemin de traitement qui pose une question ONGOING peut être exécuté une fois par tentative, donc potentiellement plusieurs fois pour un même appel client si plusieurs questions y sont répondues au cours de tentatives successives. Cela n’est pas spécifique à $responses: cela vaut également pour la réponse implicite d’autoAcknowledge.

Note:

Cette clé n’a d’effet que sur les mêmes chemins qu’autoAcknowledge: création, modification et suppression d’un enregistrement, création, modification et suppression d’une page de dossier, ainsi que la validation d’un enregistrement.

$responses est également acceptée sur une requête DELETE: le corps JSON d’une telle requête, jusqu’ici toujours absent, devient optionnel et ne sert qu’à porter cette clé. Son absence reste le cas nominal et laisse le comportement de la suppression strictement inchangé.

Attention:

Sur une requête DELETE, le corps JSON, s’il est fourni, ne peut contenir que la clé $responses: toute autre clé, ou un corps qui n’est pas un objet JSON, est rejeté par une erreur métier (code HTTP 406). Un objet ne contenant que $responses, y compris avec un tableau vide, reste valide.

Attention:

Certains intermédiaires HTTP (proxys, certains clients) suppriment le corps d’une requête DELETE. Si la question interactive levée par une suppression doit être répondue explicitement, s’assurer que la chaîne réseau utilisée transmet bien le corps de la requête.

Exemple de suppression d’une page de dossier répondant à une question de confirmation à choix YES-NO:

curl --insecure --silent \
     -H "Authorization: Bearer <bearer_token>" \
     --json '{"$responses": [{"messageId": "ADR_PAGE_SHARED_DELETION_CONFIRM", "responseId": "YES"}]}' \
     -X DELETE \
     'https://<host>/api/v1/<taskId>/<recordId>/pages/<pageId>/<occurrence>'

Codes HTTP

Les codes HTTP suivants peuvent être retournés:

Code Description Réponse
200 La requête a été traitée. Objet JSON
400 La requête n’a pas été traitée car elle est invalide. -
401 Accès non autorisé. Soit les données d’authentification sont incorrectes, soit le token a été invalidé. -
403 L’accès à l’API n’a pas été autorisé en SYS22. -
405 Méthode invalide. -
406 Erreur métier (voir ci-dessous). Objet JSON
422 Question ONGOING en attente d’une réponse (voir ci-dessous). Objet JSON
500 Erreur interne. -

Erreur métier

En cas d’erreur métier (code HTTP 406, status FAILURE) ou de question ONGOING en attente d’une réponse (code HTTP 422, status ONGOING), la réponse contient un objet JSON avec le détail de l’erreur.

{
  "status":"FAILURE",
  "type":"BUSINESS_FAILURE",
  "code":"SYSTEM_SECURITY_ACCESS_DENIED",
  "message":"Vous n\u0027avez pas les droits suffisants pour effectuer l\u0027action demandée.\nUtilisateur: <login>.\nTâche: SYS00.\nPage: -.\nDomaine: <domain>.\nAction: R."
}

La langue du message d’erreur correspond à la langue de l’utilisateur en SYS02.

Voici les différentes valeurs possibles:

Clé Valeur Description
status FAILURE Une erreur métier est survenue et le traitement a été interrompu.
SUCCESS Le traitement a été effectué sans erreur, mais un message est affiché. Ce type de message n’est pas renvoyé dans le cadre de l’API.
ONGOING Le traitement a besoin d’une confirmation de l’utilisateur. Sur les chemins de création, modification, suppression et validation d’un enregistrement (tous types de tâches concernés, B, R et F), l’API y répond implicitement par défaut lorsque la question n’a qu’une seule issue possible ; ce comportement peut être désactivé, voir autoAcknowledge. Dans les autres cas, ce message est renvoyé au client, qui peut y répondre explicitement en rejouant l’appel avec la clé $responses, en utilisant le code de ce message comme messageId.
type COMMENT Simple remarque. N’apparaît que pour les message avec le status SUCCESS ou ONGOING.
INVALID_INPUT Les données saisies sont invalides. Généralement ce type d’erreur est renvoyé lors d’une saisie.
BUSINESS_FAILURE Erreur métier.
SYSTEM_FAILURE Erreur système.
PASSWORD_EXPIRED Mot de passe expiré (uniquement lors du login, ne devrait jamais apparaître avec l’utilisation d’un compte système).
CORRUPTED_DATA Des données sont corrompues (typiquement suite à un import de données).
code Cette valeur est unique pour chaque type de message renvoyé.
message Message correspondant au code.
invalidFields Liste des champs invalides correspondants au message. Cette valeur n’est pas spécifiée lorsque l’erreur n’est pas liée à un champ.
requestType OK-CANCEL, YES-NO, YES-NO-CANCEL, CUSTOM, CUSTOM-CANCEL Uniquement présent lorsque status vaut ONGOING: type de la question interactive posée.
responses Array Uniquement présent lorsque status vaut ONGOING: liste exhaustive des réponses acceptables pour cette question, chacune sous la forme {"responseId":"<ID>","label":"<LIBELLÉ>"}. La clé label n’est présente que pour les boutons personnalisés (CUSTOM, CUSTOM-CANCEL) ; pour OK, YES et NO, elle est absente (les valeurs null ne sont pas sérialisées). CANCEL n’y figure jamais: ce n’est pas une réponse acceptable, voir $responses.
Note:

Les codes possibles pour les erreurs métiers sont nombreux et variés et ne sont pas décrits. Ils sont toujours accompagnés d’un message détaillé.

Exemple de réponse 422 pour une question ONGOING de type CUSTOM-CANCEL (boutons personnalisés):

{
  "status":"ONGOING",
  "type":"COMMENT",
  "code":"STO30_LIGNE_ACTION",
  "message":"Que souhaitez-vous faire de cette ligne?",
  "requestType":"CUSTOM-CANCEL",
  "responses":[
    {"responseId":"KEEP","label":"Conserver"},
    {"responseId":"SPLIT","label":"Scinder"}
  ]
}

Exemple de réponse 422 pour une question ONGOING de type YES-NO:

{
  "status":"ONGOING",
  "type":"COMMENT",
  "code":"LIGNE_REMISE_EXCESSIVE",
  "message":"La remise dépasse le maximum autorisé, confirmer la ligne?",
  "requestType":"YES-NO",
  "responses":[
    {"responseId":"YES"},
    {"responseId":"NO"}
  ]
}