Démarrer avec l'API v2¶
En bref
L'API v2 du connecteur SenSaaS expose Sage 100c en HTTP/JSON : 728 opérations sur 398 chemins,
réparties en 108 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 JWT porteur.
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 port 443. 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 (surface historique) | GET /swagger/v1/swagger.json |
anonyme, toujours exposé |
| Outil de test intégré | GET /test |
seulement si l'option SwaggerUI 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 ».
L'outil de test embarqué permet d'essayer une opération depuis un navigateur : on colle le jeton dans « Authorize », puis on déclenche l'appel.
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¶
Obtenir un jeton¶
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-09-11T10:20:00",
"refreshToken": "…",
"refreshTokenExpiration": "2026-09-18T10:00:00",
"b2b": false
},
"meta": { "correlationId": "…", "durationMs": 42, "dossiers": null }
}
Utiliser le jeton¶
Le jeton se place dans l'en-tête Authorization de chaque appel :
GET /v2/tiersbanque?targets=DEMO&limit=50 HTTP/1.1
Host: votre-connecteur
Authorization: Bearer <jeton>
Accept: application/json
| Point | Valeur |
|---|---|
| Signature | HMAC-SHA512, clé propre à l'installation |
| Durée de vie du jeton | 20 minutes en production, 4 heures en débogage |
| 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 |
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.
Dix-sept 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"]
}
}
| 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 et meta.warnings existent dans le contrat mais ne sont pas encore alimentés.
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 non autorisé est écarté.
Écritures : un seul dossier
Une écriture (POST, PUT, DELETE) doit viser un dossier unique. Plusieurs valeurs dans
targets renvoient l'erreur DOSSIER.AMBIGUOUS (400).
Les lectures de listes (GET /v2/<objet>) renvoient un résultat indexé par dossier et
renseignent meta.dossiers.
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.*,
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 |
|---|---|---|
DOSSIER.NOT_AUTHORIZED |
403 | l'utilisateur n'a pas accès au dossier demandé |
DOSSIER.AMBIGUOUS |
400 | écriture visant plusieurs dossiers |
SAGE.NOT_FOUND |
404 | clé inexistante dans Sage |
SAGE.READONLY |
409 | objet en lecture seule côté interop |
SAGE.COM.UNAVAILABLE |
503 | écritures v2 désactivées sur le connecteur |
VALIDATION.MODEL |
400 | corps vide ou invalide |
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"})
Où aller ensuite¶
| Besoin | Page |
|---|---|
| Format exact des corps, taxonomie des erreurs | Conventions de payload |
| Indicateurs clés (128 indicateurs, 18 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).GET /v2/serveur/version— version du connecteur.
Questions fréquentes¶
Faut-il utiliser /v2 ou les anciennes routes ?
Les anciennes routes (sans /v2) restent servies et figurent dans le même contrat, mais elles
sont figées et destinées à disparaître. Une intégration neuve part de /v2 : elle bénéficie de
l'enveloppe, des ProblemDetails et de la corrélation.
Pourquoi mes écritures renvoient-elles 503 ?
Les écritures v2 sont derrière un drapeau du connecteur. Tant qu'il n'est pas activé, toute
écriture répond 503 avec le code SAGE.COM.UNAVAILABLE.
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.