Démarrage rapide
Avant de commencer, il te faut un compte Era avec au moins une institution branchée — sans connexion, ces points de terminaison n'ont rien à retourner.
- 1
Connecte-toi à Era et branche une institution, si ce n'est pas déjà fait.
- 2
Ouvre tes clés d'API dans le tableau de bord et crée une clé. Toutes les portées sont cochées au départ, alors décoche celles dont tu n'as pas besoin : pour ces points de terminaison, il reste banking:read. Tu choisis aussi une expiration ; il n'y a pas d'option « jamais ».
- 3
Copie la clé. Elle s'affiche une seule fois, et on ne peut pas la réafficher. Copie-la et conserve-la en lieu sûr, par exemple dans un gestionnaire de secrets. Si tu perds une clé, tu ne peux plus la consulter. Crée-en une nouvelle à la place.
- 4
Envoie-la dans un en-tête avec ta requête.
cURL
curl "https://forge.era.app/api/banking/transactions?page=1&pageSize=20" \
-H "X-API-Key: fmk_your_key_here"
Réponse · 200
{
"transactions": [ … ],
"pagination": {
"currentPage": 1,
"pageSize": 20,
"totalItems": 412,
"totalPages": 21
},
"historyWindowApplied": true,
"historyWindowFloorDate": "2026-06-28",
"historyWindowHiddenCount": 137,
"historyWindowEarliestDate": "2024-03-02",
"historyWindowDegraded": false
}
API disponibles
L'API Era comprend les API suivantes :
- GET/banking/accounts
Chaque compte que tu peux voir, sur tous tes établissements connectés.
- GET/banking/accounts/{accountId}/balance
Le solde d'un compte, avec les champs de crédit renseignés s'il s'agit d'un passif.
- GET/banking/accounts/summary
Les totaux sur tous les comptes que tu peux voir.
- GET/banking/transactions
Tes transactions, une page à la fois.
- PUT/banking/transactions/{id}
Modifier une transaction
- PUT/banking/transactions/bulk
Modifier jusqu'à 100 à la fois
- GET/banking/categories
Toute la taxonomie des catégories, avec les sous-catégories imbriquées.
- POST/banking/categories
Ajouter une catégorie
- GET/banking/tags
Toutes les étiquettes de ton compte, en une seule réponse.
- POST/banking/tags
Créer une étiquette
Authentification
Envoie ta clé de l'une de ces deux façons :
| Méthode | Identifiant |
|---|---|
| En-tête | X-API-Key: fmk_your_key_here |
| Jeton bearer | Authorization: Bearer fmk_your_key_here |
Chaque requête est chiffrée avec TLS.
Les clés expirent, et tu choisis dans combien de temps à la création. Le maximum est de 90 jours sur le forfait gratuit et de 365 sur un forfait payant — il n'existe pas d'option « n'expire jamais », alors ce que tu bâtis là-dessus a besoin d'un plan pour faire tourner la clé avant qu'elle tombe.
En-têtes de réponse
Chaque réponse inclut :
| En-tête | Description |
|---|---|
| fly-request-id | Un identifiant unique pour la requête. Indique-le quand tu contactes le support à propos d'une requête précise — voir ID de requête |
Erreurs
L'API renvoie ces codes de statut d'erreur :
- 400
Entrée invalide : un paramètre incorrect, une mise à jour groupée vide ou de plus de 100 éléments, ou une écriture qui définit et efface le même champ dans le même appel.
- 401
Pas de clé, ou une clé qui ne s'analyse pas. Envoie-la dans l'en-tête X-API-Key ou comme jeton bearer.
- 402
Un quota du forfait fait obstacle — aujourd'hui, cela ne concerne que la création de catégories.
- 403
La clé ne porte pas la portée dont cet appel a besoin — ou, sur l'une des deux écritures de transactions, l'id appartient à quelqu'un d'autre ou n'existe pas. L'API ne distingue pas ces deux cas.
- 409
Autre chose a modifié la ligne pendant que tu écrivais. Relis-la et renvoie ton écriture.
Formes d'erreur
Chaque erreur revient sous la même forme : statusCode, message et un objet errors qui nomme ce qui n'allait pas. Une clé absente ou invalide peut revenir sans corps du tout.
Exemple
{
"statusCode": 403,
"message": "One or more errors occurred!",
"errors": {
"generalErrors": ["Transaction does not belong to the authenticated user"]
}
}
ID de requête
Chaque réponse porte un en-tête fly-request-id. Indique-le quand tu contactes le support à propos d'une requête précise.
cURL
# Print the response headers, including fly-request-id; discard the body
curl -sS -D - -o /dev/null "https://forge.era.app/api/banking/transactions?page=1&pageSize=20" \
-H "X-API-Key: fmk_your_key_here"
cURL (écriture)
# A write call: same header, plus a JSON body
curl -sS -D - -X PUT "https://forge.era.app/api/banking/transactions/utgr_your_transaction_id" \
-H "X-API-Key: fmk_your_key_here" \
-H "Content-Type: application/json" \
-d '{"categoryKey": "fcat_dining", "merchantName": "Corner Cafe"}'
Erreurs courantes
Définir un champ et l'effacer dans la même écriture — 400.
Plus de 100 id dans une mise à jour groupée — 400, et rien n'est modifié. Moins d'un id donne le même résultat.
Une transaction qui n'est pas la tienne, ou qui n'existe pas — 403, jamais 404. La réponse ne te dit donc jamais si un id existe, seulement qu'il n'est pas à toi.
Autre chose a modifié la ligne en premier — 409.
Limites
Deux des dix endpoints documentés plafonnent ce que tu peux demander en un seul appel. Les huit autres non.
Ça ne veut pas dire illimité : les clés expirent toujours, les écritures ont toujours des limites de longueur de champ, et ton forfait peut toujours masquer l'historique ancien. Rien de tout ça n'est une limite de débit — voir plus bas.
Comptes, solde, résumé, catégories, tags et l'écriture d'une seule transaction n'ont aucun plafond de volume par appel. Tu reçois l'ensemble au complet, ou la seule ligne que tu as nommée.
pageSize est plafonné à 100, jamais refusé. Demande-en plus et tu reçois 100 lignes avec un 200 — lis pagination.pageSize dans la réponse plutôt que de faire confiance à ce que tu as envoyé.
L'écriture groupée de transactions est plafonnée à 100 id, et contrairement à pageSize elle est refusée plutôt que plafonnée : envoie 101 et tu reçois un 400, rien ne change.
Les clés expirent selon un calendrier que tu choisis à la création — jusqu'à 90 jours sur le forfait gratuit, 365 sur un forfait payant. Il n'existe pas d'option sans expiration.
Ton forfait peut appliquer un plancher de fenêtre d'historique qui masque les transactions plus anciennes. La réponse des transactions porte les champs historyWindow qui indiquent si un plancher s'est appliqué et où il se situe.
Les forfaits indiquent aussi une allocation de requêtes API — 500 sur le forfait gratuit, plus sur chaque forfait payant.
Ce qui n'existe pas : aucune limitation par requête sur REST aujourd'hui, pas de 429, pas d'en-têtes de limite de débit — une clé qui a fuité n'est donc ralentie par rien de notre côté. Si tu as un doute sur une clé, révoque-la : cela la coupe immédiatement (voir Sécurité et gestion des clés plus haut). Et, cette API étant en bêta, ne présume pas que cela restera vrai.
Conventions
Les champs de réponse sont en camelCase. Les paramètres de requête, eux, ne sont pas sensibles à la casse, alors le camelCase marche là aussi — la spec publiée les écrit en PascalCase, c'est pour ça que tu verras les deux formes circuler.
Un champ qui nomme un jour de calendrier est en YYYY-MM-DD. Un champ qui nomme un instant précis est en ISO 8601 avec un décalage horaire.
Les tailles de page sont ramenées dans les bornes, pas refusées. Demande un pageSize de 500 et tu obtiens 100 lignes et un 200, pas une erreur — alors relis pagination.pageSize dans la réponse plutôt que de te fier à ce que tu as envoyé.
Une réponse peut porter des champs que cette page ne liste pas. Ignore ceux que tu ne reconnais pas plutôt que de planter dessus — c'est ce qui garde ton client fonctionnel à mesure que l'API s'étoffe.
REST est du HTTP ordinaire, donc aucun SDK n'est nécessaire pour l'appeler — n'importe quel langage avec un client HTTP fait l'affaire. Rien à installer.
Sécurité et gestion des clés
Approuver un agent crée une clé
Quand tu approuves un agent en OAuth, Era lui crée une clé d'API. Elle atterrit dans la même liste du tableau de bord que celles que tu crées, sous un nom qu'Era compose à partir du nom du client lui-même.
Comment elle s'appelle
Auto -- ClaudeElle porte exactement les portées que tu as approuvées sur cet écran, et rien d'autre. Révoque-la depuis le tableau de bord et l'agent cesse d'atteindre ton compte jusqu'à ce que tu l'approuves à nouveau.
Les simples appels REST ne sont pas journalisés
Créer et révoquer une clé apparaissent tous les deux dans ton journal d'activité, tout comme chaque appel d'outil qu'un agent fait en MCP. Les appels REST individuels, non — lectures comme écritures — il n'y a pas de journal requête par requête sur ce chemin aujourd'hui.
Si jamais tu doutes d'une clé, révoque-la. La révocation est immédiate, coupe la clé autant en REST qu'en MCP, et en refaire une te prend une minute.
| Fait | Ce que ça veut dire |
|---|---|
| Les portées sont larges | banking:read couvre bien plus que les six lectures de cette page — la même portée couvre aussi le reste des lectures de ton compte : soldes, positions, connexions, dépenses. Une seule portée, il n'y a pas plus étroit. Celles d'écriture sont aussi au menu, comme celles de lecture — alors traite n'importe quelle clé comme un mot de passe. Elle agit comme ton compte, pas juste une partie de celui-ci. banking:write peut modifier des catégories, des étiquettes et les métadonnées d'une transaction, gérer des comptes et des soldes manuels, et connecter ou déconnecter des institutions — aucune portée de cette page ne peut déplacer de l'argent entre tes comptes bancaires. |
| Les portées ne se mettent pas à jour | Les portées d'une clé sont fixées à sa création et ne changent jamais ensuite. Ça compte pour tout ce qu'une portée couvre et qui n'est pas encore activé : accorde social:write aujourd'hui et la clé l'aura encore le jour où les vues partagées arriveront. Accorde ce que tu utilises maintenant, pas ce que tu utiliseras peut-être. |
| Aucune approbation requise | Tu es déjà connecté à ton propre compte, alors créer une clé n'a besoin de l'accord de personne d'autre — il n'y a ni revue ni liste d'attente, et personne chez Era n'approuve la demande. C'est inscrit dans ton journal d'activité dès que tu la crées, alors une clé que tu ne reconnais pas est facile à repérer. |
| L'accès bancaire reste hors de portée | Une clé n'atteint pas ton accès bancaire, parce qu'Era ne l'a jamais. Tu le saisis dans le flux de connexion géré par le fournisseur de données, pas sur un écran d'Era — ce qu'Era conserve ensuite, c'est un jeton d'accès par connexion, chiffré au repos avec AES-256, que tu peux jeter en débranchant l'institution. |
| Les clés sont hachées, pas stockées | Ta clé, c'est 256 bits de données aléatoires, hachées en SHA-256 avant d'être stockées. On garde le haché, pas la clé. Si tu la perds, révoque-la et crées-en une autre. |
Ressources principales
Comptes
Portée requise
banking:readTous les comptes que tu peux voir, à travers chaque institution branchée, avec à côté le nombre de ceux qui sont laissés de côté. Chaque compte porte son accountGroupKey — la valeur que le point de terminaison de solde prend dans son chemin — et le connectionId auquel il appartient, alors c'est le premier appel à faire. Accepte connectionId pour se limiter à une seule connexion, et includeExcluded pour ramener les comptes que tu as masqués.
connectionIdoptionnel | Limite la liste aux comptes d'une seule connexion. |
|---|---|
includeExcludedoptionnel | Inclut aussi les comptes exclus par palier et les comptes masqués, avec leurs soldes brouillés. Par défaut, false. |
Réponse · 200
{
"accounts": [
{
"accountGroupKey": "uagr_7f3c9a21",
"connectionId": "ucon_4b19e02c",
"name": "Everyday Checking",
"currentBalance": 4820.16,
"supportsTransactions": true,
…
}
],
"excludedAccountCount": 1
}
Sur un compte que tu as masqué ou que ton forfait exclut, les champs de solde reviennent à null plutôt qu'à zéro — null veut dire retenu, pas vide. supportsTransactions vaut null dans le même esprit : ça veut dire qu'Era ne peut pas se prononcer, jamais que la réponse est non.
Solde d'un compte
Portée requise
banking:readLe solde d'un seul compte, avec les champs de crédit remplis quand le compte est un passif. Le chemin prend l'accountGroupKey de ce compte — la même valeur que /banking/accounts retourne pour lui. Une clé d'une autre forme est rejetée avant même que la recherche s'exécute.
Réponse · 200
{
"accountGroupKey": "uagr_7f3c9a21",
"currentBalance": 4820.16,
"availableBalance": 4712.03,
"creditLimit": null,
"currencyCode": "USD",
"availableCredit": null,
"asOf": "2026-08-11T09:32:00Z",
"visibility": null
}
Un compte masqué, ou un compte dont la connexion a été coupée, répond quand même 200 — avec les champs de solde à null. Seul un compte qui n'existe vraiment pas te donne un 404. Surveille le champ visibility ici : il vaut null quand le compte est visible, et une chaîne comme tier_excluded quand il ne l'est pas.
Sommaire des comptes
Portée requise
banking:readLes totaux sur les comptes que tu peux voir : totalAssets, totalLiabilities et netWorthHint, qui est le premier moins le second. N'accepte aucun paramètre.
Réponse · 200
{
"userId": "7d1c0b93a8e24f60",
"accounts": [ … ],
"totalVisibleCount": 6,
"totalHiddenCount": 2,
"totalAssets": 48210.75,
"totalLiabilities": 9327.40,
"netWorthHint": 38883.35,
"computedAt": "2026-08-11T09:32:00Z"
}
netWorthHint ne compte que les comptes présents dans cette réponse, alors totalHiddenCount te dit ce qui lui manque. Prends-le comme un chiffre de départ plutôt que comme une valeur nette faisant autorité.
Transactions
Portée requise
banking:readTes transactions, une page à la fois, enveloppées avec les compteurs de pagination à côté. Accepte page et pageSize (100 au maximum), plus des filtres optionnels par compte, plage de dates, règles appliquées et étiquettes assignées.
accountIdoptionnel | Limite aux transactions d'un compte, par son accountGroupKey. |
|---|---|
fromDateoptionnel | Seulement les transactions à cette date ou après. |
toDateoptionnel | Seulement les transactions à cette date ou avant. |
pageoptionnel | Numéro de page, à partir de 1. Par défaut, 1. |
pageSizeoptionnel | Lignes par page. Par défaut, 50, plafonné à 100. |
sortByoptionnel | Champ de tri : transactionDate, amount, description, category ou merchantName. |
sortDirectionoptionnel | asc ou desc. Par défaut, décroissant. |
categoryKeyoptionnel | Seulement les transactions d'une catégorie, par sa clé fcat_. |
searchoptionnel | Recherche plein texte sur le commerçant, la description, la catégorie, le nom du compte et le montant. |
ruleIdsoptionnel | Seulement les transactions qu'une règle d'automatisation a touchées, par la clé de la règle. |
tagKeysoptionnel | Seulement les transactions qui portent l'une de ces étiquettes. |
reviewStatusoptionnel | needs_review, reviewed ou flagged. |
includeChildrenoptionnel | Avec categoryKey défini, inclut aussi ses sous-catégories. Par défaut, false. |
Réponse · 200
{
"transactions": [ … ],
"pagination": {
"currentPage": 1,
"pageSize": 20,
"totalItems": 412,
"totalPages": 21
},
"historyWindowApplied": true,
"historyWindowFloorDate": "2026-06-28",
"historyWindowHiddenCount": 137,
"historyWindowEarliestDate": "2024-03-02",
"historyWindowDegraded": false
}
Ton forfait peut appliquer un plancher de fenêtre d'historique, qui cache les transactions plus anciennes que lui. C'est pour ça que la réponse porte les champs historyWindow : historyWindowApplied te dit qu'un plancher a bel et bien caché quelque chose, historyWindowFloorDate indique où il tombe, historyWindowHiddenCount combien de lignes sont derrière, et historyWindowEarliestDate jusqu'où ton historique remonte vraiment. Sans eux, un résultat court est impossible à distinguer d'un compte sans transactions plus anciennes. Deux d'entre eux changent ce que tu écris : historyWindowHiddenCount peut valoir null même quand un plancher s'est appliqué, alors lis null comme une valeur inconnue plutôt que comme un zéro; et quand historyWindowDegraded vaut true, Era n'a pas pu confirmer ton forfait sur cette lecture, donc la date du plancher est une supposition et non un fait. Sur une lecture payante confirmée, aucun plancher ne s'applique et historyWindowApplied revient à false.
Les forfaits annoncent aussi un quota de requêtes d'API — 500 sur le gratuit, davantage sur chaque forfait payant. Les chiffres actuels sont listés avec le reste des limites de ton forfait.
Modifier une transaction
Quatre choses sur une transaction sont à toi de redéfinir : sa catégorie, le nom du commerçant, une note de ton choix, et son statut de révision. Envoie seulement celles que tu changes — tout ce que tu omets reste tel quel. L'id dans le chemin est la clé utgr_ de la transaction. Ça modifie des données, alors ça prend banking:write plutôt que banking:read.
Portée requise
banking:writecategoryKeyoptionnel | La clé fcat_ de la catégorie à assigner. Omets ce paramètre et la transaction garde la catégorie qu'elle a déjà. |
|---|---|
merchantNameoptionnel | Un nom de commerçant de ton choix, jusqu'à 1000 caractères. Omets ce paramètre et le nom actuel reste. |
descriptionoptionnel | Une note de ton choix sur cette transaction, jusqu'à 5000 caractères. Omets ce paramètre et la note actuelle reste. |
reviewStatusoptionnel | Marque-la needs_review, reviewed ou flagged. |
clearCategoryoptionnel | Retire ta redéfinition de catégorie, pour que la catégorisation d'Era reprenne le dessus. Par défaut, false. |
clearMerchantNameoptionnel | Retire ta redéfinition du nom de commerçant, pour que le nom envoyé par ta banque revienne. Par défaut, false. |
clearDescriptionoptionnel | Retire ta redéfinition de la description, pour que la description envoyée par ta banque revienne. Par défaut, false. |
clearReviewStatusoptionnel | Retire ta redéfinition du statut de révision. Par défaut, false. |
Réponse · 200
{
"transaction": { … }
}
Tu récupères la transaction mise à jour au complet, dans la même forme que retourne la liste ci-dessus — non reproduite ici, parce que c'est un gros objet encore en mouvement. Définir un champ et le vider dans le même appel te donne 400. Une transaction qui n'est pas la tienne, ou qui n'existe pas du tout, te donne 403 — l'API ne fait pas la différence entre les deux. Et si autre chose a changé la même ligne pendant que tu écrivais, tu obtiens 409 : relis-la et renvoie-la.
Modifier jusqu'à 100 à la fois
Les quatre mêmes redéfinitions, appliquées à une liste de transactions en un seul appel. Chaque id de la liste reçoit les mêmes changements — il n'y a pas de variation par transaction. Ça modifie des données, alors ça prend banking:write plutôt que banking:read.
Portée requise
banking:writetransactionIds | Les clés utgr_ des transactions à modifier. Au moins une, et au plus 100. Au-delà de 100, c'est refusé plutôt que tronqué — contrairement à pageSize plus haut, tu obtiens un 400 et rien n'est modifié. |
|---|---|
categoryKeyoptionnel | La clé fcat_ de la catégorie à assigner. Omets ce paramètre et la transaction garde la catégorie qu'elle a déjà. |
merchantNameoptionnel | Un nom de commerçant de ton choix, jusqu'à 1000 caractères. Omets ce paramètre et le nom actuel reste. |
descriptionoptionnel | Une note de ton choix sur cette transaction, jusqu'à 5000 caractères. Omets ce paramètre et la note actuelle reste. |
reviewStatusoptionnel | Marque-la needs_review, reviewed ou flagged. |
clearCategoryoptionnel | Retire ta redéfinition de catégorie, pour que la catégorisation d'Era reprenne le dessus. Par défaut, false. |
clearMerchantNameoptionnel | Retire ta redéfinition du nom de commerçant, pour que le nom envoyé par ta banque revienne. Par défaut, false. |
clearDescriptionoptionnel | Retire ta redéfinition de la description, pour que la description envoyée par ta banque revienne. Par défaut, false. |
clearReviewStatusoptionnel | Retire ta redéfinition du statut de révision. Par défaut, false. |
Réponse · 200
{
"transactions": [ … ]
}
Tu récupères les transactions mises à jour, dans la même forme que retourne la liste ci-dessus. Définir un champ et le vider dans le même appel te donne 400, tout comme une liste vide. Une liste contenant une transaction qui n'est pas la tienne, ou qui n'existe pas du tout, te donne 403 pour l'appel entier — rien n'est modifié. Si autre chose a changé l'une de ces lignes pendant que tu écrivais, tu obtiens 409 : relis-les et renvoie-les.
Catégories
Portée requise
banking:readToute la taxonomie des catégories : chaque ensemble de catégories, avec ses sous-catégories imbriquées dedans. La taxonomie est partagée, pas propre à un compte.
Réponse · 200
{
"packs": [
{
"packSlug": "default",
"packName": "Era default categories",
"isDefault": true,
"categories": [
{
"projectionKey": "fcat_food_dining",
"categoryName": "Food & dining",
"isTopLevel": true,
"children": [ … ]
}
]
}
],
"meterLimit": 25,
"canCreateCustomCategories": true
}
Ajouter une catégorie
Une catégorie définie par l'utilisateur, sous un parent existant. Ça modifie des données, alors ça prend banking:write plutôt que banking:read.
Portée requise
banking:writeslug | Identifiant compatible URL — lettres minuscules, chiffres et traits d'union, de 2 à 50 caractères. |
|---|---|
parentCategoryKey | La clé fcat_ de la catégorie sous laquelle celle-ci s'imbrique. |
name | Nom affiché. |
descriptionoptionnel | Description optionnelle. |
iconNameoptionnel | Nom d'icône optionnel. |
spendingTypeoptionnel | Classification de dépense optionnelle. |
displayOrderoptionnel | Position de tri optionnelle parmi ses catégories sœurs. |
assignmentEligibilityoptionnel | Règle optionnelle sur les transactions auxquelles cette catégorie peut être assignée. |
sourceSystemKeysoptionnel | Liste optionnelle de clés de catégories existantes dont les transactions doivent être redirigées ici à partir de maintenant. |
applyRetroactivelyoptionnel | Si true, réévalue aussi les transactions passées selon le nouvel acheminement. Par défaut, false. |
Réponse · 201
{
"categoryKey": "fcat_side_hustle_9f2a",
"overlayProjectionKey": "fcov_9f2a1c",
"action": "created",
"isQuotaExceeded": false,
"createdMappingRuleKeys": [ … ],
…
}
La réponse porte aussi retroactiveAffectedCount, mergeSourcesHiddenCount, mergeSourcesTotalCount et meterGate — des champs que cet appel partage avec les fusions de catégories et les créations limitées par quota, non montrés ici.