Aller au contenu

Démarrer avec l'API v2

En bref

L'API v2 du connecteur SenSaaS expose Sage 100 en HTTP/JSON, sous des routes /v2/… réparties en groupes. Toute réponse en succès est emballée dans une enveloppe { success, data, meta } ; toute erreur sort en ProblemDetails RFC 9457. L'authentification se fait par jeton porteur : jeton du connecteur ou jeton du serveur d'identité SenSaaS.

Cette page s'adresse à l'intégrateur : elle donne le minimum pour appeler le connecteur depuis un programme. Le détail opération par opération est dans les groupes, les règles de format dans les conventions.

URL de base et contrat

Le connecteur écoute en HTTPS : sur le réseau privé EasyConnect (port 4000 par défaut) ou sur le port ouvert choisi à l'installation. Toutes les adresses de cette section sont relatives à l'hôte du connecteur, noté https://<votre-connecteur> dans les exemples.

Ressource Adresse Accès
Contrat OpenAPI v2 (3.0.4) GET /swagger/v2/swagger.json anonyme, toujours exposé
Contrat OpenAPI v1 (routes historiques, dépréciées) GET /swagger/v1/swagger.json anonyme, toujours exposé
Outil de test intégré GET /test, document v2 sur /test/#api-sensaas-v2 seulement si l'option Activer l'outil de test / debug de l'API est active

Le contrat JSON est toujours servi, même quand l'outil de test est désactivé : il ne contient aucune donnée métier, seulement la description des routes. C'est lui qui alimente l'outillage, dont le serveur MCP "sensaas-connecteur" et les pages de groupes de cette documentation, générées à partir de lui.

L'outil de test embarqué permet d'essayer une opération depuis un navigateur : on colle le jeton seul dans "Authorize", puis on déclenche l'appel. Les anciennes adresses /swagger et /swagger/index.html redirigent vers l'outil de test.

Capture à venir

Outil de test de l'API SenSaaS, liste des opérations v2 - identifiant connecteur/swagger-ui. L'image sera ajoutée lors de la prochaine campagne de captures.

S'authentifier

Chaque appel protégé porte l'en-tête Authorization: Bearer <jeton>. Deux jetons sont acceptés en parallèle, toujours :

Mode Obtention Renouvellement
Jeton du connecteur POST /v2/auth/login (ou POST /auth/login) avec l'identifiant et le mot de passe d'un utilisateur du connecteur POST /v2/auth/refresh-token
Jeton d'accès du serveur d'identité SenSaaS (Keycloak) Connexion par la webapp SenSaaS Auprès du serveur d'identité, jamais par le connecteur

Un jeton du serveur d'identité est associé à l'utilisateur actif du connecteur qui porte la même adresse email vérifiée ; ensuite, droits, dossiers et filtres s'appliquent exactement comme pour un jeton du connecteur. Ses exigences (audience, client, rôle) sont décrites dans Sécurité du connecteur.

Obtenir un jeton du connecteur

POST /v2/auth/login prend un corps JSON { "username": …, "password": … } et renvoie un jeton dans l'enveloppe de réponse.

curl -s -X POST "https://<votre-connecteur>/v2/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"username": "integration", "password": "<mot-de-passe>"}'
{
  "success": true,
  "data": {
    "userName": "integration",
    "serverName": "SENSAAS-DEMO",
    "success": true,
    "message": "",
    "token": "eyJhbGciOiJIUzUxMiIs…",
    "tokenExpiration": "2026-10-04T10:20:00",
    "refreshToken": "…",
    "refreshTokenExpiration": "2026-10-11T10:00:00",
    "b2b": false
  },
  "meta": { "correlationId": "…", "durationMs": 42, "dossiers": null }
}

Utiliser le jeton

GET /v2/tiersbanque?targets=DEMO&limit=50 HTTP/1.1
Host: votre-connecteur
Authorization: Bearer <jeton>
Accept: application/json
Point Valeur (jeton du connecteur)
Signature HMAC-SHA512, clé propre à l'installation
Durée de vie du jeton 20 minutes
Renouvellement POST /v2/auth/refresh-token ; le jeton de renouvellement vit 7 jours
Stockage du renouvellement cookie HttpOnly, révocable côté connecteur
Limite de débit 30 requêtes par minute et par adresse IP sur les routes d'authentification

Refus d'authentification

Statut Code Conduite à tenir
401 AUTH.INVALID_TOKEN Jeton absent ou refusé (signature, émetteur, audience, rôle, email non vérifié) : se reconnecter. L'en-tête WWW-Authenticate: Bearer error="invalid_token" accompagne la réponse dès qu'un jeton a été présenté
401 AUTH.TOKEN_EXPIRED Renouveler le jeton, puis rejouer l'appel
403 AUTH.UTILISATEUR_INCONNU Jeton du serveur d'identité valide, mais aucun utilisateur actif du connecteur pour cet email : ne pas renouveler ni rejouer ; l'administrateur crée ou active le compte

Pas de CORS

Aucune politique CORS n'est enregistrée, volontairement : l'API vise des clients natifs et des services, pas des pages web tierces. Un appel depuis un navigateur en cross-origin sera refusé par le navigateur.

Quelques opérations /v2 sont anonymes et n'exigent pas de jeton : les entrées d'authentification (/v2/auth/login, /v2/auth/login-token, /v2/auth/refresh-token, /v2/auth/register, /v2/auth/ping), les sondes du serveur (/v2/serveur/ping, /v2/serveur/check, /v2/serveur/sync-confirm), les routes réseau /v2/reseau/… et le service d'images /v2/images/{imageHash}/{maxWidth}/{maxHeight}. Les pages de groupe le signalent opération par opération.

L'enveloppe de réponse

Toute réponse 2xx de l'API v2 a la même forme, en camelCase :

{
  "success": true,
  "data": [],
  "meta": {
    "correlationId": "8f2a…",
    "durationMs": 128,
    "dossiers": ["DEMO", "DEMO2"],
    "pagination": null,
    "warnings": null
  }
}
Champ Contenu
success true pour toute réponse 2xx
data la charge utile : objet, tableau, ou résultat indexé par dossier
meta.correlationId identifiant de corrélation, égal à l'en-tête X-Correlation-Id de la réponse
meta.durationMs durée de traitement côté connecteur
meta.dossiers dossiers Sage réellement interrogés ; null pour les opérations mono-objet
meta.pagination { limit, offset, total } sur les lectures par critères paginées
meta.warnings dégradations non bloquantes { code, message }, par exemple DOSSIER.IGNORED ou QUERY.COLUMN_IGNORED

Corrélation

Envoyez votre propre X-Correlation-Id : le connecteur le reprend, l'inscrit dans ses journaux et le renvoie dans meta.correlationId. C'est la clé pour faire relier un appel côté client à une ligne de journal côté serveur.

Paramètres transverses

Trois paramètres de requête sont reconnus au-delà de ceux propres à chaque opération.

Paramètre Rôle Détail
targets dossier(s) Sage visé(s) Forme canonique v2, valeurs séparées par des virgules : ?targets=DEMO,DEMO2. Sans valeur, tous les dossiers autorisés à l'utilisateur sont interrogés. Alias hérités acceptés : target, target_base, base, bases (séparateur , ou \|).
limit nombre maximum de lignes 0 = illimité. S'applique par dossier.
offset décalage pagination, à combiner avec limit.

Les autorisations de l'utilisateur filtrent toujours targets : un dossier inconnu ou non autorisé est écarté, et peut être signalé dans meta.warnings par le code DOSSIER.IGNORED.

?targets= fait foi sur le corps

Quand la requête porte ?targets=, une cible écrite dans le corps (target_base de /v2/query, dossier d'une transformation ou d'un PDF) n'est acceptée que si elle en fait partie. Sinon, la réponse est DOSSIER.CONFLICT (400).

Écritures : un seul dossier

Une écriture (POST, PUT, DELETE) doit viser un dossier unique. Plusieurs valeurs dans targets renvoient DOSSIER.AMBIGUOUS (400) ; aucune valeur alors que l'utilisateur a plusieurs dossiers autorisés renvoie DOSSIER.REQUIRED (400).

Lire par critères

Les listes d'objets (GET /v2/<objet>) renvoient un résultat indexé par dossier et acceptent, en plus de limit et offset, un tri sort (colonnes SQL séparées par des virgules, préfixe - pour l'ordre décroissant) et un filtre where. Chaque objet propose aussi POST /v2/<objet>/recherche (critères dans le corps) et GET | POST /v2/<objet>/count. Les colonnes se désignent par leur nom SQL Sage, jamais par du SQL brut. Le détail est dans les conventions.

Les erreurs

Une réponse ≥ 400 ne passe pas par l'enveloppe : elle sort en application/problem+json, au format ProblemDetails (RFC 9457), enrichi de deux extensions SenSaaS.

{
  "type": "https://sensaas.fr/errors/sage-not-found",
  "title": "Enregistrement introuvable",
  "status": 404,
  "detail": "…",
  "instance": "/v2/tiers/clients/C0001",
  "code": "SAGE.NOT_FOUND",
  "correlationId": "8f2a…"
}

code appartient à une taxonomie stable (AUTH.*, VALIDATION.*, DOSSIER.*, SAGE.*, CONFIG.*, PDF.*, LICENCE.*, QUERY.*, INTERNAL) : c'est sur lui qu'il faut brancher la logique du client, jamais sur title ou detail. Le détail complet est dans les conventions.

Codes les plus courants à traiter :

Code Statut Situation
AUTH.INVALID_TOKEN, AUTH.TOKEN_EXPIRED 401 jeton absent, refusé ou expiré
AUTH.FORBIDDEN 403 droit retiré à l'appelant : opération de l'API, document, écritures comptables
DOSSIER.NOT_AUTHORIZED 403 l'utilisateur n'a pas accès au dossier demandé
DOSSIER.AMBIGUOUS, DOSSIER.REQUIRED 400 écriture sans dossier unique
DOSSIER.CONFLICT 400 cible du corps absente de ?targets=
VALIDATION.MODEL 400 corps vide ou invalide
QUERY.INVALID 400 requête /v2/query refusée
SAGE.NOT_FOUND 404 clé inexistante dans Sage
SAGE.READONLY 409 opération non disponible par cette voie ; sageDetail indique la voie à utiliser
SAGE.IN_USE 409 suppression d'un tiers qui porte des pièces
SAGE.COM.PROCESS 422 refus de Sage, message dans sageDetail
SAGE.COM.UNAVAILABLE 503 écritures v2 désactivées, ou Sage injoignable
CONFIG.INCOMPLETE 409 ou 503 paramétrage manquant (modèles PDF) ou installation incomplète

Écrire dans Sage

Les écritures v2 sont ouvertes par défaut. Si l'option Autoriser les écritures API v2 (écran Serveur Web) a été désactivée, toute écriture répond 503 avec le code SAGE.COM.UNAVAILABLE. Quand elles sont ouvertes :

  • le corps est un JSON partiel du DTO : seules les propriétés présentes sont écrites ;
  • les droits de l'appelant s'appliquent : droits par opération, droits sur les documents (type et statut, lignes, échéancier) ;
  • les valeurs forcées des filtres de l'appelant peuvent remplacer la valeur envoyée pour certaines colonnes ;
  • les documents passent par des objets spécialisés par domaine : vente (/v2/documentvente), achat (/v2/documentachat), stock (/v2/documentstock), interne (/v2/documentinterne), et leurs lignes ;
  • les écritures comptables se saisissent et se suppriment par pièce équilibrée (/v2/ecriturepiece) ; /v2/ecriture ne permet que la lecture et la modification.

Les règles détaillées sont dans les conventions.

Premier appel de bout en bout

HOTE="https://<votre-connecteur>"

# 1. Authentification
JETON=$(curl -s -X POST "$HOTE/v2/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"username":"integration","password":"<mot-de-passe>"}' \
  | python -c "import json,sys; print(json.load(sys.stdin)['data']['token'])")

# 2. Dossiers Sage visibles
curl -s "$HOTE/v2/sage/dossiers" -H "Authorization: Bearer $JETON"

# 3. Une liste, sur un dossier, limitée
curl -s "$HOTE/v2/tiersbanque?targets=DEMO&limit=10" \
  -H "Authorization: Bearer $JETON"

# 4. Un indicateur
curl -s "$HOTE/v2/kpi/clients-ca/ca-n?targets=DEMO" \
  -H "Authorization: Bearer $JETON"
"""Client minimal de l'API SenSaaS v2 : jeton, enveloppe, ProblemDetails."""
import requests

HOTE = "https://<votre-connecteur>"
VERIFIER_TLS = True  # False seulement face à un certificat auto-signé interne


class ErreurSenSaaS(Exception):
    """Erreur renvoyée par le connecteur, porteuse du code de la taxonomie."""

    def __init__(self, probleme: dict):
        self.code = probleme.get("code")
        self.statut = probleme.get("status")
        self.correlation = probleme.get("correlationId")
        super().__init__(f"{self.statut} {self.code} : {probleme.get('detail') or probleme.get('title')}")


class ClientSenSaaS:
    def __init__(self, hote: str = HOTE, verifier_tls: bool = VERIFIER_TLS):
        self.hote = hote.rstrip("/")
        self.session = requests.Session()
        self.session.verify = verifier_tls

    def connexion(self, utilisateur: str, mot_de_passe: str) -> None:
        reponse = self.session.post(
            f"{self.hote}/v2/auth/login",
            json={"username": utilisateur, "password": mot_de_passe},
            timeout=30,
        )
        jeton = self._deballer(reponse)["token"]
        self.session.headers["Authorization"] = f"Bearer {jeton}"

    def appeler(self, methode: str, chemin: str, *, params=None, corps=None):
        reponse = self.session.request(
            methode, f"{self.hote}{chemin}", params=params, json=corps, timeout=120
        )
        return self._deballer(reponse)

    @staticmethod
    def _deballer(reponse):
        if reponse.status_code >= 400:
            raise ErreurSenSaaS(reponse.json())
        charge = reponse.json()
        # Les réponses 2xx de l'API v2 sont emballées ; on rend directement « data ».
        return charge.get("data") if isinstance(charge, dict) and "success" in charge else charge


client = ClientSenSaaS()
client.connexion("integration", "<mot-de-passe>")

dossiers = client.appeler("GET", "/v2/sage/dossiers")
banques = client.appeler("GET", "/v2/tiersbanque", params={"targets": "DEMO", "limit": 10})
ca = client.appeler("GET", "/v2/kpi/clients-ca/ca-n", params={"targets": "DEMO"})

Certaines réponses d'erreur n'ont pas de corps JSON (par exemple 429 ou 405) et feraient échouer reponse.json() : en production, testez le type de contenu avant de lire le corps.

Où aller ensuite

Besoin Page
Format exact des corps, taxonomie des erreurs Conventions de payload
Indicateurs clés (156 indicateurs, 22 familles) Indicateurs (KPI)
Faire découvrir l'API à une IA Serveur MCP "sensaas-connecteur"
Toutes les opérations, groupe par groupe Groupes de l'API v2

Points d'entrée utiles pour explorer :

  • GET /v2/entites : catalogue des objets métier accessibles, sans accès à Sage.
  • GET /v2/sage/dossiers : dossiers Sage visibles par l'utilisateur connecté.
  • POST /v2/query : requête de masse sur le schéma Sage (modèle hérité v1, champs en minuscules, validation stricte).
  • GET /v2/serveur/version : version du connecteur et horodatage du binaire déployé.

Questions fréquentes

Faut-il utiliser /v2 ou les anciennes routes ?

Toujours /v2. Les routes historiques (sans /v2) sont obsolètes : elles portent les en-têtes Deprecation: true et Sunset, et leur retrait est prévu au 1er janvier 2027, date révisable selon le calendrier de déploiement. Seul /auth, commun aux deux versions, n'est pas concerné.

Pourquoi mes écritures renvoient-elles 503 ?

Les écritures v2 sont derrière une option du connecteur, activée par défaut. Si elle a été désactivée, toute écriture répond 503 avec le code SAGE.COM.UNAVAILABLE. Si le code est CONFIG.INCOMPLETE, l'installation du connecteur est incomplète : voir Dépannage.

Pourquoi un 403 alors que mon jeton est valide ?

Lisez le code : AUTH.UTILISATEUR_INCONNU (aucun utilisateur du connecteur pour cet email), DOSSIER.NOT_AUTHORIZED (dossier non accordé) ou AUTH.FORBIDDEN (droit retiré dans l'outil de configuration, le detail nomme le droit et la route).

Comment cibler plusieurs dossiers ?

?targets=DEMO,DEMO2 en lecture. En écriture, un seul dossier est accepté.

Le corps de ma requête est volumineux, y a-t-il une limite ?

Oui : la taille maximale d'un corps de requête est de 50 Mo. Les réponses JSON sont compressées en gzip quand le client l'accepte.

Voir aussi

Documentation de SenSaaS v2. Chaque page est écrite à partir du produit livré ; ce qui reste à vérifier y est signalé.