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/ecriturene 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.