Vous devez extraire des factures fournisseurs, alimenter un outil de trésorerie ou vérifier un import ? Avec l'API REST de Sage Intacct, l'outil à connaître en premier est le service Query. Une seule route permet de lire presque n'importe quel objet, en choisissant ses champs, ses filtres, son tri et sa pagination.

Ce guide reprend ce que j'enseignais en atelier chez Sage. Le code complet en C# / .NET 8 est disponible dans mon dépôt public intacct_rest_api.

Prérequis

Il vous faut, côté Sage Intacct :

  • un Client ID et un Client Secret d'application ;
  • un utilisateur Web Services (par exemple webservice@ma-societe) avec les droits nécessaires sur les objets à lire ;
  • l'application autorisée pour votre société (voir avec votre administrateur Sage Intacct).

Toutes les URL ci-dessous sont relatives à la base https://api.intacct.com/ia/api/v1/.

Étape 1 : obtenir un jeton OAuth2

L'API REST utilise OAuth2. Deux flux sont possibles :

  • Client Credentials : l'application s'authentifie elle-même, sans connexion d'un utilisateur. C'est le cas typique d'une interface ou d'un traitement planifié.
  • Authorization Code : un utilisateur se connecte via son navigateur, l'application reçoit un code puis l'échange contre un jeton. C'est le cas d'un outil utilisé par des personnes, chacune avec ses propres droits.

En Client Credentials, on envoie un POST sur oauth2/token avec grant_type=client_credentials, client_id, client_secret et username. La réponse contient un access_token (à envoyer dans l'en-tête Authorization: Bearer …), sa durée de validité (expires_in) et un refresh_token.

Bonne pratique : ne mettez jamais les secrets dans le code. Utilisez un fichier de configuration exclu du dépôt, des variables d'environnement ou un coffre-fort de secrets. Mémorisez la date d'expiration du jeton et renouvelez-le avant qu'il n'expire.

Étape 2 : votre première requête Query

On envoie un POST sur services/core/query avec un corps JSON. Exemple : les factures fournisseurs de janvier dont le montant dépasse 100, triées par montant décroissant.

{
  "object": "accounts-payable/bill",
  "fields": ["id", "billNumber", "vendor.id", "vendor.name", "postingDate", "totalTxnAmount"],
  "filters": [
    { "$gt": { "totalTxnAmount": "100" } },
    { "$between": { "postingDate": ["2026-01-01", "2026-01-31"] } }
  ],
  "filterExpression": "1 and 2",
  "orderBy": [ { "totalTxnAmount": "desc" } ],
  "start": 1,
  "size": 100
}

Décryptage :

  • object : le nom API de l'objet, sous la forme module/objet (accounts-payable/bill, accounts-receivable/invoice, accounts-payable/vendor, general-ledger/account…).
  • fields : les champs à renvoyer. La notation pointée (vendor.name) permet de lire un champ d'un objet lié sans seconde requête.
  • filters : une liste de conditions au format { "$opérateur": { "champ": valeur } }.
  • filterExpression : la combinaison des filtres, référencés par leur position à partir de 1 ("1 and 2", "(1 and 2) or 3").
  • orderBy : une liste de paires { "champ": "asc" | "desc" }.
  • start / size : la pagination (jusqu'à 4 000 enregistrements par page).

Les opérateurs de filtre à connaître

BesoinOpérateurs
Égalité$eq, $ne
Comparaison$lt, $lte, $gt, $gte
Plage$between, $notBetween (valeur = tableau [min, max])
Liste$in, $notIn (valeur = tableau)
Texte$contains, $notContains, $startsWith, $notStartsWith, $endsWith, $notEndsWith

Les dates s'écrivent au format AAAA-MM-JJ. Des paramètres complémentaires (filterParameters) permettent notamment de régler la sensibilité à la casse.

Piège classique : l'ordre des filtres dans la liste détermine leur numéro dans filterExpression. Si vous insérez un filtre au milieu, renumérotez l'expression. Dans mon code, une petite classe utilitaire construit l'expression et vérifie que chaque numéro existe, ce qui évite des requêtes silencieusement fausses.

Étape 3 : lire la réponse et paginer

La réponse contient deux blocs :

  • ia::result : un tableau d'enregistrements, dont les clés sont les noms de champs demandés ;
  • ia::meta : totalCount, start, pageSize, ainsi que next et previous pour la pagination.

Pour tout récupérer, bouclez tant que next est renseigné, en relançant la même requête avec le start suivant. Côté code, désérialiser ia::result en liste de dictionnaires (champ → valeur) permet de traiter n'importe quel objet sans écrire un modèle par objet.

Bonus : le même corps, en fichier

Le service Export (services/core/export) reprend exactement la même requête et renvoie un fichier : PDF, CSV, Word, XML ou XLSX. Le corps devient :

{
  "query": { "object": "accounts-payable/bill", "fields": ["id", "billNumber", "totalTxnAmount"] },
  "fileType": "csv"
}

C'est pratique pour un dépôt de fichier planifié vers un outil tiers : la requête est mise au point une fois, puis réutilisée telle quelle.

Query ou GET sur l'objet ?

L'API expose aussi des routes orientées ressource, comme GET objects/accounts-receivable/invoice (liste) et GET objects/accounts-receivable/invoice/{key} (détail). La liste ne renvoie que des références légères (clé, id, lien). Elle est utile pour tester un jeton ou lire un enregistrement précis, pas pour faire de l'extraction. Pour filtrer, choisir les champs et paginer, utilisez Query.

Cinq conseils tirés du terrain

  1. Ne demandez que les champs utiles : la réponse est plus légère et la requête plus rapide.
  2. Filtrez côté serveur, jamais en rapatriant tout pour filtrer ensuite dans votre code.
  3. Pour l'incrémental, filtrez sur la date de dernière modification de l'enregistrement et triez sur ce même champ.
  4. Journalisez chaque appel (requête, durée, nombre de lignes, erreurs) : c'est indispensable pour le support.
  5. Pour écrire en volume, regardez les modes Batch (plusieurs enregistrements d'un même objet en un appel, avec un mode transactionnel), Bulk (traitement asynchrone avec suivi de statut) et Composite (plusieurs sous-requêtes en un appel). Ils sont tous illustrés dans le dépôt.

Pour aller plus loin

  • Le dépôt intacct_rest_api : une leçon par tag Git (authentification, Query et Export, CRUD, Batch, Bulk, Composite), plus un Worker Cloudflare de démonstration du flux Authorization Code.
  • La documentation officielle Sage Intacct pour la liste des objets et des champs.

Vous avez une interface Sage Intacct à construire ou à fiabiliser ? Parlons-en.