# Developers > Documentation de l'API dafexpert : authentification, entreprises, événements comptables, factures FNE, serveur MCP. ## Prise en main - [Vue d'ensemble de l'API](https://developers.stg.dafexpert.com/article/getting-started.txt): Ce que permet l'API dafexpert, à qui elle s'adresse, les environnements et votre premier appel. - [Choisir son intégration](https://developers.stg.dafexpert.com/article/choose-integration.txt): API partenaires, serveur MCP, SSO : comparer les surfaces d'intégration et choisir celle qui correspond à votre besoin. - [Obtenir ses identifiants](https://developers.stg.dafexpert.com/article/credentials.txt): Comment l'administrateur d'un espace connecte votre application, vous transmet un client_id et un client_secret, renouvelle le secret ou coupe l'accès. - [Authentification OAuth 2.0](https://developers.stg.dafexpert.com/article/authentication.txt): Obtenir un jeton avec OAuth 2.0 client credentials, le mettre en cache, le renouveler et gérer les réponses 401. - [Connexion des utilisateurs (SSO)](https://developers.stg.dafexpert.com/article/sso.txt): Permettre à vos utilisateurs de se connecter une seule fois pour accéder à dafexpert et à votre application, avec OpenID Connect. - [Erreurs et codes de réponse](https://developers.stg.dafexpert.com/article/errors.txt): Les codes HTTP renvoyés par l'API, le format du corps d'erreur, les résultats partiels des lots et quand réessayer. - [Idempotence et rejeu](https://developers.stg.dafexpert.com/article/idempotency.txt): Pourquoi rejouer un appel ne crée jamais de doublon, quelle clé sert pour chaque ressource, et comment se comportent les renvois avec un contenu différent. - [Webhooks](https://developers.stg.dafexpert.com/article/webhooks.txt): Les webhooks ne sont pas encore disponibles : comment suivre aujourd'hui le statut de vos événements par synchronisation. ## Tutoriels - [Cas d'usage pris en charge](https://developers.stg.dafexpert.com/article/use-cases.txt): Les intégrations que l'API dafexpert permet aujourd'hui : gestion de coopératives, applications terrain, facturation et assistants IA. - [Créer ou mettre à jour une entreprise](https://developers.stg.dafexpert.com/article/create-company.txt): Créer une coopérative et l'un de ses exploitants avec PUT /v1/partners/companies/{external_id}, puis les tenir à jour. - [Créer des entreprises en masse](https://developers.stg.dafexpert.com/article/bulk-companies.txt): Reprendre tout un portefeuille de coopératives et d'exploitants avec POST /v1/partners/companies, puis suivre le lot. - [Gérer les utilisateurs](https://developers.stg.dafexpert.com/article/manage-users.txt): Donner à un gérant ou à un comptable l'accès à ses entreprises avec PUT /v1/partners/users/{external_id}, puis le retirer avec DELETE. - [Envoyer des événements comptables](https://developers.stg.dafexpert.com/article/send-events.txt): Un parcours complet : avance de campagne, achat de cacao à un producteur, puis paiement en mobile money avec retenue de l'avance. - [Suivre le traitement des événements](https://developers.stg.dafexpert.com/article/track-events.txt): Lire le statut d'un événement, synchroniser tous les changements avec un curseur et traiter les rejets. - [Émettre une pièce FNE](https://developers.stg.dafexpert.com/article/fne-documents.txt): Émettre un bordereau d'achat de produits agricoles et une facture client certifiés FNE, puis récupérer leur PDF. - [Brancher un assistant IA (MCP)](https://developers.stg.dafexpert.com/article/connect-ai-assistant.txt): Ajouter le serveur MCP de dafexpert dans Claude pour interroger ses dossiers en langage naturel, en lecture seule. ## Concepts et bonnes pratiques - [Espaces et entreprises](https://developers.stg.dafexpert.com/article/workspaces-companies.txt): Comment dafexpert organise vos clients : un espace par organisation, une entreprise par comptabilité, et le lien avec vos identifiants externes. - [Référentiels comptables](https://developers.stg.dafexpert.com/article/accounting-frameworks.txt): SYSCOHADA, SYCEBNL et PCG : comment le pays d'une entreprise fixe son référentiel, sa devise et les comptes utilisés pour vos événements. - [Catalogue des événements](https://developers.stg.dafexpert.com/article/event-catalog.txt): Tous les types d'événements de la version 2 : à quoi ils servent, leurs champs clés, leurs contrôles et ce qu'ils créent dans dafexpert. - [Événements v1 et v2](https://developers.stg.dafexpert.com/article/event-versions.txt): Les deux versions du contrat d'événements, les versions successives d'un même événement, les effets par entreprise et les écarts. - [Montants et devises](https://developers.stg.dafexpert.com/article/amounts-currencies.txt): Le format attendu pour les montants (FCFA entiers, sans décimales), la devise de l'entreprise, les arrondis et les contrôles de total. - [Limites des champs](https://developers.stg.dafexpert.com/article/field-limits.txt): Longueurs maximales, formats et tailles de listes acceptés par l'API : identifiants, dates, NCC, téléphones, e-mails, lots. ## Guides avancés - [Limites de débit](https://developers.stg.dafexpert.com/article/rate-limiting.txt): Les limites de volume de l'API aujourd'hui, les règles d'usage raisonnable et comment se préparer aux limites à venir. - [Versionnement](https://developers.stg.dafexpert.com/article/versioning.txt): Comment l'API évolue sans casser votre intégration : version dans l'URL, champ version des corps, contrat v2 des événements et règles de compatibilité. ## Migration et notes de version - [Migrer vers les événements v2](https://developers.stg.dafexpert.com/article/migrate-events-v2.txt): Passer du contrat v1 des événements (correspondances paramétrées) au contrat v2 (catalogue de types, pièces de gestion, effets par entreprise, versions). - [Feuille de route](https://developers.stg.dafexpert.com/article/roadmap.txt): Les évolutions prévues de l'API dafexpert. ## Ressources - [Glossaire](https://developers.stg.dafexpert.com/article/glossary.txt): Les termes de dafexpert, de la comptabilité OHADA et de la facturation électronique ivoirienne utilisés dans cette documentation. - [Besoin d'aide ?](https://developers.stg.dafexpert.com/article/support.txt): Comment contacter l'équipe dafexpert et quelles informations joindre pour obtenir une réponse rapide. ## Introduction - [Introduction](https://developers.stg.dafexpert.com/article/api-introduction.txt): URL de base, authentification, format des requêtes, des identifiants, des dates, des montants et des erreurs. ## Partenaire - [Vérifier ses identifiants](https://developers.stg.dafexpert.com/article/get-partner-me.txt): Vérifie de bout en bout qu'un jeton est valide et que l'accès partenaire est actif. ## Entreprises - [Créer ou mettre à jour une entreprise](https://developers.stg.dafexpert.com/article/put-company.txt): Crée une coopérative, une union, un acheteur ou un exploitant avec son premier exercice, ou met à jour son identité. - [Créer des entreprises en masse](https://developers.stg.dafexpert.com/article/post-companies.txt): Envoie jusqu'à 500 entreprises en un appel ; elles sont créées ou mises à jour en arrière-plan. - [Suivre un lot d'entreprises](https://developers.stg.dafexpert.com/article/get-company-lot.txt): Avancement d'un lot de création en masse : compteurs par statut et résultat de chaque entreprise. ## Utilisateurs - [Créer ou mettre à jour un utilisateur](https://developers.stg.dafexpert.com/article/put-user.txt): Crée ou rattache le compte de connexion d'un utilisateur et fixe la liste de ses accès aux entreprises. - [Retirer un utilisateur](https://developers.stg.dafexpert.com/article/delete-user.txt): Retire à un utilisateur tous ses accès aux entreprises de votre espace, sans supprimer son compte. ## Événements - [Envoyer des événements](https://developers.stg.dafexpert.com/article/post-events.txt): Envoie un événement ou un lot de 500 événements à comptabiliser ; ils sont traités en arrière-plan. - [Consulter un événement](https://developers.stg.dafexpert.com/article/get-event.txt): Statut de comptabilisation d'un événement, avec ses pièces et écritures par entreprise. - [Synchroniser les événements](https://developers.stg.dafexpert.com/article/list-events.txt): Liste les événements modifiés depuis un curseur, du plus ancien au plus récent, pour suivre tous les changements de statut. ## Pièces fiscales (FNE) - [Émettre un bordereau d'achat](https://developers.stg.dafexpert.com/article/post-bordereau.txt): Émet un bordereau d'achat de produits agricoles, le certifie FNE, produit son PDF et le comptabilise, en un seul appel. - [Consulter un bordereau d'achat](https://developers.stg.dafexpert.com/article/get-bordereau.txt): État actuel d'un bordereau d'achat : certification FNE, numéro fiscal, comptabilisation et vente miroir. - [Télécharger le PDF d'un bordereau](https://developers.stg.dafexpert.com/article/get-bordereau-pdf.txt): Donne un lien de téléchargement temporaire (5 minutes) du PDF d'un bordereau d'achat. - [Émettre une facture](https://developers.stg.dafexpert.com/article/post-invoice.txt): Émet une facture client numérotée par dafexpert, la certifie FNE et la comptabilise, en un seul appel. - [Consulter une facture](https://developers.stg.dafexpert.com/article/get-invoice.txt): État actuel d'une facture : numéro, certification FNE et comptabilisation. - [Télécharger le PDF d'une facture](https://developers.stg.dafexpert.com/article/get-invoice-pdf.txt): Donne un lien de téléchargement temporaire (5 minutes) du PDF d'une facture. ## Serveur MCP - [Serveur MCP](https://developers.stg.dafexpert.com/article/mcp-overview.txt): Connecter un assistant IA (Claude, etc.) aux dossiers dafexpert d'un utilisateur, en lecture seule. - [Lister mes dossiers](https://developers.stg.dafexpert.com/article/mcp-list-companies.txt): Les dossiers (entreprises) auxquels l'utilisateur a accès, avec devise, pays, rôle et solde. - [Lister les exercices](https://developers.stg.dafexpert.com/article/mcp-list-exercises.txt): Les exercices comptables d'un dossier : dates, statut et référentiel. - [Balance générale](https://developers.stg.dafexpert.com/article/mcp-get-balance.txt): La balance générale d'un exercice, par compte, avec le comparatif N-1. - [Anomalies de la balance](https://developers.stg.dafexpert.com/article/mcp-get-balance-anomalies.txt): Les contrôles de révision sur la balance d'un exercice, avec une conclusion. - [Compte de résultat](https://developers.stg.dafexpert.com/article/mcp-get-income-statement.txt): Le compte de résultat d'un exercice, par postes du référentiel du dossier. - [Lister les factures clients](https://developers.stg.dafexpert.com/article/mcp-list-invoices.txt): Les factures clients d'un dossier, avec un filtre facultatif par statut. - [Détail d'une facture](https://developers.stg.dafexpert.com/article/mcp-get-invoice.txt): Le détail d'une facture client : lignes, totaux, client, paiements et transactions liées. - [Trésorerie](https://developers.stg.dafexpert.com/article/mcp-get-treasury-overview.txt): La vue trésorerie d'un dossier : solde, flux, échéances à venir et dernières transactions. ## Nouveautés - [Pièces fiscales FNE par API](https://developers.stg.dafexpert.com/article/changelog-2026-10-12-fne-documents.txt): Deux routes synchrones émettent des bordereaux d'achat et des factures certifiés FNE, avec numéro fiscal, QR code et PDF. - [Événements version 2](https://developers.stg.dafexpert.com/article/changelog-2026-10-12-events-v2.txt): Les événements en `"version": 2` gagnent un catalogue de types, des versions, des effets par entreprise et la détection des doublons probables. - [Fiche entreprise complète](https://developers.stg.dafexpert.com/article/changelog-2026-10-12-company-fields.txt): `PUT /v1/partners/companies/{external_id}` accepte de nouveaux types d'entreprise et les champs fiscaux, de localisation et d'exploitant. - [Création des entreprises en masse](https://developers.stg.dafexpert.com/article/changelog-2026-10-12-bulk-companies.txt): `POST /v1/partners/companies` reprend jusqu'à 500 entreprises par appel, traitées en arrière-plan avec un suivi par lot. - [Gestion des utilisateurs](https://developers.stg.dafexpert.com/article/changelog-2026-10-09-users.txt): `PUT /v1/partners/users/{id}` crée ou rattache un compte dafexpert et lui donne accès à des entreprises. - [Connexion unique (SSO)](https://developers.stg.dafexpert.com/article/changelog-2026-10-08-sso.txt): Vos utilisateurs se connectent une seule fois pour accéder à dafexpert et à votre application. - [Comptabilisation automatique des événements](https://developers.stg.dafexpert.com/article/changelog-2026-10-08-event-accounting.txt): Les événements reçus sont traités en arrière-plan et deviennent des écritures, selon des correspondances paramétrables. - [Envoi et suivi des événements](https://developers.stg.dafexpert.com/article/changelog-2026-10-08-events.txt): `POST /v1/partners/events` reçoit les événements métier, un par un ou par lots de 500, et `GET /v1/partners/events` les synchronise. - [Création des entreprises](https://developers.stg.dafexpert.com/article/changelog-2026-10-08-companies.txt): `PUT /v1/partners/companies/{external_id}` crée ou met à jour une coopérative ou un exploitant, avec son premier exercice. - [Lancement de l'API partenaires](https://developers.stg.dafexpert.com/article/changelog-2026-10-08-partner-api.txt): Authentification OAuth 2.0 client credentials par espace et route `GET /v1/partners/me` pour vérifier ses identifiants. - [Lancement du serveur MCP](https://developers.stg.dafexpert.com/article/changelog-2026-10-06-mcp-server.txt): Les clients dafexpert peuvent connecter Claude ou un autre assistant IA à leurs dossiers, en lecture seule. --- # Vue d'ensemble de l'API URL: https://developers.stg.dafexpert.com/article/getting-started > Ce que permet l'API dafexpert, à qui elle s'adresse, les environnements et votre premier appel. dafexpert est la plateforme de comptabilité et de gestion des PME et des coopératives de la zone OHADA et de France. L'**API dafexpert** permet à votre logiciel d'alimenter directement la comptabilité de vos clients : créer leurs entreprises, donner accès à leurs utilisateurs, envoyer les opérations à comptabiliser et émettre des pièces fiscales certifiées FNE. Votre application reste le logiciel métier (pesées, achats, ventes, paie, intrants…). dafexpert tient la comptabilité officielle (SYSCOHADA, SYCEBNL ou PCG selon le pays) et produit les pièces légales. ## À qui s'adresse cette documentation - **Éditeurs de logiciels métier** (gestion de coopératives, applications terrain, ERP sectoriels) qui veulent pousser leurs données vers dafexpert. - **Intégrateurs** qui raccordent le système d'information d'une fédération ou d'un groupe à dafexpert. - **Utilisateurs de dafexpert** qui veulent interroger leurs dossiers depuis un assistant IA (Claude, etc.) grâce au serveur MCP. ## Ce que permet l'API | Domaine | Routes | Usage | |---|---|---| | Partenaire | `GET /v1/partners/me` | Vérifier vos identifiants et l'espace auquel ils donnent accès. | | Entreprises | `PUT /v1/partners/companies/{external_id}`, `POST /v1/partners/companies` | Créer ou mettre à jour une coopérative, un exploitant, une PME, une par une ou en masse. | | Utilisateurs | `PUT` et `DELETE /v1/partners/users/{id}` | Créer un compte de connexion et donner ou retirer l'accès aux entreprises. | | Événements | `POST /v1/partners/events`, `GET /v1/partners/events` | Envoyer les opérations à comptabiliser (paiements, dépenses, paie…) et suivre leur statut. | | Pièces fiscales | `POST /v1/partners/companies/{external_id}/bordereaux` et `/invoices` | Émettre un bordereau d'achat ou une facture certifiée FNE, puis récupérer son PDF. | Le détail de chaque route est dans la [Référence API](/article/api-introduction). ## Environnements | Environnement | URL de l'API | URL du jeton (OAuth 2.0) | |---|---|---| | Staging | `https://api.stg.dafexpert.com/v1` | `https://auth.stg.dafexpert.com/realms/dafexpert/protocol/openid-connect/token` | | Production | communiquée à la mise en production | communiquée à la mise en production | > **info** Développez et testez toute votre intégration en **staging**. Les identifiants de staging et de production sont distincts : un `client_id` de staging ne fonctionne pas en production, et inversement. Toutes les routes de l'API partenaires sont préfixées par `/v1`. Les corps de requête et de réponse sont en JSON (`Content-Type: application/json`), les dates au format `AAAA-MM-JJ`. ## Les surfaces d'intégration ### API partenaires Appels de **serveur à serveur**, sans utilisateur, authentifiés par OAuth 2.0 *client credentials*. C'est la surface principale : entreprises, utilisateurs, événements, pièces fiscales. Vos identifiants sont liés à **un espace** dafexpert (par exemple une fédération de coopératives). ### Serveur MCP Un serveur [Model Context Protocol](/article/mcp-overview) permet à un assistant IA de lire les dossiers d'un utilisateur dafexpert : entreprises, exercices, balance, compte de résultat, factures, trésorerie. Il agit **au nom de l'utilisateur connecté** et ne fait que de la lecture. Adresse en staging : `https://api.stg.dafexpert.com/mcp`. ### Connexion unique (SSO) Vos utilisateurs se connectent **une seule fois** pour accéder à dafexpert et à votre application, grâce à OpenID Connect. Voir [Connexion des utilisateurs (SSO)](/article/sso). > **info** **Bientôt** : une API publique pour les entreprises clientes de dafexpert (accès à leurs propres données, sans passer par un partenaire). Voir [Choisir son intégration](/article/choose-integration). ## Principes à connaître - **Un espace par jeu d'identifiants.** L'espace est déduit de vos identifiants, jamais lu dans la requête. Un identifiant ne peut agir que dans son espace. - **Vos identifiants, pas les nôtres.** Chaque entreprise, utilisateur, événement ou pièce est désigné par **votre** identifiant (`external_id`, `id`). Vous n'avez pas à stocker les identifiants dafexpert. - **Idempotence.** Rejouer un appel ne crée jamais de doublon. Voir [Idempotence et rejeu](/article/idempotency). - **Contrat en français.** Les noms de champs (`nom`, `pays`, `statut`…) et les messages de validation sont en français. - **Versionnement explicite.** Chaque corps porte un champ `version`. Voir [Versionnement](/article/versioning). ## Votre premier appel en 2 minutes **1. Obtenir vos identifiants** L'administrateur de l'espace connecte votre application et vous transmet un `client_id` et un `client_secret`. Voir [Obtenir ses identifiants](/article/credentials). **2. Demander un jeton** Échangez vos identifiants contre un jeton d'accès valable 5 minutes. **3. Appeler `GET /v1/partners/me`** Cette route confirme que le jeton est valide et que la connexion à l'espace est active. **cURL** ```bash # 1. Jeton TOKEN=$(curl -s -X POST "https://auth.stg.dafexpert.com/realms/dafexpert/protocol/openid-connect/token" \ -d grant_type=client_credentials \ -d client_id=partner-acme-coop \ -d client_secret="$DAFEXPERT_CLIENT_SECRET" | jq -r .access_token) # 2. Premier appel curl https://api.stg.dafexpert.com/v1/partners/me \ -H "Authorization: Bearer $TOKEN" ``` **Réponse** ```json { "partner": "acme", "workspaceId": "3f6b2c1e-8a4d-4f1a-9b7e-2d5c8e9f0a12", "apiVersion": 1 } ``` ## Étapes suivantes - [Choisir son intégration](/article/choose-integration) : API partenaires, MCP ou SSO : quelle surface pour quel besoin. - [Authentification](/article/authentication) : OAuth 2.0 client credentials, renouvellement du jeton, gestion des 401. - [Créer une entreprise](/article/create-company) : Le premier appel métier : une coopérative et son premier exercice. - [Envoyer des événements](/article/send-events) : Paiements, dépenses, paie : les opérations à comptabiliser. - [Erreurs et codes de réponse](/article/errors) : Format des erreurs et quand réessayer. - [Référence API](/article/api-introduction) : Toutes les routes, leurs paramètres et leurs réponses. --- # Choisir son intégration URL: https://developers.stg.dafexpert.com/article/choose-integration > API partenaires, serveur MCP, SSO : comparer les surfaces d'intégration et choisir celle qui correspond à votre besoin. dafexpert propose plusieurs façons de se raccorder. Elles ne s'excluent pas : un éditeur de logiciel métier utilise en général **l'API partenaires** pour les données et **le SSO** pour ses utilisateurs. Cette page vous aide à choisir. ## Comparatif | | API partenaires | Serveur MCP | SSO (OpenID Connect) | API publique des entreprises | |---|---|---|---|---| | Pour qui | Éditeurs et intégrateurs | Utilisateurs de dafexpert et leurs assistants IA | Éditeurs dont les utilisateurs utilisent aussi dafexpert | Entreprises clientes de dafexpert | | Qui agit | Votre serveur, sans utilisateur | L'utilisateur connecté | L'utilisateur connecté | Bientôt | | Authentification | OAuth 2.0 client credentials | OAuth 2.0 Authorization Code + PKCE | OAuth 2.0 Authorization Code (PKCE facultatif) | Bientôt | | Périmètre | Un espace : ses entreprises, utilisateurs, événements, pièces fiscales | Les dossiers auxquels l'utilisateur a accès | Identité de l'utilisateur (`openid email profile`) | Bientôt | | Lecture / écriture | Écriture et suivi | Lecture seule | Connexion uniquement | Bientôt | | Adresse (staging) | `https://api.stg.dafexpert.com/v1/partners/...` | `https://api.stg.dafexpert.com/mcp` | `https://auth.stg.dafexpert.com/realms/dafexpert` | Bientôt | | État | Disponible | Disponible | Disponible | Bientôt | ## API partenaires L'API de **serveur à serveur**. Votre application crée les entreprises de vos clients dans dafexpert, leur donne des utilisateurs, envoie les opérations à comptabiliser et émet les pièces fiscales certifiées FNE. ### Choisissez-la si - vous éditez un logiciel métier (coopératives, collecte agricole, ERP sectoriel) et vos clients veulent une comptabilité officielle sans double saisie ; - vous devez créer des entreprises en masse, par exemple toutes les coopératives d'une fédération ; - vous devez émettre des bordereaux d'achat ou des factures certifiés FNE (Côte d'Ivoire) depuis votre application. Pour démarrer : [Obtenir ses identifiants](/article/credentials) puis [Authentification OAuth 2.0](/article/authentication). Les cas d'usage types sont décrits dans [Cas d'usage](/article/use-cases). ## Serveur MCP Le serveur MCP expose les dossiers d'un utilisateur dafexpert aux assistants IA compatibles avec le [Model Context Protocol](/article/mcp-overview) : Claude (connecteur personnalisé, Claude Desktop, Claude Code) et les autres clients MCP. L'assistant voit exactement ce que l'utilisateur voit dans dafexpert, en lecture seule. ### Choisissez-le si - vous voulez interroger votre comptabilité en langage naturel (« Quel est mon résultat à fin septembre ? ») ; - vous construisez un agent qui a besoin de la balance, du compte de résultat, des factures ou de la trésorerie d'un dossier ; - aucune écriture n'est nécessaire. Pour démarrer : [Connecter un assistant IA](/article/connect-ai-assistant). ## Connexion unique (SSO) Vos utilisateurs se connectent une seule fois pour accéder à dafexpert et à votre application. Après la connexion, un utilisateur qui a accès aux deux choisit l'application à ouvrir et peut passer de l'une à l'autre. ### Choisissez-la si - vos utilisateurs (gérants de coopérative, comptables) travaillent à la fois dans votre application et dans dafexpert ; - vous créez leurs comptes par l'API partenaires (`PUT /v1/partners/users/{id}`) et voulez leur éviter un second mot de passe. Pour démarrer : [Connexion des utilisateurs (SSO)](/article/sso). ## API publique des entreprises > **info** **Bientôt.** Une API destinée aux entreprises clientes de dafexpert, pour accéder à leurs propres données sans passer par un partenaire. Elle n'est pas encore disponible. En attendant, le [serveur MCP](/article/mcp-overview) donne un accès en lecture aux dossiers d'un utilisateur. ## Combinaisons fréquentes | Votre situation | Surfaces recommandées | |---|---| | Logiciel de gestion de coopératives, avec des utilisateurs qui consultent aussi leur comptabilité | API partenaires + SSO | | Reprise ponctuelle des entreprises d'une fédération | API partenaires (création en masse) | | Assistant IA pour un cabinet ou un dirigeant | Serveur MCP | | Application terrain qui émet des bordereaux d'achat certifiés | API partenaires (pièces fiscales) + suivi des événements | ## Étapes suivantes - [Obtenir ses identifiants](/article/credentials) : Connexion de votre application à un espace. - [Connexion des utilisateurs](/article/sso) : Mettre en place le SSO. - [Serveur MCP](/article/mcp-overview) : Les outils exposés aux assistants IA. --- # Obtenir ses identifiants URL: https://developers.stg.dafexpert.com/article/credentials > Comment l'administrateur d'un espace connecte votre application, vous transmet un client_id et un client_secret, renouvelle le secret ou coupe l'accès. L'API partenaires utilise un `client_id` et un `client_secret` **par espace**. Ce n'est pas vous qui les créez : c'est l'**administrateur de l'espace** dafexpert de votre client qui connecte votre application, puis vous les transmet. Ce fonctionnement garantit que votre client garde la main sur l'accès à ses données. ## Avant de commencer - Votre application doit être **référencée** par dafexpert et rendue visible pour l'espace concerné. Écrivez à [support@dafexpert.com](mailto:support@dafexpert.com) pour devenir partenaire. - Votre interlocuteur chez le client doit être **propriétaire ou administrateur** de l'espace. Les autres membres ne voient pas l'écran de connexion des applications. ## Connexion de votre application à l'espace **1. Ouvrir les applications connectées** Dans dafexpert, l'administrateur de l'espace ouvre *Paramètres › Applications connectées*. Votre application y figure. **2. Cliquer sur « Connecter »** dafexpert crée vos identifiants techniques pour cet espace. **3. Copier les identifiants** Le `client_id` et le `client_secret` sont affichés **une seule fois**. L'administrateur les copie immédiatement. **4. Vous les transmettre par un canal sûr** Gestionnaire de mots de passe partagé, coffre de secrets, message chiffré. Jamais par e-mail en clair ni dans un ticket. **5. Vérifier les identifiants** Demandez un jeton puis appelez `GET /v1/partners/me` (voir ci-dessous). > **warning** Le `client_secret` n'est **plus jamais affiché** après la connexion. S'il est perdu, l'administrateur doit en générer un nouveau. ## Ce que vous recevez | Élément | Exemple | Remarque | |---|---|---| | `client_id` | `partner-acme-coop` | Propre au couple « votre application × cet espace ». Il n'est pas secret, mais ne change jamais. | | `client_secret` | `••••••••` | Secret. À conserver dans un coffre, jamais dans le code ni dans une application mobile. | | URL du jeton | `https://auth.stg.dafexpert.com/realms/dafexpert/protocol/openid-connect/token` | Staging. Celle de production est communiquée à la mise en production. | | URL de l'API | `https://api.stg.dafexpert.com/v1` | Staging. | ### Plusieurs espaces, plusieurs jeux d'identifiants Si votre application sert plusieurs clients (par exemple plusieurs fédérations), chacun connecte votre application à **son** espace et vous recevez **un jeu d'identifiants par espace**. Stockez-les par client, et utilisez toujours le bon jeu : l'espace est déduit des identifiants, il n'existe aucun paramètre pour le choisir dans la requête. ## Vérifier ses identifiants **cURL** ```bash TOKEN=$(curl -s -X POST "https://auth.stg.dafexpert.com/realms/dafexpert/protocol/openid-connect/token" \ -d grant_type=client_credentials \ -d client_id=partner-acme-coop \ -d client_secret="$DAFEXPERT_CLIENT_SECRET" | jq -r .access_token) curl https://api.stg.dafexpert.com/v1/partners/me -H "Authorization: Bearer $TOKEN" ``` **200 OK** ```json { "partner": "acme", "workspaceId": "3f6b2c1e-8a4d-4f1a-9b7e-2d5c8e9f0a12", "apiVersion": 1 } ``` Un `200` confirme à la fois que le jeton est valide et que la connexion à l'espace est active. Un `401` indique des identifiants erronés, ou une application déconnectée de l'espace. ## Générer un nouveau secret Dans *Paramètres › Applications connectées*, l'administrateur peut **générer un nouveau secret**, par exemple après le départ d'un collaborateur ou une fuite suspectée. Le nouveau secret est affiché une seule fois, comme le premier. Le `client_id` ne change pas. > **warning** **L'ancien secret cesse immédiatement de fonctionner.** Les demandes de jeton avec l'ancien secret échouent dès la génération du nouveau. Convenez d'un créneau avec l'administrateur et mettez à jour votre configuration aussitôt. Une période de grâce pendant laquelle les deux secrets restent valides est prévue, mais pas encore disponible. Les jetons déjà émis restent valables jusqu'à leur expiration (5 minutes au plus). Prévoyez donc dans votre code de recharger le secret depuis votre coffre sans redémarrer le service. ## Déconnecter l'application L'administrateur peut aussi **déconnecter** votre application. L'accès est coupé : les appels suivants reçoivent un `401`, y compris avec un jeton émis avant la déconnexion (au plus tard 30 secondes après). Côté intégration, un `401` qui persiste après avoir demandé un jeton neuf signifie que l'accès a été révoqué. Arrêtez les envois, gardez les données en file et prévenez l'administrateur de l'espace. Voir [Authentification](/article/authentication). L'administrateur voit aussi dans cet écran la **date de votre dernier appel**, ce qui l'aide à repérer une intégration arrêtée. ## Bonnes pratiques de sécurité - Gardez le `client_secret` **côté serveur** uniquement : coffre de secrets, variable d'environnement chiffrée. Jamais dans une application mobile, un navigateur ou un dépôt Git. - N'écrivez ni le secret ni les jetons dans vos journaux. - Utilisez des identifiants **distincts** pour le staging et la production. - Demandez un nouveau secret dès qu'une fuite est possible. ## Étapes suivantes - [Authentification OAuth 2.0](/article/authentication) : Demander, mettre en cache et renouveler le jeton. - [GET /v1/partners/me](/article/get-partner-me) : La route de vérification des identifiants. --- # Authentification OAuth 2.0 URL: https://developers.stg.dafexpert.com/article/authentication > Obtenir un jeton avec OAuth 2.0 client credentials, le mettre en cache, le renouveler et gérer les réponses 401. L'API partenaires utilise le flux OAuth 2.0 **client credentials** : le mécanisme standard d'authentification d'un serveur auprès d'un autre, sans utilisateur. Votre serveur échange son `client_id` et son `client_secret` contre un **jeton d'accès de courte durée** (5 minutes), qu'il joint ensuite à chaque appel. > **info** Vous n'avez pas encore d'identifiants ? Voir [Obtenir ses identifiants](/article/credentials). ## Le flux en bref 1. Votre serveur envoie `client_id` et `client_secret` à l'URL du jeton. 1. Il reçoit un `access_token` valable `expires_in` secondes (300). 1. Il appelle l'API avec l'en-tête `Authorization: Bearer `. 1. Peu avant l'expiration, il demande un nouveau jeton avec les mêmes identifiants. ## URL du jeton | Environnement | URL du jeton | URL de l'API | |---|---|---| | Staging | `https://auth.stg.dafexpert.com/realms/dafexpert/protocol/openid-connect/token` | `https://api.stg.dafexpert.com/v1` | | Production | communiquée à la mise en production | communiquée à la mise en production | ## Demander un jeton `POST` sur l'URL du jeton, avec un corps encodé en `application/x-www-form-urlencoded` (et non en JSON). - `grant_type` (string, body, required) : Toujours `client_credentials`. - `client_id` (string, body, required) : Votre identifiant pour cet espace, par exemple `partner-acme-coop`. - `client_secret` (string, body, required) : Le secret transmis par l'administrateur de l'espace. ### Réponse - `access_token` (string) : Le jeton (JWT) à placer dans l'en-tête `Authorization`. Traitez-le comme une valeur opaque : ne décodez pas son contenu pour en tirer des décisions. - `expires_in` (integer) : Durée de validité en secondes : `300` (5 minutes). - `token_type` (string) : `Bearer`. La réponse peut contenir d'autres champs standards du fournisseur d'identité : ignorez-les. **cURL** ```bash curl -X POST "https://auth.stg.dafexpert.com/realms/dafexpert/protocol/openid-connect/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d grant_type=client_credentials \ -d client_id=partner-acme-coop \ -d client_secret="$DAFEXPERT_CLIENT_SECRET" ``` **Node.js** ```javascript const res = await fetch(process.env.DAFEXPERT_TOKEN_URL, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'client_credentials', client_id: process.env.DAFEXPERT_CLIENT_ID, client_secret: process.env.DAFEXPERT_CLIENT_SECRET, }), }); if (!res.ok) throw new Error('Jeton refusé : ' + res.status); const { access_token, expires_in } = await res.json(); ``` **Python** ```python import os, requests r = requests.post( os.environ["DAFEXPERT_TOKEN_URL"], data={ "grant_type": "client_credentials", "client_id": os.environ["DAFEXPERT_CLIENT_ID"], "client_secret": os.environ["DAFEXPERT_CLIENT_SECRET"], }, timeout=10, ) r.raise_for_status() token = r.json()["access_token"] ``` **PHP** ```php true, CURLOPT_RETURNTRANSFER => true, CURLOPT_POSTFIELDS => http_build_query([ 'grant_type' => 'client_credentials', 'client_id' => getenv('DAFEXPERT_CLIENT_ID'), 'client_secret' => getenv('DAFEXPERT_CLIENT_SECRET'), ]), ]); $body = json_decode(curl_exec($ch), true); $token = $body['access_token']; ``` **Réponse** ```json { "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ...", "expires_in": 300, "token_type": "Bearer" } ``` Si les identifiants sont faux, ou si l'application a été déconnectée de l'espace, la demande de jeton échoue avec une erreur OAuth standard (en général `invalid_client`) : vérifiez les identifiants avec l'administrateur de l'espace avant de réessayer. ## Appeler l'API avec le jeton **cURL** ```bash curl https://api.stg.dafexpert.com/v1/partners/me \ -H "Authorization: Bearer $TOKEN" ``` **Node.js** ```javascript const me = await fetch('https://api.stg.dafexpert.com/v1/partners/me', { headers: { Authorization: 'Bearer ' + accessToken }, }).then((r) => r.json()); ``` **Python** ```python me = requests.get( "https://api.stg.dafexpert.com/v1/partners/me", headers={"Authorization": f"Bearer {token}"}, timeout=10, ).json() ``` **PHP** ```php true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token], ]); $me = json_decode(curl_exec($ch), true); ``` ## Renouveler le jeton Il n'y a **pas de jeton de renouvellement** (*refresh token*) : quand le jeton expire, demandez-en simplement un nouveau avec les mêmes identifiants. - **Gardez le jeton en mémoire** et réutilisez-le pour tous les appels pendant sa durée de vie. Ne demandez pas un jeton par appel. - **Renouvelez-le environ 30 secondes avant** `expires_in`, pour ne jamais envoyer un jeton qui expire en route. - **Un jeton par espace** : si vous servez plusieurs espaces, gardez un cache par `client_id`. - Avec plusieurs processus ou fils d'exécution, évitez que chacun demande son propre jeton au même instant : partagez le cache ou protégez le renouvellement par un verrou. > **tips** Les bibliothèques OAuth 2.0 courantes gèrent tout cela nativement : `openid-client` (Node.js), `requests-oauthlib` ou `authlib` (Python), `league/oauth2-client` (PHP), Spring Security (Java). ## Gérer les 401 Un `401` a deux causes possibles : le jeton a expiré, ou l'accès a été révoqué (application déconnectée de l'espace). La conduite à tenir : 1. Sur un `401`, oubliez le jeton en cache, demandez-en un **nouveau une seule fois** et rejouez la requête. C'est sans risque : toutes les écritures de l'API sont [idempotentes](/article/idempotency). 1. Si le `401` revient avec un jeton neuf, l'accès a été **révoqué**. Arrêtez les envois, gardez les données en file d'attente et prévenez l'administrateur de l'espace. > **warning** Ne bouclez jamais sur les `401` : des demandes de jeton répétées n'y changeront rien si l'application a été déconnectée. ### Exemple complet : cache et nouvel essai **Node.js** ```javascript const TOKEN_URL = process.env.DAFEXPERT_TOKEN_URL; const API_URL = 'https://api.stg.dafexpert.com/v1'; let cached = null; async function getToken() { if (cached && Date.now() < cached.expiresAt - 30_000) return cached.token; const res = await fetch(TOKEN_URL, { method: 'POST', body: new URLSearchParams({ grant_type: 'client_credentials', client_id: process.env.DAFEXPERT_CLIENT_ID, client_secret: process.env.DAFEXPERT_CLIENT_SECRET, }), }); if (!res.ok) throw new Error('Jeton refusé : ' + res.status); const body = await res.json(); cached = { token: body.access_token, expiresAt: Date.now() + body.expires_in * 1000 }; return cached.token; } async function callApi(method, path, body) { const send = async (token) => fetch(API_URL + path, { method, headers: { Authorization: 'Bearer ' + token, 'Content-Type': 'application/json' }, body: body ? JSON.stringify(body) : undefined, }); let res = await send(await getToken()); if (res.status === 401) { cached = null; res = await send(await getToken()); if (res.status === 401) { throw new Error("Accès révoqué : contacter l'administrateur de l'espace"); } } return res; } ``` **Python** ```python import os, time, requests TOKEN_URL = os.environ["DAFEXPERT_TOKEN_URL"] API_URL = "https://api.stg.dafexpert.com/v1" _cache = {"token": None, "expires_at": 0.0} def get_token(): if _cache["token"] and time.time() < _cache["expires_at"] - 30: return _cache["token"] r = requests.post(TOKEN_URL, data={ "grant_type": "client_credentials", "client_id": os.environ["DAFEXPERT_CLIENT_ID"], "client_secret": os.environ["DAFEXPERT_CLIENT_SECRET"], }, timeout=10) r.raise_for_status() body = r.json() _cache.update(token=body["access_token"], expires_at=time.time() + body["expires_in"]) return _cache["token"] def call_api(method, path, json=None): def send(token): return requests.request(method, API_URL + path, json=json, timeout=30, headers={"Authorization": f"Bearer {token}"}) res = send(get_token()) if res.status_code == 401: _cache["token"] = None res = send(get_token()) if res.status_code == 401: raise RuntimeError("Accès révoqué : contacter l'administrateur de l'espace") return res ``` **PHP** ```php token && time() < $this->expiresAt - 30) { return $this->token; } $ch = curl_init($this->tokenUrl); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_POSTFIELDS => http_build_query([ 'grant_type' => 'client_credentials', 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, ]), ]); $body = json_decode(curl_exec($ch), true); $this->token = $body['access_token']; $this->expiresAt = time() + $body['expires_in']; return $this->token; } private function send(string $method, string $path, ?array $json, string $token): array { $ch = curl_init($this->apiUrl . $path); curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => $method, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, 'Content-Type: application/json'], CURLOPT_POSTFIELDS => $json === null ? null : json_encode($json), ]); $body = curl_exec($ch); return [curl_getinfo($ch, CURLINFO_RESPONSE_CODE), json_decode($body, true)]; } public function call(string $method, string $path, ?array $json = null): array { [$status, $body] = $this->send($method, $path, $json, $this->token()); if ($status === 401) { $this->token = null; [$status, $body] = $this->send($method, $path, $json, $this->token()); if ($status === 401) { throw new RuntimeException("Accès révoqué : contacter l'administrateur de l'espace"); } } return [$status, $body]; } } ``` ## Ce que le jeton permet, et ce qu'il ne permet pas - Le jeton donne accès aux routes `/v1/partners/*`, **dans l'espace lié à vos identifiants** uniquement. - Il est refusé sur les routes réservées aux utilisateurs de dafexpert. À l'inverse, un jeton d'utilisateur est refusé sur les routes partenaires. - Une révocation par l'administrateur de l'espace prend effet au plus tard 30 secondes après, même pour un jeton encore valide. ## Étapes suivantes - [Erreurs et codes de réponse](/article/errors) : Le format des erreurs et quand réessayer. - [Idempotence et rejeu](/article/idempotency) : Pourquoi rejouer une requête est sans risque. - [Créer une entreprise](/article/create-company) : Votre premier appel métier. --- # Connexion des utilisateurs (SSO) URL: https://developers.stg.dafexpert.com/article/sso > Permettre à vos utilisateurs de se connecter une seule fois pour accéder à dafexpert et à votre application, avec OpenID Connect. Avec la connexion unique (SSO), vos utilisateurs se connectent **une seule fois** pour accéder à dafexpert et à votre application. La session est partagée : un gérant de coopérative connecté à votre application ouvre dafexpert sans ressaisir son mot de passe, et inversement. Le SSO repose sur **OpenID Connect** (OAuth 2.0 Authorization Code), avec le fournisseur d'identité de dafexpert. > **info** Le SSO est **indépendant** de l'API partenaires : vous recevez un `client_id` dédié au SSO, distinct de celui de vos appels de serveur à serveur. Ne les confondez pas. ## Ce que vous nous fournissez La mise en place se fait avec l'équipe dafexpert ([support@dafexpert.com](mailto:support@dafexpert.com)). Préparez : - vos **adresses de retour après connexion**, par exemple `https://app.acme-agri.com/*` ; - vos **adresses de retour après déconnexion** ; - si vous en avez une, votre **URL de déconnexion front-channel**, pour la déconnexion unique (se déconnecter de dafexpert déconnecte aussi de votre application). ## Ce que nous vous transmettons | Élément | Valeur | |---|---| | Fournisseur (issuer) | `https://auth.stg.dafexpert.com/realms/dafexpert` (staging) | | Configuration automatique (discovery) | `https://auth.stg.dafexpert.com/realms/dafexpert/.well-known/openid-configuration` | | `client_id` | le code de votre application, par exemple `acme-agri` | | `client_secret` | affiché une seule fois, à la création du client | | Flux | Authorization Code. PKCE (`S256`) accepté et recommandé, mais facultatif. | | Scopes | `openid email profile` | > **warning** Le client SSO est **confidentiel** : son `client_secret` reste sur votre serveur. L'échange du code contre les jetons se fait côté serveur, jamais dans le navigateur ou l'application mobile. ## Le parcours de connexion **1. Lire la configuration** Récupérez la configuration OpenID de l'issuer. Elle donne les URL d'autorisation, de jeton, de déconnexion et les clés de signature. Ne codez pas ces URL en dur. **2. Rediriger l'utilisateur** Envoyez l'utilisateur vers `authorization_endpoint` avec votre `client_id`, votre adresse de retour, les scopes et un `state` (et, de préférence, un `code_challenge` PKCE). **3. Échanger le code** dafexpert renvoie l'utilisateur sur votre adresse de retour avec un `code`. Votre serveur l'échange contre un `id_token` et un `access_token` sur `token_endpoint`. **4. Identifier l'utilisateur** Validez l'`id_token` (signature, émetteur, audience) et lisez l'`email` : c'est l'identifiant de connexion, le même que celui envoyé à `PUT /v1/partners/users/{id}`. **1. Discovery** ```bash curl https://auth.stg.dafexpert.com/realms/dafexpert/.well-known/openid-configuration ``` **2. Autorisation** ```text GET {authorization_endpoint} ?response_type=code &client_id=acme-agri &redirect_uri=https://app.acme-agri.com/auth/callback &scope=openid%20email%20profile &state=8f3c1a... &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM &code_challenge_method=S256 ``` **3. Échange du code** ```bash curl -X POST "{token_endpoint}" \ -d grant_type=authorization_code \ -d code="$CODE" \ -d redirect_uri=https://app.acme-agri.com/auth/callback \ -d client_id=acme-agri \ -d client_secret="$SSO_CLIENT_SECRET" \ -d code_verifier="$CODE_VERIFIER" ``` **Node.js (openid-client)** ```javascript import * as client from 'openid-client'; const config = await client.discovery( new URL('https://auth.stg.dafexpert.com/realms/dafexpert'), 'acme-agri', process.env.SSO_CLIENT_SECRET, ); // Connexion : redirection vers dafexpert const codeVerifier = client.randomPKCECodeVerifier(); const codeChallenge = await client.calculatePKCECodeChallenge(codeVerifier); const state = client.randomState(); const url = client.buildAuthorizationUrl(config, { redirect_uri: 'https://app.acme-agri.com/auth/callback', scope: 'openid email profile', code_challenge: codeChallenge, code_challenge_method: 'S256', state, }); // ... conserver codeVerifier et state en session, puis rediriger vers url // Retour : échange du code const tokens = await client.authorizationCodeGrant(config, currentUrl, { pkceCodeVerifier: codeVerifier, expectedState: state, }); const { email } = tokens.claims(); ``` ## Créer les comptes de vos utilisateurs Le SSO ne crée pas les comptes. Créez d'abord chaque utilisateur avec `PUT /v1/partners/users/{id}` : son compte de connexion est créé (ou rattaché s'il existe déjà pour cet e-mail) et il reçoit l'accès aux entreprises listées. Il peut ensuite se connecter depuis votre application ou depuis dafexpert. Voir [Gérer les utilisateurs](/article/manage-users). ## Choix de l'application après la connexion Un utilisateur qui se connecte **depuis dafexpert** et qui a accès à votre application choisit celle à ouvrir : DAF Expert ou votre application. Il peut cocher « Toujours ouvrir cette application ». Un sélecteur dans l'en-tête de dafexpert permet ensuite de passer de l'une à l'autre. Un utilisateur qui vient **de votre application** y est renvoyé directement, sans écran de choix. ## Déconnexion unique Pour que la déconnexion d'un côté déconnecte aussi l'autre : 1. transmettez-nous votre URL de déconnexion front-channel ; 1. pour vous déconnecter, redirigez l'utilisateur vers `end_session_endpoint` (donné par la configuration) avec une de vos adresses de retour après déconnexion. ## Bonnes pratiques - Utilisez une bibliothèque OpenID Connect éprouvée plutôt qu'une implémentation maison. - Vérifiez toujours le `state` au retour, et utilisez PKCE même s'il est facultatif. - Identifiez l'utilisateur par son `email` (ou le `sub` de l'`id_token`), jamais par un paramètre de l'URL de retour. ## Étapes suivantes - [Gérer les utilisateurs](/article/manage-users) : Créer les comptes et donner l'accès aux entreprises. - [Choisir son intégration](/article/choose-integration) : Combiner SSO et API partenaires. --- # Erreurs et codes de réponse URL: https://developers.stg.dafexpert.com/article/errors > Les codes HTTP renvoyés par l'API, le format du corps d'erreur, les résultats partiels des lots et quand réessayer. L'API utilise les codes HTTP standards : `2xx` pour un succès, `4xx` pour une requête à corriger, `5xx` pour un incident de notre côté. Chaque erreur porte un corps JSON avec un message lisible et un code stable, `error_code`, sur lequel votre code peut s'appuyer. ## Codes de réponse | Code | Signification | Que faire | |---|---|---| | `200 OK` | Lecture réussie, ou mise à jour (par exemple `PUT` sur une entreprise qui existait déjà). | Marquer comme envoyé. | | `201 Created` | Ressource créée (entreprise, utilisateur, pièce fiscale). | Marquer comme envoyé, conserver l'identifiant renvoyé si utile. | | `202 Accepted` | Reçu et mis en file : événements, création d'entreprises en masse. Le traitement se fait en arrière-plan. | Lire le résultat **par élément** (voir plus bas), puis suivre le statut. | | `400 Bad Request` | Requête illisible, par exemple un JSON mal formé. | Corriger la requête. | | `401 Unauthorized` | Jeton absent, expiré ou invalide, ou accès révoqué. | Redemander un jeton **une fois** et rejouer. Si l'erreur persiste, contacter l'administrateur de l'espace. | | `403 Forbidden` | La ressource est rattachée à un autre espace (entreprise, utilisateur). | Ne pas renvoyer. Vérifier l'identifiant et signaler le problème. | | `404 Not Found` | Ressource inconnue dans votre espace (événement, lot, pièce, utilisateur). | Vérifier l'identifiant. Ne pas réessayer en boucle. | | `409 Conflict` | Conflit métier : NCC déjà utilisé, e-mail déjà pris, pièce en cours d'émission… | Lire le message. Seule la pièce en cours d'émission se réessaie telle quelle (voir plus bas). | | `413 Payload Too Large` | Corps de requête trop volumineux. | Découper l'envoi en plusieurs lots. | | `422 Unprocessable Entity` | Données invalides ou refusées par une règle métier, avec le détail par champ. | Corriger les données. **Ne pas renvoyer telles quelles.** | | `429 Too Many Requests` | Trop de requêtes. | Réessayer plus tard avec un délai croissant. Voir [Limites de débit](/article/rate-limiting). | | `500` / `502` / `503` / `504` | Incident ou indisponibilité temporaire. | Réessayer plus tard avec un délai croissant. | ## Format d'une erreur Toutes les erreurs ont la même forme de base : - `statusCode` (integer) : Le code HTTP, répété dans le corps. - `message` (string) : Message lisible. Pour les erreurs de validation et les règles métier de l'API partenaires, il est en français et peut être montré à un utilisateur. - `error_code` (string) : Code stable, pour votre logique (voir le tableau ci-dessous). Il peut manquer sur les erreurs levées avant l'API elle-même : JSON illisible, corps trop volumineux, erreur `500` inattendue. - `erreurs` (array) : Pour un `422` de validation : la liste des champs en erreur, chacun avec `champ` (chemin du champ, par exemple `adresse.ville` ou `lignes.0.montant`) et `message`. ### Erreur de validation (422) ```json { "statusCode": 422, "error_code": "validation_failed", "message": "Données invalides.", "erreurs": [ { "champ": "pays", "message": "Code pays ISO à deux lettres attendu (ex. CI)." }, { "champ": "debut_exercice", "message": "Date attendue au format AAAA-MM-JJ." } ] } ``` ### Règle métier (409) ```json { "statusCode": 409, "message": "Le NCC 1234567A est déjà utilisé par une autre entreprise de l'espace.", "error_code": "conflict" } ``` ### Ressource inconnue (404) ```json { "statusCode": 404, "message": "Événement inconnu.", "error_code": "not_found" } ``` ### Authentification (401) ```json { "statusCode": 401, "message": "Unauthorized", "error_code": "unauthenticated" } ``` Quand le jeton est valide mais que l'application a été déconnectée de l'espace, le message l'indique : `Partner access is not connected or has been revoked.` ## Valeurs de `error_code` | `error_code` | Statut | Cas | |---|---|---| | `validation_failed` | `422` | Un ou plusieurs champs invalides. Détail dans `erreurs`. | | `error` | `422` | Refus par une règle métier (pays inconnu, forme juridique hors catalogue, exercice impossible à ouvrir, rôle inconnu…). Détail dans `message`. | | `unauthenticated` | `401` | Jeton absent, expiré, invalide, ou accès révoqué. | | `forbidden` | `403` | Ressource d'un autre espace. | | `not_found` | `404` | Ressource inconnue dans votre espace. | | `conflict` | `409` | Conflit métier. | | `too_many_requests` | `429` | Trop de requêtes. | | `internal_error` | `5xx` | Incident de notre côté. | > **tips** Fondez votre logique sur le **code HTTP** et sur `error_code`, pas sur le texte de `message`, qui peut être reformulé. De nouveaux codes peuvent apparaître : traitez un code inconnu selon sa famille HTTP. ## Résultats partiels dans les lots Les envois en lot (`POST /v1/partners/events`, `POST /v1/partners/companies`) répondent `202` **même si certains éléments sont refusés**. Chaque élément du lot est validé seul : un élément invalide ne bloque pas les autres. Lisez donc toujours le tableau `results`, élément par élément. ```json { "results": [ { "id": "PAY-2b77e0", "statut": "recu", "doublon": false, "version": 1 }, { "index": 1, "id": "DEP-33ab", "statut": "invalide", "erreurs": [{ "champ": "montant", "message": "montant (45000) différent de la somme des lignes (40000)." }] }, { "id": "AVA-11c0", "statut": "refuse", "erreur": "L'exploitation de cet événement est rattachée à un autre espace." } ] } ``` | Statut de l'élément | Sens | Que faire | |---|---|---| | `recu` (ou le statut actuel) | Enregistré. | Marquer comme envoyé, puis suivre le statut. | | `invalide` | Non enregistré : données invalides. `index` donne sa position dans le lot. | Corriger puis renvoyer cet élément seul. | | `refuse` | Non enregistré : l'entreprise ou l'`id` appartient à un autre espace. | Ne pas renvoyer. Vérifier les identifiants. | > **warning** Un événement **envoyé seul** (hors lot) et invalide reçoit directement un `422`, et non un `202` avec un élément `invalide`. ## Quand réessayer | Situation | Réessayer ? | Comment | |---|---|---| | Coupure réseau, délai dépassé, pas de réponse | Oui | Renvoyer la **même** requête, avec les mêmes identifiants. L'[idempotence](/article/idempotency) évite les doublons. | | `429`, `500`, `502`, `503`, `504` | Oui | Délai croissant avec une part d'aléatoire (par exemple 1 s, 2 s, 4 s, 8 s…), en plafonnant le nombre d'essais. | | `401` | Une fois | Avec un jeton neuf. Ensuite, arrêter et prévenir l'administrateur. | | `409` « Pièce en cours d'émission » | Oui | Après quelques secondes, avec le **même** `id`. | | Autres `409`, `403`, `404`, `422` | Non | Corriger la cause d'abord. Un renvoi à l'identique donnera la même erreur. | ### Exemple : nouvel essai avec délai croissant **Node.js** ```javascript const RETRYABLE = new Set([429, 500, 502, 503, 504]); async function withRetry(doRequest, maxAttempts = 6) { for (let attempt = 1; ; attempt++) { let res; try { res = await doRequest(); } catch (networkError) { if (attempt >= maxAttempts) throw networkError; } if (res && !RETRYABLE.has(res.status)) return res; if (attempt >= maxAttempts) return res; const delay = Math.min(2 ** attempt * 500, 60_000) * (0.5 + Math.random()); await new Promise((r) => setTimeout(r, delay)); } } ``` **Python** ```python import random, time, requests RETRYABLE = {429, 500, 502, 503, 504} def with_retry(do_request, max_attempts=6): for attempt in range(1, max_attempts + 1): try: res = do_request() if res.status_code not in RETRYABLE: return res except requests.ConnectionError: if attempt == max_attempts: raise if attempt == max_attempts: return res time.sleep(min(2 ** attempt * 0.5, 60) * (0.5 + random.random())) ``` ## Étapes suivantes - [Idempotence et rejeu](/article/idempotency) : Rejouer une requête sans créer de doublon. - [Limites de débit](/article/rate-limiting) : Bonnes pratiques de volume. - [Suivre les événements](/article/track-events) : Retrouver les rejets et leurs motifs. --- # Idempotence et rejeu URL: https://developers.stg.dafexpert.com/article/idempotency > Pourquoi rejouer un appel ne crée jamais de doublon, quelle clé sert pour chaque ressource, et comment se comportent les renvois avec un contenu différent. Les réseaux coupent, les serveurs redémarrent, les applications mobiles envoient leurs opérations au retour du réseau. L'API dafexpert est conçue pour que vous puissiez **renvoyer sans crainte** : rejouer un appel ne crée jamais de doublon. Il n'y a pas d'en-tête `Idempotency-Key` à gérer : **votre propre identifiant** de la ressource sert de clé. C'est pourquoi il doit être stable. ## La clé de chaque ressource | Ressource | Route | Clé | Comportement au rejeu | |---|---|---|---| | Entreprise | `PUT /v1/partners/companies/{external_id}` | `external_id` (URL) | Le premier appel crée (`201`), les suivants mettent à jour (`200`). Des appels simultanés sur le même `external_id` ne créent qu'une seule entreprise. | | Entreprises en masse | `POST /v1/partners/companies` | `external_id` de chaque entreprise | Chaque entreprise est traitée comme par le `PUT` : création ou mise à jour. | | Utilisateur | `PUT /v1/partners/users/{id}` | `id` (URL) | Création (`201`) puis mise à jour (`200`). La liste `acces` envoyée fait référence. | | Événement | `POST /v1/partners/events` | `id` de l'événement | Enregistré une seule fois. Un renvoi répond `doublon: true` avec le statut actuel. | | Bordereau d'achat | `POST /v1/partners/companies/{external_id}/bordereaux` | `id` du bordereau, par entreprise | Un rejeu renvoie la **même pièce**, avec le même numéro et le même numéro fiscal (`200` au lieu de `201`). | | Facture | `POST /v1/partners/companies/{external_id}/invoices` | `id` de la facture, par entreprise | Idem : même facture, même numéro fiscal. | ## Choisir de bons identifiants - **Stables** : le même objet de votre application garde toujours le même identifiant. Dérivez-le de votre clé interne, par exemple `ACH-` suivi du code de la pesée, `PAY-` suivi du code du paiement. - **Uniques** dans votre application, pour un type de ressource donné. - **Jamais tirés au hasard à l'envoi** : un identifiant généré à chaque tentative transforme chaque rejeu en nouvel objet, donc en doublon. - Caractères autorisés : lettres, chiffres, `.` `_` `:` `-`, 100 caractères au plus. > **warning** Une même opération envoyée sous **deux `id` différents** est vue comme deux opérations. dafexpert signale les doublons probables, mais la vraie protection reste un `id` stable. ## Renvoi du même événement ### Même contenu L'événement n'est pas modifié. La réponse indique `doublon: true` et le **statut actuel**, ce qui permet de savoir où en est le traitement. ```json { "results": [ { "id": "PAY-2b77e0", "statut": "comptabilise", "doublon": true, "version": 1 } ] } ``` ### Contenu différent : une nouvelle version Si vous renvoyez un `id` connu avec un contenu **différent**, dafexpert l'enregistre comme une **nouvelle version** de l'événement. Ce qu'elle devient dépend de l'état de l'événement : | État de l'événement | Effet de la nouvelle version | Réponse | |---|---|---| | Pas encore comptabilisé (`recu`, `en_attente_entreprise`, `en_attente_piece`, `rejete`) | Elle **remplace** le contenu et l'événement est retraité. | `contenu_different: true`, `version_appliquee: true`, `version` incrémentée | | En cours de traitement (`en_cours`) | Elle est gardée, et appliquée seulement si le traitement échoue. | `version_appliquee: false` et un `message` | | Déjà comptabilisé ou clos | Elle est gardée pour information, **sans effet**, et signalée dans dafexpert. | `version_appliquee: false` et un `message` | ```json { "results": [ { "id": "PAY-2b77e0", "statut": "comptabilise", "doublon": true, "version": 1, "contenu_different": true, "version_appliquee": false, "message": "Nouvelle version reçue après comptabilisation : elle n'est pas appliquée. Pour corriger, envoyer une annulation puis un nouvel événement." } ] } ``` Toutes les versions reçues sont conservées. Le détail est dans [Versions d'un événement](/article/event-versions). > **tips** Pour corriger un événement **déjà comptabilisé** avec le contrat v2 : envoyez un événement `annulation` qui cite l'`id` d'origine, puis renvoyez l'opération correcte avec un **nouvel** `id`. ### Doublons dans un même lot Si le même `id` apparaît deux fois dans un lot, il n'est enregistré qu'une fois : la seconde occurrence répond `doublon: true`. Pour la création d'entreprises en masse, en revanche, un `external_id` présent deux fois dans le lot rend la seconde entrée `invalide`. ## Pièces fiscales L'émission d'un bordereau ou d'une facture est **synchrone** : la réponse contient la pièce, son numéro et son numéro fiscal. Si la réponse se perd, renvoyez la même requête avec le même `id` : vous obtenez la même pièce, sans en émettre une seconde. - Si la certification FNE ou la comptabilisation n'avait pas abouti, le rejeu les **relance**. - Si la même pièce est déjà en cours d'émission (deux appels simultanés), le second reçoit un `409` « Pièce en cours d'émission : réessayer dans quelques secondes avec le même id. » Réessayez après quelques secondes, avec le même `id`. > **warning** Ne changez jamais l'`id` d'une pièce pour « forcer » une nouvelle émission : vous émettriez une seconde pièce fiscale, avec son propre numéro fiscal. ## Stratégie de renvoi recommandée 1. Enregistrez l'opération dans une file locale **avant** de l'envoyer, avec son identifiant définitif. 1. Envoyez-la. Sur un succès (`200`, `201`, `202` avec un élément accepté), marquez-la « envoyée ». 1. Sur une coupure réseau, un délai dépassé, un `429` ou un `5xx` : renvoyez **la même requête** plus tard, avec un délai croissant. 1. Sur un `422` ou un élément `invalide` : corrigez les données avant tout renvoi. 1. Suivez ensuite le statut des événements jusqu'à `comptabilise`. Voir [Suivre les événements](/article/track-events). ## Étapes suivantes - [Erreurs et codes de réponse](/article/errors) : Quand réessayer, quand corriger. - [Versions d'un événement](/article/event-versions) : Remplacement, versions gardées, annulation. - [Envoyer des événements](/article/send-events) : Lots, statuts et premiers envois. --- # Webhooks URL: https://developers.stg.dafexpert.com/article/webhooks > Les webhooks ne sont pas encore disponibles : comment suivre aujourd'hui le statut de vos événements par synchronisation. > **info** **Bientôt.** dafexpert vous préviendra par webhook quand un événement est comptabilisé (`event.posted`) ou rejeté (`event.rejected`). Cette fonctionnalité n'est pas encore disponible. ## En attendant : la synchronisation par curseur Aujourd'hui, c'est votre application qui interroge dafexpert. La route `GET /v1/partners/events` renvoie les événements **modifiés** depuis votre dernier passage, du plus ancien au plus récent. Vous récupérez ainsi tous les changements de statut, sans en manquer et sans doublon. 1. Au premier appel, n'envoyez pas de curseur. 1. Traitez les `evenements` reçus (statut, pièce produite, motif de rejet). 1. Gardez la valeur de `suivant` et passez-la dans `apres` au prochain appel. 1. Quand `suivant` vaut `null`, tout est lu : attendez avant le prochain passage. **cURL** ```bash curl "https://api.stg.dafexpert.com/v1/partners/events?limite=100&apres=$CURSEUR" \ -H "Authorization: Bearer $TOKEN" ``` **Node.js** ```javascript async function synchroniser(store) { let curseur = await store.lireCurseur(); for (;;) { const qs = new URLSearchParams({ limite: '500' }); if (curseur) qs.set('apres', curseur); const res = await callApi('GET', '/partners/events?' + qs); const page = await res.json(); for (const e of page.evenements) await store.mettreAJour(e.id, e.statut, e); if (page.suivant) { curseur = page.suivant; await store.enregistrerCurseur(curseur); } if (!page.suivant) return; } } ``` **Réponse** ```json { "evenements": [ { "id": "PAY-2b77e0", "type": "paiement_producteur", "exploitation": "COOP-SOUBRE", "date": "2026-10-13", "montant": 1035000, "devise": "XOF", "statut": "comptabilise", "erreur": null, "ecriture": "BQ-2026-000187", "recu_le": "2026-10-13T09:12:04.000Z", "mis_a_jour_le": "2026-10-13T09:12:07.000Z", "version": 1 } ], "suivant": "MjAyNi0xMC0xM1QwOToxMjowNy4wMDBafDk..." } ``` ## Bonnes pratiques - **Enregistrez le curseur** après chaque page traitée : en cas d'arrêt, vous reprenez exactement où vous en étiez. - **Traitez le curseur comme une valeur opaque** : ne le construisez pas et ne le décodez pas. - Pour ne suivre que les problèmes, filtrez par statut : `statut=rejete`. - Adaptez la fréquence de synchronisation à votre besoin (par exemple toutes les quelques minutes) plutôt que d'interroger chaque événement un par un. - Pour un seul événement, `GET /v1/partners/events/{id}` donne son état détaillé. ## Étapes suivantes - [Suivre les événements](/article/track-events) : Le tutoriel complet de synchronisation. - [GET /v1/partners/events](/article/list-events) : Paramètres et format de la réponse. --- # Cas d'usage pris en charge URL: https://developers.stg.dafexpert.com/article/use-cases > Les intégrations que l'API dafexpert permet aujourd'hui : gestion de coopératives, applications terrain, facturation et assistants IA. dafexpert tient la comptabilité officielle des entreprises et émet leurs pièces fiscales. Votre application reste le logiciel métier : elle enregistre les opérations (pesées, achats, paiements, ventes, paie) et les transmet à dafexpert, qui crée les pièces, les certifie à la FNE quand la loi l'exige et les comptabilise. Cette page vous aide à repérer le cas qui ressemble au vôtre et les tutoriels à suivre. Chaque cas s'appuie sur les mêmes routes, avec les mêmes [identifiants](/article/credentials). ## Vue d'ensemble | Cas d'usage | Ce que vous envoyez | Ce que dafexpert produit | Tutoriels | |---|---|---|---| | Gestion de coopératives et de filières agricoles | Entreprises, utilisateurs, événements comptables | Comptabilité de chaque coopérative, pièces, écritures | [Créer une entreprise](/article/create-company), [Envoyer des événements](/article/send-events) | | Application terrain (mobile, hors ligne) | Achats bord champ, paiements, avances, dépenses | Bordereaux d'achat certifiés, règlements, écritures | [Émettre une pièce FNE](/article/fne-documents), [Suivre le traitement](/article/track-events) | | Outil de facturation ou de vente | Factures clients | Facture numérotée, certifiée FNE, comptabilisée, PDF | [Émettre une pièce FNE](/article/fne-documents) | | Assistant IA (Claude…) | Rien : l'utilisateur se connecte | Lecture des dossiers : balance, résultat, factures, trésorerie | [Brancher un assistant IA](/article/connect-ai-assistant) | ## Gestion de coopératives et de filières agricoles C'est le cas d'origine de l'API : une fédération (par exemple une interprofession ou un programme de filière) suit des coopératives et leurs producteurs dans votre logiciel, et veut une comptabilité SYSCOHADA à jour pour chacune. ### Parcours type - **Une fois** : créer chaque coopérative, union ou exploitant suivi avec `PUT /v1/partners/companies/{external_id}`, ou en masse lors de la reprise avec `POST /v1/partners/companies`. - **Une fois par personne** : donner accès aux gérants et aux comptables avec `PUT /v1/partners/users/{external_id}`. Ils se connectent ensuite par [connexion unique](/article/sso). - **Au quotidien** : envoyer chaque opération validée sous forme d'[événement](/article/event-catalog) (paiement producteur, avance, cession d'intrants, achat fournisseur, dépense, paie, cotisation, parts sociales). - **En continu** : synchroniser les statuts avec `GET /v1/partners/events` pour afficher, dans votre application, ce qui est comptabilisé et ce qui est en écart. > **tips** Un espace dafexpert correspond à votre client (la fédération). Toutes les coopératives que vous créez avec les mêmes identifiants y sont rangées. Voir [Espaces et entreprises](/article/workspaces-companies). ## Applications terrain Une application mobile d'agents ou de pisteurs enregistre les achats aux producteurs, souvent sans réseau. Le producteur doit recevoir un **bordereau d'achat de produits agricoles** certifié FNE (en Côte d'Ivoire). - À la validation de la pesée, l'application (ou son serveur) appelle `POST /v1/partners/companies/{external_id}/bordereaux`. La réponse, synchrone, contient le numéro, le numéro fiscal, le QR code et le lien du PDF à remettre au producteur. - Le paiement du producteur (espèces ou mobile money), avec ses éventuelles retenues, part ensuite en événement `paiement_producteur` qui cite le bordereau. - Hors ligne, l'application garde les opérations et les envoie au retour du réseau, avec leur vraie date. Le même `id` n'est jamais traité deux fois : voir [Idempotence](/article/idempotency). ## Outils de facturation ou de vente Votre outil gère les ventes (par exemple des livraisons de cacao à un exportateur). dafexpert émet la facture légale : numérotation, certification FNE, PDF et écriture comptable, en un seul appel `POST /v1/partners/companies/{external_id}/invoices`. Le paiement du client est ensuite envoyé en événement `encaissement_client`. > **info** Vous ne produisez plus vos propres factures ni bordereaux : la pièce fiscale vient de dafexpert, ce qui évite toute double numérotation. ## Assistants IA Les utilisateurs de dafexpert peuvent brancher leur assistant IA (Claude aujourd'hui) sur leurs dossiers grâce au serveur MCP. L'assistant lit la balance, le compte de résultat, les factures et la trésorerie, uniquement pour les entreprises auxquelles l'utilisateur a accès. Le serveur est en lecture seule. ## Ce qui n'est pas encore disponible - **Webhooks** : les changements de statut ne sont pas encore poussés vers votre serveur. En attendant, utilisez la synchronisation par curseur décrite dans [Suivre le traitement des événements](/article/track-events). - **Avoirs sur pièces certifiées** : une route dédiée est prévue. En attendant, l'avoir d'une facture ou d'un bordereau certifié s'émet dans dafexpert. - **Écriture par le serveur MCP** : les outils exposés aux assistants IA sont en lecture seule. > **note** Bientôt : ces fonctions seront annoncées dans les Nouveautés. ## Tutoriels - [Créer une entreprise](/article/create-company) : Coopérative, union, acheteur ou exploitant. - [Envoyer des événements](/article/send-events) : Un achat bord champ, puis son paiement. - [Émettre une pièce FNE](/article/fne-documents) : Bordereaux d'achat et factures certifiés. - [Brancher un assistant IA](/article/connect-ai-assistant) : Le serveur MCP dans Claude. --- # Créer ou mettre à jour une entreprise URL: https://developers.stg.dafexpert.com/article/create-company > Créer une coopérative et l'un de ses exploitants avec PUT /v1/partners/companies/{external_id}, puis les tenir à jour. ## Objectif À la fin de ce tutoriel, vous aurez créé dans dafexpert une coopérative ivoirienne et un exploitant qui lui est rattaché. Chacun aura sa propre comptabilité : un premier exercice ouvert, ses périodes mensuelles, ses journaux et son plan comptable. Vous saurez aussi mettre à jour leur fiche et lire ce qui manque avant la première pièce fiscale. Appelez cette route **à la création** d'une coopérative ou d'un exploitant dans votre application, puis **à chaque modification** de son identité. Elle est idempotente : le premier appel crée l'entreprise (`201`), les suivants la mettent à jour (`200`). ## Prérequis - Des identifiants partenaires (`client_id` et `client_secret`) pour l'espace de votre client : voir [Obtenir ses identifiants](/article/credentials). - Un jeton d'accès valide : voir [Authentification](/article/authentication). Les exemples utilisent la variable `$TOKEN`. - Un appel réussi à `GET /v1/partners/me`, qui confirme que la connexion à l'espace est active. > **info** Toutes les URL de ce tutoriel sont celles du staging : `https://api.stg.dafexpert.com/v1`. ## Étapes **1. Choisir l'identifiant externe** L'`external_id` est l'identifiant de l'entreprise **chez vous**. Il figure dans l'URL et sert de clé : dafexpert ne crée jamais deux entreprises pour le même `external_id`. Choisissez une valeur stable qui ne changera jamais (UUID, code interne). Format : 1 à 100 caractères parmi lettres, chiffres et `. _ : -`. C'est aussi la valeur du champ `entreprise` de vos événements. **2. Préparer la fiche de la coopérative** Seuls `type`, `nom` et `pays` sont obligatoires à la création. Envoyez pourtant tout ce que vous connaissez : les informations fiscales et légales sont exigées avant la première pièce fiscale. ```json { "version": 1, "type": "cooperative", "nom": "Société Coopérative Agricole de Soubré", "sigle": "SCAS", "pays": "CI", "ncc": "2034567K", "forme_juridique": "SCOOP-CA", "numero_immatriculation": "CI-SBR-2019-COOP-0142", "regime_fiscal": "Réel simplifié", "assujetti_tva": false, "centre_impots": "Centre des impôts de Soubré", "agrement_filiere": { "numero": "AGR-CC-2026-0457", "valide_jusqu_au": "2027-09-30" }, "capital_social": 5000000, "date_creation": "2019-03-12", "adresse": { "ligne1": "Quartier Commerce, lot 12", "ville": "Soubré", "commune": "Soubré" }, "localisation": { "region": "Nawa", "departement": "Soubré", "commune": "Soubré", "village": "Okrouyo", "gps": { "lat": 5.7856, "lng": -6.6083 } }, "etiquettes": { "filiere": "cacao", "zone": "Sud-Ouest", "projets": ["PRO-CACAO", "AGRI-2026"] }, "debut_exercice": "2026-01-01", "contact": { "nom": "Awa Koné", "email": "gerance@scas-soubre.ci", "telephone": "+225 07 08 12 34 56" } } ``` **3. Envoyer la requête** Appelez `PUT /v1/partners/companies/{external_id}` avec votre jeton. **cURL** ```bash curl -X PUT "https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d @coop-soubre.json ``` **Node.js** ```javascript const res = await fetch( 'https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE', { method: 'PUT', headers: { Authorization: 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify(coopSoubre), }, ); const company = await res.json(); // 201 à la création, 200 ensuite ``` Réponse `201 Created` : ```json { "external_id": "COOP-SOUBRE", "company_id": "ec6a85db-3f1e-4c55-9a0e-2b7d1f0c9a41", "statut": "cree", "referentiel": "SYSCOHADA", "devise": "XOF", "evenements_liberes": 0, "a_completer": ["connexion_fne"] } ``` **4. Vérifier la réponse** Contrôlez ces champs et enregistrez `company_id` en face de votre `external_id`. | Champ | Ce qu'il faut vérifier | |---|---| | `statut` | `cree` à la création, `mis_a_jour` ensuite. | | `referentiel` | Le référentiel comptable déduit du pays : `SYSCOHADA` en zone OHADA, `PCG` en France. Voir [Référentiels comptables](/article/accounting-frameworks). | | `devise` | La devise de l'entreprise, déduite du pays (`XOF` pour la Côte d'Ivoire). Vos montants devront être dans cette devise. | | `evenements_liberes` | Nombre d'événements reçus **avant** l'entreprise (statut `en_attente_entreprise`) et libérés par sa création. | | `a_completer` | Ce qui manque avant la première pièce fiscale. Liste vide : l'entreprise est prête. | Valeurs possibles de `a_completer` : | Valeur | Concerne | Comment compléter | |---|---|---| | `ncc` | coopérative, union, acheteur | Renvoyer la fiche avec `ncc`. | | `numero_immatriculation` | coopérative, union, acheteur | Renvoyer la fiche avec `numero_immatriculation`. | | `regime_fiscal` | coopérative, union, acheteur | Renvoyer la fiche avec `regime_fiscal`. | | `assujetti_tva` | coopérative, union, acheteur | Renvoyer la fiche avec `assujetti_tva` (`true` ou `false`). | | `connexion_fne` | entreprise en Côte d'Ivoire | L'entreprise connecte sa FNE dans dafexpert (Paramètres). Cela ne se fait pas par l'API. | | `exploitant.telephone` | exploitant | Renvoyer la fiche avec `exploitant.telephone`. | **5. Créer un exploitant rattaché** Un exploitant suivi dans dafexpert est une entreprise de type `exploitant`. `parent_id` porte l'`external_id` de sa coopérative. `nom` porte le nom de famille et le bloc `exploitant` le reste de son identité. Par défaut, sa comptabilité suit le système minimal de trésorerie (`smt`). ```bash curl -X PUT "https://api.stg.dafexpert.com/v1/partners/companies/EXPL-0457" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "version": 1, "type": "exploitant", "nom": "KOUASSI", "pays": "CI", "forme_juridique": "Entreprenant", "parent_id": "COOP-SOUBRE", "exploitant": { "prenoms": "Yao Bernard", "telephone": "0707123456", "cni": "C0012345678", "id_producteur": "CCC-123456", "sexe": "M", "date_naissance": "1978-04-02", "superficie_ha": 4.5, "cultures": ["cacao", "hévéa"] }, "localisation": { "region": "Nawa", "village": "Okrouyo" } }' ``` ```json { "external_id": "EXPL-0457", "company_id": "5b2d0f7a-91c4-4f0e-b6d1-7a3e2c9f8b10", "statut": "cree", "referentiel": "SYSCOHADA", "devise": "XOF", "evenements_liberes": 0, "a_completer": [] } ``` > **tips** L'ordre d'envoi n'a pas d'importance : un `parent_id` qui n'existe pas encore est accepté. S'il existe, son type doit respecter la hiérarchie union → coopérative → exploitant. **6. Mettre à jour la fiche** Rappelez la même route avec le même `external_id` à chaque changement dans votre application. La réponse est `200` avec `"statut": "mis_a_jour"`. Un champ absent n'est pas modifié. ```bash curl -X PUT "https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "type": "cooperative", "nom": "Société Coopérative Agricole de Soubré", "pays": "CI", "contact": { "nom": "Ibrahim Traoré", "email": "gerance@scas-soubre.ci", "telephone": "+225 05 44 21 09 87" } }' ``` ### Ce qui change et ce qui est figé | Modifiable à chaque appel | Figé après la création | |---|---| | `nom`, `forme_juridique`, `ncc`, `numero_immatriculation`, `regime_fiscal`, `assujetti_tva` | `pays` (et donc la devise et le référentiel) | | `adresse`, `contact` (le bloc envoyé remplace le précédent) | `debut_exercice` (le premier exercice est déjà ouvert) | | `parent_id`, `sigle`, `date_creation`, `localisation`, `centre_impots`, `agrement_filiere`, `capital_social`, bloc `exploitant` | `systeme_comptable` | | `etiquettes` : vos étiquettes sont remplacées, celles ajoutées dans dafexpert sont conservées | | > **warning** `type`, `nom` et `pays` restent obligatoires à chaque appel, même pour une mise à jour. ## Référence des champs Les longueurs sont des maximums, après suppression des espaces en début et fin. Le tableau complet est dans [Limites des champs](/article/field-limits). | Champ | Type | Obligatoire | Règle | |---|---|---|---| | `version` | entier | non | `1` (valeur par défaut). | | `type` | texte | oui | `cooperative`, `union`, `acheteur` ou `exploitant`. | | `nom` | texte | oui | 1 à 200 caractères. Pour un exploitant : le nom de famille. | | `pays` | texte | oui | Code ISO à deux lettres (`CI`, `SN`, `FR`…), parmi les pays pris en charge. | | `sigle` | texte | non | 40 caractères. | | `ncc` | texte | avant 1re pièce | Numéro de compte contribuable, 30 caractères. Unique dans l'espace. | | `forme_juridique` | texte | non | Code du catalogue du pays (voir plus bas). 60 caractères. | | `numero_immatriculation` | texte | avant 1re pièce | RCCM ou registre des sociétés coopératives. 60 caractères. | | `regime_fiscal` | texte | avant 1re pièce | 60 caractères. | | `assujetti_tva` | booléen | avant 1re pièce | `true` ou `false`. | | `centre_impots` | texte | non | 120 caractères. | | `agrement_filiere` | objet | non | `numero` (1 à 60 caractères), `valide_jusqu_au` (`AAAA-MM-JJ`). | | `capital_social` | nombre | non | En unités de la devise, positif ou nul. | | `date_creation` | date | non | `AAAA-MM-JJ`. | | `parent_id` | texte | non | `external_id` du parent : l'union d'une coopérative, la coopérative d'un exploitant. Remplace `cooperative_id`, toujours accepté. | | `adresse` | objet | non | `ligne1` (200), `ville` (100), `commune` (100). | | `localisation` | objet | non | `region`, `departement`, `commune`, `village` (100 chacun), `gps` (`lat` de -90 à 90, `lng` de -180 à 180). | | `etiquettes` | objet | non | 20 clés au plus (60 caractères), valeur texte (100) ou liste de 20 textes. Clés normalisées : minuscules, sans accents, `_` entre les mots. | | `exploitant` | objet | non | `prenoms` (120), `telephone` (40), `cni` (40), `id_producteur` (60), `sexe` (`M` ou `F`), `date_naissance`, `superficie_ha`, `cultures` (20 textes de 60). | | `systeme_comptable` | texte | non | `normal` ou `smt`. Par défaut `smt` pour un exploitant, `normal` sinon. | | `debut_exercice` | date | non | Le 1er d'un mois (`AAAA-MM-01`). Par défaut le 1er janvier de l'année en cours. Le premier exercice dure 12 mois. | | `contact` | objet | non | `nom` (120), `email` (adresse valide), `telephone` (40). | ### Champs selon le type Un champ envoyé pour un type qui ne le prévoit pas est refusé (`422`, « Champ non prévu pour le type »). | Champ | cooperative | union | acheteur | exploitant | |---|---|---|---|---| | `sigle`, `date_creation`, `numero_immatriculation`, `regime_fiscal`, `assujetti_tva`, `centre_impots`, `capital_social` | oui | oui | oui | non | | `agrement_filiere` | oui | non | oui | non | | `parent_id`, `cooperative_id` | une union | non | non | une coopérative | | bloc `exploitant` | non | non | non | oui | ### Formes juridiques acceptées `forme_juridique` doit être un code du catalogue de la zone du pays. La casse, les accents et la ponctuation sont tolérés (`scoop ca` devient `SCOOP-CA`). En cas de refus, le message du `422` liste les codes acceptés pour ce pays. | Zone | Codes | |---|---| | OHADA (Côte d'Ivoire, Sénégal, Cameroun…) | `SARL`, `SARLU`, `SA`, `SAS`, `SASU`, `SNC`, `SCS`, `GIE`, `SCI`, `SCOOPS`, `SCOOP-CA`, `Entreprise individuelle`, `Entreprenant`, `Association`, `ONG`, `Fondation`, `Église / Congrégation` | | France | `SARL`, `EURL`, `SAS`, `SASU`, `SA`, `SNC`, `SCI`, `SELARL`, `GIE`, `Entreprise individuelle`, `Micro-entreprise`, `Association`, `Fondation`, `Église / Congrégation` | > **tips** Coopérative : `SCOOPS` (société coopérative simplifiée) ou `SCOOP-CA` (avec conseil d'administration). Exploitant : `Entreprenant`. ## Erreurs | Code | Cas | Que faire | |---|---|---| | `422` | Données invalides : le corps contient `erreurs`, une entrée par champ. Aussi : pays inconnu, forme juridique hors catalogue, `debut_exercice` qui ne tombe pas un 1er du mois, `parent_id` d'un mauvais type, ancien champ `utilisateurs` envoyé. | Corriger puis renvoyer. | | `409` | NCC déjà utilisé par une autre entreprise de l'espace, ou espace sans propriétaire ni administrateur actif. | Vérifier le NCC ; pour l'espace, contacter son administrateur. | | `403` | Cet `external_id` est rattaché à un autre espace. | Ne pas renvoyer : vérifier les identifiants utilisés. | ```json { "statusCode": 422, "error_code": "validation_failed", "message": "Données invalides.", "erreurs": [ { "champ": "pays", "message": "Code pays ISO à deux lettres attendu (ex. CI)." }, { "champ": "agrement_filiere", "message": "Champ non prévu pour le type « union »." } ] } ``` Les refus métier (forme juridique, NCC, espace) renvoient un `message` sans tableau `erreurs`. Voir [Erreurs](/article/errors). ## Pièges courants **Changer d'external_id** Un nouvel `external_id` crée une nouvelle entreprise, avec une nouvelle comptabilité. Gardez toujours le même identifiant pour une même coopérative, même si son nom change. **Envoyer les utilisateurs dans la fiche** Le champ `utilisateurs` des premières versions est retiré : il provoque un `422`. Créez les utilisateurs après l'entreprise avec [Gérer les utilisateurs](/article/manage-users). **Mauvais pays à la création** Le pays fixe la devise et le référentiel, puis ne change plus, même si vous renvoyez un autre `pays`. Vérifiez-le avant le premier appel et, en cas d'erreur, contactez le [support](/article/support). **Champs inconnus** Un champ qui ne fait pas partie du contrat est ignoré sans erreur. Vérifiez l'orthographe des champs (par exemple `assujetti_tva`, pas `assujettie_tva`) : une faute ne lève pas d'erreur, la valeur est simplement perdue. **Exercice qui ne démarre pas en janvier** Passez `debut_exercice` au premier jour du mois d'ouverture (`2026-10-01` pour une campagne d'octobre à septembre). Une date au milieu d'un mois est refusée. ## Et ensuite 1. [Créer des entreprises en masse](/article/bulk-companies) pour la reprise de votre portefeuille. 1. [Gérer les utilisateurs](/article/manage-users) de chaque entreprise. 1. [Envoyer des événements comptables](/article/send-events). Référence complète de la route : [PUT /v1/partners/companies/{external_id}](/article/put-company). --- # Créer des entreprises en masse URL: https://developers.stg.dafexpert.com/article/bulk-companies > Reprendre tout un portefeuille de coopératives et d'exploitants avec POST /v1/partners/companies, puis suivre le lot. > **warning** **Bientôt disponible.** Cette fonctionnalité est prête mais pas encore ouverte sur le staging. Le contrat décrit ici est celui qui sera publié. ## Objectif Vous démarrez l'intégration et vos clients suivent déjà des centaines de coopératives et d'exploitants. Plutôt que d'appeler `PUT /v1/partners/companies/{external_id}` une fois par entreprise, vous les envoyez par lots de 500 au plus. dafexpert les enregistre tout de suite, les crée en arrière-plan et vous donne un identifiant de lot pour suivre l'avancement. > **info** Le traitement de chaque entreprise est exactement celui du `PUT` unitaire : mêmes champs, mêmes contrôles, même réponse. Au quotidien, le `PUT` reste la voie normale ; le lot sert à la reprise. ## Prérequis - Un jeton d'accès valide : voir [Authentification](/article/authentication). - Avoir lu [Créer ou mettre à jour une entreprise](/article/create-company) : les champs et les règles par type sont les mêmes. ## Étapes **1. Préparer le lot** Le corps contient `entreprises`, une liste de 1 à 500 entreprises. Chacune porte, en plus des champs habituels, son `external_id` (il n'est plus dans l'URL). ```json { "version": 1, "entreprises": [ { "external_id": "COOP-SOUBRE", "type": "cooperative", "nom": "Société Coopérative Agricole de Soubré", "pays": "CI", "forme_juridique": "SCOOP-CA", "ncc": "2034567K" }, { "external_id": "EXPL-0457", "type": "exploitant", "nom": "KOUASSI", "pays": "CI", "forme_juridique": "Entreprenant", "parent_id": "COOP-SOUBRE", "exploitant": { "prenoms": "Yao Bernard", "telephone": "0707123456" } }, { "external_id": "COOP-DALOA", "type": "cooperative", "nom": "Coopérative Agricole de Daloa", "pays": "Côte d'Ivoire" } ] } ``` **2. Envoyer le lot** Appelez `POST /v1/partners/companies`. La réponse arrive tout de suite avec le code `202 Accepted`. **cURL** ```bash curl -X POST "https://api.stg.dafexpert.com/v1/partners/companies" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d @reprise-lot-01.json ``` **Node.js** ```javascript const res = await fetch('https://api.stg.dafexpert.com/v1/partners/companies', { method: 'POST', headers: { Authorization: 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify({ version: 1, entreprises }), }); const { lot, recues, invalides, results } = await res.json(); // 202 ``` ```json { "lot": "3b0c8f9e-6a2d-4e71-9f0b-2c5d8a1e7b44", "recues": 2, "invalides": 1, "results": [ { "index": 0, "external_id": "COOP-SOUBRE", "id": "a1f3c2d4-0b5e-4c6f-8a9b-1c2d3e4f5a6b", "statut": "recu" }, { "index": 1, "external_id": "EXPL-0457", "id": "b2e4d3c5-1c6f-4d7a-9b0c-2d3e4f5a6b7c", "statut": "recu" }, { "index": 2, "external_id": "COOP-DALOA", "statut": "invalide", "erreurs": [{ "champ": "pays", "message": "Code pays ISO à deux lettres attendu (ex. CI)." }] } ] } ``` **3. Lire la réponse immédiate** Il y a un résultat par entreprise, dans l'ordre d'envoi (`index` commence à 0). Conservez la valeur de `lot`. | Champ | Signification | |---|---| | `lot` | Identifiant du lot, à passer à la route de suivi. `null` si aucune entreprise n'était valide. | | `recues` | Entreprises valides, mises en file. | | `invalides` | Entreprises refusées à la lecture : elles ne sont **pas** enregistrées. | | `results[].statut` | `recu` (en file) ou `invalide` (avec `erreurs`, une entrée par champ). | > **tips** Chaque entreprise est validée seule : une entreprise invalide ne bloque pas les autres. Corrigez-la et renvoyez-la dans un prochain lot, ou avec le `PUT` unitaire. **4. Suivre le lot** Interrogez `GET /v1/partners/companies/lots/{lot}` toutes les quelques secondes, jusqu'à `"termine": true`. ```bash curl "https://api.stg.dafexpert.com/v1/partners/companies/lots/3b0c8f9e-6a2d-4e71-9f0b-2c5d8a1e7b44" \ -H "Authorization: Bearer $TOKEN" ``` ```json { "lot": "3b0c8f9e-6a2d-4e71-9f0b-2c5d8a1e7b44", "total": 2, "termine": true, "compteurs": { "recu": 0, "en_cours": 0, "traite": 2, "rejete": 0 }, "entreprises": [ { "index": 0, "external_id": "COOP-SOUBRE", "id": "a1f3c2d4-0b5e-4c6f-8a9b-1c2d3e4f5a6b", "statut": "traite", "essais": 1, "company_id": "ec6a85db-3f1e-4c55-9a0e-2b7d1f0c9a41", "resultat": "cree", "referentiel": "SYSCOHADA", "devise": "XOF", "evenements_liberes": 0, "erreur": null, "traite_le": "2026-10-12T09:14:03.512Z" }, { "index": 1, "external_id": "EXPL-0457", "id": "b2e4d3c5-1c6f-4d7a-9b0c-2d3e4f5a6b7c", "statut": "traite", "essais": 1, "company_id": "5b2d0f7a-91c4-4f0e-b6d1-7a3e2c9f8b10", "resultat": "cree", "referentiel": "SYSCOHADA", "devise": "XOF", "evenements_liberes": 3, "erreur": null, "traite_le": "2026-10-12T09:14:03.871Z" } ] } ``` **5. Traiter chaque statut** Une fois le lot terminé, chaque entreprise est soit `traite`, soit `rejete`. | Statut | Signification | Que faire | |---|---|---| | `recu` | En file, pas encore traitée. | Attendre. | | `en_cours` | Création en cours. | Attendre. | | `traite` | Entreprise créée (`resultat: cree`) ou mise à jour (`resultat: mis_a_jour`). `company_id`, `referentiel` et `devise` sont renseignés. | Enregistrer `company_id` en face de votre `external_id`. | | `rejete` | Refus métier, avec le motif dans `erreur` : pays inconnu, forme juridique hors catalogue, NCC déjà pris, `external_id` d'un autre espace… | Corriger, puis renvoyer l'entreprise. | En cas d'erreur technique, dafexpert réessaie automatiquement : l'entreprise reste `recu` ou `en_cours`, `essais` augmente et `erreur` montre la dernière erreur rencontrée. Ce n'est pas un refus. Un refus métier, lui, n'est jamais retenté. ## Réponses de la route de suivi | Code | Cas | |---|---| | `200` | Le lot existe : avancement ci-dessus. | | `404` | Lot inconnu, ou lot d'un autre espace. | | `422` | Identifiant de lot mal formé (ce doit être un UUID). | ## Erreurs à l'envoi | Code | Cas | |---|---| | `202` | Lot accepté, même si certaines entreprises sont `invalide`. | | `422` | Le corps lui-même est invalide : `entreprises` absent, vide ou de plus de 500 éléments. | | `401` | Jeton expiré ou accès révoqué : voir [Authentification](/article/authentication). | ## Bonnes pratiques 1. Découpez votre portefeuille en lots de 500 au plus et envoyez-les les uns après les autres. 1. Envoyez les unions, puis les coopératives, puis les exploitants si vous le pouvez. Ce n'est pas obligatoire : un `parent_id` pas encore créé est accepté. 1. Suivez chaque lot jusqu'à `termine`, puis traitez les `rejete` avant de passer au suivant. 1. Renvoyer un lot n'est pas dangereux : une entreprise déjà créée est simplement mise à jour (`resultat: mis_a_jour`). ## Pièges courants **Le même external_id deux fois dans un lot** La seconde occurrence revient en `invalide` (« présent plusieurs fois dans le lot ») et n'est pas enregistrée. Dédoublonnez avant l'envoi. **Considérer recu comme créé** `recu` signifie seulement « enregistré en file ». L'entreprise n'existe dans dafexpert qu'une fois `traite`. N'envoyez ses utilisateurs qu'à ce moment-là ; ses événements, en revanche, peuvent partir avant : ils attendent l'entreprise en `en_attente_entreprise`. **Oublier les invalides** Les entreprises `invalide` de la réponse immédiate n'apparaissent pas dans le suivi du lot, puisqu'elles n'ont pas été enregistrées. Gardez la réponse du `POST` pour les corriger. Références : [POST /v1/partners/companies](/article/post-companies) et [GET /v1/partners/companies/lots/{lot}](/article/get-company-lot). --- # Gérer les utilisateurs URL: https://developers.stg.dafexpert.com/article/manage-users > Donner à un gérant ou à un comptable l'accès à ses entreprises avec PUT /v1/partners/users/{external_id}, puis le retirer avec DELETE. ## Objectif Quand vous créez un utilisateur dans votre application (gérant de coopérative, comptable, exploitant), vous le créez aussi dans dafexpert avec un seul appel. dafexpert crée son compte de connexion, ou rattache celui qu'il a déjà, et lui donne accès aux entreprises que vous listez. L'utilisateur peut ensuite se connecter tout de suite, depuis votre application par [connexion unique](/article/sso) ou directement sur dafexpert. ## Prérequis - Les entreprises concernées existent déjà : voir [Créer ou mettre à jour une entreprise](/article/create-company). Un utilisateur qui cite une entreprise inconnue est refusé. - Un jeton d'accès valide : voir [Authentification](/article/authentication). ## Étapes **1. Choisir l'identifiant externe** `{external_id}` est l'identifiant de l'utilisateur **chez vous** (1 à 100 caractères parmi lettres, chiffres et `. _ : -`). L'appel est idempotent : rappeler la route avec le même identifiant met l'utilisateur à jour. **2. Préparer la fiche** `email` est le seul champ obligatoire : c'est l'identifiant de connexion. `acces` liste les entreprises (par leur `external_id`) et le rôle de l'utilisateur sur chacune. ```json { "version": 1, "email": "awa.kone@scas-soubre.ci", "prenom": "Awa", "nom": "Koné", "telephone": "+225 07 08 12 34 56", "mot_de_passe": "Cacao-Soubre-2026!", "mot_de_passe_temporaire": true, "acces": [ { "exploitation": "COOP-SOUBRE", "role": "dirigeant" }, { "exploitation": "EXPL-0457", "role": "lecture-seule" } ] } ``` **3. Envoyer la requête** Appelez `PUT /v1/partners/users/{external_id}`. **cURL** ```bash curl -X PUT "https://api.stg.dafexpert.com/v1/partners/users/USR-AKONE" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d @awa-kone.json ``` **Node.js** ```javascript const res = await fetch('https://api.stg.dafexpert.com/v1/partners/users/USR-AKONE', { method: 'PUT', headers: { Authorization: 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify(awaKone), }); const user = await res.json(); // 201 à la création, 200 ensuite ``` Réponse `201 Created` : ```json { "external_id": "USR-AKONE", "user_id": "7c1e9a52-4b0d-4f3a-8e6c-5d2b1a0f9e83", "statut": "cree", "compte": "cree", "mot_de_passe": "defini", "acces": [ { "exploitation": "COOP-SOUBRE", "company_id": "ec6a85db-3f1e-4c55-9a0e-2b7d1f0c9a41", "role": "dirigeant" }, { "exploitation": "EXPL-0457", "company_id": "5b2d0f7a-91c4-4f0e-b6d1-7a3e2c9f8b10", "role": "lecture-seule" } ], "acces_retires": 0 } ``` **4. Vérifier la réponse** Les champs `compte` et `mot_de_passe` vous disent ce qui s'est réellement passé côté connexion. | Champ | Valeurs | Signification | |---|---|---| | `statut` | `cree`, `mis_a_jour` | Premier appel pour cet `external_id`, ou mise à jour. | | `compte` | `cree`, `existant` | `cree` : dafexpert a créé le compte de connexion, actif, avec l'e-mail considéré comme vérifié. `existant` : un compte existait déjà pour cet e-mail, il a été rattaché. | | `mot_de_passe` | `defini`, `ignore`, `null` | `defini` : votre mot de passe est appliqué. `ignore` : le compte existait déjà, son mot de passe n'est jamais modifié. `null` : vous n'en avez pas envoyé. | | `acces` | liste | Les accès en place après l'appel, avec le `company_id` dafexpert. | | `acces_retires` | nombre | Accès supprimés parce qu'ils ne figurent plus dans votre liste. | **5. Modifier les accès** La liste `acces` est **la référence** : à chaque appel, les accès de l'utilisateur aux entreprises créées par vous sont alignés sur la liste. Pour retirer l'accès à une entreprise, renvoyez la fiche sans elle. ```bash curl -X PUT "https://api.stg.dafexpert.com/v1/partners/users/USR-AKONE" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "email": "awa.kone@scas-soubre.ci", "prenom": "Awa", "nom": "Koné", "acces": [{ "exploitation": "COOP-SOUBRE", "role": "dirigeant" }] }' ``` ```json { "external_id": "USR-AKONE", "user_id": "7c1e9a52-4b0d-4f3a-8e6c-5d2b1a0f9e83", "statut": "mis_a_jour", "compte": "cree", "mot_de_passe": null, "acces": [ { "exploitation": "COOP-SOUBRE", "company_id": "ec6a85db-3f1e-4c55-9a0e-2b7d1f0c9a41", "role": "dirigeant" } ], "acces_retires": 1 } ``` **6. Retirer l'utilisateur** Quand l'utilisateur quitte la coopérative, appelez `DELETE /v1/partners/users/{external_id}`. Tous ses accès aux entreprises créées par vous sont retirés. Son compte de connexion est conservé, car il peut avoir d'autres accès dans dafexpert. ```bash curl -X DELETE "https://api.stg.dafexpert.com/v1/partners/users/USR-AKONE" \ -H "Authorization: Bearer $TOKEN" ``` ```json { "external_id": "USR-AKONE", "acces_retires": 1 } ``` Un nouveau `PUT` avec le même `external_id` le rattache de nouveau. ## Référence des champs | Champ | Type | Obligatoire | Règle | |---|---|---|---| | `version` | entier | non | `1` (valeur par défaut). | | `email` | texte | oui | Adresse e-mail valide. Identifiant de connexion. | | `prenom`, `nom` | texte | non | 80 caractères chacun. | | `telephone` | texte | non | 40 caractères. | | `mot_de_passe` | texte | non | 8 à 128 caractères, et conforme à la politique de sécurité de dafexpert. Appliqué uniquement aux comptes créés par vous. Jamais stocké par dafexpert. | | `mot_de_passe_temporaire` | booléen | non | `true` : l'utilisateur doit le changer à sa première connexion. `false` par défaut. | | `acces` | liste | non | 200 éléments au plus. Liste vide par défaut. | | `acces[].exploitation` | texte | oui | `external_id` d'une entreprise créée par vous dans cet espace. | | `acces[].role` | texte | non | Rôle du catalogue dafexpert, 40 caractères. `collaborateur` par défaut. | ### Rôles | Rôle | Accès sur l'entreprise | |---|---| | `dirigeant` | Responsable de **cette** entreprise, accès complet. | | `admin` | Accès complet à toutes les fonctionnalités et à la gestion. | | `comptable` | Tient la comptabilité, accède aux états et aux exports. | | `expert-comptable` | Accès cabinet : révision et clôtures. | | `collaborateur` | Accès limité (valeur par défaut). | | `lecture-seule` | Consultation uniquement, aucune modification. | > **info** Quel que soit le rôle, l'utilisateur reste **simple membre** de l'espace : il n'en devient jamais propriétaire ni administrateur, et ne voit que les entreprises de sa liste `acces`. Le parcours d'accueil de dafexpert est marqué terminé : il arrive directement dans son entreprise. ## Erreurs | Code | Cas | Que faire | |---|---|---| | `422` | Données invalides, entreprise inconnue (à créer d'abord), rôle inconnu, mot de passe refusé par la politique de sécurité. | Corriger puis renvoyer. Si le compte venait d'être créé, il est supprimé : le renvoi est sans risque. | | `409` | E-mail déjà utilisé par un autre compte (changement d'e-mail), ou compte déjà rattaché à un autre utilisateur de votre application. | Vérifier que vous n'avez pas deux `external_id` pour la même personne. | | `403` | Cet utilisateur est rattaché à un autre espace. | Ne pas renvoyer : vérifier les identifiants utilisés. | | `404` | `DELETE` d'un utilisateur inconnu. | Rien à faire. | | `503` | Le service de comptes est momentanément indisponible. | Renvoyer plus tard, avec un délai croissant. | ## Pièges courants **Envoyer une liste d'accès partielle** Un appel avec `acces` ne contenant qu'une nouvelle entreprise **retire** les autres. Renvoyez toujours la liste complète des entreprises de l'utilisateur. **Compter sur le mot de passe pour un compte existant** Si l'e-mail a déjà un compte dafexpert (`compte: existant`), son nom et son mot de passe ne sont jamais modifiés (`mot_de_passe: ignore`). L'utilisateur garde son mot de passe actuel. **Créer l'utilisateur avant l'entreprise** Une entreprise d'un lot n'existe qu'une fois `traite`. Avant, l'appel échoue en `422` (« Exploitation(s) inconnue(s) »). **Transmettre le mot de passe en clair dans vos journaux** Le mot de passe n'est jamais stocké par dafexpert, mais il transite dans votre requête. Excluez le champ `mot_de_passe` de vos propres journaux. ## Et ensuite 1. Configurer la [connexion unique (SSO)](/article/sso) pour que vos utilisateurs passent de votre application à dafexpert sans se reconnecter. 1. [Envoyer des événements comptables](/article/send-events). Références : [PUT /v1/partners/users/{external_id}](/article/put-user) et [DELETE /v1/partners/users/{external_id}](/article/delete-user). --- # Envoyer des événements comptables URL: https://developers.stg.dafexpert.com/article/send-events > Un parcours complet : avance de campagne, achat de cacao à un producteur, puis paiement en mobile money avec retenue de l'avance. > **warning** **Bientôt disponible.** Cette fonctionnalité est prête mais pas encore ouverte sur le staging. Le contrat décrit ici est celui qui sera publié. ## Objectif Un événement décrit une opération validée dans votre application : un paiement, une avance, une dépense, une paie… dafexpert le reçoit, crée la pièce de gestion correspondante (règlement, facture fournisseur, écriture) et la comptabilise dans l'entreprise concernée. Dans ce tutoriel, vous suivez un cas type de campagne cacao à Soubré : 1. En juin, la coopérative COOP-SOUBRE verse **200 000 FCFA** d'avance de campagne en espèces au producteur Yao Bernard KOUASSI. 1. Le 12 octobre, elle lui achète **1 200 kg** de cacao à **1 100 FCFA/kg**, soit **1 320 000 FCFA**. 1. Le 13 octobre, elle le paie par Orange Money, en retenant l'avance : **1 120 000 FCFA** versés. > **info** L'achat au producteur (`achat_produit`) n'est plus un événement : c'est une **pièce fiscale** émise de façon synchrone par la route des bordereaux, pour remettre tout de suite le bordereau certifié au producteur. L'étape 2 l'émet ; le paiement de l'étape 3 la cite par son `id`. ## Prérequis - La coopérative existe : voir [Créer ou mettre à jour une entreprise](/article/create-company). Ici, `COOP-SOUBRE`, en Côte d'Ivoire (devise `XOF`). - Pour émettre le bordereau en Côte d'Ivoire, la coopérative a connecté sa FNE dans dafexpert (`a_completer` ne contient plus `connexion_fne`). - Un jeton d'accès valide : voir [Authentification](/article/authentication). ## Étapes **1. Envoyer l'avance de campagne** Chaque événement porte `"version": 2`, un `id` stable et unique chez vous, son `type`, la `date` de l'opération, l'`entreprise` qui la réalise (votre `external_id`) et le `montant` en FCFA entiers. Le `tiers` est la contrepartie : ici le producteur. **cURL** ```bash curl -X POST "https://api.stg.dafexpert.com/v1/partners/events" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "version": 2, "id": "AVA-11c0", "type": "avance_producteur", "date": "2026-06-02", "entreprise": "COOP-SOUBRE", "tiers": { "role": "producteur", "id": "PROD-8c41d0", "entreprise": "EXPL-0457", "nom": "KOUASSI", "prenoms": "Yao Bernard", "telephone": "0707123456", "cni": "C0012345678" }, "montant": 200000, "devise": "XOF", "mode": "especes", "caisse": "CAISSE-SOUBRE-1", "remboursement": "retenue_sur_achats", "analytique": { "campagne": "2026-2027" } }' ``` Réponse `202 Accepted` : l'événement est enregistré et sera comptabilisé en arrière-plan. ```json { "results": [ { "id": "AVA-11c0", "statut": "recu", "doublon": false, "version": 1 } ] } ``` dafexpert passe l'écriture d'avance : 4091 Fournisseurs, avances versées au débit, caisse au crédit (comptes SYSCOHADA par défaut). **2. Émettre le bordereau d'achat** À la validation de la pesée, émettez le bordereau avec `POST /v1/partners/companies/{external_id}/bordereaux`. La réponse est synchrone : numéro, numéro fiscal FNE, QR code et lien du PDF. Le détail des champs est dans [Émettre une pièce FNE](/article/fne-documents). ```bash curl -X POST "https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/bordereaux" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "id": "ACH-6f1c2a9e", "date": "2026-10-12", "tiers": { "role": "producteur", "id": "PROD-8c41d0", "entreprise": "EXPL-0457", "nom": "KOUASSI", "prenoms": "Yao Bernard", "telephone": "0707123456" }, "lignes": [{ "produit": "CACAO", "qualite": "grade_1", "quantite_pesee": 1250, "refaction_pct": 4, "quantite": 1200, "unite": "kg", "nombre_sacs": 20, "humidite_pct": 7.5, "prix_unitaire": 1100, "montant": 1320000 }], "montant": 1320000, "paiement_prevu": "mobile_money", "reference_origine": "L-S12/10/2026", "analytique": { "campagne": "2026-2027", "parcelle": "SURF-2231" } }' ``` ```json { "id": "ACH-6f1c2a9e", "type": "bordereau_achat", "piece_id": "0f9d2c1b-7a6e-4d5c-8b3a-2e1f0d9c8b7a", "statut": "certifie", "numero": "BA-ACH-6f1c2a9e", "numero_fiscal": "FNE-2026-000184512", "qr": "https://…", "lien_pdf": "/v1/partners/companies/COOP-SOUBRE/bordereaux/ACH-6f1c2a9e/pdf", "comptabilisation": "comptabilise", "montant": 1320000, "miroir": { "entreprise": "EXPL-0457", "statut": "comptabilise" } } ``` Le bordereau est comptabilisé chez la coopérative (601 Achats au débit, 4011 producteur au crédit). Comme le producteur est aussi une entreprise suivie (`tiers.entreprise`), sa vente miroir est créée chez lui (bloc `miroir`). **3. Envoyer le paiement avec retenue** Le paiement cite le bordereau payé dans `lie_a` (montant brut affecté) et l'avance remboursée dans `retenues`. `montant` est le **net réellement versé** : somme de `lie_a` moins somme des `retenues`. En mobile money, `reference_transaction` est obligatoire. ```bash curl -X POST "https://api.stg.dafexpert.com/v1/partners/events" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "version": 2, "id": "PAY-2b77e0", "type": "paiement_producteur", "date": "2026-10-13", "entreprise": "COOP-SOUBRE", "tiers": { "role": "producteur", "id": "PROD-8c41d0", "nom": "KOUASSI", "prenoms": "Yao Bernard", "telephone": "0707123456" }, "lie_a": [{ "id": "ACH-6f1c2a9e", "montant": 1320000 }], "retenues": [{ "nature": "avance", "lie_a": "AVA-11c0", "montant": 200000 }], "montant": 1120000, "mode": "mobile_money", "operateur": "orange_money", "reference_transaction": "MP261013.1542.A12345", "caisse": "CAISSE-SOUBRE-1" }' ``` ```json { "results": [ { "id": "PAY-2b77e0", "statut": "recu", "doublon": false, "version": 1 } ] } ``` dafexpert crée un règlement fournisseur de 1 120 000 FCFA lettré avec le bordereau, et une écriture de compensation pour la retenue : 4011 producteur au débit, 4091 avances au crédit, pour 200 000 FCFA. Le bordereau passe « payé ». **4. Vérifier le traitement** Lisez l'événement avec `GET /v1/partners/events/{id}`. Le bloc `effets` détaille, par entreprise, la pièce créée et son statut. ```bash curl "https://api.stg.dafexpert.com/v1/partners/events/PAY-2b77e0" \ -H "Authorization: Bearer $TOKEN" ``` ```json { "id": "PAY-2b77e0", "type": "paiement_producteur", "exploitation": "COOP-SOUBRE", "date": "2026-10-13", "montant": 1120000, "devise": null, "statut": "comptabilise", "erreur": null, "ecriture": null, "recu_le": "2026-10-13T08:42:10.114Z", "mis_a_jour_le": "2026-10-13T08:42:12.873Z", "version": 1, "effets": [ { "entreprise": "COOP-SOUBRE", "role": "payeur", "action": "creee", "statut": "comptabilise", "piece": { "type": "supplier_settlement", "numero": "MP261013.1542.A12345", "numero_fiscal": null }, "ecriture": null, "message": "Payé 1120000 FCFA (mobile money), retenues 200000 FCFA." } ] } ``` Pour suivre des milliers d'événements sans les lire un par un, utilisez la synchronisation par curseur : voir [Suivre le traitement des événements](/article/track-events). **5. Envoyer par lots** Vous pouvez envoyer jusqu'à 500 événements par appel : `{ "version": 2, "events": [...] }`. La `version` du lot s'applique à chaque événement qui n'a pas la sienne. Chaque événement est validé seul : un événement invalide ne bloque pas les autres. ```json { "version": 2, "events": [ { "id": "DEP-33ab", "type": "depense", "date": "2026-10-12", "entreprise": "COOP-SOUBRE", "tiers": { "role": "prestataire", "id": "PRE-0042", "nom": "Transport Koné", "telephone": "0101010101" }, "lignes": [{ "nature": "transport", "libelle": "Transport cacao Okrouyo vers magasin", "montant": 45000 }], "montant": 45000, "mode": "especes", "caisse": "CAISSE-SOUBRE-1" }, { "id": "COT-0912", "type": "cotisation", "date": "2026-10-12", "entreprise": "COOP-SOUBRE", "tiers": { "role": "producteur", "id": "PROD-8c41d0", "nom": "KOUASSI" }, "nature": "cotisation_annuelle", "periode": "2026-2027", "montant": 10000, "mode": "mobile_money", "caisse": "CAISSE-SOUBRE-1" } ] } ``` ```json { "results": [ { "id": "DEP-33ab", "statut": "recu", "doublon": false, "version": 1 }, { "index": 1, "id": "COT-0912", "statut": "invalide", "erreurs": [ { "champ": "reference_transaction", "message": "reference_transaction obligatoire pour un paiement mobile money." } ] } ] } ``` ## Lire la réponse Il y a un résultat par événement, dans l'ordre d'envoi. | Champ | Signification | |---|---| | `statut` | Statut **actuel** de l'événement (`recu`, `en_attente_entreprise`…), ou `invalide`, ou `refuse`. Voir [les statuts](/article/track-events). | | `doublon` | `true` : cet `id` était déjà enregistré. Rien n'est recréé : vous pouvez renvoyer sans risque après une coupure. | | `version` | Numéro de la version appliquée (1 au premier envoi). | | `contenu_different` | Présent si cet `id` a été renvoyé avec un autre contenu : c'est une nouvelle version. | | `version_appliquee` | La nouvelle version remplace-t-elle l'ancienne ? `false` si l'événement était déjà comptabilisé ou en cours de traitement, avec l'explication dans `message`. | | `erreurs` | Seulement pour `invalide` : le détail par champ. L'événement n'est pas enregistré. | | `erreur` | Pour `refuse` (l'entreprise ou l'`id` appartient à un autre espace), ou motif d'un rejet déjà connu. | > **warning** Un événement envoyé **seul** et invalide reçoit un `422` pour tout l'appel. Dans un lot, il revient en `invalide` et les autres sont enregistrés. ## Corriger un événement ### Avant comptabilisation Renvoyez le même `id` avec le contenu corrigé. Tant que l'événement n'est pas comptabilisé (`recu`, `en_attente_entreprise`, `en_attente_piece` ou `rejete`), la nouvelle version **remplace** l'ancienne et l'événement est retraité (`version_appliquee: true`). ### Après comptabilisation La nouvelle version est gardée pour information et signalée dans dafexpert, mais **pas appliquée** (`version_appliquee: false`). Pour corriger : envoyez une `annulation` qui cite l'événement, puis renvoyez l'opération correcte avec un **nouvel `id`**. ```json { "version": 2, "id": "ANN-PAY-2b77e0", "type": "annulation", "date": "2026-10-14", "entreprise": "COOP-SOUBRE", "montant": 0, "lie_a": ["PAY-2b77e0"], "motif": "Erreur sur le montant de la retenue" } ``` L'annulation contre-passe les écritures de l'événement d'origine et supprime ses règlements. L'événement d'origine passe `annule`. Le détail est dans [Événements v1 et v2](/article/event-versions). ## Pièges courants **Un id tiré au hasard** L'`id` est la clé anti-doublon. S'il change à chaque tentative (UUID généré à l'envoi), un renvoi après coupure crée un second événement, que dafexpert bloque comme « doublon probable ». Dérivez l'`id` de l'objet source (`PAY-` + code du paiement). **Le paiement arrive avant le bordereau** Le paiement passe `en_attente_piece` (champ `en_attente_de`). Il est libéré dès que le bordereau est comptabilisé. Après 48 h sans pièce, il passe `rejete` avec le motif « Pièce citée introuvable après 48 h ». **Un bordereau non certifié** En Côte d'Ivoire, un bordereau n'est comptabilisé qu'une fois certifié FNE. Tant qu'il ne l'est pas, le paiement qui le cite attend. Rejouez l'émission du bordereau avec le même `id` pour relancer la certification. **Un montant qui ne tombe pas juste** `montant` est contrôlé : somme de `lie_a` moins `retenues` pour un paiement, somme des `lignes` pour une dépense, etc. Un écart, même d'un franc, renvoie un `422`. Voir [Montants et devises](/article/amounts-currencies). **Des décimales en FCFA** En version 2, les montants sont des FCFA entiers : `1320000`, jamais `1320000.00` sous forme de texte ni `1320000.5`. **Oublier la caisse** `caisse` est obligatoire pour tous les types qui portent un paiement (`paiement_producteur`, `avance_producteur`, `remboursement_producteur`, `encaissement_client`, `depense`, `cotisation`). ## Et ensuite 1. Parcourir le [catalogue des événements](/article/event-catalog) pour tous les types et leurs champs. 1. Mettre en place la [synchronisation des statuts](/article/track-events). Référence : [POST /v1/partners/events](/article/post-events). --- # Suivre le traitement des événements URL: https://developers.stg.dafexpert.com/article/track-events > Lire le statut d'un événement, synchroniser tous les changements avec un curseur et traiter les rejets. ## Objectif La réponse à `POST /v1/partners/events` arrive tout de suite (`202`) : elle confirme la réception, pas la comptabilisation, qui se fait en arrière-plan. Ce tutoriel met en place, dans votre application, un suivi fiable : savoir pour chaque opération si elle est comptabilisée, en attente ou rejetée, et afficher le motif à vos utilisateurs. > **info** Les webhooks (`event.posted`, `event.rejected`) sont prévus mais pas encore disponibles. En attendant, la synchronisation par curseur décrite ici est la méthode recommandée. Voir [Webhooks](/article/webhooks). ## Prérequis - Avoir envoyé des événements : voir [Envoyer des événements comptables](/article/send-events). - Un jeton d'accès valide : voir [Authentification](/article/authentication). ## Les statuts | Statut | Signification | Peut encore changer ? | |---|---|---| | `recu` | Enregistré, en attente de traitement. | Oui | | `en_attente_entreprise` | L'entreprise (`entreprise` ou `exploitation`) n'existe pas encore. L'événement est traité automatiquement dès sa création. | Oui | | `en_attente_piece` | Version 2 : une pièce ou un événement cité dans `lie_a` ou `retenues` n'est pas encore arrivé ou pas encore comptabilisé. Voir `en_attente_de`. Passé 48 h, l'événement passe `rejete`. | Oui | | `en_cours` | Traitement en cours : pièces en création, certification FNE. | Oui | | `comptabilise` | Toutes les pièces sont créées et comptabilisées. | Oui : `annule` après une annulation | | `partiel` | Version 2 : une partie des effets seulement est comptabilisée. | Oui | | `rejete` | Refusé, avec le motif dans `erreur`. Il peut être relancé dans dafexpert une fois la cause corrigée, ou remplacé par une nouvelle version. | Oui | | `ignore` | Écarté volontairement dans dafexpert. | Rarement | | `annule` | Version 2 : annulé par un événement `annulation`. | Non | | `non_comptable` | Version 2 : enregistré pour information (`recolte`, `inventaire`), jamais en écart. | Non | > **warning** Aucun statut n'est définitif au sens strict : un `rejete` peut devenir `comptabilise` après correction dans dafexpert, un `comptabilise` peut devenir `annule`. Continuez à synchroniser. ## Étapes **1. Lire un événement** `GET /v1/partners/events/{id}` renvoie l'état d'un événement à partir de **votre** `id`. Pratique pour un écran de détail ou un contrôle ponctuel. ```bash curl "https://api.stg.dafexpert.com/v1/partners/events/PAY-2b77e0" \ -H "Authorization: Bearer $TOKEN" ``` ```json { "id": "PAY-2b77e0", "type": "paiement_producteur", "exploitation": "COOP-SOUBRE", "date": "2026-10-13", "montant": 1120000, "devise": null, "statut": "en_attente_piece", "erreur": "En attente de : ACH-6f1c2a9e.", "ecriture": null, "recu_le": "2026-10-13T08:42:10.114Z", "mis_a_jour_le": "2026-10-13T08:42:11.020Z", "version": 1, "en_attente_de": ["ACH-6f1c2a9e"] } ``` | Champ | Description | |---|---| | `id`, `type`, `date`, `montant`, `devise` | Ce que vous avez envoyé (version appliquée). | | `exploitation` | `external_id` de l'entreprise (`entreprise` en version 2). | | `statut` | Statut actuel, voir le tableau ci-dessus. | | `erreur` | Motif d'un rejet ou d'une attente, sinon `null`. | | `ecriture` | Version 1 : numéro de pièce de l'écriture passée. En version 2, regardez `effets`. | | `recu_le`, `mis_a_jour_le` | Dates ISO 8601 en UTC. | | `version` | Numéro de la version appliquée. | | `version_non_appliquee` | Présent si une version reçue n'a pas été appliquée : `{ version, message }`. | | `en_attente_de` | Version 2 : les `id` cités qui manquent encore. | | `effets` | Version 2 : un effet par entreprise et par rôle, avec `action` (`creee`, `rattachee`, `ecriture`), `statut` (`comptabilise`, `en_cours`, `rejete`), `piece` (`type`, `numero`, `numero_fiscal`), `ecriture` et `message`. | Un `id` inconnu dans votre espace renvoie `404`. **2. Synchroniser les changements** `GET /v1/partners/events?apres=&limite=100` renvoie les événements **modifiés** après le curseur, du plus ancien au plus récent. Au premier appel, n'envoyez pas `apres` : vous partez du début. ```bash curl "https://api.stg.dafexpert.com/v1/partners/events?limite=100" \ -H "Authorization: Bearer $TOKEN" ``` ```json { "evenements": [ { "id": "AVA-11c0", "type": "avance_producteur", "statut": "comptabilise", "mis_a_jour_le": "2026-06-02T07:10:04.221Z", "…": "…" }, { "id": "PAY-2b77e0", "type": "paiement_producteur", "statut": "comptabilise", "mis_a_jour_le": "2026-10-13T08:42:12.873Z", "…": "…" } ], "suivant": "MjAyNi0xMC0xM1QwODo0MjoxMi44NzNafDdmM2E…" } ``` | Paramètre | Règle | |---|---| | `apres` | Curseur opaque reçu dans `suivant`, à repasser tel quel. 200 caractères au plus. Un curseur mal formé renvoie `422`. | | `limite` | Nombre d'événements par page : 1 à 500, 100 par défaut. | | `statut` | Facultatif : ne renvoyer qu'un statut (par exemple `rejete`). | **3. Paginer et mémoriser le curseur** Tant que `suivant` n'est pas `null`, la page était pleine : rappelez immédiatement avec `apres=suivant`. Quand `suivant` vaut `null`, vous avez tout lu : **gardez le dernier curseur utilisé** et réessayez plus tard. **Node.js** ```javascript // Curseur persistant (base de données, fichier…) : null au tout premier lancement. let cursor = await store.get('dafexpert_events_cursor'); async function syncEvents() { for (;;) { const url = new URL('https://api.stg.dafexpert.com/v1/partners/events'); url.searchParams.set('limite', '500'); if (cursor) url.searchParams.set('apres', cursor); const res = await callApi(url); // gère le jeton et le 401 (voir Authentification) const { evenements, suivant } = await res.json(); for (const e of evenements) { // Idempotent : un même événement peut revenir plusieurs fois. await upsertLocalStatus(e.id, e.statut, e.erreur, e.mis_a_jour_le, e.effets); } if (!suivant) return; // tout est lu : on garde le curseur actuel cursor = suivant; await store.set('dafexpert_events_cursor', cursor); } } setInterval(syncEvents, 60_000); ``` > **tips** Quand `suivant` vaut `null`, les événements de la dernière page reviendront au prochain appel avec le même curseur. Traitez-les de façon idempotente : mettez à jour votre statut local par `id`, en ignorant une version dont `mis_a_jour_le` est plus ancienne que celle déjà enregistrée. **4. Traiter les rejets** Un événement `rejete` porte son motif dans `erreur` (et, en version 2, dans `effets[].message`). Affichez-le à vos utilisateurs et corrigez selon la cause. | Motif fréquent | Que faire | |---|---| | Devise différente de celle de l'entreprise | Renvoyer le même `id` avec la bonne devise (ou sans `devise`). | | Montant incohérent, donnée manquante | Renvoyer le même `id` corrigé : un événement rejeté accepte une nouvelle version. | | Pièce citée introuvable après 48 h | Émettre la pièce manquante (bordereau, facture), puis renvoyer l'événement. | | Doublon probable de l'événement … | Même opération déjà reçue sous un autre `id`. Si c'est bien une seconde opération, elle doit être confirmée dans dafexpert. | | Aucun exercice ouvert, période close, compte non paramétré | Correction côté dafexpert par le comptable de l'entreprise ; l'événement est ensuite relancé dans dafexpert. | | FNE non connectée, téléphone ou e-mail manquant | Compléter la fiche du tiers ou de l'entreprise, puis rejouer la pièce. | Pour ne récupérer que les rejets, filtrez : `GET /v1/partners/events?statut=rejete`. Utilisez un curseur distinct de celui de la synchronisation complète. ## Fréquence d'interrogation - Synchronisation complète : toutes les minutes suffit pour la plupart des intégrations. - Juste après un envoi, si votre écran attend le résultat : interrogez `GET /v1/partners/events/{id}` quelques fois, avec un délai croissant (1 s, 2 s, 5 s…). - Sur un `429` ou un `5xx`, attendez avant de réessayer. Voir [Limites de débit](/article/rate-limiting). ## Pièges courants **Repartir de zéro à chaque fois** Sans curseur mémorisé, chaque synchronisation relit tout l'historique. Enregistrez le curseur de façon durable, à côté de vos données. **Écraser le curseur par null** Quand `suivant` vaut `null`, ne remplacez pas votre curseur : vous perdriez votre position et reliriez tout depuis le début. **Considérer comptabilise comme définitif** Une `annulation` envoyée plus tard fait passer l'événement à `annule`. Laissez votre synchronisation tourner en continu. **Interroger chaque événement un par un** Pour un suivi de masse, la route de liste est bien plus économique que des milliers d'appels `GET /v1/partners/events/{id}`. Références : [GET /v1/partners/events/{id}](/article/get-event) et [GET /v1/partners/events](/article/list-events). --- # Émettre une pièce FNE URL: https://developers.stg.dafexpert.com/article/fne-documents > Émettre un bordereau d'achat de produits agricoles et une facture client certifiés FNE, puis récupérer leur PDF. > **warning** **Bientôt disponible.** Cette fonctionnalité est prête mais pas encore ouverte sur le staging. Le contrat décrit ici est celui qui sera publié. ## Objectif En Côte d'Ivoire, toute entreprise immatriculée doit délivrer des pièces certifiées par la plateforme de facture normalisée électronique (FNE) de la DGI. Avec l'API, dafexpert émet ces pièces pour vos entreprises, en un seul appel synchrone : numérotation, certification FNE, PDF avec numéro fiscal et QR code, et comptabilisation. Deux pièces sont disponibles : - le **bordereau d'achat de produits agricoles**, émis par l'acheteur (la coopérative) quand il achète à un producteur (modèle FNE « achat ») ; - la **facture client**, émise par l'entreprise quand elle vend (vente de cacao à un exportateur, intrants vendus à un producteur…). ## Prérequis - L'entreprise émettrice existe et sa réponse ne signale plus rien dans `a_completer` : voir [Créer ou mettre à jour une entreprise](/article/create-company). - Elle a connecté sa FNE dans dafexpert (établissement et point de vente renseignés). Cela se fait dans dafexpert, par l'entreprise, pas par l'API. - Un jeton d'accès valide : voir [Authentification](/article/authentication). > **info** Hors de Côte d'Ivoire, les mêmes routes émettent la pièce sans certification : son `statut` vaut `emise` et elle est comptabilisée tout de suite. ## Partie 1 : le bordereau d'achat **1. Préparer le bordereau** Le tiers est le producteur. Pour la FNE, il lui faut un téléphone ivoirien à 10 chiffres et une adresse e-mail. À défaut d'e-mail du producteur, dafexpert utilise l'e-mail du contact de l'acheteur. ```json { "id": "ACH-6f1c2a9e", "date": "2026-10-12", "tiers": { "role": "producteur", "id": "PROD-8c41d0", "entreprise": "EXPL-0457", "nom": "KOUASSI", "prenoms": "Yao Bernard", "telephone": "0707123456", "cni": "C0012345678", "id_producteur": "CCC-123456" }, "lignes": [ { "produit": "CACAO", "qualite": "grade_1", "quantite_pesee": 1250, "refaction_pct": 4, "quantite": 1200, "unite": "kg", "nombre_sacs": 20, "humidite_pct": 7.5, "prix_unitaire": 1100, "montant": 1320000 } ], "montant": 1320000, "paiement_prevu": "mobile_money", "reference_origine": "L-S12/10/2026", "analytique": { "campagne": "2026-2027", "parcelle": "SURF-2231" } } ``` | Champ | Obligatoire | Règle | |---|---|---| | `id` | oui | Identifiant de la pièce chez vous (1 à 100 caractères, `. _ : -`). Clé d'idempotence. | | `date` | oui | `AAAA-MM-JJ`. | | `tiers` | oui | `role` doit valoir `producteur`. `id` obligatoire, `nom` ou `raison_sociale` obligatoire. `entreprise` : `external_id` de l'exploitant s'il est suivi dans dafexpert. | | `lignes` | oui | 1 à 200 lignes. `produit` (60), `quantite` strictement positive, `prix_unitaire` et `montant` en FCFA entiers. `montant` = `quantite` × `prix_unitaire`, à 1 franc près. | | `lignes[].quantite_pesee`, `refaction_pct`, `nombre_sacs`, `humidite_pct`, `qualite`, `unite` | non | Traçabilité. `quantite` est la quantité **nette payée**, après réfaction. | | `montant` | oui | Somme exacte des montants des lignes. | | `paiement_prevu` | non | `especes`, `mobile_money`, `credit`, `virement` ou `cheque`. | | `reference_origine`, `analytique`, `donnees` | non | Référence lisible (120), axes `campagne`, `parcelle`, `groupement`, `produit` (60 chacun), données libres. | **2. Émettre le bordereau** Appelez `POST /v1/partners/companies/{external_id}/bordereaux`, où `{external_id}` est l'acheteur. L'appel est synchrone : comptez quelques secondes, le temps de la certification. **cURL** ```bash curl -X POST "https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/bordereaux" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d @bordereau-ACH-6f1c2a9e.json ``` **Node.js** ```javascript const res = await fetch( 'https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/bordereaux', { method: 'POST', headers: { Authorization: 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify(bordereau), }, ); const piece = await res.json(); // 201 à l'émission, 200 sur un rejeu ``` Réponse `201 Created` : ```json { "id": "ACH-6f1c2a9e", "type": "bordereau_achat", "piece_id": "0f9d2c1b-7a6e-4d5c-8b3a-2e1f0d9c8b7a", "statut": "certifie", "numero": "BA-ACH-6f1c2a9e", "numero_fiscal": "FNE-2026-000184512", "qr": "https://…", "lien_pdf": "/v1/partners/companies/COOP-SOUBRE/bordereaux/ACH-6f1c2a9e/pdf", "comptabilisation": "comptabilise", "montant": 1320000, "miroir": { "entreprise": "EXPL-0457", "statut": "comptabilise" } } ``` **3. Lire la réponse** La même structure sert au bordereau et à la facture. | Champ | Description | |---|---| | `id` | Votre identifiant de pièce. | | `type` | `bordereau_achat` ou `facture`. | | `piece_id` | Identifiant de la pièce dans dafexpert. | | `statut` | `certifie` (certifiée FNE), `non_certifie` (Côte d'Ivoire, certification pas encore obtenue) ou `emise` (pays sans certification). | | `numero` | Numéro de la pièce. Bordereau : `BA-` suivi de votre `id`. Facture : numérotation légale de l'entreprise. | | `numero_fiscal` | Numéro attribué par la FNE, `null` tant que la pièce n'est pas certifiée. | | `qr` | Lien de vérification FNE (contenu du QR code), `null` sans certification. | | `lien_pdf` | Route de l'API qui donne le lien de téléchargement du PDF (étape 4). | | `comptabilisation` | `comptabilise` ou `non_comptabilise`. En Côte d'Ivoire, la pièce n'est comptabilisée qu'une fois certifiée. | | `montant` | Total de la pièce en FCFA (TTC pour une facture). | | `erreur` | Présent si la certification ou la comptabilisation a échoué : motif en français. | | `miroir` | Bordereau seulement, si le producteur est une entreprise suivie : la vente créée chez lui (`entreprise`, `statut`, `numero`, `erreur`). | **4. Récupérer le PDF** Appelez la route de `lien_pdf` avec votre jeton. Elle renvoie un lien de téléchargement valable **5 minutes** (`expiresIn` en secondes). Le PDF certifié est servi s'il existe, sinon l'original. ```bash curl "https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/bordereaux/ACH-6f1c2a9e/pdf" \ -H "Authorization: Bearer $TOKEN" ``` ```json { "url": "https://…", "expiresIn": 300 } ``` > **tips** Ne stockez pas `url` : il expire. Stockez `lien_pdf` et redemandez un lien au moment d'afficher ou d'imprimer le bordereau. Remettez le bordereau au producteur (impression ou envoi par SMS depuis votre application). ## Partie 2 : la facture client **5. Préparer la facture** Le tiers est le client. Les montants des lignes sont **hors taxes** ; `montant` est le **total TTC**. `taux_tva` vaut 0 par défaut. ```json { "id": "VTE-90aa13", "date": "2026-11-03", "echeance": "2026-11-18", "tiers": { "role": "client", "id": "CLI-0021", "raison_sociale": "Ivoire Cacao Export SA", "ncc": "3045678B", "telephone": "2721000000", "email": "achats@ivoirecacao-export.ci" }, "lignes": [ { "produit": "CACAO", "qualite": "grade_1", "quantite": 25000, "unite": "kg", "prix_unitaire": 1450, "taux_tva": 0, "montant": 36250000 }, { "description": "Sacs de conditionnement", "quantite": 100, "unite": "sac", "prix_unitaire": 2500, "taux_tva": 18, "montant": 250000 } ], "montant": 36545000, "mode_paiement": "virement", "reference_origine": "C-K112/11/2026" } ``` Contrôle du total : 36 250 000 + 250 000 HT, plus 18 % de TVA sur 250 000 (45 000), soit 36 545 000 FCFA TTC. | Champ | Obligatoire | Règle | |---|---|---| | `id`, `date` | oui | Comme pour le bordereau. | | `echeance` | non | `AAAA-MM-JJ`. | | `tiers` | oui | `role` doit valoir `client`. `id` obligatoire, `nom` ou `raison_sociale` obligatoire. `pays` (deux lettres) si le client est étranger. | | `lignes` | oui | 1 à 200 lignes. `produit` ou `description` (255) obligatoire, `quantite` strictement positive, `prix_unitaire` et `montant` (HT) en FCFA entiers, `taux_tva` de 0 à 100. | | `montant` | oui | Total TTC : somme des HT plus la TVA de chaque ligne arrondie au franc. | | `mode_paiement` | non | `especes`, `mobile_money`, `virement`, `cheque` ou `carte`. | ### Le modèle FNE est déduit du client | Client | Modèle FNE | Exigences | |---|---|---| | `raison_sociale` ou `ncc` renseigné, en Côte d'Ivoire | B2B | NCC de 8 caractères, e-mail et téléphone ivoirien à 10 chiffres obligatoires. | | Particulier ou producteur (ni `raison_sociale` ni `ncc`) | B2C | Pas d'exigence supplémentaire. | | `pays` différent de `CI` | B2F | Client à l'étranger. | **6. Émettre la facture** Appelez `POST /v1/partners/companies/{external_id}/invoices`. La facture est numérotée, certifiée, passée à l'état « envoyée » et comptabilisée (4111 client, ventes, TVA facturée). ```bash curl -X POST "https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/invoices" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d @facture-VTE-90aa13.json ``` ```json { "id": "VTE-90aa13", "type": "facture", "piece_id": "6a4b2c1d-9e8f-4a7b-b6c5-d4e3f2a1b0c9", "statut": "certifie", "numero": "FAC-2026-0412", "numero_fiscal": "FNE-2026-000193877", "qr": "https://…", "lien_pdf": "/v1/partners/companies/COOP-SOUBRE/invoices/VTE-90aa13/pdf", "comptabilisation": "comptabilise", "montant": 36545000 } ``` Le PDF s'obtient comme pour le bordereau, avec `GET /v1/partners/companies/{external_id}/invoices/{id}/pdf`. Le paiement du client s'envoie ensuite en événement `encaissement_client` qui cite `VTE-90aa13` dans `lie_a`. ## Relire et rejouer une pièce - `GET /v1/partners/companies/{external_id}/bordereaux/{id}` et `GET /v1/partners/companies/{external_id}/invoices/{id}` renvoient l'état actuel de la pièce, dans le même format. - **Rejouer** l'émission avec le même `id` ne crée jamais de seconde pièce : la réponse est `200` avec la même pièce et le même numéro fiscal. Si la pièce n'était pas encore certifiée ou comptabilisée, le rejeu relance ce qui manque. - Un appel concurrent sur le même `id`, pendant l'émission, reçoit `409` : réessayez quelques secondes plus tard avec le même `id`. ## Quand la certification échoue La pièce est quand même enregistrée, avec `statut: non_certifie`, `comptabilisation: non_comptabilise` et le motif dans `erreur`. Elle apparaît dans les écarts de l'entreprise dans dafexpert. | Motif | Que faire | |---|---| | FNE non connectée pour cette entreprise | L'entreprise connecte sa FNE dans dafexpert, puis vous rejouez l'émission. | | Connecteur FNE : établissement et point de vente à renseigner | À compléter dans dafexpert par l'entreprise. | | Téléphone ivoirien à 10 chiffres obligatoire | Corriger le téléphone du producteur ou du client, puis rejouer. | | NCC de 8 caractères obligatoire (facture B2B) | Corriger le `ncc` du client, puis rejouer. | | Certification déjà en cours ou issue inconnue | Vérifier sur la plateforme FNE avant de relancer : dafexpert ne renvoie jamais automatiquement une certification dont l'issue est inconnue. | | Refus de la plateforme FNE | Le motif de la DGI est repris dans `erreur`. | > **warning** Les coordonnées d'un tiers déjà connu ne sont jamais écrasées, seulement complétées si elles manquaient. Si un téléphone erroné a été enregistré, il se corrige dans dafexpert. ## Erreurs | Code | Cas | |---|---| | `201` | Pièce émise. | | `200` | Rejeu : la pièce existait déjà pour cet `id`. | | `404` | Entreprise inconnue (à créer d'abord), pièce inconnue pour cette entreprise, ou PDF pas encore disponible. | | `409` | Pièce en cours d'émission, ou espace sans propriétaire ni administrateur actif. | | `422` | Données invalides : rôle du tiers, total des lignes, montant TTC, format de date… | ## Pièges courants **Recalculer le prix au moment de l'envoi** Envoyez le prix **appliqué** le jour de la pesée, figé. Un prix recalculé au cours du jour fausse la pièce fiscale, qui ne peut plus être modifiée une fois certifiée. **Utiliser un nouvel id pour réessayer** Un nouvel `id` émet une nouvelle pièce, avec un nouveau numéro fiscal et une nouvelle certification FNE. Pour réessayer, gardez toujours le même `id`. **Décimales dans les montants** Les montants sont des FCFA entiers. Une quantité peut être décimale (1 200,5 kg) ; le montant de la ligne, lui, est arrondi au franc. **Annuler une pièce certifiée** Une pièce certifiée ne se supprime pas. La route d'avoir partenaire n'est pas encore disponible (Bientôt) : l'avoir s'émet dans dafexpert. ## Et ensuite 1. [Envoyer le paiement](/article/send-events) qui solde le bordereau. 1. [Suivre le traitement des événements](/article/track-events). Références : [POST bordereaux](/article/post-bordereau), [GET bordereau](/article/get-bordereau), [PDF du bordereau](/article/get-bordereau-pdf), [POST invoices](/article/post-invoice), [GET invoice](/article/get-invoice), [PDF de la facture](/article/get-invoice-pdf). --- # Brancher un assistant IA (MCP) URL: https://developers.stg.dafexpert.com/article/connect-ai-assistant > Ajouter le serveur MCP de dafexpert dans Claude pour interroger ses dossiers en langage naturel, en lecture seule. ## Objectif Le Model Context Protocol (MCP) est un standard ouvert qui permet à un assistant IA d'utiliser des outils externes. dafexpert expose un serveur MCP : une fois branché, l'assistant peut répondre à des questions comme « Quelle est la trésorerie de la coopérative de Soubré ? » ou « Liste les factures en retard » à partir des vraies données de vos dossiers. Ce tutoriel s'adresse aux **utilisateurs** de dafexpert (et aux éditeurs qui veulent le documenter pour leurs clients). Il ne demande aucun identifiant partenaire : l'utilisateur se connecte avec son propre compte dafexpert. ## Ce que l'assistant peut faire | Outil | Ce qu'il renvoie | |---|---| | `list_companies` | Les dossiers (entreprises) accessibles à l'utilisateur, avec devise, pays, rôle et solde. | | `list_exercises` | Les exercices d'un dossier : dates, statut ouvert ou clôturé, référentiel. | | `get_balance` | La balance générale d'un exercice (débit, crédit, solde N et N-1 par compte), éventuellement bornée à une période. | | `get_balance_anomalies` | Les contrôles de révision sur la balance (soldes anormaux, comptes d'attente…) avec une conclusion. | | `get_income_statement` | Le compte de résultat par postes du référentiel. | | `list_invoices` | Les factures clients, avec filtre facultatif par statut. | | `get_invoice` | Le détail d'une facture : lignes, totaux, client, paiements. | | `get_treasury_overview` | La trésorerie : solde, encaissements et décaissements sur 7 jours, 30 jours ou 1 an, échéances à venir. | > **info** Le serveur est en **lecture seule** : l'assistant ne peut ni créer, ni modifier, ni supprimer quoi que ce soit. Il ne voit que les dossiers auxquels l'utilisateur connecté a accès, avec ses droits. ## Prérequis - Un compte dafexpert avec accès à au moins une entreprise. - Un assistant compatible MCP avec connexion OAuth : Claude (web, application de bureau) ou Claude Code. ## Informations de connexion | Élément | Valeur | |---|---| | URL du serveur (staging) | `https://api.stg.dafexpert.com/mcp` | | URL du serveur (production) | communiquée à la mise en production | | Transport | HTTP diffusable (Streamable HTTP), requêtes `POST` uniquement | | Authentification | OAuth 2.0, code d'autorisation avec PKCE (S256) | | Identifiant client OAuth (Client ID) | `dafexpert-mcp` | | Secret client | aucun (client public) | L'assistant découvre seul le serveur d'autorisation : au premier appel sans jeton, le serveur MCP répond `401` avec un en-tête `WWW-Authenticate` qui pointe vers `/.well-known/oauth-protected-resource/mcp`. > **warning** dafexpert ne propose pas l'enregistrement dynamique de clients OAuth. Renseignez donc toujours l'identifiant client `dafexpert-mcp` dans les paramètres avancés de votre assistant, sinon la connexion échoue. ## Étapes dans Claude (web et bureau) **1. Ouvrir les connecteurs** Dans Claude, ouvrez **Paramètres**, puis **Connecteurs**, et choisissez **Ajouter un connecteur personnalisé**. **2. Renseigner le serveur** Nom : `dafexpert`. URL : `https://api.stg.dafexpert.com/mcp`. Ouvrez les **paramètres avancés** et saisissez l'identifiant client OAuth `dafexpert-mcp`. Laissez le secret vide. **3. Se connecter** Cliquez sur **Se connecter**. La page de connexion de dafexpert s'ouvre : connectez-vous avec votre compte habituel, puis acceptez l'écran de consentement « Accès en lecture à vos dossiers DafExpert (comptabilité, factures, trésorerie) ». **4. Activer le connecteur dans une conversation** Dans une nouvelle conversation, activez le connecteur `dafexpert` dans le menu des outils, puis posez votre question. ## Étapes dans Claude Code **1. Ajouter le serveur** Dans un terminal : ```bash claude mcp add --transport http --client-id dafexpert-mcp dafexpert https://api.stg.dafexpert.com/mcp ``` **2. S'authentifier** Dans Claude Code, tapez `/mcp`, choisissez `dafexpert` puis l'authentification. Le navigateur s'ouvre sur la page de connexion de dafexpert ; le retour se fait sur une adresse locale (`localhost`). ## Autres assistants Le client OAuth `dafexpert-mcp` n'accepte pour l'instant que les adresses de retour de Claude (claude.ai et claude.com), les adresses locales (`localhost`, `127.0.0.1`) et l'outil de test MCP Inspector (`http://localhost:6274`). | Assistant | État | |---|---| | Claude (web, bureau) | Disponible | | Claude Code | Disponible | | MCP Inspector (tests) | Disponible | | ChatGPT | Bientôt | | Cursor | Bientôt | > **note** Un autre client MCP qui fait son retour OAuth sur une adresse locale peut fonctionner, mais n'est pas encore testé par dafexpert. ## Exemples de questions - « Liste mes dossiers et leur devise. » - « Pour la Société Coopérative Agricole de Soubré, donne-moi le compte de résultat de l'exercice 2026. » - « Y a-t-il des anomalies dans la balance de l'exercice en cours ? » - « Quelles factures clients sont en retard, et pour quel montant ? » - « Quels encaissements sont prévus dans les 30 prochains jours ? » ### Montants Les champs dont le nom finit par `Cents` sont en centimes de la devise du dossier, y compris pour le FCFA : `132000000` vaut 1 320 000 FCFA. L'assistant en est informé par le serveur, mais vérifiez les ordres de grandeur. Voir [Montants et devises](/article/amounts-currencies). ## Dépannage **La connexion échoue avant la page dafexpert** L'identifiant client n'a pas été renseigné. Supprimez le connecteur, puis ajoutez-le de nouveau en saisissant `dafexpert-mcp` dans les paramètres avancés. **Erreur 401 après un certain temps** La session a expiré. Reconnectez le connecteur (Claude) ou relancez l'authentification avec `/mcp` (Claude Code). **Un dossier n'apparaît pas** L'assistant ne voit que les entreprises de votre compte. Vérifiez dans dafexpert que vous y avez bien accès, ou demandez l'accès à l'administrateur de l'espace. **Erreur 405 en ouvrant l'URL dans un navigateur** Normal : le serveur MCP ne répond qu'aux requêtes `POST` envoyées par un assistant. Une requête `GET` renvoie `405 Method not allowed`. ## Et ensuite 1. Parcourir la [référence du serveur MCP](/article/mcp-overview) : paramètres et réponses de chaque outil. 1. Découvrir les autres [cas d'usage](/article/use-cases). --- # Espaces et entreprises URL: https://developers.stg.dafexpert.com/article/workspaces-companies > Comment dafexpert organise vos clients : un espace par organisation, une entreprise par comptabilité, et le lien avec vos identifiants externes. Deux notions structurent toute l'API : l'**espace**, qui dit *qui travaille*, et l'**entreprise**, qui dit *sur quelle comptabilité*. Les comprendre évite la plupart des erreurs `403` et `404`. ## L'espace Un espace est une organisation cliente de dafexpert : par exemple une fédération de coopératives, une interprofession ou un programme de filière. Il regroupe des membres et des entreprises. - Votre application est **connectée à un espace** par son administrateur. Il vous transmet un `client_id` et un `client_secret` propres à cet espace : voir [Obtenir ses identifiants](/article/credentials). - L'espace est **déduit de vos identifiants**. Il n'apparaît jamais dans les URL ni dans les corps de requête. - Si votre application sert plusieurs clients, vous avez une paire d'identifiants **par espace**. Utilisez toujours la bonne paire pour les données de chaque client. `GET /v1/partners/me` confirme à quel espace vos identifiants donnent accès : ```json { "partner": "gsmiles-agri", "workspaceId": "9d3f6c1a-2b4e-4f80-a1c7-5e6d8b9a0f12", "apiVersion": 1 } ``` ## L'entreprise Une entreprise a **sa propre comptabilité** : ses exercices et périodes, ses journaux, son plan comptable, ses tiers, ses pièces. Une coopérative est une entreprise ; un exploitant suivi dans dafexpert en est une autre. | Type | Exemple | Parent possible | Système comptable par défaut | |---|---|---|---| | `union` | Union de coopératives d'une région | aucun | `normal` | | `cooperative` | Société coopérative de producteurs de cacao | une `union` | `normal` | | `acheteur` | Traitant, exportateur | aucun | `normal` | | `exploitant` | Planteur suivi individuellement | une `cooperative` | `smt` (système minimal de trésorerie) | La hiérarchie union → coopérative → exploitant sert au rattachement et aux regroupements. Chaque niveau garde sa propre comptabilité : une union ne consolide pas automatiquement la comptabilité de ses coopératives. ### Identifiant externe et identifiant dafexpert Vous désignez toujours une entreprise par **votre** identifiant, l'`external_id` : dans l'URL des routes d'entreprise et de pièces, dans le champ `entreprise` (ou `exploitation` en version 1) des événements, dans `acces[].exploitation` des utilisateurs. dafexpert renvoie en plus son propre identifiant, `company_id`, utile pour vos échanges avec le support. | Vous envoyez | dafexpert renvoie | |---|---| | `external_id` : `COOP-SOUBRE` | `company_id` : `ec6a85db-3f1e-4c55-9a0e-2b7d1f0c9a41` | > **warning** Un `external_id` appartient à un seul espace. L'utiliser avec les identifiants d'un autre espace renvoie `403` pour une entreprise ou un utilisateur, et `refuse` pour un événement. ## Les utilisateurs Les personnes que vous créez avec `PUT /v1/partners/users/{external_id}` deviennent **membres simples** de l'espace. Elles ne voient que les entreprises listées dans leurs accès, avec un rôle par entreprise. Elles ne deviennent jamais propriétaires ni administratrices de l'espace : un gérant de coopérative ne voit donc pas les autres coopératives de la fédération. Voir [Gérer les utilisateurs](/article/manage-users). Les entreprises que vous créez appartiennent à l'espace. Pour les créer, l'espace doit avoir au moins un propriétaire ou un administrateur actif, sinon l'appel renvoie `409`. ## Entreprise ou simple tiers ? Un producteur, un fournisseur ou un client apparaît dans vos événements sous forme de `tiers`. Il devient : - un **tiers** dans la comptabilité de l'entreprise qui réalise l'opération : fournisseur pour un producteur ou un fournisseur d'intrants, client pour un acheteur. Il est créé au premier envoi puis retrouvé grâce à son `tiers.id` (ou à son NCC) ; - **et**, si `tiers.entreprise` est renseigné, une **entreprise** de l'espace dont la comptabilité reçoit la pièce miroir (par exemple la vente de cacao chez l'exploitant, en face du bordereau d'achat de la coopérative). | Opération | Chez l'entreprise | Chez `tiers.entreprise` | |---|---|---| | Bordereau d'achat (achat au producteur) | Bordereau certifié FNE, achat | Vente sans FNE, justifiée par le numéro fiscal du bordereau | Les coordonnées d'un tiers déjà connu ne sont jamais écrasées par vos envois : seules les informations manquantes (téléphone, e-mail, NCC) sont complétées. ## En résumé - **Un espace** par organisation cliente, **une paire d'identifiants** par espace. - **Une entreprise** par comptabilité, désignée par votre `external_id`. - Des **utilisateurs** membres simples, avec des accès par entreprise. - Des **tiers** propres à chaque entreprise, éventuellement reliés à une autre entreprise de l'espace. À lire ensuite : [Référentiels comptables](/article/accounting-frameworks) et [Créer ou mettre à jour une entreprise](/article/create-company). --- # Référentiels comptables URL: https://developers.stg.dafexpert.com/article/accounting-frameworks > SYSCOHADA, SYCEBNL et PCG : comment le pays d'une entreprise fixe son référentiel, sa devise et les comptes utilisés pour vos événements. Chaque entreprise tient sa comptabilité selon un **référentiel comptable** : le plan de comptes, les journaux, les états financiers et les contrôles qui s'appliquent. Vous n'envoyez jamais de numéro de compte : dafexpert choisit les comptes selon le référentiel de l'entreprise. ## Les trois référentiels | Référentiel | Pour qui | Zone | |---|---|---| | `SYSCOHADA` | Entreprises (sociétés, coopératives, exploitants…). Système normal ou système minimal de trésorerie (SMT). | Les 17 pays de l'OHADA | | `SYCEBNL` | Entités à but non lucratif : associations, ONG, fondations, congrégations. | OHADA | | `PCG` | Plan comptable général français. | France | ## Comment le référentiel est choisi À la création d'une entreprise par l'API, le référentiel est **déduit du pays** (`pays`) : - `FR` → `PCG` ; - tout autre pays pris en charge → `SYSCOHADA`. La réponse de `PUT /v1/partners/companies/{external_id}` l'indique dans `referentiel`. Il est figé ensuite, comme le pays et la devise. > **info** `SYCEBNL` ne s'obtient pas par l'API partenaires aujourd'hui : il est retenu quand une entité à but non lucratif est créée dans dafexpert. Les coopératives, unions, acheteurs et exploitants relèvent du `SYSCOHADA`. ### Pays pris en charge | Zone | Pays (`pays`) | Devise | Référentiel | |---|---|---|---| | UEMOA | `CI`, `BJ`, `BF`, `GW`, `ML`, `NE`, `SN`, `TG` | `XOF` (franc CFA BCEAO) | `SYSCOHADA` | | CEMAC | `CM`, `CF`, `CG`, `GA`, `GQ`, `TD` | `XAF` (franc CFA BEAC) | `SYSCOHADA` | | Autres pays OHADA | `KM`, `GN`, `CD` | `KMF`, `GNF`, `CDF` | `SYSCOHADA` | | France | `FR` | `EUR` | `PCG` | Un pays absent de cette liste renvoie `422` (« Pays inconnu »). ## Système normal et système minimal de trésorerie En SYSCOHADA, le champ `systeme_comptable` choisit la présentation des états financiers du premier exercice : - `normal` : bilan, compte de résultat, tableau des flux de trésorerie et notes annexes complets. Valeur par défaut pour une coopérative, une union ou un acheteur. - `smt` : système minimal de trésorerie, une comptabilité allégée tenue à partir des encaissements et des décaissements. Valeur par défaut pour un exploitant. Le système est figé à la création, avec le premier exercice. ## Le premier exercice À la création, dafexpert ouvre le premier exercice de l'entreprise, avec ses périodes mensuelles, ses journaux et son plan comptable : - il commence à `debut_exercice` (le 1er d'un mois), par défaut le 1er janvier de l'année en cours ; - il dure 12 mois : `2026-10-01` donne un exercice d'octobre 2026 à septembre 2027, pratique pour suivre une campagne. Un événement daté hors de tout exercice ouvert est rejeté avec un motif explicite. ## Comptes utilisés pour vos événements Les événements et les pièces sont comptabilisés sur des comptes par défaut propres à chaque référentiel. Chaque entreprise peut les adapter dans dafexpert (Paramètres, comptes de comptabilisation), par exemple pour distinguer le 601 (négoce) du 602 (transformation). | Usage | SYSCOHADA | PCG | |---|---|---| | Achats de produits agricoles (bordereau) | 601 | 607 | | Ventes de produits | 702 | 701 | | Fournisseurs, producteurs | 4011 | 401 | | Clients | 4111 | 4111 | | Avances versées aux producteurs | 4091 | 4091 | | Intrants achetés ou remis en avance | 604 | 602 | | Matériel (ligne `immobilisation`) | 241 | 215 | | Salaires bruts | 661 | 641 | | Charges patronales | 664 | 645 | | Personnel, rémunérations dues | 422 | 421 | | Organismes sociaux | 431 | 431 | | État, impôts retenus | 447 | 447 | | Capital souscrit | 1013 | 1013 | | Capital souscrit, appelé | 4613 | 4562 | | Cotisations des membres | 7588 | 758 | | Dépenses : transport | 618 | 6241 | | Dépenses : carburant | 6042 | 6061 | | Dépenses : main-d'œuvre | 637 | 621 | | Dépenses : autres | 605 | 6068 | > **tips** La trésorerie (caisse, banque, monnaie électronique) utilise les comptes de trésorerie par défaut de l'entreprise. Le code `caisse` que vous envoyez est conservé mais n'est pas encore rattaché à un journal propre. ## Ce que cela change pour vous - Envoyez des **natures** et des **produits**, jamais des comptes : le même événement fonctionne quel que soit le référentiel. - Vérifiez `referentiel` et `devise` dans la réponse de création : ce sont eux qui gouvernent la suite. - Un rejet « compte non paramétré » se corrige dans dafexpert par le comptable de l'entreprise, pas dans votre envoi. À lire ensuite : [Montants et devises](/article/amounts-currencies) et [Catalogue des événements](/article/event-catalog). --- # Catalogue des événements URL: https://developers.stg.dafexpert.com/article/event-catalog > Tous les types d'événements de la version 2 : à quoi ils servent, leurs champs clés, leurs contrôles et ce qu'ils créent dans dafexpert. > **warning** **Bientôt disponible.** Cette fonctionnalité est prête mais pas encore ouverte sur le staging. Le contrat décrit ici est celui qui sera publié. Un événement correspond à une opération validée dans votre application, qui a un impact financier. Ce catalogue décrit les types acceptés par la version 2 du contrat (`"version": 2`). Pour un exemple complet d'envoi, voir [Envoyer des événements comptables](/article/send-events). > **info** Un type absent de ce catalogue est refusé (`422`, ou `invalide` dans un lot). La version 1 accepte des types libres : voir [Événements v1 et v2](/article/event-versions). ## Vue d'ensemble | Type | Opération | Ce que dafexpert crée | |---|---|---| | `paiement_producteur` | Paiement d'un ou plusieurs achats à un producteur (espèces, mobile money…), avec retenues éventuelles. | Règlement fournisseur lettré avec les bordereaux, écriture de compensation pour les retenues. | | `avance_producteur` | Avance ou crédit de campagne en argent. | Écriture : 4091 avances au débit, trésorerie au crédit. | | `remboursement_producteur` | Remboursement en argent d'une avance ou d'intrants, hors paiement d'achat. | Encaissement lettré avec les factures d'intrants citées, sinon écriture sur 4091. | | `encaissement_client` | Paiement reçu d'un client. | Règlement client lettré avec les factures citées. | | `cession_intrant` | Intrants remis à un producteur en avance (à rembourser). | Écriture d'avance en nature : 4091 au débit, 604 au crédit. | | `achat_fournisseur` | Facture d'un fournisseur (intrants, marchandises, services, matériel). | Facture fournisseur ventilée par nature et comptabilisée, ou rattachement à la facture déjà présente. | | `depense` | Dépense de caisse : transport, main-d'œuvre, carburant… | Écriture de charge payée. | | `paie` | Clôture d'une période de paie. | Écriture de paie : brut, charges, net, cotisations, impôts. | | `souscription_parts` | Souscription et libération de parts sociales. | Écriture de capital, puis encaissement de la part libérée. | | `cotisation` | Droit d'adhésion ou cotisation d'un membre. | Encaissement et produit. | | `inventaire` | Quantités comptées en fin de campagne ou d'exercice. | Rien pour l'instant : enregistré `non_comptable`. | | `annulation` | Correction d'un événement déjà envoyé. | Contre-passation des écritures et suppression des règlements de l'événement cité. | | `recolte` | Bulletin de récolte (traçabilité). | Aucune pièce : statut `non_comptable`. | ### Achats et ventes : des pièces, pas des événements L'achat à un producteur et la vente à un client produisent une **pièce fiscale** que vous devez remettre tout de suite. Ils passent donc par des routes synchrones, pas par les événements : | Opération | Route | Pièce | |---|---|---| | Achat de produits à un producteur (ex. `achat_produit`) | `POST /v1/partners/companies/{external_id}/bordereaux` | Bordereau d'achat certifié FNE | | Vente à un client (ex. `vente_produit`), intrants vendus | `POST /v1/partners/companies/{external_id}/invoices` | Facture client certifiée FNE | Les événements citent ensuite ces pièces par leur `id`. Voir [Émettre une pièce FNE](/article/fne-documents). ## Enveloppe commune Tous les types partagent ces champs. | Champ | Obligatoire | Règle | |---|---|---| | `version` | oui | `2` (ou hérité du lot). | | `id` | oui | Identifiant stable et unique chez vous (1 à 100 caractères, `. _ : -`). Clé anti-doublon. | | `type` | oui | Un type du catalogue. | | `date` | oui | Date de l'opération, `AAAA-MM-JJ`. Elle détermine l'exercice et la période. | | `entreprise` | oui | `external_id` de l'entreprise qui réalise l'opération : le payeur, l'acheteur, l'employeur. | | `montant` | oui | Total de contrôle en FCFA entiers, positif ou nul. Sa règle de calcul dépend du type. | | `devise` | non | Code ISO à trois lettres. S'il est fourni, ce doit être la devise de l'entreprise. | | `reference_origine` | non | Référence lisible chez vous (n° de bon, de lot), 120 caractères. | | `analytique` | non | `campagne`, `parcelle`, `groupement`, `produit` (60 caractères chacun). | | `donnees` | non | Objet libre, 32 000 caractères au plus une fois sérialisé. Conservé, non utilisé pour la comptabilisation. | ### Le tiers | Champ | Règle | |---|---| | `role` | `producteur`, `fournisseur`, `client`, `employe` ou `prestataire`. | | `id` | Obligatoire. Identifiant du tiers chez vous : il sert à le retrouver d'un envoi à l'autre. | | `nom` ou `raison_sociale` | L'un des deux est obligatoire (200 caractères). `prenoms` : 120. | | `entreprise` | `external_id` du tiers s'il est aussi une entreprise de l'espace. | | `telephone` | 40 caractères. Indispensable pour un producteur (pièces FNE). | | `email` | Adresse valide. | | `cni`, `id_producteur` | 40 et 60 caractères. Recommandés pour reconnaître un même producteur. | | `ncc` | 30 caractères. Indispensable pour un client professionnel. | | `pays` | Code ISO à deux lettres. | ### Le paiement Les types qui portent un mouvement d'argent (`paiement_producteur`, `avance_producteur`, `remboursement_producteur`, `encaissement_client`, `depense`, `cotisation`) ont aussi : | Champ | Obligatoire | Règle | |---|---|---| | `mode` | oui | `especes`, `mobile_money`, `virement` ou `cheque`. | | `caisse` | oui | Code de la caisse ou du compte de paiement chez vous (format d'identifiant). | | `operateur` | non | Opérateur mobile money (`orange_money`, `mtn`, `wave`…), 40 caractères. | | `reference_transaction` | si `mobile_money` | Référence de la transaction, 120 caractères. | ## Champs et contrôles par type | Type | Champs propres | Contrôle de `montant` | |---|---|---| | `paiement_producteur` | `tiers`, `lie_a` (1 à 200 : `{ id, montant }`, les bordereaux payés et le brut affecté), `retenues` (50 au plus : `{ nature, lie_a, montant }`, nature `avance`, `intrants` ou `cotisation`), paiement. | Somme de `lie_a` moins somme des `retenues` (net versé). | | `avance_producteur` | `tiers`, `remboursement` (`retenue_sur_achats` ou `especes`), paiement. | Montant de l'avance. | | `remboursement_producteur` | `tiers`, `lie_a` (avances ou factures d'intrants remboursées), paiement. | Somme de `lie_a`. | | `encaissement_client` | `tiers`, `lie_a` (factures payées), paiement. | Somme de `lie_a`. | | `cession_intrant` | `tiers`, `lignes` (`produit`, `quantite`, `unite`, `prix_unitaire`, `montant`), `a_credit`, `remboursement`, `financement`. | Somme des lignes. | | `achat_fournisseur` | `tiers`, `facture_fournisseur` (`numero` obligatoire, `numero_fiscal`, `date`, `echeance`), `lignes` (`nature` : `intrant`, `marchandise`, `service`, `immobilisation` ; `montant` HT ; `taux_tva` ; `duree_annees`), `piece_jointe` (URL). | Somme des montants HT des lignes. dafexpert calcule la TVA. | | `depense` | `tiers` (facultatif), `lignes` (`nature` en texte libre, `libelle`, `montant`), `piece_jointe`, paiement. | Somme des lignes. | | `paie` | `periode` (`AAAA-MM`), `lignes` (1 à 2 000 : `employe { id, nom }`, `brut`, `retenues_salariales`, `charges_patronales`, `net`). | Somme des nets ; chaque `net` = `brut` moins retenues salariales. | | `souscription_parts` | `tiers`, `nombre_parts`, `valeur_nominale`, `montant_libere`, `mode` et `caisse` si une part est libérée. | `nombre_parts` × `valeur_nominale` ; `montant_libere` au plus égal. | | `cotisation` | `tiers`, `nature` (`droit_adhesion`, `cotisation_annuelle`, `autre`), `periode`, paiement. | Montant de la cotisation. | | `inventaire` | `lignes` (`produit`, `entrepot`, `quantite`, `unite`). | Libre (souvent 0). | | `annulation` | `lie_a` (exactement un `id` d'événement), `motif` (300 caractères). | Libre (souvent 0). | | `recolte` | `tiers` (facultatif). | Libre. | ### Comment sont classées les dépenses Pour `depense`, la `nature` de chaque ligne choisit le compte de charge : un texte contenant transport, déplacement ou fret va en transport ; carburant, essence, gasoil ou gazole en carburant ; main-d'œuvre, manœuvre, journalier ou ouvrier en main-d'œuvre ; tout le reste en autres charges. Sans `piece_jointe`, la dépense est signalée « Justificatif manquant », sans être bloquée. ### Comment sont réparties les retenues de paie Pour `paie`, une retenue salariale dont la `nature` évoque la protection sociale (cnps, cnss, social, retraite, santé) va aux organismes sociaux ; les autres (ITS…) vont à l'État. ## Références entre événements `lie_a` et `retenues[].lie_a` citent des `id` que vous avez déjà envoyés : - un bordereau ou une facture émis par les routes de pièces ; - un événement déjà comptabilisé (une avance, une cession d'intrants). Si l'un d'eux manque, l'événement attend en `en_attente_piece` et la liste des manquants apparaît dans `en_attente_de`. Il est libéré à l'arrivée de la pièce, ou passe `rejete` après 48 h. ```json { "version": 2, "id": "ENC-77d1", "type": "encaissement_client", "date": "2026-11-18", "entreprise": "COOP-SOUBRE", "tiers": { "role": "client", "id": "CLI-0021", "raison_sociale": "Ivoire Cacao Export SA" }, "lie_a": [{ "id": "VTE-90aa13", "montant": 36545000 }], "montant": 36545000, "mode": "virement", "reference_transaction": "VIR-SGCI-20261118-0042", "caisse": "BANQUE-SGCI" } ``` ## Limites actuelles - `inventaire` est enregistré sans écriture : la valorisation des stocks à la clôture n'est pas encore automatique. - `cession_intrant` est traité comme une avance en nature. Des intrants **vendus** passent par la route des factures. - Une ligne `immobilisation` de `achat_fournisseur` est comptabilisée en matériel ; sa fiche d'immobilisation est à compléter dans dafexpert. - Le versement des salaires après une `paie` n'a pas encore de type d'événement. - Pour un paiement producteur, l'encaissement miroir chez l'exploitant suivi n'est pas encore créé automatiquement. - L'annulation d'un `achat_fournisseur` ne supprime pas la facture fournisseur créée : un avoir est à saisir dans dafexpert. Toutes les longueurs maximales sont dans [Limites des champs](/article/field-limits). --- # Événements v1 et v2 URL: https://developers.stg.dafexpert.com/article/event-versions > Les deux versions du contrat d'événements, les versions successives d'un même événement, les effets par entreprise et les écarts. > **warning** **Bientôt disponible.** Cette fonctionnalité est prête mais pas encore ouverte sur le staging. Le contrat décrit ici est celui qui sera publié. Le mot « version » recouvre deux notions différentes dans l'API des événements : - la **version du contrat** (`"version": 1` ou `"version": 2`), qui fixe la forme de l'événement ; - la **version d'un événement** (1, 2, 3…), qui compte les envois successifs d'un même `id` avec un contenu différent. ## Contrat v1 et contrat v2 | | Version 1 | Version 2 | |---|---|---| | Entreprise | `exploitation` | `entreprise` | | Types | Libres (lettres, chiffres, `_ . -`, 60 caractères) | Catalogue fermé : voir [Catalogue des événements](/article/event-catalog) | | Montant | Deux décimales au plus, peut être négatif | FCFA entiers, positif ou nul, contrôlé par type | | Détail | `quantites` et `donnees` libres | Champs typés : `tiers`, `lignes`, `lie_a`, `retenues`, paiement… | | Comptabilisation | Une écriture, construite par des correspondances paramétrées dans dafexpert pour chaque type | Pièces de gestion (règlements, factures fournisseurs, écritures) créées dans chaque entreprise concernée | | Suivi | `ecriture` : numéro de l'écriture | `effets` par entreprise, `en_attente_de` | | Statuts | `recu`, `en_attente_entreprise`, `en_cours`, `comptabilise`, `rejete`, `ignore` | Les mêmes, plus `en_attente_piece`, `partiel`, `annule`, `non_comptable` | Les deux versions coexistent sur la même route `POST /v1/partners/events`. Pour un lot, la `version` du lot (1 par défaut) s'applique aux événements qui n'ont pas la leur ; un événement peut porter sa propre version. > **warning** En version 1, les champs de la version 2 (`tiers`, `lignes`, `lie_a`…) ne sont pas lus : ils sont perdus. N'envoyez pas d'événements v2 sans `"version": 2`. ### Exemple v1 ```json { "version": 1, "id": "9a2e41c7", "type": "achat_cacao", "exploitation": "COOP-SOUBRE", "date": "2026-10-08", "quantites": { "kg": 1200 }, "montant": 1320000, "devise": "XOF", "donnees": { "producteur": "KOUASSI Yao Bernard", "campagne": "2026-2027", "prix": 1100 } } ``` En version 1, chaque `type` doit avoir une correspondance comptable configurée dans dafexpert (journal, comptes, expressions de montant sur `montant`, `quantites` et `donnees`). Sans correspondance, l'événement est `rejete`. Pour passer à la version 2, voir [Migrer vers les événements v2](/article/migrate-events-v2). ## Versions successives d'un même événement L'`id` est la clé : renvoyer un `id` déjà reçu ne crée jamais un second événement. Ce qui se passe dépend du contenu et de l'état de l'événement. | Situation | Réponse | Effet | |---|---|---| | Même `id`, même contenu | `doublon: true`, statut actuel | Rien : renvoi sans risque après une coupure. | | Même `id`, autre contenu, événement pas encore comptabilisé (`recu`, `en_attente_entreprise`, `en_attente_piece`, `rejete`) | `contenu_different: true`, `version_appliquee: true`, `version` incrémentée | La nouvelle version remplace l'ancienne ; l'événement est retraité. | | Même `id`, autre contenu, traitement en cours | `version_appliquee: false`, `message` | Version gardée ; appliquée si le traitement échoue, sinon signalée comme arrivée après comptabilisation. | | Même `id`, autre contenu, événement comptabilisé (ou annulé, ignoré…) | `version_appliquee: false`, `message` | Version gardée pour information et signalée dans dafexpert, sans effet. | ```json { "results": [ { "id": "PAY-2b77e0", "statut": "comptabilise", "doublon": true, "version": 1, "contenu_different": true, "version_appliquee": false, "message": "Nouvelle version reçue après comptabilisation : elle n'est pas appliquée. Pour corriger, envoyer une annulation puis un nouvel événement." } ] } ``` La lecture de l'événement (`GET /v1/partners/events/{id}`) montre alors `version_non_appliquee: { version, message }`. Toutes les versions reçues sont conservées et consultables dans dafexpert. ### Corriger un événement comptabilisé Un événement comptabilisé ne se modifie pas. Envoyez une `annulation` qui le cite dans `lie_a`, avec un `motif`, puis renvoyez l'opération correcte sous un **nouvel `id`**. L'annulation contre-passe ses écritures et supprime ses règlements ; l'événement d'origine passe `annule`. > **info** Une pièce certifiée FNE (bordereau, facture) ne s'annule pas par un événement : elle demande un avoir, à émettre dans dafexpert tant que la route d'avoir partenaire n'est pas disponible. ## Effets En version 2, un événement peut toucher plusieurs entreprises, chacune avec son rôle (`payeur`, `preteur`, `beneficiaire`, `cedant`, `acheteur`, `employeur`, `cooperative`…). Chaque couple entreprise et rôle est un **effet**, avec son propre statut. | Champ de l'effet | Valeurs | |---|---| | `entreprise` | `external_id` de l'entreprise concernée. | | `role` | Rôle de l'entreprise dans l'opération. | | `action` | `creee` (pièce créée), `rattachee` (pièce déjà présente dans dafexpert, rapprochée), `ecriture` (écriture seule). | | `statut` | `comptabilise`, `en_cours` ou `rejete`. | | `piece` | `type` (`supplier_settlement`, `settlement`, `supplier_invoice`, `entry`), `numero`, `numero_fiscal`. | | `ecriture` | Numéro de l'écriture, si l'effet en a produit une. | | `message` | Note ou motif de rejet, en français. | Le statut de l'événement résume ses effets : tous comptabilisés → `comptabilise` ; une partie seulement → `partiel` ; aucun → `rejete`, avec les motifs des effets dans `erreur`. ### Rapprochement avec une pièce déjà présente Pour `achat_fournisseur`, dafexpert cherche d'abord la facture dans la comptabilité (même fournisseur et même numéro, ou même numéro fiscal), par exemple si elle a été reçue par e-mail. Une seule correspondance au même montant : l'événement s'y rattache (`action: rattachee`). Plusieurs correspondances, ou un montant différent : l'événement est rejeté « À rapprocher » et se règle dans dafexpert. ## Écarts Un **écart** est un événement qui n'est pas (ou pas entièrement) comptabilisé : `rejete`, `partiel`, ou en attente trop longue. Les écarts sont affichés dans dafexpert, en haut de l'écran des événements de chaque entreprise et dans la vue de l'espace, pour que le comptable les traite. | Cause fréquente | Exemple de motif | |---|---| | Pièce citée absente | « Pièce citée introuvable après 48 h : ACH-6f1c2a9e » | | Doublon probable | « Doublon probable de l'événement PAY-2b77e0 (même tiers, date, montant) : confirmer dans dafexpert pour le comptabiliser. » | | Incohérence | « Retenues incohérentes avec les bordereaux payés. » | | Comptabilité | Aucun exercice ouvert à la date, période close, compte non paramétré | | Devise | « Devise EUR différente de celle de l'entreprise (XOF). » | ### Doublons probables dafexpert calcule une empreinte métier de chaque événement v2, indépendante de son `id` : type, entreprise, tiers, date, montant, lignes et pièces citées (plus le numéro de facture pour `achat_fournisseur`). Si un autre événement actif a la même empreinte, le second est rejeté comme doublon probable. S'il s'agit bien de deux opérations distinctes, le comptable le confirme dans dafexpert. `annulation`, `recolte` et `inventaire` ne sont pas contrôlés. À lire ensuite : [Suivre le traitement des événements](/article/track-events) et [Idempotence](/article/idempotency). --- # Montants et devises URL: https://developers.stg.dafexpert.com/article/amounts-currencies > Le format attendu pour les montants (FCFA entiers, sans décimales), la devise de l'entreprise, les arrondis et les contrôles de total. La plupart des refus `422` sur les événements et les pièces viennent d'un montant mal formé ou d'un total qui ne tombe pas juste. Cette page réunit les règles. ## La devise est celle de l'entreprise Chaque entreprise a une devise, déduite de son pays à la création et figée ensuite. Tous les montants que vous envoyez pour elle sont dans cette devise. | Pays | Devise | |---|---| | UEMOA : `CI`, `BJ`, `BF`, `GW`, `ML`, `NE`, `SN`, `TG` | `XOF` (franc CFA BCEAO) | | CEMAC : `CM`, `CF`, `CG`, `GA`, `GQ`, `TD` | `XAF` (franc CFA BEAC) | | `KM`, `GN`, `CD` | `KMF`, `GNF`, `CDF` | | `FR` | `EUR` | Le champ `devise` des événements est facultatif. S'il est présent, c'est un code ISO à trois lettres (`XOF`), qui doit être **celui de l'entreprise** : sinon l'événement est enregistré mais passe `rejete` avec le motif « Devise … différente de celle de l'entreprise ». Les routes de pièces n'ont pas de champ `devise` : la devise de l'entreprise s'applique. > **info** Il n'y a pas de conversion de devise : une opération en euros d'une coopérative ivoirienne doit être convertie en FCFA par votre application avant l'envoi. ## Format des montants Un montant est un **nombre JSON**, exprimé en **unités** de la devise (pas en centimes). | Valeur | Accepté ? | |---|---| | `1320000` | Oui | | `"1320000"` | Non : texte au lieu d'un nombre | | `1320000.5` | Non en version 2 et pour les pièces ; oui en version 1 | | `1 320 000` ou `1,320,000` | Non : JSON invalide | | `132000000` pour 1 320 000 FCFA | Non : ce n'est pas un montant en centimes | ### Version 2 et pièces fiscales : FCFA entiers Dans les événements v2, les bordereaux et les factures, chaque montant (`montant`, `prix_unitaire`, `lie_a[].montant`, `retenues[].montant`, `brut`, `net`, `valeur_nominale`…) doit être : - un **entier** : le franc CFA n'a pas de subdivision (sinon « Montant en FCFA entiers attendu. ») ; - **positif ou nul**, et au plus 10 000 000 000 000 (sinon « Montant hors bornes. »). Une correction de montant ne passe pas par un montant négatif : envoyez une `annulation`, puis la bonne opération. ### Version 1 En version 1, `montant` accepte **deux décimales au plus** (sinon « Deux décimales au plus. ») et peut être négatif, en valeur absolue jusqu'à 10 000 000 000 000. Un montant négatif inverse le sens des lignes de la correspondance comptable. ## Quantités et pourcentages | Champ | Règle | |---|---| | `quantite` (bordereau, facture) | Nombre strictement positif, décimales permises, au plus 1 000 000 000. | | `quantite` (événements v2) | Nombre positif ou nul, décimales permises, au plus 1 000 000 000. | | `taux_tva` | De 0 à 100 (en %). 0 par défaut sur les factures. 18 pour la TVA ivoirienne au taux normal. | | `refaction_pct`, `humidite_pct` | De 0 à 100. | | `superficie_ha` | Positif ou nul, au plus 1 000 000. | | `capital_social` | Positif ou nul, au plus 10 000 000 000 000. | Une quantité décimale (1 200,5 kg) est acceptée : sur la facture, la ligne devient « 1 × montant » avec la quantité exacte dans la désignation. ## Arrondis et contrôles de total dafexpert recalcule les totaux et refuse un écart. Calculez vos montants comme lui : | Contrôle | Règle | Tolérance | |---|---|---| | Ligne de bordereau ou de facture | `montant` = arrondi de `quantite` × `prix_unitaire` | 1 franc | | Total d'un bordereau | `montant` = somme des `montant` des lignes | aucune | | Total d'une facture (TTC) | `montant` = somme des HT + TVA de chaque ligne arrondie au franc | 1 franc par ligne | | `paiement_producteur` | `montant` = somme de `lie_a` − somme des `retenues` | aucune | | `remboursement_producteur`, `encaissement_client` | `montant` = somme de `lie_a` | aucune | | `cession_intrant`, `depense` | `montant` = somme des lignes | aucune | | `achat_fournisseur` | `montant` = somme des montants **HT** des lignes ; la TVA est calculée par dafexpert | aucune | | `paie` | `montant` = somme des `net` ; chaque `net` = `brut` − retenues salariales | aucune | | `souscription_parts` | `montant` = `nombre_parts` × `valeur_nominale` | aucune | Exemple de facture avec deux taux : ```text ligne 1 : 25 000 kg × 1 450 = 36 250 000 HT, TVA 0 % → 36 250 000 ligne 2 : 100 × 2 500 = 250 000 HT, TVA 18 % → 295 000 (TVA 45 000) montant TTC attendu = 36 545 000 ``` > **warning** Envoyez des montants **figés au moment de l'opération** : le prix appliqué ce jour-là, pas un prix recalculé au cours du jour. Une pièce certifiée ne se modifie plus. ## Montants dans les réponses - API partenaires : les montants des réponses (`montant` d'un événement, d'un bordereau, d'une facture) sont en unités de la devise, comme à l'envoi. - Serveur MCP : les champs dont le nom finit par `Cents` sont en centièmes de la devise, y compris pour le FCFA. `132000000` vaut 1 320 000 FCFA. À lire ensuite : [Limites des champs](/article/field-limits) et [Erreurs](/article/errors). --- # Limites des champs URL: https://developers.stg.dafexpert.com/article/field-limits > Longueurs maximales, formats et tailles de listes acceptés par l'API : identifiants, dates, NCC, téléphones, e-mails, lots. Cette page réunit les limites appliquées par l'API. Une valeur hors limite renvoie `422` avec le champ en cause dans `erreurs[].champ` (par exemple `lignes.0.produit`). > **tips** Les espaces en début et en fin de texte sont supprimés avant le contrôle de longueur. Les longueurs sont comptées en caractères. ## Identifiants et formats communs | Format | Règle | Champs concernés | |---|---|---| | Identifiant externe | 1 à 100 caractères parmi lettres, chiffres et `. _ : -` | `{external_id}` des URL, `external_id` des lots, `id` des événements et des pièces, `tiers.id`, `tiers.entreprise`, `parent_id`, `cooperative_id`, `acces[].exploitation`, `lie_a`, `caisse`, `employe.id` | | Type d'événement v1 | 1 à 60 caractères, commence par une lettre ou un chiffre, puis lettres, chiffres, `_ . -` | `type` (version 1) | | Date | `AAAA-MM-JJ` | `date`, `echeance`, `date_creation`, `date_naissance`, `valide_jusqu_au`, `facture_fournisseur.date` | | Début d'exercice | `AAAA-MM-01` (le 1er d'un mois) | `debut_exercice` | | Période de paie | `AAAA-MM` | `periode` (`paie`) | | Pays | Deux lettres (`CI`), parmi les pays pris en charge pour une entreprise | `pays`, `tiers.pays` | | Devise | Trois lettres (`XOF`) | `devise` | | E-mail | Adresse e-mail valide | `contact.email`, `email` (utilisateur), `tiers.email` | | URL | URL valide | `piece_jointe` | | Identifiant de lot | UUID | `{lot}` | ## Entreprises `PUT /v1/partners/companies/{external_id}` et chaque élément de `POST /v1/partners/companies`. | Champ | Limite | |---|---| | `nom` | 1 à 200 | | `sigle` | 40 | | `ncc` | 30 | | `forme_juridique`, `numero_immatriculation`, `regime_fiscal` | 60 | | `centre_impots` | 120 | | `agrement_filiere.numero` | 1 à 60 | | `adresse.ligne1` | 200 | | `adresse.ville`, `adresse.commune` | 100 | | `localisation.region`, `departement`, `commune`, `village` | 100 | | `localisation.gps.lat` / `lng` | -90 à 90 / -180 à 180 | | `etiquettes` | 20 clés de 1 à 60 caractères ; valeur de 100 caractères ou liste de 20 valeurs | | `exploitant.prenoms` | 120 | | `exploitant.telephone`, `exploitant.cni` | 40 | | `exploitant.id_producteur` | 60 | | `exploitant.sexe` | `M` ou `F` | | `exploitant.superficie_ha` | 0 à 1 000 000 | | `exploitant.cultures` | 20 valeurs de 1 à 60 caractères | | `contact.nom` | 120 | | `contact.telephone` | 40 | | `capital_social` | 0 à 10 000 000 000 000 | ## Utilisateurs | Champ | Limite | |---|---| | `prenom`, `nom` | 80 | | `telephone` | 40 | | `mot_de_passe` | 8 à 128, plus la politique de sécurité de dafexpert | | `acces` | 200 éléments | | `acces[].role` | 1 à 40 | ## Événements ### Toutes versions | Champ | Limite | |---|---| | `events` (lot) | 1 à 500 événements | | `donnees` | 32 000 caractères une fois sérialisé en JSON | | `montant` | v1 : deux décimales, valeur absolue jusqu'à 10^13. v2 : entier de 0 à 10^13 | ### Version 1 | Champ | Limite | |---|---| | `quantites` | Clés de 30 caractères, valeurs numériques | ### Version 2 | Champ | Limite | |---|---| | `reference_origine` | 120 | | `analytique.campagne`, `parcelle`, `groupement`, `produit` | 60 | | `tiers.nom`, `tiers.raison_sociale` | 200 | | `tiers.prenoms` | 120 | | `tiers.telephone`, `tiers.cni` | 40 | | `tiers.id_producteur` | 60 | | `tiers.ncc` | 30 | | `operateur` | 40 | | `reference_transaction` | 120 | | `lie_a` (paiements, remboursements, encaissements) | 1 à 200 éléments | | `lie_a` (`annulation`) | exactement 1 | | `retenues` | 50 éléments | | `lignes` (`cession_intrant`, `achat_fournisseur`) | 1 à 200 | | `lignes` (`depense`) | 1 à 100 | | `lignes` (`paie`, `inventaire`) | 1 à 2 000 | | `lignes[].produit` | 60 (1 à 60 quand il est obligatoire) | | `lignes[].libelle` | 200 | | `lignes[].nature` (`depense`) | 1 à 40 | | `lignes[].unite` | 20 | | `lignes[].entrepot` | 60 | | `lignes[].quantite` | 0 à 1 000 000 000 | | `lignes[].taux_tva` | 0 à 100 | | `lignes[].duree_annees` | entier de 1 à 100 | | `facture_fournisseur.numero` | 1 à 60 | | `facture_fournisseur.numero_fiscal` | 120 | | `financement` | 120 | | `periode` (`cotisation`) | 20 | | `motif` (`annulation`) | 1 à 300 | | `employe.nom` | 1 à 200 | | `retenues_salariales`, `charges_patronales` | 30 éléments ; `nature` de 1 à 40 | | `nombre_parts` | entier, au moins 1 | ### Synchronisation | Paramètre | Limite | |---|---| | `limite` | 1 à 500, 100 par défaut | | `apres` | 200 caractères | ## Pièces fiscales (bordereaux, factures) | Champ | Limite | |---|---| | `lignes` | 1 à 200 | | `lignes[].produit` | 60 (1 à 60 sur un bordereau, où il est obligatoire) | | `lignes[].qualite` | 40 | | `lignes[].description` (facture) | 255 | | `lignes[].unite` | 20 | | `lignes[].quantite` | strictement positive, au plus 1 000 000 000 | | `lignes[].nombre_sacs` | entier positif ou nul | | `lignes[].refaction_pct`, `humidite_pct`, `taux_tva` | 0 à 100 | | `reference_origine` | 120 | | `analytique.*` | 60 | | `tiers.*` | comme pour les événements v2 | ## Lots d'entreprises | Champ | Limite | |---|---| | `entreprises` | 1 à 500 | | `external_id` | unique dans le lot | ## Exigences de la FNE (Côte d'Ivoire) Ces règles ne sont pas contrôlées à l'envoi : un champ conforme aux limites ci-dessus est accepté. Elles sont vérifiées au moment de la certification, et une pièce qui ne les respecte pas reste `non_certifie` avec le motif dans `erreur`. | Donnée | Règle FNE | Concerne | |---|---|---| | Téléphone | 10 chiffres ivoiriens. Les espaces et signes sont ignorés, l'indicatif `+225` est retiré (`+225 07 07 12 34 56` devient `0707123456`). | Producteur d'un bordereau, client d'une facture B2B | | NCC | 8 caractères, lettres et chiffres (espaces ignorés). | Client d'une facture B2B | | E-mail | Obligatoire. Pour un bordereau, l'e-mail du contact de l'acheteur est utilisé à défaut de celui du producteur. | Producteur d'un bordereau, client d'une facture B2B | | Devise | `XOF` uniquement. | Bordereau | | Quantité | Strictement positive. | Toutes les lignes | ## Champs inconnus - Un champ qui ne fait pas partie du contrat est ignoré, sans erreur. Une faute de frappe dans un nom de champ fait donc perdre la valeur sans alerte. - Exceptions : l'ancien champ `utilisateurs` d'une entreprise est refusé (`422`), et un champ prévu pour un autre type d'entreprise aussi (« Champ non prévu pour le type »). À lire ensuite : [Montants et devises](/article/amounts-currencies) et [Erreurs](/article/errors). --- # Limites de débit URL: https://developers.stg.dafexpert.com/article/rate-limiting > Les limites de volume de l'API aujourd'hui, les règles d'usage raisonnable et comment se préparer aux limites à venir. L'API partenaires n'applique **pas encore de limite de débit** par partenaire ou par espace. Cela ne veut pas dire que tout volume est sans conséquence : un usage raisonnable garde l'API rapide pour tous, et votre intégration prête pour les limites à venir. > **info** **Bientôt.** Des limites de débit seront mises en place et documentées ici, avec leurs valeurs. Une requête au-delà de la limite recevra un `429 Too Many Requests`. Votre code doit déjà savoir le gérer. ## Limites qui s'appliquent déjà Ce ne sont pas des limites de débit, mais des tailles maximales par appel : | Élément | Limite | |---|---| | Événements par lot (`POST /v1/partners/events`) | 500 | | Entreprises par lot (`POST /v1/partners/companies`) | 500 | | Événements par page de synchronisation (`limite`) | 100 par défaut, 500 au plus | | Données libres `donnees` d'un événement | 32 Ko | | Durée de vie d'un jeton | 5 minutes | Les autres limites de champs sont décrites dans [Limites des champs](/article/field-limits). Un lot qui dépasse sa taille maximale est refusé en entier avec un `422`. ## Usage raisonnable - **Réutilisez votre jeton** pendant ses 5 minutes de validité. Demander un jeton à chaque appel double votre trafic et sollicite inutilement le fournisseur d'identité. - **Regroupez vos envois** : un lot de 200 événements vaut mieux que 200 appels. Pour la reprise d'un historique, utilisez la création d'entreprises en masse. - **Limitez la concurrence** : quelques requêtes en parallèle suffisent. Inutile d'ouvrir des dizaines de connexions simultanées. - **Synchronisez par curseur** plutôt que d'interroger chaque événement un par un. Voir [Suivre les événements](/article/track-events). - **Étalez les reprises de masse** et prévenez-nous à l'avance pour les gros volumes ([support@dafexpert.com](mailto:support@dafexpert.com)). - **Ne bouclez pas sur une erreur** : un `422` renvoyé à l'identique donnera toujours la même erreur. ## Se préparer aux `429` Traitez dès maintenant `429` comme une erreur temporaire : attendez, puis renvoyez la même requête avec un délai croissant. Si la réponse porte un en-tête standard `Retry-After`, respectez-le. Grâce à l'[idempotence](/article/idempotency), le renvoi est sans risque. ```javascript async function sendWithBackoff(doRequest, maxAttempts = 6) { for (let attempt = 1; attempt <= maxAttempts; attempt++) { const res = await doRequest(); if (res.status !== 429 && res.status < 500) return res; if (attempt === maxAttempts) return res; const retryAfter = Number(res.headers.get('retry-after')); const delay = retryAfter > 0 ? retryAfter * 1000 : Math.min(2 ** attempt * 500, 60_000) * (0.5 + Math.random()); await new Promise((r) => setTimeout(r, delay)); } } ``` ## Étapes suivantes - [Erreurs et codes de réponse](/article/errors) : Quand réessayer, quand corriger. - [Créer des entreprises en masse](/article/bulk-companies) : Reprendre un historique en quelques appels. --- # Versionnement URL: https://developers.stg.dafexpert.com/article/versioning > Comment l'API évolue sans casser votre intégration : version dans l'URL, champ version des corps, contrat v2 des événements et règles de compatibilité. L'API dafexpert est versionnée à deux niveaux : **l'URL** (`/v1`) et le champ `version` de chaque corps de requête. Les évolutions courantes n'ajoutent que des champs, pour que votre intégration continue de fonctionner sans changement. ## Les deux niveaux de version | Niveau | Où | Valeur actuelle | Rôle | |---|---|---|---| | Version de l'API | Le chemin : `/v1/partners/...` | `v1` | L'ensemble des routes, de l'authentification et des formats communs. | | Version du contrat | Le champ `version` du corps | `1` (entreprises, utilisateurs, événements) et `2` (événements terrain) | Le schéma d'un type de corps donné. | `GET /v1/partners/me` indique la version de l'API que vous utilisez : ```json { "partner": "acme", "workspaceId": "3f6b2c1e-...", "apiVersion": 1 } ``` ## Le champ `version` des corps Chaque appel porte `"version": 1`. Il vaut `1` par défaut s'il est absent, mais nous vous recommandons de toujours l'envoyer explicitement : votre code dit ainsi quel contrat il parle. ```json { "version": 1, "type": "cooperative", "nom": "Coopérative Agricole de Daloa", "pays": "CI" } ``` ## Ce qui peut changer sans nouvelle version Les évolutions de la version 1 ne font qu'**ajouter** : - de nouveaux champs facultatifs dans les requêtes ; - de nouveaux champs dans les réponses ; - de nouvelles valeurs de statut ou de nouveaux codes `error_code` ; - de nouvelles routes. **Retirer ou renommer** un champ, ou en changer le sens, passera par une nouvelle version, **annoncée à l'avance** dans les [nouveautés](/changelog). ## Écrire une intégration robuste - **Ignorez les champs inconnus** dans les réponses : de nouveaux champs peuvent apparaître à tout moment. - **Prévoyez un cas par défaut** pour les statuts et les codes d'erreur inconnus. - Ne dépendez pas de l'ordre des champs dans un objet JSON. - Côté requêtes, un champ inconnu des entreprises est **ignoré sans erreur**. Vous pouvez donc envoyer un contrat plus riche que celui accepté aujourd'hui : les champs seront pris en compte dès leur mise en service. > **warning** Exception : un champ **retiré** d'un contrat peut être refusé explicitement pour éviter une erreur silencieuse. C'est le cas de l'ancien champ `utilisateurs` des entreprises, remplacé par `PUT /v1/partners/users/{id}` : s'il est envoyé, l'appel reçoit un `422`. ## Le contrat v2 des événements Les événements existent en deux versions de contrat, acceptées **sur la même route** `POST /v1/partners/events` : | | Version 1 | Version 2 | |---|---|---| | Champ `version` | `1` (ou absent) | `2` | | Entreprise concernée | `exploitation` | `entreprise` | | Types | Libres, comptabilisés selon des correspondances paramétrées | Catalogue fixe (`paiement_producteur`, `depense`, `paie`…) | | Ce qui est créé | Une écriture | Une pièce de gestion (règlement, facture fournisseur, dépense…) puis son écriture, pour chaque entreprise concernée | La version d'un événement est lue dans l'événement lui-même, sinon dans le lot. Un même lot peut donc mêler des événements v1 et v2 : ```json { "version": 1, "events": [ { "id": "EVT-001", "type": "achat_cacao", "exploitation": "COOP-SOUBRE", "date": "2026-10-08", "montant": 1500000 }, { "version": 2, "id": "DEP-33ab", "type": "depense", "entreprise": "COOP-SOUBRE", "date": "2026-10-12", "lignes": [{ "nature": "transport", "montant": 45000 }], "montant": 45000, "mode": "especes", "caisse": "CAISSE-SOUBRE-1" } ] } ``` Pour passer d'une version à l'autre, voir [Migrer vers les événements v2](/article/migrate-events-v2). ### À ne pas confondre : les versions d'un événement Le champ `version` renvoyé dans la réponse d'un événement (`"version": 2`) n'est pas la version du contrat : c'est le **numéro de révision** de cet événement, incrémenté quand vous le renvoyez avec un contenu différent. Voir [Versions d'un événement](/article/event-versions). ## Serveur MCP Le serveur MCP n'est pas versionné dans son adresse (`/mcp`). Ses outils suivent la même règle : on ajoute des outils ou des champs, on n'en retire pas sans annonce. ## Étapes suivantes - [Migrer vers les événements v2](/article/migrate-events-v2) : Les différences et le plan de migration. - [Feuille de route](/article/roadmap) : Ce qui arrive prochainement. - [Nouveautés](/changelog) : Les évolutions datées de l'API. --- # Migrer vers les événements v2 URL: https://developers.stg.dafexpert.com/article/migrate-events-v2 > Passer du contrat v1 des événements (correspondances paramétrées) au contrat v2 (catalogue de types, pièces de gestion, effets par entreprise, versions). > **warning** **Bientôt disponible.** Cette fonctionnalité est prête mais pas encore ouverte sur le staging. Le contrat décrit ici est celui qui sera publié. Le contrat **v2** des événements (`"version": 2`) remplace la comptabilisation par correspondances de la v1 par un **catalogue de types** connus de dafexpert. Pour chaque événement, dafexpert crée d'abord une **pièce de gestion** (règlement, facture fournisseur, dépense, fiche de paie…), puis la comptabilise automatiquement, pour chaque entreprise concernée. > **info** La version 1 reste **acceptée telle quelle**, sur la même route. Rien ne casse si vous ne migrez pas. Mais seule la v2 crée des pièces et donne le détail des effets. ## Ce que la v2 apporte - **Des pièces, pas seulement des écritures** : un paiement de producteur devient un vrai règlement fournisseur, lettré avec les achats qu'il solde. - **Des effets par entreprise** : quand la contrepartie est elle aussi une entreprise de l'espace (un exploitant suivi, une autre coopérative), sa pièce miroir est créée chez elle. - **Des contrôles à l'entrée** : le `montant` doit égaler la somme des lignes, le net d'une paie doit égaler le brut moins les retenues, sinon `422`. - **L'attente de pièce** : un événement qui cite une pièce pas encore arrivée attend (`en_attente_piece`) au lieu d'être rejeté. - **La détection des doublons probables** : une même opération envoyée sous deux `id` est repérée avant d'être comptabilisée deux fois. ## Les différences | Sujet | Version 1 | Version 2 | |---|---|---| | `version` | `1` ou absent | `2`, obligatoire | | Entreprise qui réalise l'opération | `exploitation` | `entreprise` | | `type` | Libre (lettres, chiffres, `_ . -`), comptabilisé selon une correspondance paramétrée | Un type du catalogue. Un type inconnu est refusé (`422`). | | Montant | Dans la devise de l'entreprise, deux décimales au plus | **FCFA entiers**, égal à la somme des lignes | | Détail | `quantites` et `donnees` | `lignes`, `tiers`, `lie_a`, `analytique`, `reference_origine`, selon le type | | Contrepartie | Dans `donnees`, sans effet | `tiers`, créé ou mis à jour chez dafexpert | | Ce qui est créé | Une écriture | Une pièce de gestion et son écriture, par entreprise concernée | | Achats et ventes de produits | Événements | **Routes de pièces fiscales** synchrones (bordereaux d'achat, factures) | ### Le catalogue v2 | Type | Opération | |---|---| | `paiement_producteur` | Paiement d'un ou plusieurs achats à un producteur, avec ses retenues | | `avance_producteur` | Avance ou crédit de campagne en argent | | `remboursement_producteur` | Remboursement en argent d'une avance ou d'intrants | | `cession_intrant` | Intrants remis à un producteur | | `encaissement_client` | Paiement reçu d'un acheteur | | `achat_fournisseur` | Facture d'un fournisseur (intrants, marchandises, services, matériel) | | `depense` | Dépense de caisse | | `paie` | Clôture d'une période de paie | | `souscription_parts` | Souscription ou libération de parts sociales | | `cotisation` | Cotisation ou droit d'adhésion | | `inventaire` | Quantités en stock de fin de campagne ou d'exercice | | `annulation` | Annulation d'un événement déjà envoyé | | `recolte` | Bulletin de récolte, pour la traçabilité (aucune pièce) | Les champs de chaque type sont décrits dans le [Catalogue des événements](/article/event-catalog). ### Les achats et ventes de produits passent par les pièces fiscales En v2, un achat de produits à un producteur ou une vente à un acheteur n'est **plus un événement** : c'est une pièce fiscale, émise de façon synchrone pour obtenir tout de suite son numéro fiscal et son PDF. | Avant | Maintenant | |---|---| | Événement d'achat de produit | `POST /v1/partners/companies/{external_id}/bordereaux` | | Événement de vente de produit, ou intrants vendus | `POST /v1/partners/companies/{external_id}/invoices` | Voir [Émettre des pièces FNE](/article/fne-documents). Les événements suivants (paiements, encaissements) citent ces pièces par leur `id` dans `lie_a`. ## Exemple : une dépense de transport **Version 1** ```json { "version": 1, "id": "DEP-33ab", "type": "depense_transport", "exploitation": "COOP-SOUBRE", "date": "2026-10-12", "montant": 45000, "devise": "XOF", "donnees": { "prestataire": "Transport Koné", "caisse": "CAISSE-SOUBRE-1" } } ``` **Version 2** ```json { "version": 2, "id": "DEP-33ab", "type": "depense", "entreprise": "COOP-SOUBRE", "date": "2026-10-12", "tiers": { "role": "prestataire", "id": "PRE-0012", "nom": "Transport Koné", "telephone": "0101010101" }, "lignes": [ { "nature": "transport", "libelle": "Transport cacao Dignago vers magasin", "montant": 45000 } ], "montant": 45000, "devise": "XOF", "mode": "especes", "caisse": "CAISSE-SOUBRE-1", "analytique": { "campagne": "2026-2027" } } ``` ## Ce qui change dans les réponses La route d'envoi répond toujours `202` avec un résultat par événement. `GET /v1/partners/events/{id}` et la synchronisation renvoient en plus, pour la v2 : | Champ | Sens | |---|---| | `effets` | Un effet par entreprise concernée : `entreprise`, `role`, `action` (`creee`, `rattachee`, `ecriture`), `statut`, `piece` (`type`, `numero`, `numero_fiscal`), `ecriture`, `message`. | | `en_attente_de` | Les `id` des pièces citées qui ne sont pas encore arrivées. | | `version` | Le numéro de révision appliqué (1 au premier envoi). | | `version_non_appliquee` | Une version reçue mais pas appliquée, avec son `message`. | > **note** Dans les réponses, l'entreprise de l'événement reste dans le champ `exploitation`, en v1 comme en v2. ### Nouveaux statuts | Statut | Sens | |---|---| | `en_attente_piece` | Une pièce citée dans `lie_a` n'est pas encore arrivée. L'événement est libéré à son arrivée. | | `partiel` | Une partie seulement des effets a abouti (par exemple la pièce de l'acheteur, mais pas la pièce miroir). | | `annule` | Annulé par un événement `annulation`. | | `non_comptable` | Enregistré pour information (`recolte`), jamais en écart. | Si votre code traite les statuts dans un `switch`, ajoutez ces valeurs, et un cas par défaut pour celles qui viendraient plus tard. ## Plan de migration **1. Recenser vos types v1** Faites correspondre chacun de vos types libres à un type du catalogue v2. Les achats et ventes de produits partent vers les routes de pièces fiscales. **2. Compléter vos données** Tiers (téléphone obligatoire pour un producteur), lignes, codes de caisse, liens `lie_a` vers les pièces d'origine. Les montants passent en FCFA entiers. **3. Tester en staging** Envoyez des événements `"version": 2` sur une entreprise de test, puis vérifiez `effets` et `statut` avec `GET /v1/partners/events/{id}`. **4. Basculer type par type** Un lot peut mêler v1 et v2 : chaque événement porte sa propre `version`, sinon celle du lot. Basculez progressivement. **5. Adapter le suivi** Lisez les nouveaux statuts et les `effets`, récupérez numéros et PDF des pièces fiscales. ## Points d'attention - **Ne mélangez pas les versions pour une même opération.** Un événement v1 ne crée pas de pièce et **perd** tous les champs propres à la v2 : n'envoyez pas vos opérations réelles en v1 si vous comptez sur les pièces. - **Les `id` restent la clé anti-doublon** : un événement déjà envoyé en v1 et renvoyé en v2 avec le même `id` est traité comme une nouvelle version du même événement, pas comme un nouvel événement. - **Corrections** : un événement comptabilisé ne se modifie pas. Envoyez une `annulation` qui le cite dans `lie_a`, puis l'opération correcte avec un nouvel `id`. - **Paiements mobile money** : `reference_transaction` est obligatoire. - **Ne produisez plus vos propres pièces fiscales** (factures, bordereaux) une fois la v2 en place : elles viennent de dafexpert. ## Étapes suivantes - [Catalogue des événements](/article/event-catalog) : Les champs de chaque type v2. - [Émettre des pièces FNE](/article/fne-documents) : Bordereaux d'achat et factures certifiés. - [Versions d'un événement](/article/event-versions) : Renvoyer un événement modifié. --- # Feuille de route URL: https://developers.stg.dafexpert.com/article/roadmap > Les évolutions prévues de l'API dafexpert. Voici les évolutions **prévues** de l'API. Elles ne sont pas encore disponibles : ne les utilisez pas tant qu'elles ne figurent pas dans les [nouveautés](/changelog). > **note** Cette feuille de route donne une direction, pas un engagement de date. Les changements qui demandent une adaptation de votre part sont toujours annoncés à l'avance. ## Prévu | Évolution | Ce que cela change pour vous | |---|---| | **Webhooks** `event.posted` et `event.rejected` | dafexpert vous prévient quand un événement est comptabilisé ou rejeté. Plus besoin d'interroger en boucle. Voir [Webhooks](/article/webhooks). | | **Avoirs certifiés par l'API** | Annuler un bordereau ou une facture certifié FNE directement par l'API, avec émission d'un avoir certifié. | | **Période de grâce à la rotation du secret** | L'ancien et le nouveau `client_secret` restent valides pendant un temps, pour changer de secret sans interruption. Voir [Obtenir ses identifiants](/article/credentials). | | **Limites de débit** | Des limites documentées, avec une réponse `429`. Voir [Limites de débit](/article/rate-limiting). | | **Comptabilisation des inventaires** | Les événements `inventaire` sont aujourd'hui enregistrés pour information. La valorisation des stocks et la variation de stock à la clôture seront automatisées. | | **API publique des entreprises** | Un accès des entreprises clientes de dafexpert à leurs propres données, sans passer par un partenaire. | | **URL de production** | Communiquée à la mise en production. | ## Une idée, un besoin ? Votre intégration a besoin d'une route ou d'un champ qui n'existe pas encore ? Écrivez-nous à [support@dafexpert.com](mailto:support@dafexpert.com) en décrivant votre cas d'usage. - [Nouveautés](/changelog) : Ce qui est déjà disponible, daté. - [Versionnement](/article/versioning) : Comment l'API évolue sans casser votre intégration. --- # Glossaire URL: https://developers.stg.dafexpert.com/article/glossary > Les termes de dafexpert, de la comptabilité OHADA et de la facturation électronique ivoirienne utilisés dans cette documentation. Les noms de champs de l'API sont en français. Ce glossaire donne le sens des termes que vous rencontrerez, avec leur équivalent dans l'API quand il existe. ## Organisation | Terme | Dans l'API | Définition | |---|---|---| | **Espace** | `workspaceId` | Une organisation chez dafexpert : une fédération, un cabinet comptable, un groupe. Vos identifiants partenaires sont liés à un espace et n'agissent que dans celui-ci. Voir [Espaces et entreprises](/article/workspaces-companies). | | **Entreprise** (ou dossier) | `external_id`, `company_id` | Une entité qui tient sa propre comptabilité : coopérative, union, acheteur, exploitant, PME. Elle appartient à un espace. | | **Exploitation** | `exploitation` | Nom historique de l'entreprise concernée par un événement v1 (et dans les réponses). En v2, le champ s'appelle `entreprise`. | | **Partenaire** | `partner` | Un éditeur d'application connecté à un ou plusieurs espaces par l'API partenaires. | | **Administrateur de l'espace** | | Propriétaire ou administrateur de l'espace : il connecte les applications partenaires et gère leurs identifiants. | | **Membre** | | Un utilisateur de l'espace. Un utilisateur créé par l'API est simple membre, avec un rôle sur chaque entreprise listée (`dirigeant`, `comptable`, `collaborateur`, `lecture-seule`…). | | `external_id` | `external_id`, `id` | Votre identifiant d'une ressource, stable, qui sert aussi de clé d'[idempotence](/article/idempotency). | ## Comptabilité | Terme | Dans l'API | Définition | |---|---|---| | **Référentiel comptable** | `referentiel` | Le cadre comptable de l'entreprise, déduit de son pays. Voir [Référentiels comptables](/article/accounting-frameworks). | | **SYSCOHADA** | `SYSCOHADA` | Système comptable de l'OHADA, appliqué par les entreprises des 17 pays de l'espace OHADA (Côte d'Ivoire, Sénégal, Cameroun…). | | **SYCEBNL** | | Système comptable OHADA des entités à but non lucratif (associations, fondations, projets). | | **PCG** | | Plan comptable général, le référentiel français. | | **Système normal / SMT** | `systeme_comptable` | Le système normal est la comptabilité complète. Le système minimal de trésorerie (SMT) est une comptabilité allégée des très petites entités, par défaut pour un exploitant. | | **Exercice** | `debut_exercice` | La période comptable annuelle (en général 12 mois), découpée en périodes mensuelles. Le premier exercice est ouvert à la création de l'entreprise. | | **Période close** | | Une période où l'on ne peut plus passer d'écriture. Un événement daté dans une période close est rejeté. | | **Écriture** | `ecriture` | L'enregistrement comptable d'une opération : des lignes au débit et au crédit de comptes, équilibrées. | | **Brouillard** | | État d'une écriture enregistrée mais pas encore validée par le comptable. Les écritures issues de l'API y arrivent. | | **Journal** | | Le registre où sont classées les écritures d'une même nature (achats, ventes, banque, caisse…). | | **Plan comptable** | | La liste des comptes de l'entreprise, propre à son référentiel. Votre application n'envoie jamais de numéro de compte. | | **Tiers** | `tiers` | Une contrepartie : client, fournisseur, producteur, employé, prestataire. Il a un compte auxiliaire dans la comptabilité de l'entreprise. | | **Lettrage** | | Le rapprochement d'une facture et de son règlement sur le compte d'un tiers, pour savoir ce qui reste dû. | | **Pièce** | `piece` | Le document qui justifie une écriture : facture, bordereau d'achat, règlement, note de dépense, fiche de paie… | | **Avoir** | | Une pièce qui annule tout ou partie d'une facture. | | **AN (à-nouveaux)** | | Les soldes reportés d'un exercice clôturé vers l'exercice suivant. | | **Analytique** | `analytique` | Une ventilation complémentaire des opérations (campagne, parcelle, groupement, produit) pour suivre coûts et marges. | | **Campagne** | `analytique.campagne` | La saison de commercialisation d'une filière agricole, par exemple `2026-2027`. | | **FCFA** | `XOF` | Le franc CFA d'Afrique de l'Ouest, sans décimales. Voir [Montants et devises](/article/amounts-currencies). | ## Fiscalité et identifiants (Côte d'Ivoire) | Terme | Dans l'API | Définition | |---|---|---| | **NCC** | `ncc` | Numéro de compte contribuable, l'identifiant fiscal d'une entreprise ivoirienne. Unique dans un espace. Sans NCC, pas de pièce certifiée FNE. | | **DGI** | | Direction générale des impôts de Côte d'Ivoire, qui opère la FNE. | | **FNE** | | Facture normalisée électronique : le dispositif de la DGI qui certifie les factures et bordereaux. Une pièce certifiée reçoit un numéro fiscal et un QR code. Voir [Émettre des pièces FNE](/article/fne-documents). | | **Numéro fiscal** | `numero_fiscal` | Le numéro attribué par la FNE à une pièce certifiée. | | **Bordereau d'achat** | | La pièce que l'acheteur émet quand il achète des produits agricoles à un producteur. Certifié FNE (type `purchase`). | | **Sticker (FNE)** | | Un timbre virtuel acheté à l'avance par l'entreprise. Chaque pièce certifiée en consomme un. | | **RCCM** | `numero_immatriculation` | Registre du commerce et du crédit mobilier (ou registre des sociétés coopératives) : le numéro d'immatriculation légal. | | **Forme juridique** | `forme_juridique` | Le statut juridique : `SCOOPS` ou `SCOOP-CA` pour une coopérative, `Entreprenant` pour un exploitant, SARL, SA… | | **SCOOPS / SCOOP-CA** | | Société coopérative simplifiée / société coopérative avec conseil d'administration, au sens de l'acte uniforme OHADA. | ## API | Terme | Définition | |---|---| | **Événement** | Une opération de votre application à comptabiliser (paiement, dépense, paie…). Voir [Catalogue des événements](/article/event-catalog). | | **Effet** | Ce qu'un événement v2 produit pour une entreprise : une pièce et son écriture. | | **Écart** | Un événement pas encore comptabilisé (rejeté, en attente, partiel), signalé dans dafexpert pour être traité. | | **Lot** | Un envoi groupé : jusqu'à 500 événements ou 500 entreprises par appel. | | **Curseur** | La valeur opaque `suivant` qui permet de reprendre la synchronisation des événements là où elle s'était arrêtée. | | **Client credentials** | Le flux OAuth 2.0 d'authentification d'un serveur, sans utilisateur. Voir [Authentification](/article/authentication). | | **SSO** | Connexion unique : un utilisateur se connecte une fois pour dafexpert et votre application. Voir [Connexion des utilisateurs](/article/sso). | | **MCP** | Model Context Protocol, le protocole qui permet à un assistant IA d'utiliser des outils. Voir [Serveur MCP](/article/mcp-overview). | --- # Besoin d'aide ? URL: https://developers.stg.dafexpert.com/article/support > Comment contacter l'équipe dafexpert et quelles informations joindre pour obtenir une réponse rapide. Une question sur l'API, un comportement inattendu, un besoin d'intégration ? Écrivez à l'équipe dafexpert : **[support@dafexpert.com](mailto:support@dafexpert.com)**. ## Avant de nous écrire - Relisez le **message** et l'`error_code` de la réponse : ils indiquent souvent la cause exacte. Voir [Erreurs et codes de réponse](/article/errors). - Pour un `401` persistant, vérifiez d'abord avec l'**administrateur de l'espace** que votre application est toujours connectée et que le secret n'a pas été renouvelé. Voir [Obtenir ses identifiants](/article/credentials). - Pour un événement rejeté, consultez son motif avec `GET /v1/partners/events/{id}` (champ `erreur`). Beaucoup de rejets se corrigent dans dafexpert : compte non paramétré, exercice non ouvert, période close. ## Ce qu'il faut joindre Chaque appel à l'API partenaires est journalisé de notre côté. Avec les éléments ci-dessous, nous retrouvons votre appel exact : | Élément | Exemple | |---|---| | Environnement | Staging ou production | | Votre `client_id` | `partner-acme-coop` | | Date et heure de l'appel, avec le fuseau | `2026-10-12 14:32 UTC` | | Méthode et route | `POST /v1/partners/events` | | Code HTTP et corps de la réponse | `422` avec son `error_code`, son `message` et ses `erreurs` | | Identifiants concernés | `external_id` de l'entreprise, `id` de l'événement ou de la pièce, `lot` | | Corps de la requête | Expurgé des données sensibles (voir ci-dessous) | | Ce que vous attendiez | Le résultat attendu, et en quoi il diffère | > **error** **N'envoyez jamais** votre `client_secret`, un jeton d'accès (`access_token`) ou un mot de passe d'utilisateur (`mot_de_passe`), ni par e-mail ni dans un ticket. Nous ne vous les demanderons jamais. Si un secret a fuité, demandez à l'administrateur de l'espace d'en générer un nouveau. ## Exemple de demande ```text Objet : [Staging] 422 sur POST /v1/partners/events, événement DEP-33ab Environnement : staging client_id : partner-acme-coop Appel : POST /v1/partners/events, le 2026-10-12 à 14:32 UTC Réponse : 202, élément DEP-33ab en "invalide" erreurs : [{ "champ": "montant", "message": "montant (45000) différent de la somme des lignes (40000)." }] Corps envoyé (expurgé) : voir pièce jointe Attendu : la dépense devrait être acceptée, la seconde ligne de 5000 FCFA manque-t-elle ? ``` ## Autres demandes - **Devenir partenaire** et faire référencer votre application : écrivez-nous avec une présentation de votre produit et de vos clients. - **Mise en place du SSO** : envoyez vos adresses de retour. Voir [Connexion des utilisateurs (SSO)](/article/sso). - **Reprise de gros volumes** : prévenez-nous avant une création d'entreprises ou un envoi d'événements en masse. - **Une erreur dans cette documentation** : signalez-la à la même adresse, avec le lien de la page. - [Erreurs et codes de réponse](/article/errors) : Comprendre une erreur avant de nous écrire. - [Glossaire](/article/glossary) : Les termes de dafexpert et de la comptabilité OHADA. - [Feuille de route](/article/roadmap) : Ce qui arrive prochainement. --- # Introduction URL: https://developers.stg.dafexpert.com/article/api-introduction > URL de base, authentification, format des requêtes, des identifiants, des dates, des montants et des erreurs. L'API partenaires de dafexpert est une API REST : elle reçoit et renvoie du JSON, et chaque route est décrite dans cette référence avec ses paramètres, ses réponses et ses erreurs. Elle est appelée par **vos serveurs**, jamais par le navigateur d'un utilisateur. Un jeton d'accès est rattaché à **un espace** dafexpert (par exemple une fédération de coopératives). Toutes les entreprises, utilisateurs, événements et pièces que vous créez vivent dans cet espace : il est déduit du jeton et n'est jamais lu dans la requête. ## URL de base | Environnement | URL de l'API | |---|---| | Staging | `https://api.stg.dafexpert.com/v1` | | Production | communiquée à la mise en production | Toutes les routes de cette référence commencent par `/v1/partners`. Le numéro de version fait partie du chemin. ## Authentification Chaque appel porte un jeton OAuth 2.0 obtenu avec le flux *client credentials*, dans l'en-tête `Authorization`. Le jeton vaut 5 minutes ; il n'y a pas de jeton de renouvellement : demandez-en simplement un nouveau avec les mêmes identifiants. ```http Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9… ``` | Élément | Valeur | |---|---| | En-tête | `Authorization: Bearer ` | | Flux | OAuth 2.0 client credentials | | Portées (scopes) | Aucune : un jeton valide donne accès à toutes les routes partenaires de son espace | | Révocation | Une déconnexion par l'administrateur de l'espace est prise en compte en 30 secondes au plus (`401`) | Le détail (URL du jeton, mise en cache, gestion du `401`) est dans [Authentification](/article/authentication). Pour tester vos identifiants, appelez [GET /v1/partners/me](/article/get-partner-me). ## Format des requêtes - Corps en JSON, encodé en UTF-8, avec l'en-tête `Content-Type: application/json`. - Les noms de champs sont en français et en minuscules (`exploitation`, `montant`, `lie_a`). Un champ inconnu est ignoré sans erreur, sauf mention contraire. - Les champs `version` des corps indiquent la version du contrat : `1` par défaut, `2` pour le catalogue d'événements de [la version 2](/article/event-versions). **cURL** ```bash curl -X PUT https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE \ -H "Authorization: Bearer $DAFEXPERT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "type": "cooperative", "nom": "Coopérative Agricole de Soubré", "pays": "CI" }' ``` **Node.js** ```javascript const res = await fetch('https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE', { method: 'PUT', headers: { Authorization: 'Bearer ' + process.env.DAFEXPERT_TOKEN, 'Content-Type': 'application/json', }, body: JSON.stringify({ type: 'cooperative', nom: 'Coopérative Agricole de Soubré', pays: 'CI' }), }); ``` **Python** ```python import os, requests resp = requests.put( "https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE", headers={"Authorization": f"Bearer {os.environ['DAFEXPERT_TOKEN']}"}, json={"type": "cooperative", "nom": "Coopérative Agricole de Soubré", "pays": "CI"}, timeout=30, ) ``` ## Identifiants Vous désignez chaque objet par **votre propre identifiant** (`external_id`) : celui de la coopérative, de l'exploitant, de l'utilisateur, de l'événement ou de la pièce dans votre système. dafexpert ne vous demande jamais de stocker ses identifiants internes. | Règle | Valeur | |---|---| | Longueur | 1 à 100 caractères | | Caractères | lettres, chiffres et `. _ : -` | | Exemples | `COOP-SOUBRE`, `EXPL-0457`, `ACH-2026-000412`, `5f1c2a9e-3b7d-4c11-9e0a-8d2f6b1c4e77` | | Stabilité | Un identifiant ne doit jamais changer : il sert de clé d'idempotence | Les réponses contiennent aussi les identifiants dafexpert correspondants (`company_id`, `user_id`, `piece_id`, au format UUID), à titre d'information. ## Dates et montants | Donnée | Format | Exemple | |---|---|---| | Date | `AAAA-MM-JJ` | `2026-10-08` | | Horodatage (réponses) | ISO 8601 en UTC | `2026-10-08T10:00:03.000Z` | | Pays | code ISO à deux lettres | `CI` | | Devise | code ISO à trois lettres | `XOF` | | Montant des pièces fiscales et des événements v2 | nombre entier, en FCFA | `2160000` | | Montant des événements v1 | nombre, deux décimales au plus, dans la devise de l'entreprise | `1500000` | Les montants sont des nombres JSON, jamais des chaînes, sans séparateur de milliers. Voir [Montants et devises](/article/amounts-currencies) et [Limites des champs](/article/field-limits). ## Codes de réponse | Code | Signification | |---|---| | `200` | Succès (lecture, mise à jour, ou rejeu d'une création déjà faite) | | `201` | Objet créé | | `202` | Reçu et mis en file : le traitement continue en arrière-plan | | `401` | Jeton absent, expiré ou invalide, ou accès révoqué | | `403` | Objet rattaché à un autre espace | | `404` | Objet inconnu dans votre espace | | `409` | Conflit (NCC déjà pris, pièce en cours d'émission, espace sans administrateur actif…) | | `422` | Données invalides ou refus métier, avec le motif | | `5xx` | Erreur ou indisponibilité temporaire : réessayer plus tard avec un délai croissant | ## Format des erreurs Toutes les erreurs ont le même corps JSON. Les erreurs de validation détaillent chaque champ refusé dans `erreurs`, avec son chemin (`lignes.0.montant`). ```json { "statusCode": 422, "error_code": "validation_failed", "message": "Données invalides.", "erreurs": [ { "champ": "pays", "message": "Code pays ISO à deux lettres attendu (ex. CI)." }, { "champ": "debut_exercice", "message": "Date attendue au format AAAA-MM-JJ." } ] } ``` | Champ | Type | Description | |---|---|---| | `statusCode` | integer | Code HTTP de la réponse | | `error_code` | string | Code stable : `validation_failed`, `unauthenticated`, `forbidden`, `not_found`, `conflict`… | | `message` | string | Motif lisible. En français pour les refus métier | | `erreurs` | array | Validation seulement : une entrée `{ champ, message }` par champ refusé | Le traitement conseillé de chaque code est dans [Erreurs](/article/errors). ## Idempotence Rejouer un appel ne crée jamais de doublon : votre identifiant sert de clé. Une entreprise, un utilisateur, un événement ou une pièce envoyé deux fois n'existe qu'une fois, et le second appel renvoie l'état actuel. Vous pouvez donc rejouer sans risque après une coupure réseau ou un `5xx`. Voir [Idempotence](/article/idempotency). > **tips** Il n'y a pas d'en-tête `Idempotency-Key` : l'identifiant dans le chemin ou dans le corps joue ce rôle. ## Routes | Route | Rôle | |---|---| | [GET /v1/partners/me](/article/get-partner-me) | Vérifier ses identifiants | | [PUT /v1/partners/companies/{external_id}](/article/put-company) | Créer ou mettre à jour une entreprise | | [POST /v1/partners/companies](/article/post-companies) | Créer des entreprises en masse | | [GET /v1/partners/companies/lots/{lot}](/article/get-company-lot) | Suivre un lot d'entreprises | | [PUT /v1/partners/users/{external_id}](/article/put-user) | Créer ou mettre à jour un utilisateur | | [DELETE /v1/partners/users/{external_id}](/article/delete-user) | Retirer un utilisateur | | [POST /v1/partners/events](/article/post-events) | Envoyer des événements | | [GET /v1/partners/events/{id}](/article/get-event) | Consulter un événement | | [GET /v1/partners/events](/article/list-events) | Synchroniser les événements | | [POST …/companies/{external_id}/bordereaux](/article/post-bordereau) | Émettre un bordereau d'achat | | [GET …/bordereaux/{id}](/article/get-bordereau) | Consulter un bordereau d'achat | | [GET …/bordereaux/{id}/pdf](/article/get-bordereau-pdf) | Télécharger le PDF d'un bordereau | | [POST …/companies/{external_id}/invoices](/article/post-invoice) | Émettre une facture | | [GET …/invoices/{id}](/article/get-invoice) | Consulter une facture | | [GET …/invoices/{id}/pdf](/article/get-invoice-pdf) | Télécharger le PDF d'une facture | Pour un parcours complet, commencez par [Créer une entreprise](/article/create-company), puis [Envoyer des événements](/article/send-events) et [Pièces fiscales FNE](/article/fne-documents). --- # Vérifier ses identifiants URL: https://developers.stg.dafexpert.com/article/get-partner-me > Vérifie de bout en bout qu'un jeton est valide et que l'accès partenaire est actif. Renvoie le partenaire et l'espace associés au jeton. C'est le premier appel à faire avec de nouveaux identifiants, et un bon contrôle de santé : il réussit seulement si le jeton est valide **et** si l'accès n'a pas été révoqué par l'administrateur de l'espace. | Authentification | Valeur | |---|---| | En-tête | `Authorization: Bearer ` | | Jeton | OAuth 2.0 client credentials, voir [Authentification](/article/authentication) | | Portées | Aucune | ## Paramètres Cette route ne prend aucun paramètre. ## Exemple de requête **cURL** ```bash curl https://api.stg.dafexpert.com/v1/partners/me \ -H "Authorization: Bearer $DAFEXPERT_TOKEN" ``` **Node.js** ```javascript const res = await fetch('https://api.stg.dafexpert.com/v1/partners/me', { headers: { Authorization: 'Bearer ' + process.env.DAFEXPERT_TOKEN }, }); console.log(res.status, await res.json()); ``` **Python** ```python import os, requests resp = requests.get( "https://api.stg.dafexpert.com/v1/partners/me", headers={"Authorization": f"Bearer {os.environ['DAFEXPERT_TOKEN']}"}, timeout=30, ) print(resp.status_code, resp.json()) ``` ## Réponse `200 OK` - `partner` (string) : Code du partenaire auquel appartiennent les identifiants. - `workspaceId` (string (uuid)) : Identifiant de l'espace dafexpert dans lequel le jeton agit. - `apiVersion` (integer) : Version de l'API partenaires. Vaut `1`. ```json { "partner": "gsmiles-agri", "workspaceId": "3c9f1e52-7a4b-4d0e-9b61-2f8a5c7d1e90", "apiVersion": 1 } ``` ## Erreurs | Code | Cas | |---|---| | `401` | Jeton absent, expiré ou invalide, jeton qui n'est pas celui d'un client partenaire, ou accès déconnecté par l'administrateur de l'espace. | ```json { "statusCode": 401, "error_code": "unauthenticated", "message": "Partner access is not connected or has been revoked.", "error": "Unauthorized" } ``` Sur un `401`, redemandez un jeton une fois puis rejouez l'appel. Si l'erreur persiste avec un jeton neuf, l'accès a été révoqué : contactez l'administrateur de l'espace. Voir [Erreurs](/article/errors). --- # Créer ou mettre à jour une entreprise URL: https://developers.stg.dafexpert.com/article/put-company > Crée une coopérative, une union, un acheteur ou un exploitant avec son premier exercice, ou met à jour son identité. Crée l'entreprise désignée par votre `external_id` dans votre espace, ou la met à jour si elle existe déjà. À la création, dafexpert ouvre aussi son premier exercice, avec ses périodes mensuelles, ses journaux et son plan comptable (référentiel déduit du pays). L'appel est **idempotent** : le premier appel crée l'entreprise (`201`), les suivants la mettent à jour (`200`). Plusieurs appels simultanés sur le même `external_id` ne créent qu'une seule entreprise. Pour une reprise de plusieurs centaines d'entreprises, utilisez [POST /v1/partners/companies](/article/post-companies). | Authentification | Valeur | |---|---| | En-tête | `Authorization: Bearer ` | | Jeton | OAuth 2.0 client credentials, voir [Authentification](/article/authentication) | | Portées | Aucune | ## Paramètres de chemin - `external_id` (string, path, required) : Votre identifiant de l'entreprise : 1 à 100 caractères, lettres, chiffres et `. _ : -`. C'est aussi la valeur du champ `exploitation` (v1) ou `entreprise` (v2) de vos événements. ## Corps de la requête ### Identité - `version` (integer, body) : `1`. Valeur par défaut : `1`. - `type` (string, body, required) : `cooperative`, `union`, `acheteur` ou `exploitant`. Détermine les champs acceptés (voir plus bas) et le système comptable par défaut. - `nom` (string, body, required) : Raison sociale, 1 à 200 caractères. Pour un exploitant : le nom de famille. - `pays` (string, body, required) : Code ISO à deux lettres (`CI`, `SN`, `FR`…). Détermine la devise et le référentiel comptable. Figé après la création. - `sigle` (string, body) : Nom court, 40 caractères au plus. - `date_creation` (string (date), body) : Date de création de la structure, `AAAA-MM-JJ`. - `parent_id` (string | null, body) : `external_id` de la structure d'appartenance : l'union d'une coopérative, la coopérative d'un exploitant. Le parent peut être créé après l'enfant. - `cooperative_id` (string | null, body) : Ancien nom de `parent_id`, toujours accepté. S'il est envoyé avec `parent_id`, les deux valeurs doivent être identiques. - `contact.nom` (string, body) : 120 caractères au plus. - `contact.email` (string (email), body) : Adresse e-mail du contact. - `contact.telephone` (string, body) : 40 caractères au plus. - `adresse.ligne1` (string, body) : 200 caractères au plus. - `adresse.ville` (string, body) : 100 caractères au plus. - `adresse.commune` (string, body) : 100 caractères au plus. - `localisation.region` (string, body) : 100 caractères au plus. De même pour `localisation.departement`, `localisation.commune` et `localisation.village`. - `localisation.gps.lat` (number, body) : Latitude, de -90 à 90. Obligatoire avec `localisation.gps.lng` (de -180 à 180). - `etiquettes` (object, body) : Regroupements libres (zone, filière, projet, bailleur) : clé (60 caractères au plus) vers un texte ou une liste de 20 textes au plus, 100 caractères par valeur. 20 clés au plus. Les clés sont normalisées (minuscules, sans accents). Chaque appel **remplace** vos étiquettes. ### Fiscal et légal - `ncc` (string, body) : Numéro de compte contribuable, 30 caractères au plus. Unique dans l'espace. Nécessaire avant la première pièce fiscale. - `forme_juridique` (string, body) : Code du catalogue des formes juridiques du pays (`SCOOPS`, `SCOOP-CA`, `Entreprenant`…). Casse, accents et ponctuation tolérés. Une valeur inconnue est refusée avec la liste des codes acceptés. - `numero_immatriculation` (string, body) : Registre des sociétés coopératives ou RCCM, 60 caractères au plus. - `regime_fiscal` (string, body) : Régime fiscal (réel normal, réel simplifié…), 60 caractères au plus. - `assujetti_tva` (boolean, body) : `true` si l'entreprise est assujettie à la TVA. - `centre_impots` (string, body) : 120 caractères au plus. - `agrement_filiere.numero` (string, body) : Numéro d'agrément de la filière, 1 à 60 caractères. Avec `agrement_filiere.valide_jusqu_au` (`AAAA-MM-JJ`, facultatif). - `capital_social` (number, body) : Capital social en FCFA, positif ou nul. ### Exploitant - `exploitant.prenoms` (string, body) : 120 caractères au plus. `nom` porte le nom de famille. - `exploitant.telephone` (string, body) : 40 caractères au plus. Nécessaire avant le premier bordereau d'achat FNE. - `exploitant.cni` (string, body) : Numéro de pièce d'identité, 40 caractères au plus. - `exploitant.id_producteur` (string, body) : Identifiant de filière (carte du producteur), 60 caractères au plus. - `exploitant.sexe` (string, body) : `M` ou `F`. - `exploitant.date_naissance` (string (date), body) : `AAAA-MM-JJ`. - `exploitant.superficie_ha` (number, body) : Superficie exploitée en hectares, positive ou nulle. - `exploitant.cultures` (array of string, body) : 20 cultures au plus, 60 caractères chacune (`cacao`, `anacarde`…). ### Comptabilité - `debut_exercice` (string (date), body) : Début du premier exercice, un 1er du mois (`AAAA-MM-01`). Par défaut, le 1er janvier de l'année en cours. Le premier exercice dure 12 mois. Figé après la création. - `systeme_comptable` (string, body) : `normal` ou `smt` (système minimal de trésorerie). Par défaut : `smt` pour un exploitant, `normal` sinon. Pris en compte à la création. ### Champs selon le type Un champ envoyé pour un type qui ne le prévoit pas est refusé (`422`). | Champ | cooperative | union | acheteur | exploitant | |---|---|---|---|---| | `sigle`, `date_creation`, `numero_immatriculation`, `regime_fiscal`, `assujetti_tva`, `centre_impots`, `capital_social` | oui | oui | oui | non | | `agrement_filiere` | oui | non | oui | non | | `parent_id` | une union | non | non | une coopérative | | `exploitant` | non | non | non | oui | > **warning** L'ancien champ `utilisateurs` est refusé : créez les utilisateurs avec [PUT /v1/partners/users/{external_id}](/article/put-user) après l'entreprise. ### Mise à jour Un champ absent n'est pas modifié. Le **pays**, la **devise**, le **référentiel** et l'**exercice** sont figés après la création, puisque la comptabilité a pu commencer. ## Exemple de requête **cURL** ```bash curl -X PUT https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE \ -H "Authorization: Bearer $DAFEXPERT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "version": 1, "type": "cooperative", "nom": "Coopérative Agricole de Soubré", "sigle": "CAS", "pays": "CI", "ncc": "2104587K", "forme_juridique": "SCOOP-CA", "numero_immatriculation": "CI-SBR-2015-C-0127", "regime_fiscal": "Réel simplifié", "assujetti_tva": false, "adresse": { "ligne1": "Quartier Commerce", "ville": "Soubré", "commune": "Soubré" }, "localisation": { "region": "Nawa", "departement": "Soubré" }, "etiquettes": { "filiere": "cacao", "zone": "Centre-Ouest" }, "debut_exercice": "2026-01-01", "contact": { "nom": "Awa Koné", "email": "gerance@coop-soubre.ci", "telephone": "+225 07 07 12 34 56" } }' ``` **Node.js** ```javascript const res = await fetch('https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE', { method: 'PUT', headers: { Authorization: 'Bearer ' + process.env.DAFEXPERT_TOKEN, 'Content-Type': 'application/json', }, body: JSON.stringify({ version: 1, type: 'cooperative', nom: 'Coopérative Agricole de Soubré', pays: 'CI', ncc: '2104587K', forme_juridique: 'SCOOP-CA', adresse: { ligne1: 'Quartier Commerce', ville: 'Soubré', commune: 'Soubré' }, debut_exercice: '2026-01-01', }), }); const company = await res.json(); // 201 à la création, 200 à la mise à jour ``` **Python** ```python import os, requests resp = requests.put( "https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE", headers={"Authorization": f"Bearer {os.environ['DAFEXPERT_TOKEN']}"}, json={ "version": 1, "type": "cooperative", "nom": "Coopérative Agricole de Soubré", "pays": "CI", "ncc": "2104587K", "forme_juridique": "SCOOP-CA", "adresse": {"ligne1": "Quartier Commerce", "ville": "Soubré", "commune": "Soubré"}, "debut_exercice": "2026-01-01", }, timeout=30, ) company = resp.json() # 201 à la création, 200 à la mise à jour ``` ## Réponse `201 Created` à la création, `200 OK` à la mise à jour. - `external_id` (string) : Votre identifiant de l'entreprise. - `company_id` (string (uuid)) : Identifiant de l'entreprise chez dafexpert. - `statut` (string) : `cree` ou `mis_a_jour`. - `referentiel` (string | null) : Référentiel comptable de l'entreprise (`SYSCOHADA`…). - `devise` (string) : Devise de l'entreprise (`XOF`…). - `evenements_liberes` (integer) : Nombre d'événements reçus avant l'entreprise (statut `en_attente_entreprise`) et libérés par sa création. Toujours `0` à la mise à jour. - `a_completer` (array of string) : Ce qui manque avant la première pièce fiscale : `ncc`, `numero_immatriculation`, `regime_fiscal`, `assujetti_tva`, `connexion_fne` (Côte d'Ivoire), ou `exploitant.telephone` pour un exploitant. Vide quand l'entreprise est prête. ```json { "external_id": "COOP-SOUBRE", "company_id": "ec6a85db-2f41-4c8e-b1d3-6a7f9e0c2b58", "statut": "cree", "referentiel": "SYSCOHADA", "devise": "XOF", "evenements_liberes": 0, "a_completer": ["connexion_fne"] } ``` ## Erreurs | Code | Cas | |---|---| | `401` | Jeton absent, expiré ou accès révoqué. | | `403` | Cet `external_id` est déjà rattaché à un autre espace. | | `409` | NCC déjà utilisé par une autre entreprise de l'espace, ou espace sans propriétaire ni administrateur actif. | | `422` | Données invalides (détail dans `erreurs`), champ non prévu pour le type, champ `utilisateurs` envoyé, pays inconnu, forme juridique hors du catalogue du pays, `debut_exercice` qui ne tombe pas un 1er du mois, `parent_id` qui ne suit pas la hiérarchie union, coopérative, exploitant, ou exercice impossible à ouvrir. | ```json { "statusCode": 409, "error_code": "conflict", "message": "Le NCC 2104587K est déjà utilisé par une autre entreprise de l'espace." } ``` Voir aussi [Créer une entreprise](/article/create-company) et [Limites des champs](/article/field-limits). --- # Créer des entreprises en masse URL: https://developers.stg.dafexpert.com/article/post-companies > Envoie jusqu'à 500 entreprises en un appel ; elles sont créées ou mises à jour en arrière-plan. > **warning** **Bientôt disponible.** Cette fonctionnalité est prête mais pas encore ouverte sur le staging. Le contrat décrit ici est celui qui sera publié. Reçoit un lot de 500 entreprises au plus, pour la reprise des entreprises que vous suivez déjà. Chaque entreprise est validée seule : une entreprise invalide est signalée sans bloquer les autres. Les entreprises valides sont mises en file, puis créées ou mises à jour exactement comme par [PUT /v1/partners/companies/{external_id}](/article/put-company). La réponse `202` arrive tout de suite. Suivez ensuite l'avancement avec [GET /v1/partners/companies/lots/{lot}](/article/get-company-lot). Au quotidien, préférez le `PUT` unitaire. | Authentification | Valeur | |---|---| | En-tête | `Authorization: Bearer ` | | Jeton | OAuth 2.0 client credentials, voir [Authentification](/article/authentication) | | Portées | Aucune | ## Corps de la requête - `version` (integer, body) : `1`. Valeur par défaut : `1`. - `entreprises` (array of object, body, required) : 1 à 500 entreprises. - `entreprises[].external_id` (string, body, required) : Votre identifiant de l'entreprise : 1 à 100 caractères, lettres, chiffres et `. _ : -`. Un même `external_id` deux fois dans le lot : la seconde occurrence est `invalide`. - `entreprises[].type` (string, body, required) : `cooperative`, `union`, `acheteur` ou `exploitant`. - `entreprises[].nom` (string, body, required) : 1 à 200 caractères. - `entreprises[].pays` (string, body, required) : Code ISO à deux lettres (`CI`…). - `entreprises[].…` (, body) : Tous les autres champs du corps de [PUT /v1/partners/companies/{external_id}](/article/put-company), avec les mêmes règles (`ncc`, `forme_juridique`, `parent_id`, `exploitant`, `debut_exercice`…). ## Exemple de requête **cURL** ```bash curl -X POST https://api.stg.dafexpert.com/v1/partners/companies \ -H "Authorization: Bearer $DAFEXPERT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "version": 1, "entreprises": [ { "external_id": "COOP-SOUBRE", "type": "cooperative", "nom": "Coopérative Agricole de Soubré", "pays": "CI", "forme_juridique": "SCOOP-CA" }, { "external_id": "EXPL-0457", "type": "exploitant", "nom": "KOUASSI", "pays": "CI", "forme_juridique": "Entreprenant", "parent_id": "COOP-SOUBRE", "exploitant": { "prenoms": "Yao", "telephone": "+225 07 58 21 44 10", "cultures": ["cacao"] } } ] }' ``` **Node.js** ```javascript const res = await fetch('https://api.stg.dafexpert.com/v1/partners/companies', { method: 'POST', headers: { Authorization: 'Bearer ' + process.env.DAFEXPERT_TOKEN, 'Content-Type': 'application/json', }, body: JSON.stringify({ version: 1, entreprises: [ { external_id: 'COOP-SOUBRE', type: 'cooperative', nom: 'Coopérative Agricole de Soubré', pays: 'CI', forme_juridique: 'SCOOP-CA' }, { external_id: 'EXPL-0457', type: 'exploitant', nom: 'KOUASSI', pays: 'CI', forme_juridique: 'Entreprenant', parent_id: 'COOP-SOUBRE', exploitant: { prenoms: 'Yao', telephone: '+225 07 58 21 44 10' } }, ], }), }); const { lot, results } = await res.json(); // 202 ``` **Python** ```python import os, requests resp = requests.post( "https://api.stg.dafexpert.com/v1/partners/companies", headers={"Authorization": f"Bearer {os.environ['DAFEXPERT_TOKEN']}"}, json={ "version": 1, "entreprises": [ {"external_id": "COOP-SOUBRE", "type": "cooperative", "nom": "Coopérative Agricole de Soubré", "pays": "CI", "forme_juridique": "SCOOP-CA"}, {"external_id": "EXPL-0457", "type": "exploitant", "nom": "KOUASSI", "pays": "CI", "forme_juridique": "Entreprenant", "parent_id": "COOP-SOUBRE", "exploitant": {"prenoms": "Yao", "telephone": "+225 07 58 21 44 10"}}, ], }, timeout=60, ) lot = resp.json()["lot"] # 202 ``` ## Réponse `202 Accepted`. Un résultat par entreprise, dans l'ordre d'envoi. - `lot` (string (uuid) | null) : Identifiant du lot, à passer à [GET /v1/partners/companies/lots/{lot}](/article/get-company-lot). `null` si aucune entreprise n'est valide. - `recues` (integer) : Nombre d'entreprises valides, mises en file. - `invalides` (integer) : Nombre d'entreprises refusées à la validation, non enregistrées. - `results` (array of object) : Un résultat par entreprise envoyée. - `results[].index` (integer) : Position de l'entreprise dans `entreprises` (à partir de 0). - `results[].external_id` (string | null) : `external_id` de l'entreprise, ou `null` s'il manque ou n'est pas un texte. - `results[].statut` (string) : `recu` (mise en file) ou `invalide` (non enregistrée). - `results[].id` (string (uuid)) : Identifiant de la ligne du lot. Présent si `statut` vaut `recu`. - `results[].erreurs` (array of object) : Présent si `statut` vaut `invalide` : une entrée `{ champ, message }` par champ refusé. ```json { "lot": "b7e2c4a1-5d3f-4e8b-9a6c-1f0d2e3b4c5a", "recues": 2, "invalides": 0, "results": [ { "index": 0, "external_id": "COOP-SOUBRE", "id": "0c4f9a7e-1b2d-4e3f-8a5b-6c7d8e9f0a1b", "statut": "recu" }, { "index": 1, "external_id": "EXPL-0457", "id": "9d8c7b6a-5f4e-4d3c-8b2a-1f0e9d8c7b6a", "statut": "recu" } ] } ``` Résultat d'une entreprise invalide (ici, un exploitant envoyé avec un agrément de filière) : ```json { "index": 1, "external_id": "EXPL-0457", "statut": "invalide", "erreurs": [{ "champ": "agrement_filiere", "message": "Champ non prévu pour le type « exploitant »." }] } ``` > **info** Le refus métier d'une entreprise valide (pays inconnu, forme juridique hors catalogue, NCC déjà pris, autre espace) n'apparaît pas ici : la ligne passe au statut `rejete` dans le suivi du lot, avec son motif. ## Erreurs | Code | Cas | |---|---| | `401` | Jeton absent, expiré ou accès révoqué. | | `422` | Corps invalide : `entreprises` absent, vide ou de plus de 500 éléments. Aucune entreprise n'est enregistrée. | Voir [Créer des entreprises en masse](/article/bulk-companies) pour la stratégie de reprise. --- # Suivre un lot d'entreprises URL: https://developers.stg.dafexpert.com/article/get-company-lot > Avancement d'un lot de création en masse : compteurs par statut et résultat de chaque entreprise. > **warning** **Bientôt disponible.** Cette fonctionnalité est prête mais pas encore ouverte sur le staging. Le contrat décrit ici est celui qui sera publié. Renvoie l'avancement d'un lot reçu par [POST /v1/partners/companies](/article/post-companies) : le statut de chaque entreprise, l'entreprise créée ou le motif du refus. Interrogez cette route jusqu'à ce que `termine` vaille `true`. | Authentification | Valeur | |---|---| | En-tête | `Authorization: Bearer ` | | Jeton | OAuth 2.0 client credentials, voir [Authentification](/article/authentication) | | Portées | Aucune | ## Paramètres de chemin - `lot` (string (uuid), path, required) : Identifiant du lot, renvoyé dans le champ `lot` de la réponse du `POST`. ## Exemple de requête **cURL** ```bash curl https://api.stg.dafexpert.com/v1/partners/companies/lots/b7e2c4a1-5d3f-4e8b-9a6c-1f0d2e3b4c5a \ -H "Authorization: Bearer $DAFEXPERT_TOKEN" ``` **Node.js** ```javascript const lot = 'b7e2c4a1-5d3f-4e8b-9a6c-1f0d2e3b4c5a'; const res = await fetch('https://api.stg.dafexpert.com/v1/partners/companies/lots/' + lot, { headers: { Authorization: 'Bearer ' + process.env.DAFEXPERT_TOKEN }, }); const { termine, compteurs } = await res.json(); ``` **Python** ```python import os, requests lot = "b7e2c4a1-5d3f-4e8b-9a6c-1f0d2e3b4c5a" resp = requests.get( f"https://api.stg.dafexpert.com/v1/partners/companies/lots/{lot}", headers={"Authorization": f"Bearer {os.environ['DAFEXPERT_TOKEN']}"}, timeout=30, ) print(resp.json()["compteurs"]) ``` ## Réponse `200 OK` - `lot` (string (uuid)) : Identifiant du lot. - `total` (integer) : Nombre d'entreprises mises en file dans ce lot (les entreprises invalides à la réception n'en font pas partie). - `termine` (boolean) : `true` quand plus aucune entreprise n'est `recu` ni `en_cours`. - `compteurs` (object) : Nombre d'entreprises par statut : `recu`, `en_cours`, `traite`, `rejete`. - `entreprises` (array of object) : Une ligne par entreprise, dans l'ordre d'envoi. - `entreprises[].index` (integer) : Position de l'entreprise dans le lot envoyé. - `entreprises[].external_id` (string) : Votre identifiant de l'entreprise. - `entreprises[].id` (string (uuid)) : Identifiant de la ligne du lot. - `entreprises[].statut` (string) : `recu` (en file), `en_cours`, `traite` (créée ou mise à jour) ou `rejete` (refus métier, sans nouvel essai : corriger puis renvoyer). - `entreprises[].essais` (integer) : Nombre de tentatives. Une erreur technique est réessayée automatiquement. - `entreprises[].company_id` (string (uuid) | null) : Identifiant de l'entreprise chez dafexpert, une fois traitée. - `entreprises[].resultat` (string | null) : `cree` ou `mis_a_jour`, une fois traitée. - `entreprises[].referentiel` (string | null) : Référentiel comptable, une fois traitée. - `entreprises[].devise` (string | null) : Devise de l'entreprise, une fois traitée. - `entreprises[].evenements_liberes` (integer | null) : Événements en attente libérés par la création. - `entreprises[].erreur` (string | null) : Motif du refus (`rejete`), ou dernière erreur technique d'une ligne encore en file. - `entreprises[].traite_le` (string (date-time) | null) : Date de traitement, en UTC. ```json { "lot": "b7e2c4a1-5d3f-4e8b-9a6c-1f0d2e3b4c5a", "total": 2, "termine": true, "compteurs": { "recu": 0, "en_cours": 0, "traite": 1, "rejete": 1 }, "entreprises": [ { "index": 0, "external_id": "COOP-SOUBRE", "id": "0c4f9a7e-1b2d-4e3f-8a5b-6c7d8e9f0a1b", "statut": "traite", "essais": 1, "company_id": "ec6a85db-2f41-4c8e-b1d3-6a7f9e0c2b58", "resultat": "cree", "referentiel": "SYSCOHADA", "devise": "XOF", "evenements_liberes": 0, "erreur": null, "traite_le": "2026-10-12T09:14:07.512Z" }, { "index": 1, "external_id": "COOP-GAGNOA", "id": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d", "statut": "rejete", "essais": 1, "company_id": null, "resultat": null, "referentiel": null, "devise": null, "evenements_liberes": null, "erreur": "Le NCC 2104587K est déjà utilisé par une autre entreprise de l'espace.", "traite_le": "2026-10-12T09:14:08.031Z" } ] } ``` ## Erreurs | Code | Cas | |---|---| | `401` | Jeton absent, expiré ou accès révoqué. | | `404` | Lot inconnu, ou lot d'un autre espace (`Lot inconnu.`). | | `422` | L'identifiant de lot n'est pas un UUID. | Voir [Créer des entreprises en masse](/article/bulk-companies). --- # Créer ou mettre à jour un utilisateur URL: https://developers.stg.dafexpert.com/article/put-user > Crée ou rattache le compte de connexion d'un utilisateur et fixe la liste de ses accès aux entreprises. Crée l'utilisateur désigné par votre `external_id` (gérant de coopérative, exploitant, comptable…) et lui donne accès aux entreprises listées dans `acces`. À appeler **après** la création des entreprises avec [PUT /v1/partners/companies/{external_id}](/article/put-company). L'appel est **idempotent** : `201` à la création, `200` à la mise à jour. La liste `acces` est la référence : un nouvel appel retire les accès aux entreprises de votre espace qui n'y figurent plus. | Authentification | Valeur | |---|---| | En-tête | `Authorization: Bearer ` | | Jeton | OAuth 2.0 client credentials, voir [Authentification](/article/authentication) | | Portées | Aucune | ## Ce que fait l'appel - **Compte de connexion** : sans compte existant pour cet e-mail, il est créé, actif, avec l'e-mail considéré comme vérifié. Si un compte existe déjà, il est rattaché ; son nom et son mot de passe ne sont alors jamais modifiés. - **Accès** : l'utilisateur devient simple membre de l'espace, jamais propriétaire ni administrateur. Il ne voit que les entreprises listées, avec le rôle indiqué pour chacune. - **Première connexion** : l'utilisateur arrive directement dans son entreprise, depuis dafexpert ou depuis votre application (SSO). ## Paramètres de chemin - `external_id` (string, path, required) : Votre identifiant de l'utilisateur : 1 à 100 caractères, lettres, chiffres et `. _ : -`. ## Corps de la requête - `version` (integer, body) : `1`. Valeur par défaut : `1`. - `email` (string (email), body, required) : Adresse e-mail, identifiant de connexion. - `prenom` (string, body) : 80 caractères au plus. - `nom` (string, body) : 80 caractères au plus. - `telephone` (string, body) : 40 caractères au plus. - `mot_de_passe` (string, body) : 8 à 128 caractères, soumis à la politique de sécurité de dafexpert. Appliqué seulement aux comptes créés par vous ; jamais stocké par dafexpert. - `mot_de_passe_temporaire` (boolean, body) : `true` : l'utilisateur doit changer son mot de passe à la première connexion. Valeur par défaut : `false`. - `acces` (array of object, body) : Entreprises accessibles, 200 au plus. Valeur par défaut : liste vide (aucun accès). - `acces[].exploitation` (string, body, required) : `external_id` d'une entreprise déjà créée dans votre espace. - `acces[].role` (string, body) : Rôle sur cette entreprise, 40 caractères au plus : `collaborateur` (par défaut), `dirigeant`, `comptable`, `lecture-seule`… `dirigeant` fait de l'utilisateur le responsable de cette entreprise seulement. ## Exemple de requête **cURL** ```bash curl -X PUT https://api.stg.dafexpert.com/v1/partners/users/USR-GERANT-SOUBRE \ -H "Authorization: Bearer $DAFEXPERT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "version": 1, "email": "awa.kone@coop-soubre.ci", "prenom": "Awa", "nom": "Koné", "telephone": "+225 07 07 12 34 56", "mot_de_passe": "Cacao-Soubre-2026!", "mot_de_passe_temporaire": true, "acces": [ { "exploitation": "COOP-SOUBRE", "role": "dirigeant" } ] }' ``` **Node.js** ```javascript const res = await fetch('https://api.stg.dafexpert.com/v1/partners/users/USR-GERANT-SOUBRE', { method: 'PUT', headers: { Authorization: 'Bearer ' + process.env.DAFEXPERT_TOKEN, 'Content-Type': 'application/json', }, body: JSON.stringify({ version: 1, email: 'awa.kone@coop-soubre.ci', prenom: 'Awa', nom: 'Koné', mot_de_passe: process.env.INITIAL_PASSWORD, mot_de_passe_temporaire: true, acces: [{ exploitation: 'COOP-SOUBRE', role: 'dirigeant' }], }), }); const user = await res.json(); // 201 ou 200 ``` **Python** ```python import os, requests resp = requests.put( "https://api.stg.dafexpert.com/v1/partners/users/USR-GERANT-SOUBRE", headers={"Authorization": f"Bearer {os.environ['DAFEXPERT_TOKEN']}"}, json={ "version": 1, "email": "awa.kone@coop-soubre.ci", "prenom": "Awa", "nom": "Koné", "mot_de_passe": os.environ["INITIAL_PASSWORD"], "mot_de_passe_temporaire": True, "acces": [{"exploitation": "COOP-SOUBRE", "role": "dirigeant"}], }, timeout=30, ) user = resp.json() # 201 ou 200 ``` ## Réponse `201 Created` à la création, `200 OK` à la mise à jour. - `external_id` (string) : Votre identifiant de l'utilisateur. - `user_id` (string (uuid)) : Identifiant de l'utilisateur chez dafexpert. - `statut` (string) : `cree` ou `mis_a_jour`. - `compte` (string) : `cree` : compte de connexion créé par cet appel ou un appel précédent de votre part. `existant` : compte déjà présent pour cet e-mail, rattaché. - `mot_de_passe` (string | null) : `defini` : mot de passe appliqué. `ignore` : compte existant que vous n'avez pas créé, mot de passe non modifié. `null` : aucun mot de passe envoyé. - `acces` (array of object) : Accès accordés, un par entreprise de la requête. - `acces[].exploitation` (string) : `external_id` de l'entreprise. - `acces[].company_id` (string (uuid)) : Identifiant de l'entreprise chez dafexpert. - `acces[].role` (string) : Rôle accordé. - `acces_retires` (integer) : Nombre d'accès retirés à des entreprises de votre espace absentes de `acces`. ```json { "external_id": "USR-GERANT-SOUBRE", "user_id": "8f2d6c1a-4b3e-4a5d-9c7f-0e1b2a3c4d5e", "statut": "cree", "compte": "cree", "mot_de_passe": "defini", "acces": [ { "exploitation": "COOP-SOUBRE", "company_id": "ec6a85db-2f41-4c8e-b1d3-6a7f9e0c2b58", "role": "dirigeant" } ], "acces_retires": 0 } ``` ## Erreurs | Code | Cas | |---|---| | `401` | Jeton absent, expiré ou accès révoqué. | | `403` | Cet utilisateur est rattaché à un autre espace. | | `409` | E-mail déjà utilisé par un autre compte (changement d'e-mail), ou compte déjà rattaché à un autre utilisateur de votre part. | | `422` | Données invalides, entreprise inconnue dans l'espace (à créer d'abord), rôle inconnu, ou mot de passe refusé par la politique de sécurité. Si le compte venait d'être créé, il est supprimé : l'appel peut être renvoyé sans risque. | | `503` | Création de comptes momentanément impossible : réessayer plus tard. | ```json { "statusCode": 422, "error_code": "error", "message": "Exploitation(s) inconnue(s) dans l'espace : COOP-DALOA. Créez-les d'abord (PUT /v1/partners/companies)." } ``` > **tips** Pour retirer tous les accès d'un utilisateur, utilisez [DELETE /v1/partners/users/{external_id}](/article/delete-user). Voir aussi [Gérer les utilisateurs](/article/manage-users). --- # Retirer un utilisateur URL: https://developers.stg.dafexpert.com/article/delete-user > Retire à un utilisateur tous ses accès aux entreprises de votre espace, sans supprimer son compte. Retire à l'utilisateur tous ses accès aux entreprises de votre espace et supprime le lien avec votre `external_id`. Son compte de connexion est **conservé**, car il peut avoir d'autres accès dans dafexpert. Un nouvel appel à [PUT /v1/partners/users/{external_id}](/article/put-user) rattache de nouveau l'utilisateur. | Authentification | Valeur | |---|---| | En-tête | `Authorization: Bearer ` | | Jeton | OAuth 2.0 client credentials, voir [Authentification](/article/authentication) | | Portées | Aucune | ## Paramètres de chemin - `external_id` (string, path, required) : Votre identifiant de l'utilisateur, celui utilisé lors du `PUT`. ## Exemple de requête **cURL** ```bash curl -X DELETE https://api.stg.dafexpert.com/v1/partners/users/USR-GERANT-SOUBRE \ -H "Authorization: Bearer $DAFEXPERT_TOKEN" ``` **Node.js** ```javascript const res = await fetch('https://api.stg.dafexpert.com/v1/partners/users/USR-GERANT-SOUBRE', { method: 'DELETE', headers: { Authorization: 'Bearer ' + process.env.DAFEXPERT_TOKEN }, }); console.log(await res.json()); ``` **Python** ```python import os, requests resp = requests.delete( "https://api.stg.dafexpert.com/v1/partners/users/USR-GERANT-SOUBRE", headers={"Authorization": f"Bearer {os.environ['DAFEXPERT_TOKEN']}"}, timeout=30, ) print(resp.json()) ``` ## Réponse `200 OK` - `external_id` (string) : Votre identifiant de l'utilisateur. - `acces_retires` (integer) : Nombre d'accès retirés. ```json { "external_id": "USR-GERANT-SOUBRE", "acces_retires": 1 } ``` ## Erreurs | Code | Cas | |---|---| | `401` | Jeton absent, expiré ou accès révoqué. | | `404` | Utilisateur inconnu dans votre espace, ou déjà retiré (`Utilisateur inconnu.`). | | `422` | `external_id` invalide (caractères ou longueur). | > **info** Un second `DELETE` sur le même utilisateur renvoie `404` : vous pouvez traiter ce cas comme un succès. Voir [Gérer les utilisateurs](/article/manage-users). --- # Envoyer des événements URL: https://developers.stg.dafexpert.com/article/post-events > Envoie un événement ou un lot de 500 événements à comptabiliser ; ils sont traités en arrière-plan. Enregistre les opérations de votre système à comptabiliser : paiements aux producteurs, avances, dépenses, paie… La réponse `202` arrive tout de suite ; la comptabilisation se fait en arrière-plan. Suivez ensuite chaque événement avec [GET /v1/partners/events/{id}](/article/get-event) ou [GET /v1/partners/events](/article/list-events). Le corps est **soit un événement seul**, **soit un lot** `{ "events": [...] }` de 500 événements au plus. Dans un lot, chaque événement est validé seul : un événement invalide est signalé sans bloquer les autres. | Authentification | Valeur | |---|---| | En-tête | `Authorization: Bearer ` | | Jeton | OAuth 2.0 client credentials, voir [Authentification](/article/authentication) | | Portées | Aucune | ## Corps de la requête ### Lot - `version` (integer, body) : `1` ou `2` : version du contrat appliquée aux événements du lot qui ne précisent pas la leur. Valeur par défaut : `1`. - `events` (array of object, body, required) : 1 à 500 événements, au format de la version 1 ou 2. Un événement peut porter son propre champ `version`. ### Événement, version 2 La version 2 suit le [catalogue des événements](/article/event-catalog) : chaque `type` a ses champs propres (tiers, pièces citées dans `lie_a`, lignes, mode de paiement…). Un événement envoyé seul doit porter `"version": 2`. Champs communs à tous les types : - `version` (integer, body, required) : `2` (facultatif dans un lot de version 2). - `id` (string, body, required) : Votre identifiant de l'événement, 1 à 100 caractères, lettres, chiffres et `. _ : -`. Clé anti-doublon. - `type` (string, body, required) : `paiement_producteur`, `avance_producteur`, `remboursement_producteur`, `encaissement_client`, `cession_intrant`, `achat_fournisseur`, `depense`, `paie`, `souscription_parts`, `cotisation`, `inventaire`, `annulation` ou `recolte`. - `entreprise` (string, body, required) : `external_id` de l'entreprise concernée. - `date` (string (date), body, required) : Date de l'opération, `AAAA-MM-JJ`. - `montant` (integer, body, required) : Montant total en FCFA entiers, de 0 à 10 000 milliards. Il doit correspondre au détail du type (somme des lignes, de `lie_a`…). - `devise` (string, body) : Code ISO à trois lettres (`XOF`). S'il est fourni, il doit être celui de l'entreprise. - `reference_origine` (string, body) : Référence dans votre système (numéro de reçu…), 120 caractères au plus. - `analytique.campagne` (string, body) : 60 caractères au plus. De même pour `analytique.parcelle`, `analytique.groupement` et `analytique.produit`. - `donnees` (object, body) : Données libres de votre système, 32 000 octets au plus en JSON. ### Événement, version 1 La version 1 reste acceptée : la comptabilisation de chaque `type` est paramétrée chez dafexpert. - `version` (integer, body) : `1`. Valeur par défaut : `1`. - `id` (string, body, required) : Votre identifiant de l'événement, 1 à 100 caractères, lettres, chiffres et `. _ : -`. Clé anti-doublon. - `type` (string, body, required) : Type de l'événement, 1 à 60 caractères : lettres, chiffres et `_ . -`, en commençant par une lettre ou un chiffre. - `exploitation` (string, body, required) : `external_id` de l'entreprise concernée. - `date` (string (date), body, required) : Date de l'opération, `AAAA-MM-JJ`. - `montant` (number, body, required) : Montant dans la devise de l'entreprise, deux décimales au plus. - `devise` (string, body) : Code ISO à trois lettres. S'il diffère de celui de l'entreprise, l'événement est `rejete`. - `quantites` (object, body) : Quantités par unité, par exemple `{ "kg": 1200 }` (clé de 30 caractères au plus). - `donnees` (object, body) : Données de l'opération utilisées par les règles de comptabilisation, 32 000 octets au plus en JSON. ## Exemple de requête Un lot en version 2 avec un paiement à un producteur, net d'une retenue d'avance, par mobile money : **cURL** ```bash curl -X POST https://api.stg.dafexpert.com/v1/partners/events \ -H "Authorization: Bearer $DAFEXPERT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "version": 2, "events": [{ "id": "PAI-2026-000318", "type": "paiement_producteur", "entreprise": "COOP-SOUBRE", "date": "2026-10-09", "montant": 2010000, "tiers": { "role": "producteur", "id": "PROD-0457", "entreprise": "EXPL-0457", "nom": "KOUASSI", "prenoms": "Yao", "telephone": "+225 07 58 21 44 10" }, "lie_a": [{ "id": "ACH-2026-000412", "montant": 2160000 }], "retenues": [{ "nature": "avance", "lie_a": "AV-2026-000051", "montant": 150000 }], "mode": "mobile_money", "operateur": "Orange Money", "reference_transaction": "OM-CI-8812734501", "caisse": "CAISSE-OM-SOUBRE", "analytique": { "campagne": "2026-2027", "produit": "cacao" } }] }' ``` **Node.js** ```javascript const res = await fetch('https://api.stg.dafexpert.com/v1/partners/events', { method: 'POST', headers: { Authorization: 'Bearer ' + process.env.DAFEXPERT_TOKEN, 'Content-Type': 'application/json', }, body: JSON.stringify({ version: 2, events: [{ id: 'PAI-2026-000318', type: 'paiement_producteur', entreprise: 'COOP-SOUBRE', date: '2026-10-09', montant: 2010000, tiers: { role: 'producteur', id: 'PROD-0457', entreprise: 'EXPL-0457', nom: 'KOUASSI', prenoms: 'Yao' }, lie_a: [{ id: 'ACH-2026-000412', montant: 2160000 }], retenues: [{ nature: 'avance', lie_a: 'AV-2026-000051', montant: 150000 }], mode: 'mobile_money', operateur: 'Orange Money', reference_transaction: 'OM-CI-8812734501', caisse: 'CAISSE-OM-SOUBRE', }], }), }); const { results } = await res.json(); // 202 ``` **Python** ```python import os, requests resp = requests.post( "https://api.stg.dafexpert.com/v1/partners/events", headers={"Authorization": f"Bearer {os.environ['DAFEXPERT_TOKEN']}"}, json={ "version": 2, "events": [{ "id": "PAI-2026-000318", "type": "paiement_producteur", "entreprise": "COOP-SOUBRE", "date": "2026-10-09", "montant": 2010000, "tiers": {"role": "producteur", "id": "PROD-0457", "entreprise": "EXPL-0457", "nom": "KOUASSI", "prenoms": "Yao"}, "lie_a": [{"id": "ACH-2026-000412", "montant": 2160000}], "retenues": [{"nature": "avance", "lie_a": "AV-2026-000051", "montant": 150000}], "mode": "mobile_money", "operateur": "Orange Money", "reference_transaction": "OM-CI-8812734501", "caisse": "CAISSE-OM-SOUBRE", }], }, timeout=60, ) results = resp.json()["results"] # 202 ``` Un événement seul en version 1 (le `type` est celui de votre paramétrage) : ```json { "version": 1, "id": "9a2e41c7-0b5d-4f3e-8a6c-2d1e0f9b8a7c", "type": "achat_cacao", "exploitation": "COOP-SOUBRE", "date": "2026-10-08", "quantites": { "kg": 1200 }, "montant": 2160000, "devise": "XOF", "donnees": { "producteur": "EXPL-0457", "campagne": "2026-2027" } } ``` ## Réponse `202 Accepted`. Un résultat par événement, dans l'ordre d'envoi. - `results` (array of object) : Un résultat par événement envoyé. - `results[].id` (string | null) : Votre identifiant de l'événement (`null` pour un événement invalide sans `id` lisible). - `results[].statut` (string) : Statut actuel : `recu`, `en_attente_entreprise`, `en_attente_piece`, `en_cours`, `comptabilise`, `partiel`, `rejete`, `ignore`, `annule`, `non_comptable`. Ou, à la réception : `invalide` (non enregistré, lot seulement) ou `refuse` (entreprise ou `id` d'un autre espace). - `results[].doublon` (boolean) : `true` si cet `id` était déjà enregistré : le statut renvoyé est le statut actuel. - `results[].version` (integer) : Numéro de la version appliquée de l'événement (`1` au premier envoi). - `results[].contenu_different` (boolean) : Présent (`true`) quand l'`id` a été renvoyé avec un contenu différent. - `results[].version_appliquee` (boolean) : Avec `contenu_different` : `true` si la nouvelle version a remplacé l'ancienne, `false` si elle est gardée sans effet (événement déjà comptabilisé ou en cours de traitement). - `results[].message` (string) : Explication quand une nouvelle version n'est pas appliquée. - `results[].erreur` (string) : Motif d'un rejet ou d'un refus. - `results[].index` (integer) : Événement invalide d'un lot : sa position dans `events` (à partir de 0). - `results[].erreurs` (array of object) : Événement invalide d'un lot : une entrée `{ champ, message }` par champ refusé. ```json { "results": [ { "id": "PAI-2026-000318", "statut": "recu", "doublon": false, "version": 1 }, { "id": "PAI-2026-000319", "statut": "comptabilise", "doublon": true, "version": 1 }, { "id": "PAI-2026-000320", "statut": "comptabilise", "doublon": true, "version": 1, "contenu_different": true, "version_appliquee": false, "message": "Nouvelle version reçue après comptabilisation : elle n'est pas appliquée. Pour corriger, envoyer une annulation puis un nouvel événement." }, { "index": 3, "id": "PAI-2026-000321", "statut": "invalide", "erreurs": [{ "champ": "reference_transaction", "message": "reference_transaction obligatoire pour un paiement mobile money." }] }, { "id": "PAI-2026-000322", "statut": "refuse", "erreur": "L'exploitation de cet événement est rattachée à un autre espace." } ] } ``` ### Comportements à connaître - **Doublon** : renvoyer un événement déjà reçu ne modifie rien et renvoie son statut actuel. Vous pouvez rejouer un lot entier après une coupure. - **Nouvelle version** : le même `id` avec un autre contenu remplace l'événement tant qu'il n'est pas comptabilisé. Après comptabilisation, envoyez une `annulation` puis un nouvel événement. Voir [Versions d'un événement](/article/event-versions). - **Entreprise pas encore créée** : l'événement est conservé en `en_attente_entreprise` et libéré à la création de l'entreprise. ## Erreurs | Code | Cas | |---|---| | `401` | Jeton absent, expiré ou accès révoqué. | | `422` | Événement seul invalide (détail dans `erreurs`), ou lot mal formé (`events` vide ou de plus de 500 éléments, `version` autre que 1 ou 2). Rien n'est enregistré. | > **info** Dans un lot, un événement invalide ne provoque pas de `422` : il revient avec `"statut": "invalide"` et les autres sont enregistrés. Voir [Envoyer des événements](/article/send-events) et [Suivre les événements](/article/track-events). --- # Consulter un événement URL: https://developers.stg.dafexpert.com/article/get-event > Statut de comptabilisation d'un événement, avec ses pièces et écritures par entreprise. Renvoie l'état actuel d'un événement envoyé par [POST /v1/partners/events](/article/post-events) : son statut, le numéro de l'écriture passée, le motif d'un rejet et, en version 2, les pièces et écritures produites pour chaque entreprise concernée. | Authentification | Valeur | |---|---| | En-tête | `Authorization: Bearer ` | | Jeton | OAuth 2.0 client credentials, voir [Authentification](/article/authentication) | | Portées | Aucune | ## Paramètres de chemin - `id` (string, path, required) : Votre identifiant de l'événement, celui envoyé dans le champ `id`. ## Exemple de requête **cURL** ```bash curl https://api.stg.dafexpert.com/v1/partners/events/PAI-2026-000318 \ -H "Authorization: Bearer $DAFEXPERT_TOKEN" ``` **Node.js** ```javascript const res = await fetch('https://api.stg.dafexpert.com/v1/partners/events/PAI-2026-000318', { headers: { Authorization: 'Bearer ' + process.env.DAFEXPERT_TOKEN }, }); const event = await res.json(); console.log(event.statut, event.erreur); ``` **Python** ```python import os, requests resp = requests.get( "https://api.stg.dafexpert.com/v1/partners/events/PAI-2026-000318", headers={"Authorization": f"Bearer {os.environ['DAFEXPERT_TOKEN']}"}, timeout=30, ) event = resp.json() print(event["statut"], event["erreur"]) ``` ## Réponse `200 OK` - `id` (string) : Votre identifiant de l'événement. - `type` (string) : Type de l'événement. - `exploitation` (string) : `external_id` de l'entreprise (champ `exploitation` en v1, `entreprise` en v2). - `date` (string (date)) : Date de l'opération. - `montant` (number) : Montant de l'événement. - `devise` (string | null) : Devise envoyée, ou `null`. - `statut` (string) : `recu`, `en_attente_entreprise`, `en_attente_piece`, `en_cours`, `comptabilise`, `partiel`, `rejete`, `ignore`, `annule` ou `non_comptable`. Voir [Suivre les événements](/article/track-events). - `erreur` (string | null) : Motif du rejet ou de l'écart, sinon `null`. - `ecriture` (string | null) : Numéro de pièce de l'écriture comptable passée, sinon `null`. - `recu_le` (string (date-time)) : Date de réception, en UTC. - `mis_a_jour_le` (string (date-time)) : Date du dernier changement, en UTC. - `version` (integer) : Numéro de la version appliquée (`1` = premier envoi). - `version_non_appliquee` (object) : Version reçue mais non appliquée : `version` (integer) et `message` (string). - `en_attente_de` (array of string) : Version 2 : identifiants des pièces citées qui ne sont pas encore arrivées. - `effets` (array of object) : Version 2 : un effet par entreprise concernée. - `effets[].entreprise` (string) : `external_id` de l'entreprise. - `effets[].role` (string) : Rôle de l'entreprise dans l'opération (`payeur`, `preteur`, `beneficiaire`, `cedant`, `acheteur`, `employeur`, `cooperative`…). - `effets[].action` (string) : `creee` (pièce créée), `rattachee` (pièce existante rattachée) ou `ecriture` (écriture seule). - `effets[].statut` (string) : `comptabilise`, `en_cours` ou `rejete`. - `effets[].piece` (object | null) : Pièce produite : `type` (`supplier_invoice`, `supplier_settlement`, `settlement` ou `entry`), `numero` et `numero_fiscal` (ces deux derniers peuvent valoir `null`). - `effets[].ecriture` (string | null) : Numéro de pièce de l'écriture comptable. - `effets[].message` (string | null) : Motif en cas de rejet, ou résumé de ce qui a été fait. ```json { "id": "PAI-2026-000318", "type": "paiement_producteur", "exploitation": "COOP-SOUBRE", "date": "2026-10-09", "montant": 2010000, "devise": null, "statut": "comptabilise", "erreur": null, "ecriture": null, "recu_le": "2026-10-09T16:02:11.304Z", "mis_a_jour_le": "2026-10-09T16:02:14.877Z", "version": 1, "effets": [ { "entreprise": "COOP-SOUBRE", "role": "payeur", "action": "creee", "statut": "comptabilise", "piece": { "type": "supplier_settlement", "numero": "OM-CI-8812734501", "numero_fiscal": null }, "ecriture": null, "message": "Payé 2010000 FCFA (mobile money), retenues 150000 FCFA." } ] } ``` ## Erreurs | Code | Cas | |---|---| | `401` | Jeton absent, expiré ou accès révoqué. | | `404` | Événement inconnu dans votre espace (`Événement inconnu.`). | | `422` | Identifiant invalide (caractères ou longueur). | Pour suivre de nombreux événements, préférez la synchronisation par curseur : [GET /v1/partners/events](/article/list-events). --- # Synchroniser les événements URL: https://developers.stg.dafexpert.com/article/list-events > Liste les événements modifiés depuis un curseur, du plus ancien au plus récent, pour suivre tous les changements de statut. Renvoie les événements de votre espace **modifiés** après le curseur `apres`, du plus ancien au plus récent. Gardez le dernier `suivant` reçu et repassez-le au prochain appel : vous récupérez ainsi tous les changements de statut, sans en manquer et sans doublon. | Authentification | Valeur | |---|---| | En-tête | `Authorization: Bearer ` | | Jeton | OAuth 2.0 client credentials, voir [Authentification](/article/authentication) | | Portées | Aucune | ## Paramètres de requête - `apres` (string, query) : Curseur opaque renvoyé dans `suivant` par l'appel précédent, 200 caractères au plus. Absent : depuis le début. - `limite` (integer, query) : Nombre d'événements par page, de 1 à 500. Valeur par défaut : `100`. - `statut` (string, query) : Ne renvoie que les événements de ce statut : `recu`, `en_attente_entreprise`, `en_attente_piece`, `en_cours`, `comptabilise`, `partiel`, `rejete`, `ignore`, `annule` ou `non_comptable`. ## Exemple de requête **cURL** ```bash curl "https://api.stg.dafexpert.com/v1/partners/events?limite=100&apres=MjAyNi0xMC0wOVQxNjowMjoxNC44NzdafDNm..." \ -H "Authorization: Bearer $DAFEXPERT_TOKEN" ``` **Node.js** ```javascript // Boucle de synchronisation : le curseur est conservé entre deux exécutions. let cursor = await loadCursor(); // null au premier passage for (;;) { const url = new URL('https://api.stg.dafexpert.com/v1/partners/events'); url.searchParams.set('limite', '500'); if (cursor) url.searchParams.set('apres', cursor); const res = await fetch(url, { headers: { Authorization: 'Bearer ' + process.env.DAFEXPERT_TOKEN }, }); const { evenements, suivant } = await res.json(); for (const e of evenements) await updateLocalStatus(e.id, e.statut, e.erreur); if (!suivant) break; cursor = suivant; await saveCursor(cursor); } ``` **Python** ```python import os, requests cursor = load_cursor() # None au premier passage while True: params = {"limite": 500} if cursor: params["apres"] = cursor resp = requests.get( "https://api.stg.dafexpert.com/v1/partners/events", headers={"Authorization": f"Bearer {os.environ['DAFEXPERT_TOKEN']}"}, params=params, timeout=30, ) page = resp.json() for e in page["evenements"]: update_local_status(e["id"], e["statut"], e["erreur"]) if not page["suivant"]: break cursor = page["suivant"] save_cursor(cursor) ``` ## Réponse `200 OK` - `evenements` (array of object) : Événements modifiés, du plus ancien au plus récent. Chaque élément a la même forme que la réponse de [GET /v1/partners/events/{id}](/article/get-event) (`id`, `type`, `exploitation`, `statut`, `erreur`, `ecriture`, `effets`…). - `suivant` (string | null) : Curseur à repasser dans `apres` pour la page suivante. `null` quand la page n'est pas pleine : tout est lu pour l'instant. ```json { "evenements": [ { "id": "PAI-2026-000318", "type": "paiement_producteur", "exploitation": "COOP-SOUBRE", "date": "2026-10-09", "montant": 2010000, "devise": null, "statut": "comptabilise", "erreur": null, "ecriture": null, "recu_le": "2026-10-09T16:02:11.304Z", "mis_a_jour_le": "2026-10-09T16:02:14.877Z", "version": 1 }, { "id": "DEP-2026-001207", "type": "depense", "exploitation": "COOP-SOUBRE", "date": "2026-10-10", "montant": 45000, "devise": "EUR", "statut": "rejete", "erreur": "Devise EUR différente de celle de l'entreprise (XOF).", "ecriture": null, "recu_le": "2026-10-10T08:41:52.019Z", "mis_a_jour_le": "2026-10-10T08:41:53.660Z", "version": 1 } ], "suivant": null } ``` > **tips** Un événement réapparaît dans la synchronisation à chaque changement de statut. Mettez simplement à jour votre copie locale avec la dernière valeur reçue. ## Erreurs | Code | Cas | |---|---| | `401` | Jeton absent, expiré ou accès révoqué. | | `422` | Paramètre invalide : `statut` inconnu, `limite` hors de 1 à 500, ou curseur `apres` illisible (`Curseur invalide.`). | Voir [Suivre les événements](/article/track-events). --- # Émettre un bordereau d'achat URL: https://developers.stg.dafexpert.com/article/post-bordereau > Émet un bordereau d'achat de produits agricoles, le certifie FNE, produit son PDF et le comptabilise, en un seul appel. > **warning** **Bientôt disponible.** Cette fonctionnalité est prête mais pas encore ouverte sur le staging. Le contrat décrit ici est celui qui sera publié. Émet le bordereau d'achat de produits agricoles remis par l'entreprise acheteuse (`external_id`) à un producteur. L'appel est **synchrone** : la réponse donne directement le numéro, le numéro fiscal, le QR code de vérification, le lien du PDF et l'état de comptabilisation. - Le bordereau est enregistré comme une facture fournisseur de l'acheteur, numérotée `BA-`. - En Côte d'Ivoire, il est certifié auprès de la FNE (facture normalisée électronique, modèle *purchase*). Il n'est comptabilisé qu'une fois certifié. - Hors Côte d'Ivoire, il est émis et comptabilisé sans certification (`statut` : `emise`). - Si le producteur est lui-même une entreprise de l'espace (`tiers.entreprise`), la vente correspondante est aussi enregistrée dans sa comptabilité (`miroir`). L'appel est **idempotent** sur votre `id` : `201` à la première émission, `200` à un rejeu, qui renvoie le même bordereau et le même numéro fiscal. Un rejeu relance aussi la certification ou la comptabilisation qui manquait. | Authentification | Valeur | |---|---| | En-tête | `Authorization: Bearer ` | | Jeton | OAuth 2.0 client credentials, voir [Authentification](/article/authentication) | | Portées | Aucune | ## Paramètres de chemin - `external_id` (string, path, required) : `external_id` de l'entreprise acheteuse qui émet le bordereau, créée par [PUT /v1/partners/companies/{external_id}](/article/put-company). ## Corps de la requête - `id` (string, body, required) : Votre identifiant du bordereau, 1 à 100 caractères, lettres, chiffres et `. _ : -`. Clé d'idempotence. - `date` (string (date), body, required) : Date du bordereau, `AAAA-MM-JJ`. - `montant` (integer, body, required) : Total du bordereau en FCFA entiers. Doit être égal à la somme des `lignes[].montant`. - `paiement_prevu` (string, body) : `especes`, `mobile_money`, `credit`, `virement` ou `cheque`. - `reference_origine` (string, body) : Référence dans votre système, 120 caractères au plus. - `analytique.campagne` (string, body) : Campagne, 60 caractères au plus. De même pour `analytique.parcelle`, `analytique.groupement` et `analytique.produit`. - `donnees` (object, body) : Données libres de votre système. ### Tiers (le producteur) - `tiers.role` (string, body, required) : `producteur` (seule valeur acceptée pour un bordereau). - `tiers.id` (string, body, required) : Votre identifiant du producteur, 1 à 100 caractères. - `tiers.nom` (string, body) : Nom de famille, 200 caractères au plus. `nom` ou `raison_sociale` est obligatoire. - `tiers.prenoms` (string, body) : 120 caractères au plus. - `tiers.raison_sociale` (string, body) : Pour un producteur en société, 200 caractères au plus. - `tiers.entreprise` (string, body) : `external_id` du producteur s'il est aussi une entreprise de l'espace (exploitant suivi). - `tiers.telephone` (string, body) : 40 caractères au plus. Requis par la FNE pour remettre le bordereau. - `tiers.email` (string (email), body) : Adresse e-mail du producteur. - `tiers.cni` (string, body) : Numéro de pièce d'identité, 40 caractères au plus. - `tiers.id_producteur` (string, body) : Identifiant de filière, 60 caractères au plus. - `tiers.ncc` (string, body) : Numéro de compte contribuable, 30 caractères au plus. - `tiers.pays` (string, body) : Code ISO à deux lettres. ### Lignes - `lignes` (array of object, body, required) : 1 à 200 lignes. - `lignes[].produit` (string, body, required) : Produit acheté (`Cacao fèves`…), 1 à 60 caractères. - `lignes[].qualite` (string, body) : Grade ou qualité, 40 caractères au plus. - `lignes[].quantite` (number, body, required) : Quantité payée, strictement positive (décimales acceptées). - `lignes[].unite` (string, body) : `kg`, `t`, `sac`…, 20 caractères au plus. - `lignes[].prix_unitaire` (integer, body, required) : Prix unitaire en FCFA entiers. - `lignes[].montant` (integer, body, required) : Montant de la ligne en FCFA entiers : `quantite × prix_unitaire`, au franc près. - `lignes[].quantite_pesee` (number, body) : Poids brut pesé, strictement positif. - `lignes[].refaction_pct` (number, body) : Réfaction appliquée, en %, de 0 à 100. - `lignes[].humidite_pct` (number, body) : Taux d'humidité, en %, de 0 à 100. - `lignes[].nombre_sacs` (integer, body) : Nombre de sacs, positif ou nul. ## Exemple de requête **cURL** ```bash curl -X POST https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/bordereaux \ -H "Authorization: Bearer $DAFEXPERT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "id": "ACH-2026-000412", "date": "2026-10-08", "tiers": { "role": "producteur", "id": "PROD-0457", "entreprise": "EXPL-0457", "nom": "KOUASSI", "prenoms": "Yao", "telephone": "+225 07 58 21 44 10" }, "lignes": [{ "produit": "Cacao fèves", "qualite": "Grade 1", "quantite_pesee": 1236, "humidite_pct": 7.5, "nombre_sacs": 19, "quantite": 1200, "unite": "kg", "prix_unitaire": 1800, "montant": 2160000 }], "montant": 2160000, "paiement_prevu": "mobile_money", "analytique": { "campagne": "2026-2027", "produit": "cacao" } }' ``` **Node.js** ```javascript const res = await fetch( 'https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/bordereaux', { method: 'POST', headers: { Authorization: 'Bearer ' + process.env.DAFEXPERT_TOKEN, 'Content-Type': 'application/json', }, body: JSON.stringify({ id: 'ACH-2026-000412', date: '2026-10-08', tiers: { role: 'producteur', id: 'PROD-0457', entreprise: 'EXPL-0457', nom: 'KOUASSI', prenoms: 'Yao', telephone: '+225 07 58 21 44 10' }, lignes: [{ produit: 'Cacao fèves', qualite: 'Grade 1', quantite: 1200, unite: 'kg', prix_unitaire: 1800, montant: 2160000 }], montant: 2160000, paiement_prevu: 'mobile_money', }), }, ); const bordereau = await res.json(); // 201, ou 200 pour un rejeu console.log(bordereau.numero_fiscal, bordereau.statut); ``` **Python** ```python import os, requests resp = requests.post( "https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/bordereaux", headers={"Authorization": f"Bearer {os.environ['DAFEXPERT_TOKEN']}"}, json={ "id": "ACH-2026-000412", "date": "2026-10-08", "tiers": {"role": "producteur", "id": "PROD-0457", "entreprise": "EXPL-0457", "nom": "KOUASSI", "prenoms": "Yao", "telephone": "+225 07 58 21 44 10"}, "lignes": [{"produit": "Cacao fèves", "qualite": "Grade 1", "quantite": 1200, "unite": "kg", "prix_unitaire": 1800, "montant": 2160000}], "montant": 2160000, "paiement_prevu": "mobile_money", }, timeout=60, ) bordereau = resp.json() # 201, ou 200 pour un rejeu ``` ## Réponse `201 Created` à la première émission, `200 OK` pour un rejeu du même `id`. - `id` (string) : Votre identifiant du bordereau. - `type` (string) : `bordereau_achat`. - `piece_id` (string (uuid)) : Identifiant de la pièce chez dafexpert. - `statut` (string) : `certifie` (certifié FNE), `non_certifie` (Côte d'Ivoire, certification en échec : voir `erreur`) ou `emise` (pays sans certification). - `numero` (string) : Numéro du bordereau : `BA-` suivi de votre `id`. - `numero_fiscal` (string | null) : Numéro attribué par la FNE, `null` tant que le bordereau n'est pas certifié. - `qr` (string | null) : Lien de vérification FNE, à imprimer en QR code sur le bordereau remis au producteur. - `lien_pdf` (string) : Chemin de la route qui donne le lien de téléchargement du PDF : [GET …/bordereaux/{id}/pdf](/article/get-bordereau-pdf). - `comptabilisation` (string) : `comptabilise` ou `non_comptabilise`. - `montant` (integer) : Total du bordereau en FCFA. - `erreur` (string) : Motif de l'échec de certification ou de comptabilisation. Absent si tout a réussi. - `miroir` (object) : Présent quand le producteur est une entreprise de l'espace : la vente enregistrée chez lui. `entreprise` (son `external_id`), `statut` (`comptabilise`, `non_comptabilise` ou `rejete`), `numero` et `erreur` (facultatifs). ```json { "id": "ACH-2026-000412", "type": "bordereau_achat", "piece_id": "6e0b3c2a-8f1d-4a7e-b5c9-2d4f6a8b0c1e", "statut": "certifie", "numero": "BA-ACH-2026-000412", "numero_fiscal": "…", "qr": "https://…", "lien_pdf": "/v1/partners/companies/COOP-SOUBRE/bordereaux/ACH-2026-000412/pdf", "comptabilisation": "comptabilise", "montant": 2160000, "miroir": { "entreprise": "EXPL-0457", "statut": "comptabilise", "numero": "…" } } ``` > **warning** Un échec de certification ne renvoie pas d'erreur HTTP : le bordereau est enregistré avec `"statut": "non_certifie"` et le motif dans `erreur` (FNE non connectée, téléphone manquant…). Corrigez la cause, puis renvoyez le même appel : il relance la certification sans créer de second bordereau. ## Erreurs | Code | Cas | |---|---| | `401` | Jeton absent, expiré ou accès révoqué. | | `404` | Entreprise inconnue dans votre espace (à créer d'abord avec `PUT /v1/partners/companies/{external_id}`). | | `409` | Bordereau en cours d'émission par un autre appel (réessayer dans quelques secondes avec le même `id`), ou espace sans propriétaire ni administrateur actif. | | `422` | Données invalides : tiers qui n'est pas un `producteur`, `nom` et `raison_sociale` absents, montant de ligne différent de `quantite × prix_unitaire`, `montant` différent de la somme des lignes, montant non entier… Ou émission refusée, avec le motif. | ```json { "statusCode": 422, "error_code": "validation_failed", "message": "Données invalides.", "erreurs": [ { "champ": "montant", "message": "montant (2100000) différent de la somme des lignes (2160000)." } ] } ``` Voir [Pièces fiscales FNE](/article/fne-documents) et [Montants et devises](/article/amounts-currencies). --- # Consulter un bordereau d'achat URL: https://developers.stg.dafexpert.com/article/get-bordereau > État actuel d'un bordereau d'achat : certification FNE, numéro fiscal, comptabilisation et vente miroir. > **warning** **Bientôt disponible.** Cette fonctionnalité est prête mais pas encore ouverte sur le staging. Le contrat décrit ici est celui qui sera publié. Renvoie l'état actuel d'un bordereau émis par [POST …/bordereaux](/article/post-bordereau). Utile pour vérifier une certification ou une comptabilisation restée en attente. Cette route ne relance rien : pour relancer, renvoyez le `POST` avec le même `id`. | Authentification | Valeur | |---|---| | En-tête | `Authorization: Bearer ` | | Jeton | OAuth 2.0 client credentials, voir [Authentification](/article/authentication) | | Portées | Aucune | ## Paramètres de chemin - `external_id` (string, path, required) : `external_id` de l'entreprise qui a émis le bordereau. - `id` (string, path, required) : Votre identifiant du bordereau, celui envoyé dans le champ `id`. ## Exemple de requête **cURL** ```bash curl https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/bordereaux/ACH-2026-000412 \ -H "Authorization: Bearer $DAFEXPERT_TOKEN" ``` **Node.js** ```javascript const res = await fetch( 'https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/bordereaux/ACH-2026-000412', { headers: { Authorization: 'Bearer ' + process.env.DAFEXPERT_TOKEN } }, ); const bordereau = await res.json(); ``` **Python** ```python import os, requests resp = requests.get( "https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/bordereaux/ACH-2026-000412", headers={"Authorization": f"Bearer {os.environ['DAFEXPERT_TOKEN']}"}, timeout=30, ) bordereau = resp.json() ``` ## Réponse `200 OK`. Même forme que la réponse de l'émission. - `id` (string) : Votre identifiant du bordereau. - `type` (string) : `bordereau_achat`. - `piece_id` (string (uuid)) : Identifiant de la pièce chez dafexpert. - `statut` (string) : `certifie`, `non_certifie` ou `emise`. - `numero` (string) : Numéro du bordereau (`BA-` suivi de votre `id`). - `numero_fiscal` (string | null) : Numéro attribué par la FNE. - `qr` (string | null) : Lien de vérification FNE. - `lien_pdf` (string) : Chemin de la route [GET …/bordereaux/{id}/pdf](/article/get-bordereau-pdf). - `comptabilisation` (string) : `comptabilise` ou `non_comptabilise`. - `montant` (integer) : Total du bordereau en FCFA. - `erreur` (string) : Dernier motif d'échec de certification enregistré. - `miroir` (object) : Vente enregistrée chez le producteur quand il est une entreprise de l'espace : `entreprise`, `statut` (`comptabilise` ou `non_comptabilise`), `numero`. ```json { "id": "ACH-2026-000412", "type": "bordereau_achat", "piece_id": "6e0b3c2a-8f1d-4a7e-b5c9-2d4f6a8b0c1e", "statut": "non_certifie", "numero": "BA-ACH-2026-000412", "numero_fiscal": null, "qr": null, "lien_pdf": "/v1/partners/companies/COOP-SOUBRE/bordereaux/ACH-2026-000412/pdf", "comptabilisation": "non_comptabilise", "montant": 2160000, "erreur": "FNE non connectée pour cette entreprise : la pièce est enregistrée mais pas certifiée." } ``` ## Erreurs | Code | Cas | |---|---| | `401` | Jeton absent, expiré ou accès révoqué. | | `404` | Entreprise inconnue dans votre espace, ou bordereau inconnu pour cette entreprise (`Pièce inconnue pour cette entreprise.`). | | `409` | Espace sans propriétaire ni administrateur actif. | | `422` | Identifiant invalide (caractères ou longueur). | Voir [Pièces fiscales FNE](/article/fne-documents). --- # Télécharger le PDF d'un bordereau URL: https://developers.stg.dafexpert.com/article/get-bordereau-pdf > Donne un lien de téléchargement temporaire (5 minutes) du PDF d'un bordereau d'achat. > **warning** **Bientôt disponible.** Cette fonctionnalité est prête mais pas encore ouverte sur le staging. Le contrat décrit ici est celui qui sera publié. Renvoie un lien de téléchargement du PDF d'un bordereau d'achat, valable **5 minutes**. Le PDF certifié est renvoyé s'il existe, sinon le PDF d'origine. C'est la route indiquée dans le champ `lien_pdf` des réponses de bordereau. | Authentification | Valeur | |---|---| | En-tête | `Authorization: Bearer ` | | Jeton | OAuth 2.0 client credentials, voir [Authentification](/article/authentication) | | Portées | Aucune | ## Paramètres de chemin - `external_id` (string, path, required) : `external_id` de l'entreprise qui a émis le bordereau. - `id` (string, path, required) : Votre identifiant du bordereau. ## Exemple de requête **cURL** ```bash # 1. Obtenir le lien URL=$(curl -s https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/bordereaux/ACH-2026-000412/pdf \ -H "Authorization: Bearer $DAFEXPERT_TOKEN" | jq -r .url) # 2. Télécharger le PDF (sans en-tête Authorization) curl -o BA-ACH-2026-000412.pdf "$URL" ``` **Node.js** ```javascript import { writeFile } from 'node:fs/promises'; const res = await fetch( 'https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/bordereaux/ACH-2026-000412/pdf', { headers: { Authorization: 'Bearer ' + process.env.DAFEXPERT_TOKEN } }, ); const { url } = await res.json(); const pdf = await fetch(url); // lien signé : pas d'en-tête Authorization await writeFile('BA-ACH-2026-000412.pdf', Buffer.from(await pdf.arrayBuffer())); ``` **Python** ```python import os, requests link = requests.get( "https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/bordereaux/ACH-2026-000412/pdf", headers={"Authorization": f"Bearer {os.environ['DAFEXPERT_TOKEN']}"}, timeout=30, ).json() pdf = requests.get(link["url"], timeout=60) # lien signé : pas d'en-tête Authorization with open("BA-ACH-2026-000412.pdf", "wb") as f: f.write(pdf.content) ``` ## Réponse `200 OK` - `url` (string) : Lien de téléchargement signé du PDF. À appeler sans en-tête `Authorization`. - `expiresIn` (integer) : Durée de validité du lien, en secondes (`300`). ```json { "url": "https://…", "expiresIn": 300 } ``` > **tips** Ne stockez pas le lien : il expire après 5 minutes. Stockez plutôt le PDF lui-même, ou rappelez cette route au moment du téléchargement. ## Erreurs | Code | Cas | |---|---| | `401` | Jeton absent, expiré ou accès révoqué. | | `404` | Entreprise ou bordereau inconnu, ou PDF pas encore disponible (`PDF pas encore disponible pour cette pièce.`). | | `409` | Espace sans propriétaire ni administrateur actif. | | `422` | Identifiant invalide (caractères ou longueur). | Voir [Pièces fiscales FNE](/article/fne-documents). --- # Émettre une facture URL: https://developers.stg.dafexpert.com/article/post-invoice > Émet une facture client numérotée par dafexpert, la certifie FNE et la comptabilise, en un seul appel. > **warning** **Bientôt disponible.** Cette fonctionnalité est prête mais pas encore ouverte sur le staging. Le contrat décrit ici est celui qui sera publié. Émet une facture de l'entreprise `external_id` à l'un de ses clients (vente de produits, d'intrants…). L'appel est **synchrone** : la réponse donne le numéro attribué par dafexpert, le numéro fiscal, le QR code de vérification, le lien du PDF et l'état de comptabilisation. - La facture est numérotée dans la séquence de l'entreprise et passe directement au statut « envoyée » : c'est vous qui la remettez au client. - En Côte d'Ivoire, elle est certifiée auprès de la FNE, puis comptabilisée une fois certifiée. - Hors Côte d'Ivoire, elle est émise et comptabilisée sans certification (`statut` : `emise`). L'appel est **idempotent** sur votre `id` : `201` à la première émission, `200` à un rejeu, qui renvoie la même facture, le même numéro et le même numéro fiscal. Un rejeu relance aussi la certification ou la comptabilisation qui manquait. | Authentification | Valeur | |---|---| | En-tête | `Authorization: Bearer ` | | Jeton | OAuth 2.0 client credentials, voir [Authentification](/article/authentication) | | Portées | Aucune | ## Paramètres de chemin - `external_id` (string, path, required) : `external_id` de l'entreprise qui émet la facture. ## Corps de la requête - `id` (string, body, required) : Votre identifiant de la facture, 1 à 100 caractères, lettres, chiffres et `. _ : -`. Clé d'idempotence. - `date` (string (date), body, required) : Date d'émission, `AAAA-MM-JJ`. - `echeance` (string (date), body) : Date d'échéance, `AAAA-MM-JJ`. - `montant` (integer, body, required) : Total **TTC** en FCFA entiers : somme des montants HT des lignes plus la TVA de chaque ligne (arrondie au franc). Un écart d'un franc par ligne est toléré. - `mode_paiement` (string, body) : `especes`, `mobile_money`, `virement`, `cheque` ou `carte`. - `reference_origine` (string, body) : Référence dans votre système (bon de livraison, contrat…), 120 caractères au plus. Reprise sur la facture. - `analytique.campagne` (string, body) : 60 caractères au plus. De même pour `analytique.parcelle`, `analytique.groupement` et `analytique.produit`. - `donnees` (object, body) : Données libres de votre système. ### Tiers (le client) - `tiers.role` (string, body, required) : `client` (seule valeur acceptée pour une facture). - `tiers.id` (string, body, required) : Votre identifiant du client, 1 à 100 caractères. - `tiers.raison_sociale` (string, body) : Raison sociale, 200 caractères au plus. `raison_sociale` ou `nom` est obligatoire. - `tiers.nom` (string, body) : Nom d'un client particulier, 200 caractères au plus. - `tiers.prenoms` (string, body) : 120 caractères au plus. - `tiers.ncc` (string, body) : Numéro de compte contribuable du client, 30 caractères au plus. - `tiers.pays` (string, body) : Code ISO à deux lettres. Par défaut, le pays de l'entreprise. - `tiers.entreprise` (string, body) : `external_id` du client s'il est aussi une entreprise de l'espace. - `tiers.telephone` (string, body) : 40 caractères au plus. - `tiers.email` (string (email), body) : Adresse e-mail du client. - `tiers.cni` (string, body) : Numéro de pièce d'identité, 40 caractères au plus. De même `tiers.id_producteur` (60 caractères). ### Lignes - `lignes` (array of object, body, required) : 1 à 200 lignes. - `lignes[].produit` (string, body) : 60 caractères au plus. `produit` ou `description` est obligatoire. - `lignes[].description` (string, body) : Désignation libre, 255 caractères au plus. - `lignes[].qualite` (string, body) : Grade ou qualité, 40 caractères au plus. Ajoutée entre parenthèses à la désignation. - `lignes[].quantite` (number, body, required) : Quantité, strictement positive. Une quantité décimale est imprimée dans la désignation, la ligne devenant `1 × montant`. - `lignes[].unite` (string, body) : 20 caractères au plus. - `lignes[].prix_unitaire` (integer, body, required) : Prix unitaire HT en FCFA entiers. - `lignes[].taux_tva` (number, body) : Taux de TVA en %, de 0 à 100. Valeur par défaut : `0`. - `lignes[].montant` (integer, body, required) : Montant **HT** de la ligne en FCFA entiers : `quantite × prix_unitaire`, au franc près. ## Exemple de requête **cURL** ```bash curl -X POST https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/invoices \ -H "Authorization: Bearer $DAFEXPERT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "id": "VTE-2026-000087", "date": "2026-10-10", "echeance": "2026-11-09", "tiers": { "role": "client", "id": "CLI-IVOIRE-CACAO-EXPORT", "raison_sociale": "Ivoire Cacao Export SA", "ncc": "1856432B", "pays": "CI", "email": "achats@ivoirecacaoexport.ci" }, "lignes": [ { "produit": "Cacao fèves", "qualite": "Grade 1", "quantite": 25000, "unite": "kg", "prix_unitaire": 1850, "taux_tva": 0, "montant": 46250000 }, { "description": "Transport Soubré - San-Pedro", "quantite": 1, "prix_unitaire": 500000, "taux_tva": 18, "montant": 500000 } ], "montant": 46840000, "mode_paiement": "virement", "reference_origine": "BL-SBR-2026-0193" }' ``` **Node.js** ```javascript const res = await fetch( 'https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/invoices', { method: 'POST', headers: { Authorization: 'Bearer ' + process.env.DAFEXPERT_TOKEN, 'Content-Type': 'application/json', }, body: JSON.stringify({ id: 'VTE-2026-000087', date: '2026-10-10', echeance: '2026-11-09', tiers: { role: 'client', id: 'CLI-IVOIRE-CACAO-EXPORT', raison_sociale: 'Ivoire Cacao Export SA', ncc: '1856432B', pays: 'CI' }, lignes: [ { produit: 'Cacao fèves', qualite: 'Grade 1', quantite: 25000, unite: 'kg', prix_unitaire: 1850, taux_tva: 0, montant: 46250000 }, { description: 'Transport Soubré - San-Pedro', quantite: 1, prix_unitaire: 500000, taux_tva: 18, montant: 500000 }, ], montant: 46840000, // 46 250 000 + 500 000 + 90 000 de TVA mode_paiement: 'virement', }), }, ); const facture = await res.json(); // 201, ou 200 pour un rejeu ``` **Python** ```python import os, requests resp = requests.post( "https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/invoices", headers={"Authorization": f"Bearer {os.environ['DAFEXPERT_TOKEN']}"}, json={ "id": "VTE-2026-000087", "date": "2026-10-10", "echeance": "2026-11-09", "tiers": {"role": "client", "id": "CLI-IVOIRE-CACAO-EXPORT", "raison_sociale": "Ivoire Cacao Export SA", "ncc": "1856432B", "pays": "CI"}, "lignes": [ {"produit": "Cacao fèves", "qualite": "Grade 1", "quantite": 25000, "unite": "kg", "prix_unitaire": 1850, "taux_tva": 0, "montant": 46250000}, {"description": "Transport Soubré - San-Pedro", "quantite": 1, "prix_unitaire": 500000, "taux_tva": 18, "montant": 500000}, ], "montant": 46840000, # 46 250 000 + 500 000 + 90 000 de TVA "mode_paiement": "virement", }, timeout=60, ) facture = resp.json() # 201, ou 200 pour un rejeu ``` ## Réponse `201 Created` à la première émission, `200 OK` pour un rejeu du même `id`. - `id` (string) : Votre identifiant de la facture. - `type` (string) : `facture`. - `piece_id` (string (uuid)) : Identifiant de la facture chez dafexpert. - `statut` (string) : `certifie` (certifiée FNE), `non_certifie` (Côte d'Ivoire, certification en échec : voir `erreur`) ou `emise` (pays sans certification). - `numero` (string) : Numéro attribué par dafexpert dans la séquence de factures de l'entreprise. - `numero_fiscal` (string | null) : Numéro attribué par la FNE, `null` tant que la facture n'est pas certifiée. - `qr` (string | null) : Lien de vérification FNE, à imprimer en QR code. - `lien_pdf` (string) : Chemin de la route qui donne le lien de téléchargement du PDF : [GET …/invoices/{id}/pdf](/article/get-invoice-pdf). - `comptabilisation` (string) : `comptabilise` ou `non_comptabilise`. - `montant` (integer) : Total TTC de la facture en FCFA, recalculé à partir des lignes. - `erreur` (string) : Motif de l'échec de certification ou de comptabilisation. Absent si tout a réussi. ```json { "id": "VTE-2026-000087", "type": "facture", "piece_id": "2f7a9c1e-0b3d-4e5f-a6b7-c8d9e0f1a2b3", "statut": "certifie", "numero": "FAC-2026-000087", "numero_fiscal": "…", "qr": "https://…", "lien_pdf": "/v1/partners/companies/COOP-SOUBRE/invoices/VTE-2026-000087/pdf", "comptabilisation": "comptabilise", "montant": 46840000 } ``` > **warning** Un échec de certification ne renvoie pas d'erreur HTTP : la facture est enregistrée avec `"statut": "non_certifie"` et le motif dans `erreur`. Corrigez la cause (connexion FNE de l'entreprise, données du client…), puis renvoyez le même appel : il relance la certification sans créer de seconde facture. ## Erreurs | Code | Cas | |---|---| | `401` | Jeton absent, expiré ou accès révoqué. | | `404` | Entreprise inconnue dans votre espace (à créer d'abord avec `PUT /v1/partners/companies/{external_id}`). | | `409` | Facture en cours d'émission par un autre appel (réessayer dans quelques secondes avec le même `id`), ou espace sans propriétaire ni administrateur actif. | | `422` | Données invalides : tiers qui n'est pas un `client`, ligne sans `produit` ni `description`, montant de ligne différent de `quantite × prix_unitaire`, `montant` TTC différent du calcul des lignes, montant non entier… Ou émission refusée, avec le motif. | ```json { "statusCode": 422, "error_code": "validation_failed", "message": "Données invalides.", "erreurs": [ { "champ": "montant", "message": "montant TTC (46750000) différent du calcul des lignes (46840000)." } ] } ``` Voir [Pièces fiscales FNE](/article/fne-documents) et [Montants et devises](/article/amounts-currencies). --- # Consulter une facture URL: https://developers.stg.dafexpert.com/article/get-invoice > État actuel d'une facture : numéro, certification FNE et comptabilisation. > **warning** **Bientôt disponible.** Cette fonctionnalité est prête mais pas encore ouverte sur le staging. Le contrat décrit ici est celui qui sera publié. Renvoie l'état actuel d'une facture émise par [POST …/invoices](/article/post-invoice). Cette route ne relance rien : pour relancer une certification ou une comptabilisation, renvoyez le `POST` avec le même `id`. | Authentification | Valeur | |---|---| | En-tête | `Authorization: Bearer ` | | Jeton | OAuth 2.0 client credentials, voir [Authentification](/article/authentication) | | Portées | Aucune | ## Paramètres de chemin - `external_id` (string, path, required) : `external_id` de l'entreprise qui a émis la facture. - `id` (string, path, required) : Votre identifiant de la facture, celui envoyé dans le champ `id`. ## Exemple de requête **cURL** ```bash curl https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/invoices/VTE-2026-000087 \ -H "Authorization: Bearer $DAFEXPERT_TOKEN" ``` **Node.js** ```javascript const res = await fetch( 'https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/invoices/VTE-2026-000087', { headers: { Authorization: 'Bearer ' + process.env.DAFEXPERT_TOKEN } }, ); const facture = await res.json(); ``` **Python** ```python import os, requests resp = requests.get( "https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/invoices/VTE-2026-000087", headers={"Authorization": f"Bearer {os.environ['DAFEXPERT_TOKEN']}"}, timeout=30, ) facture = resp.json() ``` ## Réponse `200 OK`. Même forme que la réponse de l'émission. - `id` (string) : Votre identifiant de la facture. - `type` (string) : `facture`. - `piece_id` (string (uuid)) : Identifiant de la facture chez dafexpert. - `statut` (string) : `certifie`, `non_certifie` ou `emise`. - `numero` (string) : Numéro de la facture. - `numero_fiscal` (string | null) : Numéro attribué par la FNE. - `qr` (string | null) : Lien de vérification FNE. - `lien_pdf` (string) : Chemin de la route [GET …/invoices/{id}/pdf](/article/get-invoice-pdf). - `comptabilisation` (string) : `comptabilise` ou `non_comptabilise`. - `montant` (integer) : Total TTC en FCFA. - `erreur` (string) : Dernier motif d'échec de certification enregistré. ```json { "id": "VTE-2026-000087", "type": "facture", "piece_id": "2f7a9c1e-0b3d-4e5f-a6b7-c8d9e0f1a2b3", "statut": "certifie", "numero": "FAC-2026-000087", "numero_fiscal": "…", "qr": "https://…", "lien_pdf": "/v1/partners/companies/COOP-SOUBRE/invoices/VTE-2026-000087/pdf", "comptabilisation": "comptabilise", "montant": 46840000 } ``` ## Erreurs | Code | Cas | |---|---| | `401` | Jeton absent, expiré ou accès révoqué. | | `404` | Entreprise inconnue dans votre espace, ou facture inconnue pour cette entreprise (`Pièce inconnue pour cette entreprise.`). | | `409` | Espace sans propriétaire ni administrateur actif. | | `422` | Identifiant invalide (caractères ou longueur). | Voir [Pièces fiscales FNE](/article/fne-documents). --- # Télécharger le PDF d'une facture URL: https://developers.stg.dafexpert.com/article/get-invoice-pdf > Donne un lien de téléchargement temporaire (5 minutes) du PDF d'une facture. > **warning** **Bientôt disponible.** Cette fonctionnalité est prête mais pas encore ouverte sur le staging. Le contrat décrit ici est celui qui sera publié. Renvoie un lien de téléchargement du PDF d'une facture, valable **5 minutes**. Le PDF certifié est renvoyé s'il existe, sinon le PDF d'origine. C'est la route indiquée dans le champ `lien_pdf` des réponses de facture. | Authentification | Valeur | |---|---| | En-tête | `Authorization: Bearer ` | | Jeton | OAuth 2.0 client credentials, voir [Authentification](/article/authentication) | | Portées | Aucune | ## Paramètres de chemin - `external_id` (string, path, required) : `external_id` de l'entreprise qui a émis la facture. - `id` (string, path, required) : Votre identifiant de la facture. ## Exemple de requête **cURL** ```bash # 1. Obtenir le lien URL=$(curl -s https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/invoices/VTE-2026-000087/pdf \ -H "Authorization: Bearer $DAFEXPERT_TOKEN" | jq -r .url) # 2. Télécharger le PDF (sans en-tête Authorization) curl -o FAC-2026-000087.pdf "$URL" ``` **Node.js** ```javascript import { writeFile } from 'node:fs/promises'; const res = await fetch( 'https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/invoices/VTE-2026-000087/pdf', { headers: { Authorization: 'Bearer ' + process.env.DAFEXPERT_TOKEN } }, ); const { url } = await res.json(); const pdf = await fetch(url); // lien signé : pas d'en-tête Authorization await writeFile('FAC-2026-000087.pdf', Buffer.from(await pdf.arrayBuffer())); ``` **Python** ```python import os, requests link = requests.get( "https://api.stg.dafexpert.com/v1/partners/companies/COOP-SOUBRE/invoices/VTE-2026-000087/pdf", headers={"Authorization": f"Bearer {os.environ['DAFEXPERT_TOKEN']}"}, timeout=30, ).json() pdf = requests.get(link["url"], timeout=60) # lien signé : pas d'en-tête Authorization with open("FAC-2026-000087.pdf", "wb") as f: f.write(pdf.content) ``` ## Réponse `200 OK` - `url` (string) : Lien de téléchargement signé du PDF. À appeler sans en-tête `Authorization`. - `expiresIn` (integer) : Durée de validité du lien, en secondes (`300`). ```json { "url": "https://…", "expiresIn": 300 } ``` > **tips** Ne stockez pas le lien : il expire après 5 minutes. Stockez plutôt le PDF lui-même, ou rappelez cette route au moment du téléchargement. ## Erreurs | Code | Cas | |---|---| | `401` | Jeton absent, expiré ou accès révoqué. | | `404` | Entreprise ou facture inconnue, ou PDF pas encore disponible (`PDF pas encore disponible pour cette pièce.`). | | `409` | Espace sans propriétaire ni administrateur actif. | | `422` | Identifiant invalide (caractères ou longueur). | Voir [Pièces fiscales FNE](/article/fne-documents). --- # Serveur MCP URL: https://developers.stg.dafexpert.com/article/mcp-overview > Connecter un assistant IA (Claude, etc.) aux dossiers dafexpert d'un utilisateur, en lecture seule. Le serveur MCP (Model Context Protocol) de dafexpert permet à un assistant IA compatible, comme Claude, de consulter les dossiers d'un utilisateur : entreprises, exercices, balance, compte de résultat, factures clients et trésorerie. L'assistant agit **au nom de l'utilisateur connecté** et ne voit que ce que cet utilisateur voit dans dafexpert. Pour la mise en place pas à pas dans Claude, voir [Connecter un assistant IA](/article/connect-ai-assistant). ## Adresse | Environnement | URL du serveur MCP | |---|---| | Staging | `https://api.stg.dafexpert.com/mcp` | | Production | communiquée à la mise en production | ## Transport - Protocole **Streamable HTTP**, sans état : chaque requête JSON-RPC est envoyée en `POST` sur l'URL du serveur, aucune session n'est conservée entre deux appels. - Les méthodes `GET` et `DELETE` répondent `405` (pas de flux SSE autonome ni de session à fermer). - L'adresse n'est pas versionnée (pas de préfixe `/v1`). ## Authentification Le serveur est protégé par OAuth 2.0 avec le flux **Authorization Code + PKCE (S256)**. L'utilisateur se connecte avec son compte dafexpert habituel, puis accepte l'accès sur un écran de consentement. | Élément | Valeur | |---|---| | `client_id` | `dafexpert-mcp` (client public, sans secret) | | Serveur d'autorisation (staging) | `https://auth.stg.dafexpert.com/realms/dafexpert` | | Scopes | `openid`, `profile`, `email` | | Jeton | En-tête `Authorization: Bearer ` | > **warning** L'enregistrement dynamique de clients (DCR) n'est pas proposé. Dans les paramètres avancés du connecteur de votre assistant, renseignez le Client ID `dafexpert-mcp` et laissez le secret vide. **Découverte automatique.** Un appel sans jeton valide reçoit un `401` avec l'en-tête suivant, qui permet au client MCP de trouver le serveur d'autorisation (RFC 9728) et de lancer la connexion tout seul : ```http WWW-Authenticate: Bearer resource_metadata="https://api.stg.dafexpert.com/.well-known/oauth-protected-resource/mcp" ``` ```json { "resource": "https://api.stg.dafexpert.com/mcp", "authorization_servers": ["https://auth.stg.dafexpert.com/realms/dafexpert"], "scopes_supported": ["openid", "profile", "email"], "bearer_methods_supported": ["header"], "resource_name": "DafExpert" } ``` ## Lecture seule Tous les outils sont **en lecture seule** (annotation `readOnlyHint: true`) : l'assistant ne peut ni créer, ni modifier, ni supprimer de données. Les outils d'écriture sont à venir. ## Données visibles - L'assistant ne voit que les dossiers (entreprises) dont l'utilisateur est **membre**, comme dans l'application. - Chaque outil reçoit le `companyId` du dossier et vérifie l'accès de l'utilisateur à chaque appel. Un dossier hors de ses droits renvoie une erreur d'outil (`isError: true`) avec un message. - Retirer un utilisateur d'un dossier dans dafexpert coupe aussitôt l'accès de son assistant à ce dossier. ## Montants et dates - Les champs qui se terminent par `Cents` sont en **centièmes** de la devise du dossier : `150000000` = 1 500 000 FCFA. - La balance (`get_balance`) et le compte de résultat (`get_income_statement`) donnent leurs montants **en unités** de la devise (`1500000` = 1 500 000 FCFA). - Les dates d'entrée sont au format `AAAA-MM-JJ` ; les dates de sortie sont des horodatages ISO 8601. ## Outils L'assistant commence en général par `list_companies` pour obtenir le `companyId`, puis `list_exercises` pour les outils comptables. | Outil | Rôle | Paramètres | |---|---|---| | [list_companies](/article/mcp-list-companies) | Lister les dossiers accessibles | aucun | | [list_exercises](/article/mcp-list-exercises) | Lister les exercices comptables | `companyId` | | [get_balance](/article/mcp-get-balance) | Balance générale N et N-1 | `companyId`, `exerciseId`, `from`, `to` | | [get_balance_anomalies](/article/mcp-get-balance-anomalies) | Contrôles de révision de la balance | `companyId`, `exerciseId` | | [get_income_statement](/article/mcp-get-income-statement) | Compte de résultat par postes | `companyId`, `exerciseId`, `from`, `to` | | [list_invoices](/article/mcp-list-invoices) | Lister les factures clients | `companyId`, `status` | | [get_invoice](/article/mcp-get-invoice) | Détail d'une facture client | `companyId`, `invoiceId` | | [get_treasury_overview](/article/mcp-get-treasury-overview) | Vue de la trésorerie | `companyId`, `range` | ## Tester avec Claude Code 1. Ajouter le serveur : `claude mcp add --transport http dafexpert https://api.stg.dafexpert.com/mcp`. 1. Lancer `/mcp` dans Claude Code et choisir **dafexpert** pour se connecter. 1. Demander par exemple : « Quels sont mes dossiers dafexpert ? ». --- # Lister mes dossiers URL: https://developers.stg.dafexpert.com/article/mcp-list-companies > Les dossiers (entreprises) auxquels l'utilisateur a accès, avec devise, pays, rôle et solde. ## Description Liste les dossiers (entreprises) auxquels l'utilisateur connecté a accès, avec leur devise, leur pays, son rôle et le solde de trésorerie. C'est en général le premier outil appelé : il fournit le `companyId` demandé par tous les autres. Voir la [présentation du serveur MCP](/article/mcp-overview). ## Paramètres d'entrée Aucun. ## Sortie Un tableau de dossiers. - `id` (uuid) : Identifiant du dossier, à passer en `companyId` aux autres outils. - `name` (string) : Raison sociale. - `legalForm` (string) : Forme juridique (`SCOOP-CA`, `SARL`…). - `country` (string) : Pays (code ISO). - `currency` (string) : Devise du dossier (`XOF`, `EUR`…). - `role` (string) : Niveau d'accès de l'utilisateur au dossier : `owner`, `admin` ou `member`. - `city` (string) : Ville. - `address` (string) : Adresse. - `registrationNumber` (string) : Numéro RCCM, vide si inconnu. - `taxpayerNumber` (string) : Numéro de compte contribuable (NCC), vide si inconnu. - `taxRegime` (string) : Régime fiscal. - `manager` (string) : Dirigeant du dossier. - `accountant` (string) : Comptable du dossier, vide si aucun. - `balanceCents` (integer) : Solde des mouvements de trésorerie, en centièmes de la devise. D'autres champs d'affichage (`tint`, `commune`, `region`…) peuvent être présents. ```json [ { "id": "6f1d2c3a-8b4e-4f0a-9c1d-2e3f4a5b6c7d", "name": "Coopérative Agricole de Soubré", "legalForm": "SCOOP-CA", "country": "CI", "currency": "XOF", "role": "owner", "city": "Soubré", "address": "Quartier Commerce, BP 120", "registrationNumber": "CI-SOU-2019-B-0457", "taxpayerNumber": "1904578K", "taxRegime": "RSI", "manager": "Awa Koné", "accountant": "Cabinet Yao & Associés", "balanceCents": 1845000000 } ] ``` ## Exemple de demande > Quels sont mes dossiers dafexpert, et quel est le solde de chacun ? --- # Lister les exercices URL: https://developers.stg.dafexpert.com/article/mcp-list-exercises > Les exercices comptables d'un dossier : dates, statut et référentiel. ## Description Liste les exercices comptables d'un dossier, du plus ancien au plus récent, avec leurs dates, leur statut (ouvert ou clôturé) et leur référentiel. Fournit l'`exerciseId` demandé par les outils comptables. ## Paramètres d'entrée - `companyId` (uuid, body, required) : Identifiant du dossier, obtenu avec [list_companies](/article/mcp-list-companies). ## Sortie Un tableau d'exercices. - `id` (uuid) : Identifiant de l'exercice, à passer en `exerciseId`. - `label` (string) : Libellé de l'exercice. - `referentialCode` (string) : Référentiel comptable : `SYSCOHADA`, `SYCEBNL` ou `PCG`. - `startDate` (datetime) : Début de l'exercice. - `endDate` (datetime) : Fin de l'exercice. - `status` (string) : `open` (ouvert) ou `closed` (clôturé). - `reopenedAt` (datetime) : Date de réouverture après une clôture, sinon `null`. - `validatedEntries` (integer) : Nombre d'écritures validées. - `isFirst` (boolean) : Premier exercice du dossier. - `hasOpening` (boolean) : L'exercice contient déjà des à-nouveaux. - `chartVersionLabel` (string) : Version du plan comptable de l'exercice. ```json [ { "id": "3c9e7a10-5d2b-4b8f-a1e6-7f0c2d4e9b31", "label": "Exercice 2026", "referentialCode": "SYSCOHADA", "startDate": "2026-01-01T00:00:00.000Z", "endDate": "2026-12-31T00:00:00.000Z", "status": "open", "reopenedAt": null, "validatedEntries": 412, "isFirst": false, "hasOpening": true, "chartVersionLabel": "SYSCOHADA révisé" } ] ``` ## Exemple de demande > Quels exercices sont ouverts pour la Coopérative Agricole de Soubré ? --- # Balance générale URL: https://developers.stg.dafexpert.com/article/mcp-get-balance > La balance générale d'un exercice, par compte, avec le comparatif N-1. ## Description Renvoie la balance générale d'un exercice : débit, crédit et solde de chaque compte, avec le solde de l'exercice précédent (N-1). La balance peut être bornée à une période avec `from` et `to`. > **info** Les montants de cet outil sont **en unités** de la devise du dossier, pas en centièmes. ## Paramètres d'entrée - `companyId` (uuid, body, required) : Identifiant du dossier, obtenu avec [list_companies](/article/mcp-list-companies). - `exerciseId` (uuid, body, required) : Identifiant de l'exercice, obtenu avec [list_exercises](/article/mcp-list-exercises). - `from` (string, body) : Début de la période, `AAAA-MM-JJ`. Par défaut, début de l'exercice. - `to` (string, body) : Fin de la période, `AAAA-MM-JJ`. Par défaut, fin de l'exercice. ## Sortie - `exercise` (object) : Exercice demandé : `id`, `startDate`, `endDate`, `status`. - `previous` (object) : Exercice précédent (`id`, `endDate`, `status`), ou `null`. - `rows` (array) : Une ligne par compte : `account`, `label`, `debit`, `credit`, `solde`, `soldeN1`, `lines` (nombre de lignes d'écriture), `lettrage` (`lettre`, `partiel`, `non-lettre` ou `non-lettrable`), `known` (compte présent dans le plan du dossier). - `totals` (object) : Totaux : `debit`, `credit`, `solde`, `soldeN1`. - `result` (number) : Résultat de la période (produits moins charges). ```json { "exercise": { "id": "3c9e7a10-…", "startDate": "2026-01-01", "endDate": "2026-12-31", "status": "open" }, "previous": { "id": "a71b2c04-…", "endDate": "2025-12-31", "status": "closed" }, "rows": [ { "account": "401100", "label": "Fournisseurs", "debit": 8200000, "credit": 12500000, "solde": -4300000, "soldeN1": -3150000, "lines": 37, "lettrage": "partiel", "known": true }, { "account": "521100", "label": "Banque SGCI", "debit": 46000000, "credit": 27550000, "solde": 18450000, "soldeN1": 9800000, "lines": 112, "lettrage": "non-lettrable", "known": true } ], "totals": { "debit": 254300000, "credit": 254300000, "solde": 0, "soldeN1": 0 }, "result": 6250000 } ``` ## Exemple de demande > Montre-moi la balance de la coopérative au 30 septembre 2026, et compare les comptes de banque avec l'an dernier. --- # Anomalies de la balance URL: https://developers.stg.dafexpert.com/article/mcp-get-balance-anomalies > Les contrôles de révision sur la balance d'un exercice, avec une conclusion. ## Description Applique les contrôles de révision à la balance d'un exercice (soldes anormaux, comptes d'attente non soldés…) et renvoie les anomalies détectées, avec une conclusion générale. Chaque anomalie indique son niveau de gravité, une hypothèse et son impact. ## Paramètres d'entrée - `companyId` (uuid, body, required) : Identifiant du dossier, obtenu avec [list_companies](/article/mcp-list-companies). - `exerciseId` (uuid, body, required) : Identifiant de l'exercice, obtenu avec [list_exercises](/article/mcp-list-exercises). ## Sortie - `conclusion` (string) : Synthèse en une phrase. - `anomalies` (array) : Anomalies détectées (voir ci-dessous). - `generatedAt` (datetime) : Date du contrôle. Chaque anomalie contient : - `id` (string) : Identifiant du contrôle. - `severity` (string) : `high`, `medium` ou `low`. - `account` (string) : Compte ou racine concernée, par exemple `471`. - `accountLabel` (string) : Libellé du compte. - `title` (string) : Intitulé de l'anomalie. - `hypothesis` (string) : Explication probable. - `impact` (string) : Conséquence sur les comptes. - `accounts` (string[]) : Comptes de la balance concernés. - `status` (string) : État du point de révision : `open`, `justified` ou `resolved`. - `comment` (string) : Justification saisie par le comptable. ```json { "conclusion": "1 anomalie détectée sur la balance, dont 1 prioritaire (comptes d'attente et sous-comptes). Il est recommandé de traiter d'abord les anomalies prioritaires, de lettrer les comptes de tiers et de justifier chaque solde atypique avant la clôture.", "anomalies": [ { "id": "comptes-attente", "severity": "high", "account": "47", "accountLabel": "Comptes d'attente et sous-comptes", "title": "Soldes non apurés sur les comptes d'attente (2 350 000,00 net, 1 compte) : ces comptes doivent être soldés à la clôture.", "hypothesis": "Opérations bancaires ou diverses non affectées à leur nature réelle, lettrage manquant ou justification incomplète des mouvements.", "impact": "Ces montants faussent le résultat et le bilan (trésorerie, dettes et créances) et nuisent à la fiabilité des états financiers.", "accounts": ["471000"], "status": "open", "comment": "" } ], "generatedAt": "2026-10-12T09:14:02.000Z" } ``` ## Exemple de demande > Y a-t-il des anomalies dans la balance 2026 de la coopérative avant la clôture ? --- # Compte de résultat URL: https://developers.stg.dafexpert.com/article/mcp-get-income-statement > Le compte de résultat d'un exercice, par postes du référentiel du dossier. ## Description Renvoie le compte de résultat d'un exercice, présenté selon les postes du référentiel du dossier (SYSCOHADA, SYCEBNL ou PCG) : charges, produits, soldes intermédiaires et résultat. Le détail par compte accompagne chaque poste. Le calcul peut être borné à une période avec `from` et `to`. > **info** Les montants de cet outil sont **en unités** de la devise du dossier, pas en centièmes. ## Paramètres d'entrée - `companyId` (uuid, body, required) : Identifiant du dossier, obtenu avec [list_companies](/article/mcp-list-companies). - `exerciseId` (uuid, body, required) : Identifiant de l'exercice, obtenu avec [list_exercises](/article/mcp-list-exercises). - `from` (string, body) : Début de la période, `AAAA-MM-JJ`. Par défaut, début de l'exercice. - `to` (string, body) : Fin de la période, `AAAA-MM-JJ`. Par défaut, fin de l'exercice. ## Sortie - `referential` (string) : `SYSCOHADA`, `SYCEBNL` ou `PCG`. - `system` (string) : Système de présentation de l'exercice : `normal`, `smt` (système minimal de trésorerie) ou `pd` (projets de développement). - `columns` (array) : Colonnes du tableau (ici une seule) : `exerciseId`, `label`, `startDate`, `endDate`, `status`, `kind`. - `rows` (array) : Postes dans l'ordre de présentation : `id`, `label`, `kind` (`section`, `line`, `total` ou `result`), `values` (une valeur par colonne), `accounts` (détail par compte : `account`, `label`, `values`). - `unmapped` (array) : Comptes de charges ou de produits utilisés mais rattachés à aucun poste. ```json { "referential": "SYSCOHADA", "system": "normal", "columns": [ { "exerciseId": "3c9e7a10-…", "label": "Exercice 2026", "startDate": "2026-01-01", "endDate": "2026-12-31", "status": "open", "kind": "exercise" } ], "rows": [ { "id": "TA", "label": "Ventes de marchandises", "kind": "line", "values": [98500000], "accounts": [{ "account": "701100", "label": "Ventes de cacao", "values": [98500000] }] }, { "id": "RA", "label": "Achats de marchandises", "kind": "line", "values": [-81200000], "accounts": [{ "account": "601100", "label": "Achats de cacao", "values": [-81200000] }] }, { "id": "XI", "label": "Résultat net", "kind": "result", "values": [6250000], "accounts": [] } ], "unmapped": [], "analytic": null } ``` ## Exemple de demande > Quel est le résultat de la coopérative sur le premier semestre 2026, et quels sont les trois postes de charges les plus importants ? --- # Lister les factures clients URL: https://developers.stg.dafexpert.com/article/mcp-list-invoices > Les factures clients d'un dossier, avec un filtre facultatif par statut. ## Description Liste les factures clients d'un dossier, de la plus récente à la plus ancienne : numéro, client, statut, dates, totaux HT, TVA et TTC, montants réglés. Un filtre facultatif ne garde qu'un statut. ## Paramètres d'entrée - `companyId` (uuid, body, required) : Identifiant du dossier, obtenu avec [list_companies](/article/mcp-list-companies). - `status` (string, body) : Ne garder que ce statut (voir le tableau). | Statut | Signification | |---|---| | `draft` | Brouillon | | `created` | Finalisée, pas encore envoyée | | `sent` | Envoyée au client | | `late` | En retard de paiement | | `paid` | Payée | | `cancelled` | Annulée | ## Sortie Un tableau de factures. - `id` (uuid) : Identifiant de la facture, à passer à [get_invoice](/article/mcp-get-invoice). - `number` (string) : Numéro de facture (vide pour un brouillon). - `clientId` (uuid) : Identifiant du client. - `clientName` (string) : Nom du client. - `status` (string) : Statut de la facture. - `invoiceKind` (string) : Nature du document : `standard`, `deposit` (acompte), `intermediate`, `balance` (solde) ou `credit_note` (avoir). - `issueDate` (datetime) : Date d'émission. - `dueDate` (datetime) : Date d'échéance. - `paidAt` (datetime) : Date de paiement complet. - `currency` (string) : Devise de la facture. - `subtotalCents` (integer) : Total HT, en centièmes. - `taxCents` (integer) : TVA, en centièmes. - `totalCents` (integer) : Total TTC, en centièmes. - `collectedCents` (integer) : Montant réglé, en centièmes. - `cashedCents` (integer) : Montant déjà encaissé, en centièmes. ```json [ { "id": "b2f4c6d8-1a3e-4c5b-9d7f-0e2a4c6b8d1f", "number": "F-2026-0142", "clientId": "7e8f9a0b-…", "clientName": "SACO Abidjan", "status": "late", "invoiceKind": "standard", "issueDate": "2026-09-02T00:00:00.000Z", "dueDate": "2026-10-02T00:00:00.000Z", "paidAt": null, "currency": "XOF", "subtotalCents": 1250000000, "taxCents": 225000000, "totalCents": 1475000000, "collectedCents": 500000000, "cashedCents": 500000000 } ] ``` ## Exemple de demande > Quelles factures de la coopérative sont en retard, et combien reste-t-il à encaisser ? --- # Détail d'une facture URL: https://developers.stg.dafexpert.com/article/mcp-get-invoice > Le détail d'une facture client : lignes, totaux, client, paiements et transactions liées. ## Description Renvoie le détail complet d'une facture client : coordonnées du client, lignes, totaux, remise, règlements, transactions bancaires rapprochées, avoirs et, en Côte d'Ivoire, l'état de certification FNE. ## Paramètres d'entrée - `companyId` (uuid, body, required) : Identifiant du dossier, obtenu avec [list_companies](/article/mcp-list-companies). - `invoiceId` (uuid, body, required) : Identifiant de la facture, obtenu avec [list_invoices](/article/mcp-list-invoices). ## Sortie - `id` (uuid) : Identifiant de la facture. - `number` (string) : Numéro de facture. - `status` (string) : Statut (`draft`, `created`, `sent`, `late`, `paid`, `cancelled`). - `issueDate` (datetime) : Date d'émission. - `dueDate` (datetime) : Date d'échéance. - `currency` (string) : Devise. - `clientName` (string) : Nom du client, accompagné de `clientEmail`, `clientAddress`, `clientCity`, `clientCountry`, `clientVatNumber`. - `lineItems` (array) : Lignes : `description`, `quantity`, `unit`, `unitPriceCents`, `taxRateBp` (taux de TVA en points de base, `1800` = 18 %), `lineTotalCents`, `accountCode`. - `subtotalCents` (integer) : Total HT après remise, en centièmes. - `taxCents` (integer) : TVA, en centièmes. - `totalCents` (integer) : Total TTC, en centièmes. - `settledCents` (integer) : Montant réglé, en centièmes. - `cashedCents` (integer) : Montant encaissé, en centièmes. - `creditedCents` (integer) : Part du TTC annulée par des avoirs, en centièmes. - `linkedTransactions` (array) : Transactions rapprochées : `id`, `name`, `amountCents`, `occurredAt`. - `creditNotes` (array) : Avoirs émis sur cette facture : `id`, `number`, `issueDate`, `totalCents`. - `fiscalStatus` (string) : État de certification fiscale FNE (`none`, `pending`, `certified`, `failed`), avec `fiscalNumber` et `fiscalVerificationUrl` une fois certifiée. ```json { "id": "b2f4c6d8-1a3e-4c5b-9d7f-0e2a4c6b8d1f", "number": "F-2026-0142", "status": "late", "issueDate": "2026-09-02T00:00:00.000Z", "dueDate": "2026-10-02T00:00:00.000Z", "currency": "XOF", "clientName": "SACO Abidjan", "clientEmail": "compta@saco.ci", "clientCity": "Abidjan", "clientCountry": "CI", "lineItems": [ { "description": "Fèves de cacao grade 1", "quantity": 5000, "unit": "kg", "unitPriceCents": 250000, "taxRateBp": 1800, "lineTotalCents": 1250000000, "accountCode": "701100" } ], "subtotalCents": 1250000000, "taxCents": 225000000, "totalCents": 1475000000, "settledCents": 500000000, "cashedCents": 500000000, "creditedCents": 0, "linkedTransactions": [ { "id": "c4d5e6f7-…", "name": "VIR SACO ABIDJAN", "subtitle": null, "amountCents": 500000000, "occurredAt": "2026-09-20T00:00:00.000Z" } ], "creditNotes": [], "fiscalStatus": "certified", "fiscalNumber": "FNE-2026-000123", "fiscalVerificationUrl": "https://…" } ``` ## Exemple de demande > Détaille la facture F-2026-0142 : qu'a-t-on déjà reçu de SACO et que reste-t-il dû ? --- # Trésorerie URL: https://developers.stg.dafexpert.com/article/mcp-get-treasury-overview > La vue trésorerie d'un dossier : solde, flux, échéances à venir et dernières transactions. ## Description Renvoie la vue trésorerie d'un dossier : solde disponible (soldes bancaires réels si une banque est connectée), encaissements et décaissements sur la fenêtre choisie, créances clients et dettes fournisseurs, prochaines échéances et dernières transactions. ## Paramètres d'entrée - `companyId` (uuid, body, required) : Identifiant du dossier, obtenu avec [list_companies](/article/mcp-list-companies). - `range` (string, body) : Fenêtre d'analyse : `7d`, `30d` (par défaut) ou `1y`. ## Sortie Tous les montants sont en centièmes de la devise du dossier. - `treasuryCents` (integer) : Solde de trésorerie disponible. - `cash` (object) : Détail du solde : `source` (`bank` si des comptes bancaires sont synchronisés, sinon `transactions`), `accounts` (`name`, `type`, `bankName`, `balanceCents`, `lastSyncAt`), `asOf`. - `inflowCents` (integer) : Encaissements sur la fenêtre. - `outflowCents` (integer) : Décaissements sur la fenêtre (valeur positive). - `currentNetCents` (integer) : Flux net sur la fenêtre. - `prevNetCents` (integer) : Flux net sur la fenêtre précédente de même durée. - `receivableCents` (integer) : Reste à encaisser sur les factures clients émises, avec `receivableCount`. - `payableCents` (integer) : Reste à payer aux fournisseurs, avec `payableCount`. - `upcoming` (array) : Cinq prochaines échéances : `name`, `subtitle`, `amountCents`, `dueDate`, `kind` (`invoice` à encaisser ou `transaction` à payer). - `latest` (array) : Trois dernières transactions : `name`, `amountCents`, `kind`, `status`, `missingReceipt`, `occurredAt`. - `dailyBalances` (integer[]) : Solde jour par jour sur la fenêtre, du plus ancien au plus récent. ```json { "treasuryCents": 1845000000, "cash": { "cents": 1845000000, "source": "bank", "accounts": [ { "id": "…", "type": "bank", "bankName": "SGCI", "name": "Compte courant", "currency": "XOF", "balanceCents": 1845000000, "lastSyncAt": "2026-10-12T06:00:00.000Z" } ], "asOf": "2026-10-12T06:00:00.000Z" }, "inflowCents": 920000000, "outflowCents": 640000000, "currentNetCents": 280000000, "prevNetCents": -115000000, "receivableCents": 975000000, "receivableCount": 3, "payableCents": 412000000, "payableCount": 5, "upcoming": [ { "id": "…", "name": "SACO Abidjan", "subtitle": "Facture F-2026-0142 à encaisser", "amountCents": 975000000, "dueDate": "2026-10-02T00:00:00.000Z", "kind": "invoice" } ], "latest": [ { "id": "…", "name": "Achat intrants", "subtitle": null, "amountCents": -85000000, "kind": "out", "status": "done", "missingReceipt": true, "occurredAt": "2026-10-11T00:00:00.000Z" } ], "dailyBalances": [1565000000, 1570000000, 1845000000] } ``` ## Exemple de demande > Comment évolue la trésorerie de la coopérative sur les 30 derniers jours, et quelles sont les prochaines échéances ? --- # Pièces fiscales FNE par API URL: https://developers.stg.dafexpert.com/article/changelog-2026-10-12-fne-documents > Deux routes synchrones émettent des bordereaux d'achat et des factures certifiés FNE, avec numéro fiscal, QR code et PDF. > **warning** **Bientôt disponible.** Cette fonctionnalité est prête mais pas encore ouverte sur le staging. Le contrat décrit ici est celui qui sera publié. **12 octobre 2026** Deux routes synchrones émettent des bordereaux d'achat et des factures certifiés FNE, avec numéro fiscal, QR code et PDF. ## Ce qui change - Nouvelle route `POST /v1/partners/companies/{external_id}/bordereaux` : bordereau d'achat de produits agricoles certifié FNE, émis par l'entreprise acheteuse. - Nouvelle route `POST /v1/partners/companies/{external_id}/invoices` : facture de vente certifiée FNE. Le modèle FNE (B2B, B2C, B2F, B2G) est déduit du client. - La réponse est immédiate : numéro de pièce, numéro fiscal, QR code, lien vers le PDF et état de comptabilisation. - Les deux routes sont idempotentes sur votre identifiant : un rejeu renvoie la même pièce et le même numéro fiscal, sans double certification. - Une certification en échec (FNE non connectée, stickers épuisés, téléphone manquant) enregistre la pièce comme non certifiée et la signale en écart. ## Pourquoi c'est utile Les pièces fiscales ne transitent plus par les événements : vous obtenez le numéro fiscal et le PDF dans la réponse, et pouvez les remettre tout de suite au producteur ou au client. ## Documentation - [Pièces fiscales FNE](/article/fne-documents) - [Créer un bordereau d'achat](/article/post-bordereau) - [Créer une facture](/article/post-invoice) --- # Événements version 2 URL: https://developers.stg.dafexpert.com/article/changelog-2026-10-12-events-v2 > Les événements en `"version": 2` gagnent un catalogue de types, des versions, des effets par entreprise et la détection des doublons probables. > **warning** **Bientôt disponible.** Cette fonctionnalité est prête mais pas encore ouverte sur le staging. Le contrat décrit ici est celui qui sera publié. **12 octobre 2026** Les événements en `"version": 2` gagnent un catalogue de types, des versions, des effets par entreprise et la détection des doublons probables. ## Ce qui change - Les événements envoyés avec `"version": 2` suivent un catalogue de types (paiement producteur, avance, dépense, paie…) et citent les pièces fiscales par leur identifiant. - Versions : renvoyer un même `id` avec un autre contenu remplace l'événement tant qu'il n'est pas comptabilisé (`version_appliquee: true`). Sinon, la version est conservée sans effet et signalée. - Chaque événement indique ses effets par entreprise (pièce créée, rattachée, écriture). - Un événement qui cite une pièce pas encore reçue passe en `en_attente_piece` et repart à son arrivée. - Les doublons probables (même opération sous deux `id`) sont détectés et bloqués avant comptabilisation. - La version 1 reste acceptée. ## Pourquoi c'est utile Vous pouvez corriger un événement en le renvoyant, et suivre précisément ce qu'il a produit dans chaque entreprise. ## Documentation - [Migrer vers les événements v2](/article/migrate-events-v2) - [Versions d'un événement](/article/event-versions) - [Envoyer des événements](/article/post-events) --- # Fiche entreprise complète URL: https://developers.stg.dafexpert.com/article/changelog-2026-10-12-company-fields > `PUT /v1/partners/companies/{external_id}` accepte de nouveaux types d'entreprise et les champs fiscaux, de localisation et d'exploitant. > **warning** **Bientôt disponible.** Cette fonctionnalité est prête mais pas encore ouverte sur le staging. Le contrat décrit ici est celui qui sera publié. **12 octobre 2026** `PUT /v1/partners/companies/{external_id}` accepte de nouveaux types d'entreprise et les champs fiscaux, de localisation et d'exploitant. ## Ce qui change - Nouveaux types `union` et `acheteur`, en plus de `cooperative` et `exploitant`. - Nouveaux champs : `parent_id` (structure d'appartenance), `localisation`, `etiquettes`, informations fiscales et légales, bloc `exploitant`, `systeme_comptable` (`smt` par défaut pour un exploitant). - Un champ envoyé pour un type qui ne le prévoit pas est refusé (`422`). - La réponse liste dans `a_completer` ce qui manque avant la première pièce fiscale. ## Pourquoi c'est utile Les entreprises arrivent prêtes à émettre des pièces fiscales, et vous savez tout de suite ce qu'il reste à compléter. ## Documentation - [Créer ou mettre à jour une entreprise](/article/put-company) --- # Création des entreprises en masse URL: https://developers.stg.dafexpert.com/article/changelog-2026-10-12-bulk-companies > `POST /v1/partners/companies` reprend jusqu'à 500 entreprises par appel, traitées en arrière-plan avec un suivi par lot. > **warning** **Bientôt disponible.** Cette fonctionnalité est prête mais pas encore ouverte sur le staging. Le contrat décrit ici est celui qui sera publié. **12 octobre 2026** `POST /v1/partners/companies` reprend jusqu'à 500 entreprises par appel, traitées en arrière-plan avec un suivi par lot. ## Ce qui change - Nouvelle route `POST /v1/partners/companies` : jusqu'à 500 entreprises par appel, réponse `202` avec un identifiant de `lot`. - Chaque entreprise est validée seule : une entreprise invalide ne bloque pas les autres. - Suivi avec `GET /v1/partners/companies/lots/{lot}` : statut par entreprise (`recu`, `en_cours`, `traite`, `rejete`), compteurs et motif de rejet. - Les erreurs techniques sont retentées automatiquement. ## Pourquoi c'est utile La reprise d'un parc existant de coopératives et d'exploitants se fait en quelques appels. Au quotidien, `PUT` reste la voie normale. ## Documentation - [Créer des entreprises en masse](/article/post-companies) --- # Gestion des utilisateurs URL: https://developers.stg.dafexpert.com/article/changelog-2026-10-09-users > `PUT /v1/partners/users/{id}` crée ou rattache un compte dafexpert et lui donne accès à des entreprises. **9 octobre 2026** `PUT /v1/partners/users/{id}` crée ou rattache un compte dafexpert et lui donne accès à des entreprises. ## Ce qui change - Nouvelle route `PUT /v1/partners/users/{id_utilisateur}`, idempotente : crée le compte de connexion (ou rattache un compte existant par e-mail) et donne accès aux entreprises listées dans `acces`, avec un rôle. - Le mot de passe peut être fixé par le partenaire pour les comptes qu'il crée ; il n'est jamais stocké chez dafexpert. - La liste `acces` fait référence : un renvoi retire les accès qui n'y figurent plus. - Nouvelle route `DELETE /v1/partners/users/{id_utilisateur}` : retire tous les accès donnés par le partenaire. - Le champ `utilisateurs` de `PUT /v1/partners/companies/{external_id}` est retiré (`422` s'il est envoyé). ## Pourquoi c'est utile Vos utilisateurs se connectent à dafexpert sans invitation ni onboarding, et ne voient que leurs propres entreprises. ## Documentation - [Créer ou mettre à jour un utilisateur](/article/put-user) --- # Connexion unique (SSO) URL: https://developers.stg.dafexpert.com/article/changelog-2026-10-08-sso > Vos utilisateurs se connectent une seule fois pour accéder à dafexpert et à votre application. **8 octobre 2026** Vos utilisateurs se connectent une seule fois pour accéder à dafexpert et à votre application. ## Ce qui change - Chaque partenaire dispose d'un client OpenID Connect (flux Authorization Code, PKCE accepté) sur le fournisseur d'identité dafexpert. - La session est partagée : un utilisateur connecté à l'un est connecté à l'autre. La déconnexion unique est possible avec une URL de déconnexion front-channel. - Après connexion, l'utilisateur choisit l'application à ouvrir (dafexpert ou la vôtre) et peut passer de l'une à l'autre depuis l'en-tête. ## Pourquoi c'est utile Un seul identifiant pour vos utilisateurs, et un passage fluide entre votre outil terrain et leur comptabilité. ## Documentation - [Authentification](/article/authentication) --- # Comptabilisation automatique des événements URL: https://developers.stg.dafexpert.com/article/changelog-2026-10-08-event-accounting > Les événements reçus sont traités en arrière-plan et deviennent des écritures, selon des correspondances paramétrables. **8 octobre 2026** Les événements reçus sont traités en arrière-plan et deviennent des écritures, selon des correspondances paramétrables. ## Ce qui change - Chaque événement reçu est traité en arrière-plan, avec de nouveaux essais automatiques en cas d'erreur technique. - Il produit une écriture comptable selon une correspondance (journal, comptes, libellé, montants) paramétrée pour le partenaire, l'espace ou l'entreprise. - L'écriture passe les mêmes contrôles que la saisie manuelle et reste en brouillard jusqu'à sa validation par le comptable. - Le statut passe à `comptabilise` (avec l'écriture) ou `rejete` (avec le motif). Un événement rejeté peut être relancé depuis dafexpert après correction. ## Pourquoi c'est utile Vos opérations de terrain arrivent en comptabilité sans ressaisie, et vous suivez leur sort depuis l'API. ## Documentation - [Suivre les événements](/article/list-events) --- # Envoi et suivi des événements URL: https://developers.stg.dafexpert.com/article/changelog-2026-10-08-events > `POST /v1/partners/events` reçoit les événements métier, un par un ou par lots de 500, et `GET /v1/partners/events` les synchronise. **8 octobre 2026** `POST /v1/partners/events` reçoit les événements métier, un par un ou par lots de 500, et `GET /v1/partners/events` les synchronise. ## Ce qui change - Nouvelle route `POST /v1/partners/events` : un événement ou un lot de 500 au plus, réponse `202` avec un résultat par événement. - L'`id` de l'événement sert de clé anti-doublon : renvoyer après une coupure est sans risque. - Un événement dont l'entreprise n'existe pas encore est conservé (`en_attente_entreprise`) puis libéré à sa création. - Nouvelles routes `GET /v1/partners/events/{id}` et `GET /v1/partners/events?apres=` pour suivre les changements de statut sans en manquer. ## Pourquoi c'est utile Vous envoyez vos opérations au fil de l'eau et récupérez leur statut de comptabilisation de façon fiable. ## Documentation - [Envoyer des événements](/article/post-events) - [Suivre les événements](/article/list-events) --- # Création des entreprises URL: https://developers.stg.dafexpert.com/article/changelog-2026-10-08-companies > `PUT /v1/partners/companies/{external_id}` crée ou met à jour une coopérative ou un exploitant, avec son premier exercice. **8 octobre 2026** `PUT /v1/partners/companies/{external_id}` crée ou met à jour une coopérative ou un exploitant, avec son premier exercice. ## Ce qui change - Nouvelle route `PUT /v1/partners/companies/{external_id}`, idempotente : le premier appel crée l'entreprise, les suivants la mettent à jour. - À la création, le premier exercice est ouvert avec ses périodes, ses journaux et le plan comptable du pays (SYSCOHADA en zone OHADA). - Le pays, la devise, le référentiel et l'exercice sont figés après la création. - Les erreurs de validation renvoient un `422` avec le détail par champ. ## Pourquoi c'est utile Chaque entreprise que vous suivez a sa comptabilité prête dès le premier appel, sans intervention manuelle. ## Documentation - [Créer ou mettre à jour une entreprise](/article/put-company) --- # Lancement de l'API partenaires URL: https://developers.stg.dafexpert.com/article/changelog-2026-10-08-partner-api > Authentification OAuth 2.0 client credentials par espace et route `GET /v1/partners/me` pour vérifier ses identifiants. **8 octobre 2026** Authentification OAuth 2.0 client credentials par espace et route `GET /v1/partners/me` pour vérifier ses identifiants. ## Ce qui change - Les partenaires s'authentifient en OAuth 2.0 *client credentials*, avec un `client_id` et un `client_secret` par espace, obtenus par l'administrateur de l'espace dans *Paramètres › Applications connectées*. - Le jeton dure 5 minutes et se redemande avec les mêmes identifiants (pas de jeton de renouvellement). - L'administrateur peut générer un nouveau secret ou déconnecter le partenaire à tout moment. - Nouvelle route `GET /v1/partners/me` : renvoie le partenaire, l'espace et la version de l'API. ## Pourquoi c'est utile Une API générique, identique pour tous les partenaires, et isolée par espace : un identifiant ne peut agir que dans son espace. ## Documentation - [Authentification](/article/authentication) - [Vérifier ses identifiants](/article/get-partner-me) --- # Lancement du serveur MCP URL: https://developers.stg.dafexpert.com/article/changelog-2026-10-06-mcp-server > Les clients dafexpert peuvent connecter Claude ou un autre assistant IA à leurs dossiers, en lecture seule. **6 octobre 2026** Les clients dafexpert peuvent connecter Claude ou un autre assistant IA à leurs dossiers, en lecture seule. ## Ce qui change - Nouveau serveur MCP à l'adresse `https://api.stg.dafexpert.com/mcp` (staging), en Streamable HTTP. - Connexion avec le compte dafexpert de l'utilisateur : OAuth 2.0 + PKCE, Client ID `dafexpert-mcp`, sans secret. - Huit outils en lecture seule : dossiers, exercices, balance, anomalies de la balance, compte de résultat, factures clients, détail d'une facture, trésorerie. - L'assistant ne voit que les dossiers dont l'utilisateur est membre. ## Pourquoi c'est utile Vos utilisateurs interrogent leur comptabilité en langage naturel depuis leur assistant IA, sans export. ## Documentation - [Serveur MCP](/article/mcp-overview) - [Connecter un assistant IA](/article/connect-ai-assistant)