Référence de l'API¶
- Base :
https://<serveur>/api - Format : JSON en entrée (corps) et en sortie. Les paramètres peuvent aussi être passés en query string ; corps et query string sont fusionnés.
- Authentification : en-tête
Authorization: Bearer <token>(JWT obtenu parPOST /user/login) sur toutes les routes, sauf les routes publiques :POST /user/login,GET /bank/sync(JWT ousync_token),POST /mcpetGET /mcp(jeton d'accès ou JWT),GET /health, et les routes OAuth des agents IA (métadonnées/.well-known/oauth-*,POST /oauth/register,GET /oauth/authorize,POST /oauth/token). Un test unitaire vérifie cette liste. - Paramètres nommés : chaque clé de la requête correspond à un paramètre de la méthode PHP. Une clé inconnue renvoie une erreur 400.
- Profils : un utilisateur consulte et pointe les transactions, les budgets, les
insights et les tendances. Un administrateur gère en plus les règles, les
catégories, les comptes, les insights, le serveur MCP et les utilisateurs : les routes
marquées « administrateur » renvoient
403aux autres.
Erreurs¶
| Code | Cas |
|---|---|
| 400 | Paramètre manquant, inconnu ou invalide ; JSON invalide |
| 401 | JWT absent, invalide ou expiré ; identifiants invalides ; jeton d'accès MCP invalide |
| 403 | Action réservée à un administrateur ; serveur MCP désactivé ; origine refusée ; code d'installation erroné |
| 404 | Route ou ressource introuvable |
| 405 | GET /mcp (le serveur MCP n'ouvre pas de flux) |
| 409 | Conflit (doublon, suppression d'un utilisateur propriétaire de transactions, dernier administrateur…) |
| 423 | Compte bloqué après trop d'échecs de connexion |
| 500 | Erreur serveur (base de données, woob, APNs, migration…) |
| 503 | Carbure n'est pas encore installé (voir Assistant d'installation) |
uid identifie la requête dans data/logs/carbure_AAAAMMJJ.log.
Utilisateurs¶
POST /user/login — publique¶
| Paramètre | Type | Requis | Description |
|---|---|---|---|
username |
string | oui | Identifiant |
password |
string | oui | Mot de passe |
device |
objet {name, token} |
non | Appareil iOS à enregistrer pour les notifications |
curl -X POST https://exemple.fr/api/user/login \
-H 'Content-Type: application/json' \
-d '{"username":"alice","password":"…","device":{"name":"iPhone","token":"<token APNs>"}}'
Le jeton est valable 30 jours. La date de connexion est enregistrée (last_login).
401 si les identifiants sont faux ; 423 si le compte est bloqué : après 5 échecs
d'affilée, le compte est bloqué 24 heures, même avec le bon mot de passe. Une connexion
réussie remet le compteur à zéro.
POST /user/unlock — administrateur¶
id : débloque un compte bloqué après trop d'échecs de connexion (404 si l'utilisateur
n'existe pas).
GET /user/me¶
Profil de l'utilisateur connecté : id, username, firstname, lastname, email,
is_admin, language, alert_threshold (null si les alertes sont désactivées).
GET /user¶
Liste des utilisateurs pour un administrateur ; l'utilisateur lui-même sinon. Mêmes champs
que GET /user/me, plus last_login (dernière connexion réussie, null si jamais) et
locked_until (fin du blocage, null si le compte n'est pas bloqué).
POST /user — administrateur¶
username, password, email (requis), firstname, lastname, is_admin (facultatifs ;
profil utilisateur par défaut). 400 si l'identifiant ou l'e-mail existe déjà.
PUT /user¶
| Paramètre | Requis | Description |
|---|---|---|
id |
oui | Utilisateur à modifier (soi-même, ou n'importe qui pour un administrateur) |
email, firstname, lastname |
non | Nouvelles valeurs |
password |
non | Nouveau mot de passe (ignoré si vide) |
language |
non | fr ou en |
alertThreshold |
non | Seuil d'alerte en euros pour les nouvelles dépenses ; "" ou 0 désactive les alertes |
is_admin |
non | Profil administrateur (true) ou utilisateur (false) ; administrateurs seulement, 409 pour retirer le rôle au dernier administrateur |
curl -X PUT "https://exemple.fr/api/user?id=1" -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' -d '{"language":"en"}'
Renvoie l'utilisateur mis à jour.
DELETE /user — administrateur¶
id : impossible de se supprimer soi-même (400) ou de supprimer un utilisateur
propriétaire de transactions (409).
Comptes et synchronisation¶
Les comptes bancaires appartiennent au foyer : tous les utilisateurs voient tous les
comptes et leurs transactions. user_id d'un compte indique seulement l'utilisateur qui
l'a ajouté.
GET /bank¶
Comptes bancaires du foyer, visibles par tous les utilisateurs (un compte n'apparaît qu'une fois) :
GET /bank/accounts — administrateur¶
Tous les comptes du foyer avec l'utilisateur qui les a ajoutés et le résultat de leur
dernière synchronisation : id, bankId, account_number, bank_name, user_id et
username (qui l'a ajouté), last_sync_at, last_sync_status (OK, ERROR ou null),
last_sync_message (nombre de nouvelles transactions ou erreur).
POST /bank — administrateur¶
Suit un compte pour le foyer. Paramètres : account_number (identifiant du compte dans
woob) et bank_name (nom du backend woob) ; user_id du compte est l'administrateur qui
l'ajoute. 409 si le foyer suit déjà ce compte, 400 si un identifiant est invalide.
PUT /bank — administrateur¶
Modifie un compte suivi : id, account_number, bank_name (404 si inconnu, 409 si
le foyer suit déjà le compte visé).
DELETE /bank?id= — administrateur¶
Ne suit plus le compte ; ses transactions déjà importées sont conservées.
GET /bank/backends, GET /bank/modules — administrateur¶
Banques configurées dans woob (name, module) et banques supportées par woob (module,
description). Une banque doit être configurée dans woob (identifiants bancaires) avant
que ses comptes puissent être synchronisés : depuis le portail (routes ci-dessous).
GET /bank/module?module= — administrateur¶
Paramètres demandés par un module woob : module, description, fields (key, label,
description, default, required, masked, choices). Rien de secret n'est renvoyé.
POST /bank/backend — administrateur¶
Configure une banque dans woob : module, backend (nom, [a-z0-9_-]), settings
(objet des paramètres du module ; seules ses clés sont transmises, valeurs sur une ligne
sans espace, choix vérifiés). Les identifiants sont confiés à woob, ni stockés ni
journalisés par Carbure (settings est masqué dans les logs). Renvoie backend et les
accounts trouvés (même format que /bank/discover). 409 si une banque de ce nom existe
déjà dans woob, 400 si un paramètre manque ou si woob refuse.
GET /bank/discover — administrateur¶
Interroge woob (woob bank list) et renvoie les comptes des banques configurées :
bankId, account_number, bank_name, label, balance, currency, followed. Peut
prendre plusieurs secondes (connexion aux banques).
GET /bank/sync — flux SSE¶
Lance la synchronisation de tous les comptes, ou d'un seul avec
?account=<account_number>@<bank_name> (404 s'il n'est pas suivi). La date et le résultat
sont enregistrés pour chaque compte. Accès autorisé avec :
- un JWT valide (n'importe quel utilisateur), ou
- le
sync_tokende la configuration, dans l'en-têteX-Sync-Tokenou le paramètre?token=;
401 sinon. La réponse est un flux text/event-stream : messages data: … (progression,
résultat de chaque étape) et commentaires : heartbeat toutes les 15 s pendant
l'exécution de woob. Une seule synchronisation peut tourner à la fois (sinon :
data: Synchronization already in progress).
data: Syncing 12345678@bnp (coming)...
data: Done 12345678@bnp (coming): 3 received, 1 new
data: Syncing 12345678@bnp (history)...
data: Done 12345678@bnp (history): 100 received, 4 new
data: Notifying users of 12345678@bnp...
data: Updating missing categories...
data: Synchronization complete
Après chaque compte, tous les utilisateurs du foyer reçoivent une notification (succès
ou échec, nombre de transactions à vérifier), et chaque nouvelle dépense (transaction
absente jusque-là) dont le montant atteint le seuil alert_threshold d'un utilisateur lui
envoie une notification « Dépense importante à vérifier » (dans sa langue).
Transactions¶
GET /transaction¶
| Paramètre | Requis | Description |
|---|---|---|
month, year |
non | Mois des transactions (mois courant par défaut) |
category |
non | Limite aux transactions de cette catégorie et de ses sous-catégories |
Les transactions sont triées par date réelle décroissante. Champs : id, uuid,
imported, date, rdate, type, label, category, amount, card, pointed,
user.
curl "https://exemple.fr/api/transaction?month=9&year=2026&category=1" -H "Authorization: Bearer $TOKEN"
Types : 1 virement, 2 prélèvement, 3 chèque, 4 remise de chèque, 5 remboursement, 6 retrait DAB, 7 facture carte, 8 dépense, 9 commissions, 12 carte en cours.
GET /transaction/search¶
| Paramètre | Requis | Description |
|---|---|---|
query |
oui | Texte recherché dans le libellé (2 caractères minimum, insensible à la casse) |
limit |
non | Nombre maximal de résultats (1 à 500, défaut 100) |
PUT /transaction/category¶
id, category : affecte la catégorie et pointe la transaction (autoPointed=false
pour ne pas la pointer).
PUT /transaction/pointed¶
id, pointed (booléen, défaut true) : marque la transaction comme vérifiée ou non.
curl -X PUT https://exemple.fr/api/transaction/pointed -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' -d '{"id":42,"pointed":false}'
Budgets et tendances¶
GET /budget¶
month, year, category (catégorie parente, 0 = racine). Pour chaque catégorie :
id, name, type, icon, color, budget, consummed (somme absolue des
transactions de la catégorie et de ses sous-catégories), progress (%), children
(nombre de sous-catégories), children_budget (somme de leurs budgets) et budget_mode :
own (montant défini pour la catégorie) ou children (catégorie avec sous-catégories et
sans montant défini : budget est alors la somme des budgets des sous-catégories).
GET /budget/flow¶
month, year (mois courant par défaut) : flux d'argent du mois, pour le diagramme
« Flux du mois ». Les montants sont regroupés par catégorie de premier niveau ; les
catégories HORS-BUDGET (virements internes) sont exclues, sauf la catégorie d'épargne
(savings_category) ; les transactions non catégorisées sont conservées.
| Champ | Description |
|---|---|
month |
AAAA-MM |
income, expenses |
Revenus et dépenses par catégorie de premier niveau : id, name, color, icon, amount (positif), du plus grand au plus petit |
savings |
Montant net versé sur la catégorie d'épargne et ses sous-catégories |
totalIncome, totalExpenses |
Totaux des revenus et des dépenses |
balance |
Reste : revenus − dépenses − épargne (si elle est positive) ; négatif en cas de déficit |
POST /budget¶
category, amount (requis), month, year (mois courant par défaut) : crée ou met à
jour le budget de la catégorie pour le mois. Pour une catégorie avec sous-catégories, le
montant remplace la somme de leurs budgets ; 0 revient à cette somme.
GET /budget/trends¶
| Paramètre | Requis | Description |
|---|---|---|
months |
non | Nombre de mois (1 à 60, défaut 24) |
offset |
non | Nombre de mois entre le mois courant et le dernier mois de la période (défaut 0). Sert aux comparaisons : offset=months pour la période précédente, offset=12 pour la même période l'an dernier |
Renvoie un élément par mois (du plus ancien au plus récent, mois vides inclus) :
| Champ | Description |
|---|---|
month |
AAAA-MM |
debit |
Dépenses du mois, catégories hors budget exclues (montant positif) |
credit |
Revenus du mois, catégories hors budget exclues |
offBudget |
Solde des catégories HORS-BUDGET (ex. virements vers l'épargne) |
planned |
Somme des budgets des catégories de premier niveau (montant défini, ou somme des sous-catégories) |
savings |
Montant net versé sur la catégorie d'épargne (savings_category, Epargne par défaut) et ses sous-catégories, positif quand on épargne |
[{ "month": "2025-05", "debit": 2130.37, "credit": 5114, "offBudget": -500, "planned": 2330, "savings": 2983.63 }]
Insights¶
GET /budget/insights¶
month, year : valeur de chaque insight pour le mois (id, name, color, icon,
amount). Une requête en échec donne 0 sans bloquer les autres.
GET /budget/insights/history¶
year : pour chaque insight, la liste history des montants mois par mois.
GET /insight, POST /insight, PUT /insight, DELETE /insight?id= — administrateur¶
Gestion des insights, avec leur requête SQL :
| Paramètre | Requis | Description |
|---|---|---|
id |
PUT, DELETE |
Insight à modifier ou supprimer (404 si inconnu) |
name |
oui | 1 à 20 caractères |
color |
oui | red, orange, amber, lime, green, teal, cyan, blue, indigo, purple, pink, brown ou gray |
sql |
oui | Requête SELECT … AS amount (500 caractères au plus) ; {month} et {year} sont remplacés par le mois demandé |
icon |
non | Icône Material ([a-z0-9_]) ; choisie d'après le nom si vide |
La requête doit être un unique SELECT renvoyant une colonne amount, sans ; ni
commentaire, sans mot-clé d'écriture, d'administration, de fichier ou de temporisation, et
ne peut pas lire les tables users, api_tokens, devices ni les schémas système. Elle
est testée sur le mois courant avant l'enregistrement (400 avec la cause sinon) et
exécutée dans une transaction en lecture seule. POST et PUT renvoient l'insight avec
le montant du mois courant.
POST /insight/check — administrateur¶
sql, month, year (mois courant par défaut) : vérifie une requête sans l'enregistrer
(mêmes contrôles), pendant la saisie. Renvoie {"valid": true, "amount": …} ou
{"valid": false, "error": "…"} (toujours 200).
Catégories et règles¶
GET /category¶
Toutes les catégories : id, name, parent_category, type (DEBIT, CREDIT,
HORS-BUDGET), icon, color.
POST /category, PUT /category, DELETE /category?id= — administrateur¶
Crée ou modifie une catégorie (name, type : DEBIT, CREDIT ou HORS-BUDGET,
parent_category, icon, color ; id pour la modification) ou la supprime. 409 si le
nom existe déjà ou si la catégorie à supprimer a des sous-catégories, 400 pour la catégorie
par défaut (0) ou un parent qui n'est pas une catégorie principale. À la suppression, les
transactions passent en catégorie 0 et les budgets et règles de la catégorie sont supprimés.
GET /category/keyword¶
Règles de catégorisation automatique : id, keyword, category, category_name.
POST /category/keyword — administrateur¶
keyword (1 à 60 caractères, enregistré en majuscules), category : ajoute une règle.
Erreurs : 400 mot-clé vide, 404 catégorie inconnue, 409 mot-clé déjà existant.
curl -X POST https://exemple.fr/api/category/keyword -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' -d '{"keyword":"decathlon","category":9}'
PUT /category/keyword — administrateur¶
id, keyword, category, notify : modifie une règle (409 si le mot-clé existe déjà).
POST /category/keyword accepte aussi notify : chaque nouvelle transaction synchronisée
qui contient le mot-clé envoie alors une notification à tous les utilisateurs du foyer.
GET /category/keyword/count?id= — administrateur¶
Nombre de transactions dont le libellé contient le mot-clé (matching), dont celles déjà
dans la catégorie de la règle (categorized).
DELETE /category/keyword — administrateur¶
id : supprime une règle (404 si inconnue).
POST /category/keyword/apply — administrateur¶
Applique toutes les règles à toutes les transactions de la base, déjà catégorisées
comprises (la première règle trouvée dans le libellé l'emporte). Renvoie
{ "updated": <nombre de transactions dont la catégorie a changé> }. Créer ou modifier une
règle (POST/PUT /category/keyword) l'applique aussi immédiatement à tout l'historique et
renvoie applied, le nombre de transactions recatégorisées.
Appareils¶
GET /device¶
Appareils de l'utilisateur connecté : id, user_id, name, token, lastLogin.
DELETE /device¶
id : supprime un appareil de l'utilisateur connecté (il ne recevra plus de
notifications). 404 si l'appareil n'existe pas ou appartient à un autre utilisateur.
POST /device/push¶
Envoie une notification de test à tous les appareils de l'utilisateur connecté.
Agents IA (MCP)¶
GET /token, POST /token, DELETE /token?id=¶
Jetons d'accès de l'utilisateur connecté, pour le serveur MCP. POST (name : 1 à 50
caractères ; days : 30, 90, 365, ou 0/vide pour sans expiration) renvoie le jeton
(token, préfixe cbt_) une seule fois, avec id, name, token_hint et
expires_at ; 409 au-delà de 20 jetons. La liste ne donne que id, name,
token_hint, created_at, last_used_at, expires_at (null = sans expiration) et
expired. DELETE révoque un jeton (404 s'il n'existe pas ou appartient à un autre
utilisateur).
GET /mcp/settings, PUT /mcp/settings¶
{"enabled": true|false} : état du serveur MCP (désactivé par défaut). PUT (enabled)
l'active ou le désactive — administrateur.
POST /mcp — publique (jeton d'accès)¶
Serveur MCP (JSON-RPC 2.0, transport Streamable HTTP) en lecture seule, authentifié par
Authorization: Bearer <jeton> (ou ?token=). GET /mcp répond 405. Voir
Agents IA (MCP).
OAuth 2.1 des agents IA — publiques (sauf indication)¶
Pour les agents qui ne prennent que l'URL du serveur MCP (Claude web, Desktop, mobile).
Réponses et erreurs au format OAuth ({"error", "error_description"}), pas au format de
l'API.
| Route | Rôle |
|---|---|
GET /.well-known/oauth-protected-resource (et …/api/mcp) |
Métadonnées du serveur MCP (RFC 9728) : resource, authorization_servers |
GET /.well-known/oauth-authorization-server |
Métadonnées OAuth (RFC 8414) : points d'accès, S256, méthodes d'authentification du client |
POST /api/oauth/register |
Enregistrement dynamique (RFC 7591) : redirect_uris (HTTPS, ou HTTP sur localhost), client_name, token_endpoint_auth_method (none, client_secret_post, client_secret_basic) ; 201 avec client_id (et client_secret) |
GET /api/oauth/authorize |
response_type=code, client_id, redirect_uri, state, code_challenge (S256) : 302 vers la page de consentement du portail, ou vers l'agent avec error= ; 400 si le client ou l'URI de retour est inconnu |
GET /api/oauth/client?request= — JWT |
Nom de l'agent et hôte de retour, pour la page de consentement |
POST /api/oauth/approve — JWT |
request, approved : {"redirect"} vers l'agent, avec le code (5 minutes, usage unique) ou error=access_denied |
POST /api/oauth/token |
Formulaire ou JSON : grant_type=authorization_code (code, redirect_uri, client_id, code_verifier) ou refresh_token ; {"access_token", "token_type": "Bearer", "expires_in", "refresh_token"} |
Toutes répondent 403 (access_denied) quand le serveur MCP est désactivé.
Système¶
GET /health — publique¶
État de l'instance pour le HEALTHCHECK Docker et les outils de supervision, sans
authentification ni donnée : {"status":"ok","version":"1.2.0","database":"ok","schema":"<version du schéma>"},
500 si la base ne répond pas, {"status":"setup","version":"…"} tant que Carbure n'est
pas installé. version est la version de Carbure (fichier VERSION, dev hors release).
GET /system/logs — administrateur¶
Fichiers de log de l'instance, les plus récents d'abord : [{name, size, modified}].
GET /system/logs/entries — administrateur¶
Entrées d'un fichier, les plus récentes d'abord. Paramètres : file (nom renvoyé par
/system/logs), level facultatif (niveau minimal : DEBUG, INFO, WARN, ERROR),
search facultatif (texte ou identifiant de requête), limit (1 à 1000, 200 par défaut).
Réponse : {file, entries: [{time, level, uid, caller, message}], truncated} ; seuls les
2 derniers Mo sont lus (truncated) et les jetons sont masqués. 400 pour un nom de fichier
invalide, 404 s'il n'existe pas.
GET /system/schema — administrateur¶
Version du schéma de la base et migrations à appliquer :
{"version": "2026-10-13_base", "pending": []}.
POST /system/migrate — administrateur¶
Applique les migrations en attente (bouton Mettre à jour du portail) :
{"version": "…", "applied": ["…"]} ; 500 avec la cause si une migration échoue (les
suivantes ne sont pas exécutées).
POST /system/jwt-secret — administrateur¶
Remplace le jwtsecret de la configuration par une valeur aléatoire : toutes les sessions
(portail et application iOS) prennent fin. GET /system/schema indique weakJwtSecret
quand le secret est celui de l'exemple (ou trop court) : le portail propose alors de le
renouveler.
Assistant d'installation¶
Tant que data/conf/prod.ini n'existe pas, l'API ne répond qu'à ces routes (toutes les autres
renvoient 503 avec "setup": true) :
| Route | Rôle |
|---|---|
GET /setup |
État de l'assistant : codeRequired, dbPasswordFromEnvironment et valeurs par défaut (db_host, db_port, db_name, db_user, admin_user, language) issues de l'environnement Docker |
POST /setup/database |
Teste la connexion (db_host, db_port, db_name, db_user, db_password) et décrit l'installation : state (none = base vide, current = à jour, outdated = migrations à appliquer), version, pending, hasAdmin |
POST /setup/install |
Crée ou migre le schéma, crée le premier administrateur s'il n'y en a pas (admin_user, admin_password de 8 caractères minimum, admin_email, language) et écrit la configuration ; 409 sans backup_confirmed quand des migrations sont à appliquer |
GET /health |
{"status":"setup"} |
Depuis Internet, ou si data/conf/setup.code existe, les requêtes POST exigent le paramètre
code (code d'installation), sinon 403. Voir Sécurité.
Spécification OpenAPI¶
swagger/swagger.php génère une spécification OpenAPI 3 par réflexion sur les
attributs #[ApiRoute] ; swagger/index.html l'affiche avec Swagger UI.