Règles d'imputation bancaires
Une règle d'imputation bancaire (bank_rule) répond à une seule question : quand une opération bancaire ressemble à ceci, sur quel compte comptable son reliquat est-il imputé ? C'est ce qui évite que tout ce qui n'a pas été rapproché finisse sur le compte d'attente.
L'opération bancaire est la source, l'écriture comptable est le résultat (Écritures comptables bancaires), et une règle est ce qui se place entre les deux. Les exclusions, qui retirent une entreprise du champ d'une règle de workspace, ont leur propre page (Exclusions de règles bancaires).
Une règle impute un reliquat, elle ne lettre jamais une facture
Une règle désigne UN compte comptable, pour le reliquat, et rien d'autre. Elle ne marque aucune facture comme payée, ne crée aucun rapprochement et ne produit aucun paiement : sa charge utile ne porte ni indicateur de règlement, ni identifiant de paiement, ni identifiant de rapprochement. Imputer et rapprocher sont deux actes différents, et les confondre est la façon dont une ligne d'attente devient silencieusement un règlement.
L'ordre dans lequel le compte du reliquat est choisi est rapprochement confirmé, puis règle société, puis règle de workspace, puis compte d'attente. Une règle ne se prononce donc que sur ce que les rapprochements confirmés ont laissé.
La portée l'emporte sur la priorité. Une règle société bat toute règle de workspace, quel que soit son numéro de priorité : priority ne départage que des règles de même portée. Une règle société de priorité 900 passe avant une règle de workspace de priorité 0.
La portée est fixée par le chemin, définitivement
scope vaut tenant pour une règle de workspace - elle s'applique à toutes les entreprises du workspace, sauf celles qui en sont exclues - et company pour une règle liée à une seule entreprise. company_id est nul exactement quand scope vaut tenant.
C'est le chemin de création qui fixe la portée, et elle n'est plus modifiable ensuite. Un scope ou un company_id placés dans le corps de la requête sont ignorés, à la création comme à la modification : changer la portée d'une règle changerait son niveau de précédence.
Les endpoints
GET /api/v1/workspaces/{workspace_id}/bank_rules- lister les règles d'un workspacePOST /api/v1/workspaces/{workspace_id}/bank_rules- créer une règle de workspaceGET /api/v1/workspaces/{workspace_id}/companies/{company_id}/bank_rules- lister les règles qui gouvernent une entreprisePOST /api/v1/workspaces/{workspace_id}/companies/{company_id}/bank_rules- créer une règle sociétéGET /api/v1/bank_rules/{id}- lire une règlePATCH /api/v1/bank_rules/{id}- modifier une règleDELETE /api/v1/bank_rules/{id}- supprimer une règlePOST /api/v1/bank_rules/{id}/preview- simuler une règle
Comme sur les autres ressources bancaires, seules les listes et les créations sont adressées par workspace. Les endpoints portant un identifiant n'ont pas de workspace_id dans le chemin : l'identifiant est résolu sur l'ensemble des workspaces rattachés à votre client OAuth.
Les lectures demandent le scope read, la création et la modification le scope write, la suppression le scope destroy ou write. La simulation est un POST qui ne demande que read : elle n'écrit rien, et exiger write pour inspecter une règle serait à l'envers.
L'en-tête Idempotency-Key est accepté et facultatif sur toutes les écritures de cette page : elles ne produisent aucun effet chez un tiers et sont rejouables.
Les champs d'une règle
Quatorze champs, tous présents dans chaque réponse.
| Champ | Ce qu'il porte |
|---|---|
id | L'identifiant de la règle, celui que portent les endpoints membres |
workspace_id | Le workspace auquel la règle appartient. Jamais nul, sur une règle société comme sur une règle de workspace - c'est la seule chose qui identifie le propriétaire d'une règle de workspace, dont le company_id est nul |
scope | tenant ou company. Fixé par le chemin de création |
company_id | Nul exactement quand scope vaut tenant |
name | Unique au sein de son workspace, de sa portée et de son entreprise. 255 caractères au plus |
priority | Le plus petit passe en premier. À égalité, l'id croissant tranche, donc l'ordre est total. Vaut 0 si vous ne l'indiquez pas à la création |
enabled | Une règle désactivée ne classe rien et reste listée - c'est le but, « elle est éteinte » étant la réponse la plus fréquente à « pourquoi rien n'est classé ». Activez et désactivez avec un PATCH de ce champ ; il n'y a pas d'endpoint dédié |
valid_from | Début de la fenêtre de validité. Nul veut dire pas de borne basse |
valid_until | Fin de la fenêtre de validité. Nul veut dire pas de borne haute, et cette date ne peut pas précéder valid_from |
conditions | Ce à quoi une opération doit ressembler. Seules les clés réellement posées sont rendues |
action | Où le reliquat est imputé. Les trois clés sont toujours rendues |
excluded_company_ids | Les entreprises retirées du champ de cette règle de workspace, par identifiant croissant. Toujours vide sur une règle société |
created_at, updated_at | Horodatages ISO 8601 |
?include=bank_rule_exclusions ajoute à chaque règle la charge utile complète de ses exclusions, sous la clé bank_rule_exclusions. Sans ce paramètre la clé est absente - et non présente à null. C'est le seul include de cette ressource ; toute autre valeur est ignorée.
Créer une règle
Un token write suffit. Le chemin choisi fixe la portée : celui-ci crée une règle de workspace.
curl -X POST https://app.scribee.tech/api/v1/workspaces/YOUR_WORKSPACE_ID/bank_rules \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6c2f9a31-4b7d-4e88-9f10-1d3b5e7c2a04" \
-d '{
"name": "Frais bancaires",
"priority": 20,
"conditions": { "direction": "outgoing", "operation_type": "direct_debit" },
"action": { "account_number": "627000" }
}'
{
"data": {
"id": 88,
"workspace_id": 12,
"scope": "tenant",
"company_id": null,
"name": "Frais bancaires",
"priority": 20,
"enabled": true,
"valid_from": null,
"valid_until": null,
"conditions": {
"direction": "outgoing",
"operation_type": "direct_debit"
},
"action": {
"account_number": "627000",
"tax_code": null,
"label_template": null
},
"excluded_company_ids": [],
"created_at": "2026-08-21T15:10:00+02:00",
"updated_at": "2026-08-21T15:10:00+02:00"
}
}
Seuls name et action.account_number sont obligatoires. priority vaut 0 par défaut, enabled vaut true, les deux bornes de validité sont nulles, et conditions omis donne la règle attrape-tout légitime en bas de la liste de priorité.
conditions : le vocabulaire est fermé
Toutes les clés sont facultatives et elles sont combinées par ET. Un objet vide correspond à toutes les opérations. Le vocabulaire est fermé : une clé qui n'y figure pas fait refuser la règle en 422 avec le code invalid_argument, elle n'est pas ignorée.
| Clé | Valeur attendue | Ce qu'elle teste |
|---|---|---|
direction | incoming ou outgoing | Le sens du mouvement |
operation_type | card, deferred_debit_card, transfer, direct_debit, check, withdrawal, deposit, open_banking, unknown | Le type d'opération |
amount_min | un nombre | Le montant en valeur absolue est supérieur ou égal |
amount_max | un nombre | Le montant en valeur absolue est inférieur ou égal |
bank_account_id | un entier | L'opération appartient à ce compte bancaire. Le compte doit relever de la portée de la règle - l'entreprise de la règle pour une règle société, une entreprise du workspace pour une règle de workspace |
description_matches | une chaîne non vide de 1024 caractères au plus | Le libellé contient ce fragment, sans tenir compte des accents ni de la casse |
Seules les clés réellement posées sont rendues, et c'est ce qui rend la charge utile renvoyable telle quelle. Une clé présente à null n'est pas la même chose qu'une clé absente : {"direction": null} est refusé, parce que null n'appartient pas au vocabulaire de direction. Remplir les clés non posées avec null produirait donc un corps que vous ne pourriez pas renvoyer.
description_matches est un fragment littéral, pas une expression régulière
C'est la clé sur laquelle une intégration se trompe. description_matches recherche un fragment de texte, insensible aux accents et à la casse, dans le libellé de l'opération - jamais un motif. Les accents (é, è, ç, ...) sont ignorés des deux côtés : prelevement trouve PRÉLÈVEMENT, et Société trouve SOCIETE. Les ligatures ne sont pas décomposées : coeur ne trouve pas CŒUR. Une valeur de plus de 1024 caractères, ou vide une fois ses accents retirés (espaces seuls compris), est refusée en 422 avec le code invalid_argument. Le fragment est cherché à la fois dans le libellé brut et dans le libellé normalisé, donc une règle écrite contre l'un ou l'autre se déclenche.
Le fragment est comparé au libellé tel que la banque l'a transmis, pas à celui que l'API vous publie. Un IBAN cité dans un libellé vous est rendu masqué, FR*********************0189 (voir opérations bancaires), mais la règle le voit en entier. Deux conséquences : un fragment recopié depuis la forme masquée ne correspond à rien, faute d'astérisques dans le libellé d'origine ; et une règle qui doit reconnaître la contrepartie se déclenche plus sûrement sur son nom ou sur la référence du virement que sur un numéro de compte que vous ne lisez jamais en entier.
Une valeur qui ne peut être qu'un motif est refusée plutôt qu'acceptée. Sont refusés les caractères \, ^, $, |, ?, +, (, ), [, ], {, }, ainsi que les séquences .* et .+. Le refus est délibéré : une règle portant ^PRLV|^VIR serait acceptée sans jamais se déclencher, et chaque reliquat qu'elle devait imputer partirait en silence sur le compte d'attente. Le silence est le mode de défaillance, et la validation est ce qui le transforme en message.
L'astérisque seul et le point seul sont acceptés, parce qu'ils sont ordinaires dans un libellé bancaire : ADOBE *SUBS est la façon dont les accepteurs de cartes formatent leurs libellés, et S.A.R.L ou N.1234 sont des littéraux courants. Les refuser coûterait un message sur lequel personne ne peut agir.
Il reste un cas que cette validation ne rattrape pas : un * employé comme quantificateur et seul, COMMISSIONS*, est accepté et se comporte comme le littéral COMMISSIONS* - qui ne correspond à rien. C'est le prix à payer pour accepter ADOBE *SUBS, les deux étant indiscernables sans analyser le motif.
action : où le reliquat est imputé
Les trois clés sont toujours rendues, avec null pour celles qui ne sont pas posées, de sorte que l'objet que vous lisez est un objet que vous pouvez renvoyer inchangé.
| Clé | Ce qu'elle porte |
|---|---|
account_number | Le compte du plan comptable sur lequel le reliquat est imputé. Obligatoire : une règle sans compte n'a rien à dire. Une chaîne non vide |
tax_code | Reporté sur la ligne simulée. Envoyez null pour l'effacer |
label_template | Modèle du libellé de la ligne passée. Envoyez null pour l'effacer |
Comme pour conditions, une clé hors de ces trois fait refuser la règle avec le code invalid_argument.
Priorité, activation et fenêtre de validité
priority est croissant : le plus petit numéro est examiné en premier, et l'id croissant départage les égalités. Il ne compare que des règles de même portée - une règle société ne dispute jamais sa place à une règle de workspace.
enabled s'écrit par un PATCH du champ, il n'existe pas d'endpoint d'activation. Une règle désactivée continue d'être listée.
valid_from et valid_until sont des dates ISO 8601 (YYYY-MM-DD), et une borne nulle est une borne ouverte : une règle sans valid_from est en vigueur depuis toujours, une règle sans valid_until l'est indéfiniment. Une valeur que Scribee ne sait pas lire comme une date est enregistrée comme une borne absente plutôt que refusée - relisez la règle après l'avoir écrite si vous n'êtes pas sûr de votre format.
Lister les règles
curl "https://app.scribee.tech/api/v1/workspaces/YOUR_WORKSPACE_ID/bank_rules?enabled=true&scope=tenant" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Les deux chemins de liste ne répondent pas à la même question.
- Par workspace, vous obtenez toutes les règles du workspace, règles société comprises.
- Par entreprise, vous obtenez les règles qui gouvernent cette entreprise : ses propres règles société, plus les règles de workspace dont elle n'est pas exclue. Les règles de workspace sont là exprès - elles lient les opérations de cette entreprise et sont nommées dans l'explication que publie la simulation, donc une liste sans elles ne pourrait pas expliquer une imputation.
La liste est rendue par priority croissant puis par id croissant, et l'enveloppe data + meta suit Conventions de l'API. L'ordre de la LISTE n'est pas l'ordre de PRÉCÉDENCE : une règle société bat une règle de workspace quelle que soit sa priorité, donc servez-vous de scope pour distinguer les deux niveaux.
La pagination est par décalage, avec page et per_page - 20 par défaut, 100 au maximum - comme sur les autres ressources bancaires.
Trois filtres, tous facultatifs et cumulables :
| Filtre | Valeurs | Ce qu'il retient |
|---|---|---|
enabled | true ou false | Les règles actives, ou les règles désactivées |
scope | tenant ou company | Un seul niveau de précédence |
effective_on | une date ISO 8601 | Les règles en vigueur à cette date, une borne nulle étant une borne ouverte des deux côtés |
Une valeur qu'un filtre ne sait pas lire ne remonte rien, et ce n'est pas une erreur - ni un 422, ni une fenêtre silencieusement plus large. enabled=oui, scope=groupe et effective_on=14/07/2026 répondent chacun un 200 avec une collection vide. Les deux premiers n'acceptent que les valeurs listées ci-dessus, et effective_on n'accepte que le format ISO 8601 : 14/07/2026 ne remonte rien plutôt que d'être deviné comme le 14 juillet ou comme le 7 avril.
Un paramètre absent ou vide n'est pas un filtre : il ne restreint rien.
Lire, modifier et supprimer une règle
GET /api/v1/bank_rules/{id} rend une règle. excluded_company_ids est recalculé à chaque lecture, donc il reflète la règle telle qu'elle est maintenant.
PATCH /api/v1/bank_rules/{id} modifie la règle sur place. Chaque champ est facultatif et un champ omis conserve sa valeur, mais un corps ne portant aucun champ accepté est refusé plutôt que traité : il n'a rien demandé.
{
"error": "unprocessable_entity",
"code": "validation_failed",
"message": "La validation a échoué",
"details": {
"base": ["Renseignez au moins un des champs name, priority, enabled, valid_from, valid_until, conditions ou action"]
}
}
conditions et action sont REMPLACÉS en entier, jamais fusionnés. C'est le seul moyen de retirer une condition : renvoyez l'objet sans elle. Les champs acceptés sont name, priority, enabled, valid_from, valid_until, conditions et action ; scope et company_id n'en font pas partie.
DELETE /api/v1/bank_rules/{id} supprime la règle et ses exclusions. Elle arrête les classements FUTURS et n'en réécrit aucun passé : une règle ne possède ni écriture comptable ni paiement, donc rien de déjà comptabilisé ne bouge. Le corps de la réponse est la règle telle qu'elle était au moment où vous en avez demandé la suppression.
Simuler une règle avant de l'activer
POST /api/v1/bank_rules/{id}/preview confronte la règle à de vraies opérations et n'écrit rien : ni rapprochement, ni paiement, ni écriture comptable. C'est pour cela qu'un token read suffit.
Chaque ligne de la réponse dit, pour une opération, si la règle déterminerait son imputation, quelles lignes elle produirait, et - par overridden_by - si une source de précédence supérieure l'emporte déjà.
curl -X POST https://app.scribee.tech/api/v1/bank_rules/88/preview \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"operation_date_from": "2026-07-01",
"operation_date_to": "2026-07-31",
"direction": "outgoing"
}'
{
"data": [
{
"bank_operation_id": 30150,
"would_apply": true,
"overridden_by": null,
"explanation": {
"source": "tenant_rule",
"rule_id": 88,
"rule_name": "Frais bancaires",
"reason": "le sens est outgoing et le type d'opération est direct_debit"
},
"lines": [
{
"account_number": "627000",
"debit_amount": 12.5,
"credit_amount": 0.0,
"tax_code": null
}
]
}
],
"meta": {
"current_page": 1,
"per_page": 20,
"total_pages": 1,
"total_count": 1
}
}
L'ensemble simulé est borné par la portée de la règle : une règle société n'atteint que les opérations de son entreprise, une règle de workspace atteint celles de toutes les entreprises du workspace que vos habilitations couvrent. Les opérations sont rendues de la operation_date la plus récente à la plus ancienne, à égalité de date de l'identifiant le plus grand au plus petit.
Tous les filtres du corps sont facultatifs, et un corps vide simule la règle contre tout l'ensemble atteignable : page, per_page, bank_account_id, statement_id, direction, reconciliation_status, origin (synchronized ou statement), operation_date_from, operation_date_to, pending et archived. Les deux bornes de dates sont incluses et n'acceptent que le format ISO 8601 ; comme sur les listes, une valeur illisible ne remonte rien.
Sans pending ni archived, les opérations en attente et archivées font partie de l'ensemble simulé. Elles ne sont jamais comptabilisées, donc chacune répond would_apply: false et overridden_by: null - ajoutez "pending": false et "archived": false si vous ne voulez pas les voir.
explanation est nul dès que would_apply vaut false, et lines est alors vide. Le reason est du texte destiné à un humain, à ne pas analyser : il est traduit et son libellé ne fait pas partie du contrat.
Une ligne simulée porte le seul mouvement de la règle, jamais la ligne de banque - celle-ci appartient à la projection, pas à la règle. Les deux montants sont positifs ou nuls et exactement un des deux côtés est non nul : lisez le côté, pas le signe. Une opération sortante débite la contrepartie, une opération entrante la crédite.
Lire overridden_by
null ne veut pas dire « la règle s'applique ». Il veut dire que rien ne l'a battue, ce qui recouvre deux situations opposées, et c'est would_apply qui les distingue.
would_apply | overridden_by | Ce que cela veut dire |
|---|---|---|
true | null | La règle détermine l'imputation de cette opération |
false | null | La règle n'a pas tourné du tout. Ses conditions ne correspondent pas, ou elle est désactivée, ou l'opération est hors de sa fenêtre de validité, ou l'entreprise en est exclue, ou l'opération est archivée ou encore en attente. Rien ne l'a battue : elle n'était pas en lice |
false | company_rule ou tenant_rule | La règle était candidate et correspondait, mais une règle de précédence supérieure l'emporte |
false | allocation | Les rapprochements confirmés ne laissent aucun reliquat à imputer |
allocation n'apparaît que si le reliquat est nul. Une opération partiellement rapprochée n'est pas écrasée : une règle s'applique au reliquat, et un reliquat qui survit à un rapprochement est le cas ordinaire, pas l'exception.
overridden_by puise dans le même vocabulaire que explanation.source, de sorte que vous lisez la chaîne de précédence directement. suspense n'y apparaît jamais : c'est le dernier recours de la chaîne, il n'entre jamais en concurrence avec une règle. correction n'y apparaît pas non plus : elle ne marque que les lignes d'une écriture de correction, hors de la chaîne (Écritures comptables bancaires). Sur cet endpoint, explanation.source ne vaut donc jamais que company_rule ou tenant_rule.
Les erreurs
| Statut | error | code | Quand |
|---|---|---|---|
400 | bad_request | - | Un page qui n'est pas un entier supérieur ou égal à 1, ou une page au-delà de la dernière page d'une collection non vide |
403 | forbidden | - | Votre client OAuth n'a pas d'habilitation sur ce workspace, ou le token ne porte pas le scope requis |
404 | not_found | - | L'entreprise ou la règle n'est pas atteignable par vos habilitations |
409 | - | idempotency_key_reuse | La même Idempotency-Key a déjà servi pour un corps différent |
409 | - | idempotency_request_in_progress | Un appel antérieur portant cette clé est encore en cours |
422 | unprocessable_entity | validation_failed | name manquant ou trop long, action.account_number manquant, valid_until antérieur à valid_from, nom déjà pris à cette portée, ou corps de PATCH vide |
422 | unprocessable_entity | invalid_argument | Une clé inconnue dans conditions ou action, une valeur hors vocabulaire, un bank_account_id hors de la portée de la règle, un description_matches qui ne peut être qu'une expression régulière, qui dépasse 1024 caractères ou qui est vide une fois ses accents retirés, ou un conditions / action qui n'est pas un objet |
Les deux codes de 422 ne se corrigent pas de la même façon. validation_failed dit qu'une valeur manque ou se contredit ; invalid_argument dit que vous avez employé un mot qui n'appartient pas au vocabulaire publié. Le second est presque toujours une faute de vocabulaire, pas de données.
Le refus d'un motif dans description_matches porte le remède dans son message :
{
"error": "unprocessable_entity",
"code": "invalid_argument",
"message": "La validation a échoué",
"details": {
"conditions": ["déclare une expression régulière pour description_matches, alors que cette condition recherche un fragment littéral du libellé de l'opération et jamais un motif - supprimez le caractère de motif et indiquez le texte brut à rechercher"]
}
}
Une ressource hors de portée répond 404, jamais 403. L'atteignabilité est tranchée avant la question des droits : une règle d'un workspace que vos habilitations ne couvrent pas et une règle qui n'existe pas reçoivent la même réponse, de sorte que la réponse ne permet pas de deviner laquelle des deux vous avez rencontrée.
Référence API
- Référence API : lister les règles d'un workspace
- Référence API : créer une règle de workspace
- Référence API : lister les règles d'une entreprise
- Référence API : créer une règle société
- Référence API : lire une règle
- Référence API : modifier une règle
- Référence API : supprimer une règle
- Référence API : simuler une règle