Comptes bancaires
Un compte bancaire (bank_account) est un compte d'une entreprise de votre workspace, tel que Scribee le connaît. Il porte deux choses : de quoi le reconnaître - la banque, le libellé, l'IBAN masqué, le solde - et de quoi l'exploiter en comptabilité. C'est la seconde partie qui vous concerne : un compte ne sert à passer des écritures qu'une fois configuré.
Une connexion bancaire donne l'accès (Connexions bancaires), une synchronisation s'en sert (Synchronisations bancaires), et les comptes sont ce qu'elle rapporte.
La chose à comprendre avant tout le reste
Un compte bancaire ne se crée pas par l'API, et ne s'y supprime pas. Il n'existe ni POST ni DELETE sur cette ressource. Un compte apparaît sous une connexion à l'issue d'une synchronisation, ou lors de l'import d'un relevé. Si vous cherchez un endpoint de création, il n'y en a pas - c'est délibéré, pas une étape manquante.
Ce que vous faites d'un compte, c'est le lire et le configurer. Une seule écriture existe pour cela.
Les endpoints
GET /api/v1/workspaces/{workspace_id}/companies/{company_id}/bank_accounts- lister les comptes d'une entrepriseGET /api/v1/bank_accounts/{id}- lire un comptePATCH /api/v1/bank_accounts/{id}- configurer un compte
Comme pour les connexions bancaires, seule la liste est adressée par workspace et par entreprise. Les deux 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 deux lectures demandent le scope read, le PATCH demande le scope write.
Un compte porte dix-neuf champs, tous présents dans chaque réponse : id, company_id, bank_connection_id, bank_name, account_name, currency_code, origin, iban_masked, iban_last4, balance, accounting_account_code, ledger_id, suspense_account_code, active, archived, last_synced_at, readiness, created_at et updated_at.
L'IBAN complet n'est publié nulle part. Seuls iban_masked - le code pays, des étoiles, les quatre derniers caractères - et iban_last4 le sont, sur toutes les surfaces et sous tous les scopes. Aucun paramètre, aucun scope et aucun en-tête n'en donne la valeur entière : ne cherchez pas le drapeau qui la débloque, il n'existe pas. Le numéro de compte interne et l'identifiant du compte chez l'agrégateur ne sont publiés nulle part non plus.
Quand l'IBAN stocké fait moins de huit caractères, iban_masked étoile la valeur entière et iban_last4 vaut null. Les deux champs partagent ce seuil et se lisent ensemble : à cette longueur, quatre caractères en clair posés à côté d'un masque intégral redonneraient la valeur complète.
currency_code peut valoir null lui aussi : Scribee n'enregistre la devise rapportée par le fournisseur que si elle figure dans la liste ISO 4217, donc un compte découvert avant que le fournisseur ne la renseigne - ou avec un code que cette liste ne porte pas - se lit sans devise jusqu'à une synchronisation plus complète. La clé, elle, est toujours là.
accounting_account_code, lui, est publié en entier : c'est un code du plan comptable, pas un identifiant bancaire.
origin vaut synchronized quand un fournisseur alimente le compte, et manual sinon. Un compte issu d'un import de relevé n'enregistre aucun fournisseur : il se lit donc manual.
Lire un compte
Un token read suffit.
curl https://app.scribee.tech/api/v1/bank_accounts/7001 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
{
"data": {
"id": 7001,
"company_id": 34,
"bank_connection_id": 501,
"bank_name": "Demo Bank",
"account_name": "Compte courant",
"currency_code": "EUR",
"origin": "synchronized",
"iban_masked": "FR*********************0189",
"iban_last4": "0189",
"balance": 12450.33,
"accounting_account_code": null,
"ledger_id": null,
"suspense_account_code": null,
"active": false,
"archived": false,
"last_synced_at": "2026-08-21T09:02:11+02:00",
"readiness": {
"ready": false,
"missing": [
"not_activated",
"missing_ledger",
"missing_bank_account_code",
"missing_suspense_account_code"
],
"unposted_operations_count": 12
},
"created_at": "2026-08-21T11:15:00+02:00",
"updated_at": "2026-08-21T11:15:00+02:00"
}
}
Savoir où en est un compte : readiness
readiness est dérivé à chaque lecture et jamais stocké : il ne peut donc pas être périmé. Il porte trois clés :
ready- vrai uniquement quandmissingest vide ;missing- les conditions non remplies ;unposted_operations_count- le nombre d'opérations réglées de ce compte qui attendent encore une écriture comptable.
missing liste toutes les conditions manquantes d'un coup, jamais seulement la première. C'est délibéré : vous corrigez tout en une passe, au lieu de découvrir la condition suivante à chaque refus.
missing | Ce qu'il signifie | Comment le lever |
|---|---|---|
not_activated | Le compte n'est pas activé pour la comptabilisation | PATCH avec active à true |
missing_ledger | Aucun journal comptable n'est rattaché | PATCH avec ledger_id |
missing_bank_account_code | Aucun code de compte comptable n'est renseigné | PATCH avec accounting_account_code |
missing_suspense_account_code | Aucun compte d'attente n'est renseigné | PATCH avec suspense_account_code |
provider_unavailable | Le fournisseur ne donne plus ce compte | Aucun champ ne le lève - voir ci-dessous |
provider_unavailable ne se corrige pas par l'API. Il signifie que le fournisseur a retiré le compte, ou que l'utilisateur final a révoqué le partage de ses données. Aucun des quatre attributs acceptés par le PATCH n'y change quoi que ce soit : la reprise passe par une nouvelle session de consentement, décrite dans Connexions bancaires. Un compte manuel n'ayant pas de fournisseur, cette condition ne le concerne jamais.
unposted_operations_count est rapporté à côté de la disponibilité, pas dedans. Un compte parfaitement prêt peut très bien avoir un arriéré : ready à true ne dit rien de ce qui reste en attente.
Tant que missing n'est pas vide, aucune opération de ce compte n'est comptabilisée. Ces conditions sont la garde de la comptabilisation, pas un simple indicateur : aucun compte comptable n'est deviné à la place de ceux que vous n'avez pas renseignés, donc les opérations réglées restent non comptabilisées et continuent d'être comptées dans unposted_operations_count. L'arriéré est repris tel quel à la synchronisation suivante, une fois la dernière condition levée : vous n'avez rien à rejouer.
Il y a une seconde garde, indépendante de celle-ci. Une opération lue dans un relevé importé n'est comptabilisée qu'après validation humaine. Une opération est comptabilisable quand aucun relevé ne la porte - une ligne remontée par le fournisseur, ou saisie à la main - ou quand le relevé dont elle est issue a été validé par un opérateur. Tant que cette validation n'a pas eu lieu, les lignes lues dans le fichier restent comptées dans unposted_operations_count, y compris sur un compte dont ready vaut true. La validation se fait dans Scribee : l'API ne l'expose pas, et il n'existe aucune ressource relevé. Un compte alimenté par une connexion bancaire n'est pas concerné - ses opérations ne viennent d'aucun relevé et sont comptabilisées dès qu'il est prêt.
Il y a une troisième garde, et elle vise justement ces comptes. Une opération remontée par le fournisseur qui décrit le même mouvement qu'une ligne de relevé importé - même date, même montant, même devise - est retenue, et n'est pas comptée dans unposted_operations_count. Comptabiliser les deux enregistrerait deux fois le même montant. Elle n'est pas perdue : dès qu'un opérateur a corrigé ou supprimé la ligne de relevé en double, la retenue est levée à la synchronisation bancaire suivante et l'opération est de nouveau comptée.
Rendre un compte exploitable
Il faut quatre choses à un compte pour que ready passe à true : active à true, un ledger_id, un accounting_account_code et un suspense_account_code. Ce sont exactement les quatre attributs que le PATCH accepte, et rien d'autre. Tous sont facultatifs, donc un seul appel suffit à les poser tous.
Un token portant le scope write est requis. L'en-tête Idempotency-Key est accepté mais facultatif : l'écriture converge sur les valeurs que vous envoyez, un rejeu ne change donc rien. Si vous en envoyez un tout de même, la réponse mémorisée vous est rendue au lieu d'une seconde écriture.
curl -X PATCH https://app.scribee.tech/api/v1/bank_accounts/7001 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"active": true,
"ledger_id": 88,
"accounting_account_code": "512999",
"suspense_account_code": "471000"
}'
La réponse 200 porte le compte dans son nouvel état, readiness compris : un aller-retour suffit pour savoir s'il est désormais exploitable.
Tout autre champ présent dans le corps est ignoré, pas refusé - renvoyer un compte lu tel quel est donc sans danger.
Quatre règles à connaître sur les valeurs :
accounting_account_codedoit être un compte 512 du plan comptable général :512suivi de zéro à dix-sept chiffres,512000ou512999par exemple. Le préfixe est exigé en entier, et pas seulement la classe 5 -531000(caisse) est refusé, comme le sont un401fournisseur ou un706de produit. Ce champ n'est pas un libellé : il devient la jambe banque de chaque écriture projetée depuis ce compte, donc un code de caisse y ferait comptabiliser des mouvements bancaires en caisse, sans bruit. Sur un compte dont l'originvautmanual, ce code est en outre obligatoire : aucun fournisseur ne peut le lui donner, donc le compte doit en porter un.suspense_account_codeest la contrepartie : c'est ce code, et aucun autre, qui porte la jambe d'attente des écritures projetées depuis ce compte. Aucune valeur par défaut ne s'y substitue, et tant qu'il est nul le compte ne projette rien. Aucun format ne lui est imposé - contrairement à l'accounting_account_code, il n'est pas restreint à une classe du plan comptable - mais il est plafonné à 255 caractères : une valeur plus longue est refusée en422validation_failedsousdetails.suspense_account_code, rien n'est écrit, et elle n'est jamais tronquée pour tenir.ledger_iddoit désigner un journal de la même entreprise que le compte. Un journal d'une autre entreprise et un identifiant qui n'existe nulle part reçoivent la même réponse, sousdetails.ledger: la réponse ne vous dit jamais si un identifiant hors de votre portée existe. Devant un422sur ce champ, ne cherchez donc pas un défaut de forme - l'identifiant est hors de l'entreprise du compte, ou il n'existe pas.activen'accepte quetrueetfalse.
Un corps ne portant aucun de ces quatre attributs est refusé, en 422 validation_failed sous details.base, et non traité comme une écriture sans effet. Renseigner un champ avec la valeur qu'il porte déjà est en revanche une requête valide : elle désigne une cible et y converge, donc elle répond 200.
Envoyer une chaîne vide sur suspense_account_code, ou sur l'accounting_account_code d'un compte synchronized, efface la valeur : le champ redevient nul et la condition correspondante réapparaît dans readiness.missing. La même chaîne vide sur l'accounting_account_code d'un compte manual est refusée en 422 validation_failed sous details.accounting_account_code, par l'exigence ci-dessus, et rien n'est écrit. Une chaîne d'espaces se lit exactement comme une chaîne vide : elle est ramenée à la valeur nulle avant d'être jugée.
Une écriture qui change quelque chose émet un événement bank_account.updated vers les endpoints webhook du workspace abonnés à ce type (Webhooks) - ce PATCH comme l'activation faite depuis l'interface Scribee. Une écriture qui ne change rien n'émet rien. La synchronisation d'une connexion émet le même événement de son côté, à la découverte d'un compte comme aux transitions que le fournisseur impose - vous n'avez donc pas à sonder la liste pour voir apparaître un compte.
Lister les comptes d'une entreprise
curl "https://app.scribee.tech/api/v1/workspaces/YOUR_WORKSPACE_ID/companies/YOUR_COMPANY_ID/bank_accounts?ready=false&active=true" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
La liste est rendue du compte le plus récent au plus ancien et se pagine avec page et per_page. Cinq filtres, tous facultatifs et cumulables :
| Filtre | Valeurs | Ce qu'il retient |
|---|---|---|
origin | manual, synchronized | Les comptes qu'un fournisseur alimente, ou ceux qu'aucun n'alimente |
active | true, false | Les comptes activés pour la comptabilisation, ou tous les autres |
archived | true, false | Les comptes archivés, ou les comptes vivants |
ready | true, false | Les comptes exploitables, ou ceux qui ne le sont pas |
bank_connection_id | un entier | Les comptes arrivés sous une connexion donnée |
Toute valeur hors de ces listes ne remonte simplement rien - ce n'est pas une erreur.
Sans archived, les comptes archivés et les comptes vivants sont rendus : l'archivage n'est pas un filtre appliqué à votre place. Un compte fraîchement découvert, pour lequel personne n'a encore répondu, tombe du côté false de active, exactement comme un compte désactivé.
ready applique la même dérivation que celle publiée par readiness, évaluée en base : les deux ne peuvent pas diverger.
include=bank_connection ajoute à chaque compte la connexion dont il dépend, avec toute sa charge utile - ou null quand le compte n'en a aucune. Sans ce paramètre, la clé est absente.
Les erreurs
| Statut | code | Quand |
|---|---|---|
422 | validation_failed | L'accounting_account_code n'est pas un compte 512, ou il est effacé sur un compte dont l'origin vaut manual ; le suspense_account_code dépasse 255 caractères ; le ledger_id désigne un journal d'une autre entreprise, ou un journal qui n'existe pas ; le corps ne porte aucun des quatre attributs de configuration |
422 | invalid_argument | active vaut autre chose que true ou false |
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 |
403 | - | Votre client OAuth n'a pas d'habilitation sur ce workspace |
404 | - | L'entreprise ou le compte n'est pas atteignable par vos habilitations |
Sur un 422, details nomme le champ en cause - accounting_account_code, suspense_account_code, ledger, active, ou base quand c'est le corps entier qui n'a rien demandé - et rien n'a été écrit.
Un compte appartenant à un workspace que vos habilitations ne couvrent pas répond 404, exactement comme un compte qui n'existe pas : la réponse ne permet pas d'en deviner l'existence.