Aller au contenu

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 par POST /user/login) sur toutes les routes, sauf les routes publiques : POST /user/login, GET /bank/sync (JWT ou sync_token), POST /mcp et GET /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 403 aux 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)
{ "error": "Unauthorized - Invalid or missing JWT token", "code": 401, "uid": "6ac106d3912d9" }

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>"}}'
{ "exp": 1762000000, "sub": 1, "iat": 1759400000, "token": "eyJ0eXAiOiJKV1Qi…" }

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).

curl https://exemple.fr/api/user/me -H "Authorization: Bearer $TOKEN"

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) :

[{ "id": 1, "bankId": "12345678@bnp", "account_number": "12345678", "bank_name": "bnp" }]

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_token de la configuration, dans l'en-tête X-Sync-Token ou 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).

curl -N -H "X-Sync-Token: $SYNC_TOKEN" https://exemple.fr/api/bank/sync
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)
curl "https://exemple.fr/api/transaction/search?query=amazon" -H "Authorization: Bearer $TOKEN"

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
curl "https://exemple.fr/api/budget/trends?months=6&offset=12" -H "Authorization: Bearer $TOKEN"
[{ "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.