Era API
Endpoints, auth, paging and limits for the Era API, with copyable requests.
Last updated 1 October 2026
These endpoints work today, but the API is still evolving, so some details may change. Check the last-updated date at the top to see when this page was most recently revised.
Quickstart
Before you start you need an Era account with at least one connected institution — without a connection these endpoints have nothing to return.
- 1
Sign in to Era and connect an institution, if you haven't already.
- 2
Open your API keys in the dashboard and create a key. Most scopes are ticked to start with; the ones that can move money, place trades, change your billing, delete your documents or make more keys aren't. Untick the ones you don't need. Reading needs banking:read, and changing anything needs banking:write. You also pick an expiry; there's no never-expires option.
- 3
Copy the key and store it somewhere safe, like a secrets manager. It's shown once, so if you lose it, make a new one.
- 4
Send it in a header with your request.
curl "https://forge.era.app/api/banking/transactions?page=1&pageSize=20" \
-H "X-API-Key: fmk_your_key_here"{
"transactions": [ … ],
"pagination": {
"currentPage": 1,
"pageSize": 20,
"totalItems": 412,
"totalPages": 21
},
"historyWindowApplied": true,
"historyWindowFloorDate": "2026-06-28",
"historyWindowHiddenCount": 137,
"historyWindowEarliestDate": "2024-03-02",
"historyWindowDegraded": false
}Writing your own client? Generate it from the OpenAPI document at https://forge.era.app/api/openapi/public.json. It describes every endpoint on this page — each field, each status, and every kind of rule filter and action — and the API serves it itself, so it doesn't fall behind.
Endpoints
Seventeen endpoints: eight reads and nine writes.
- GET/banking
/accounts - GET/banking
/accounts /{accountId} /balance - GET/banking
/accounts /summary - GET/banking
/transactions - PUT/banking
/transactions /{id} - PUT/banking
/transactions /bulk - GET/banking
/categories - POST/banking
/categories - GET/banking
/tags - POST/banking
/tags - GET/banking
/rules - POST/banking
/rules - POST/banking
/rules /preview - PUT/banking
/rules /{ruleId} - POST/banking
/rules /{ruleId} /enable - POST/banking
/rules /{ruleId} /disable - DELETE/banking
/rules /{ruleId}
Authentication
Send your key either of two ways:
- Header
- X-API-Key: fmk_your_key_here
- Bearer token
- Authorization: Bearer fmk_your_key_here
Every request is encrypted with TLS.
Keys expire, and you choose how soon when you make one. The longest available is 90 days on the free plan and 365 on a paid one — there is no never-expires option, so anything you build against this needs a plan for rotating the key before it lapses.
Response headers
The X-RateLimit-* headers report your daily budget, so you'll only see them on a plan that publishes one. For now, that's just the free plan. Even there, treat them as optional: a response without them means the count is unknown, not that something went wrong. Every 429 carries Retry-After.
- X-RateLimit-Limit
- Your plan's daily budget. Sent only on plans that publish one, and never on a 429 from any other limit.
- X-RateLimit-Remaining
- What's left of your daily budget. Never below zero. Sent only on plans that publish one, and never on a 429 from any other limit.
- X-RateLimit-Reset
- When your daily budget next frees one request, as a Unix timestamp in seconds. It isn't when the whole budget refills: the budget rolls, so requests come back one at a time. Sent only on plans that publish one, and never on a 429 from any other limit. The per-minute burst cap has no header of its own. See Limits
- Retry-Afteron a 429
- Every 429 carries Retry-After: wait that many seconds, then retry. A 429 that also carries the X-RateLimit-* headers was refused by the free plan's daily budget. A 429 without them was refused by another limit, such as the per-minute burst cap. Retry-After can be anything from one second to 24 hours. See Limits
Errors
The API returns these error status codes:
- 400
Malformed input: a bad parameter, an empty or over-100 bulk update, a write that sets and clears the same field in one call, or a rule that doesn't hold together. Deleting, enabling or disabling a rule also refuses any query key it doesn't take, and the 400 names it.
- 401
No key, or one that doesn't parse. Send it as X-API-Key or as a bearer token.
- 402
A plan quota is in the way. Today that only happens when you create a category or a pattern rule. Waiting doesn't clear it, but a bigger plan does. Calling too fast is a 429 instead.
- 403
The key doesn't carry the scope this call needs — or, on either transaction write, the id belongs to someone else or doesn't exist at all. The API doesn't tell those two apart. A rule from Era's library answers 403 when you try to delete it, or to change its name, description, priority, dates or applicationScope. Changing its filters, actions or group is a 400 instead.
- 404
Something that isn't there. The balance endpoint returns it with no body, for an accountGroupKey naming no account or one that isn't shaped like a key at all. The rule endpoints return it for a rule key that isn't one of yours. Transactions never 404 — see 403.
- 409
Something else changed the row while you were writing to it — on a rule, that's a version that isn't its current one. Read it again and send your write again. On a rule, a 409 can also mean its actions would set rules off in a loop; change the actions instead.
- 429
Too many requests. You've spent your daily budget on the free plan or hit another limit, such as the per-minute burst cap. Retry-After says how long to wait, from one second to 24 hours, and unlike a 402, waiting frees your next request. See Limits for the published figures and what a 429 tells you.
Error shapes
Most errors come back in the same shape — statusCode, message, and an errors object naming what was wrong. Not all of them: a 401, and the balance endpoint's 404, come back with no body at all, so read the status before you read the body.
{
"statusCode": 403,
"message": "One or more errors occurred!",
"errors": {
"generalErrors": ["Transaction does not belong to the authenticated user"]
}
}Common errors
Setting a field and clearing it in the same write — 400.
More than 100 ids in a bulk update — 400, and nothing is changed. Fewer than one is the same.
A transaction that isn't yours, or isn't there at all — 403, never 404. So the response never tells you whether an id exists, only that it isn't yours to see.
Someone or something else changed the row first — 409.
Limits
Two of the documented endpoints cap how much you can ask for in one call. The rest don't.
Uncapped per call doesn't mean unlimited. Keys still expire, writes still have field-length limits, creating a category or a pattern rule can hit a plan quota, your plan can still hide older history, and every call counts against your plan's rate limits, covered below.
Accounts, balance, summary, the category and tag lists, creating a category, creating a tag, the single-transaction write, and every rule endpoint have no per-call volume cap. You get the whole set back, or the one row you named — except a rule preview, which counts the matches but returns at most 50 of their keys and 10 samples.
pageSize is clamped to 100, not refused. Ask for more and you get 100 rows back with a 200 — read pagination.pageSize in the response rather than trusting what you sent.
The bulk transaction write is capped at 100 ids, and unlike pageSize it's refused rather than clamped: send 101 and you get a 400 and nothing changes.
Keys expire on a schedule you choose at creation — up to 90 days on the free plan, 365 on a paid one. There's no never-expires option.
Your plan can apply a history-window floor that hides older transactions. The transactions response carries the historyWindow fields that tell you whether one applied and where it fell.
Rate limits
Era publishes two rate limits. Every plan has a burst cap: a limit on requests in any rolling minute. The free plan also has a daily budget: a limit on requests in any rolling 24 hours. A request stops counting against the burst cap a minute after you make it, and against the daily budget a day after. Paid plans have no published daily budget for now. Go over either one, or any other limit the API enforces, and the call comes back 429.
Limits belong to your account, not to a key. Every REST key you make draws on the same burst cap and, on the free plan, the same daily budget, so a second key doesn't buy more calls. MCP tool calls are counted separately, so REST calls and MCP calls never use up each other's limits.
| Plan | Daily budget | Burst cap |
|---|---|---|
| Basic | 100 a day | 10 a minute |
| Organise | None | 30 a minute |
| Automate | None | 60 a minute |
| Optimise | None | 60 a minute |
| Operate | None | 120 a minute |
For now, paid plans have no published daily budget.
Paid plans also run on fair use: the API is for scripts, dashboards and integrations on your own financial data, at a volume that fits one person.
No header reports the burst cap on any plan, so pace yourself against the table. On the free plan, X-RateLimit-Remaining can read well above zero just before a burst comes back 429.
What a 429 tells you
Which limit refused you. A 429 that carries the X-RateLimit-* headers came from the free plan's daily budget; one without them came from another limit, such as the burst cap. The body is problem details, and its detail field says when your next request frees up. When the daily budget or the burst cap refused you, it also names that limit, what it allows, and the plan that raises or removes it, or that you're already on the highest. It's written for people, so read the headers rather than parsing it.
{
"type": "https://httpstatuses.com/429",
"title": "Too Many Requests",
"status": 429,
"detail": "You've used up your daily budget of 100 requests. Your next request frees up at 2026-09-16T09:00:00Z. The Organize plan removes it."
}Every 429 also carries Retry-After: a whole number of seconds, from one second up to 24 hours. The burst cap's wait is at most a minute; the free plan's daily budget can ask for the full 24 hours. A refused call doesn't count against any limit, but don't retry early: wait out Retry-After on every 429. On the daily budget and the burst cap, an early retry is refused again, and when the wait ends, one request frees up, not the whole limit. Send it and read what comes back: another Retry-After if it's refused or, on the free plan, the headers once it's served.
Rate limits don't protect a key you've lost track of. A leaked key draws on the same limits as every other key on your account, and it can do whatever its scopes allow. Revoking it doesn't refund calls it already made. If you're unsure about a key, revoke it. See Security and key handling below.
Conventions
Response fields are camelCase. Query parameter names ignore letter case but must otherwise be spelled exactly as this page shows them, so pageSize and PageSize both work. The published spec spells most of them in PascalCase. Most endpoints ignore a query key they don't take, so a misspelled filter just doesn't filter — check what comes back. The rule delete, enable and disable calls are the exception: they refuse any other key with a 400 that names it. Send revert_retroactively with the underscore — revertRetroactively is refused — and send it only to delete and disable, because enable takes version alone.
A field naming a calendar day is YYYY-MM-DD. A field naming an instant is ISO 8601 with an offset.
Paging sizes are clamped, not refused. Ask for a pageSize of 500 and you get 100 rows and a 200, not an error — so read pagination.pageSize back rather than trusting what you sent.
A response can carry fields this page doesn't list. Ignore the ones you don't recognise rather than failing on them — that's what keeps your client working as the API grows.
REST is plain HTTP; there's no SDK to install. Any language with an HTTP client works.
Versioning and changes
The API is in beta, so what's documented here can change: an endpoint, a field, or how a call behaves.
There's no version number in the path and no version header. Each endpoint has one live version, and it's the one documented here.
Changelog
What's changed on the Era Developer Platform, including the Era API, newest first. There's no email or feed, so check the changelog when you're planning work against the API.
Security and key handling
Approving an agent creates a key
When you approve an agent over OAuth, Era makes an API key for it. It lands in the same dashboard list as the ones you make yourself, under a name Era generates from the client's own name.
How it's named
Auto -- ClaudeIt carries exactly the scopes you approved on that screen, and nothing else. Revoke it from the dashboard and the agent can't get new access until you approve it again. A token it already has keeps working until it expires, at most an hour.
Writes show up in your activity log, reads don't
Making and revoking a key both show up in your activity log, and so does every tool call an agent makes over MCP. A REST write shows up too — as the change it made, like a tag created or a transaction edited. A REST read adds no entry at all. Era records these entries on every plan, but reading the full log takes Organise or higher — below that you see only the most recent ones. A write's entry names your account, not the key that made the change.
If you're ever unsure about a key, revoke it. A key you made yourself stops working on REST and MCP from its next request. An agent's key stops getting new access right away, and any token it already has expires within an hour. Making a replacement takes a minute.
- Scopes are coarse
- banking:read reads more than the endpoints on this page — the same scope covers the rest of your account's reads too: balances, holdings, connections, spending. One scope, no narrower option. Write scopes are on the menu too, same as reads — so treat any key like a password. It acts as your Era account, not a slice of it.
- banking:write changes Era, not your bank
- banking:write changes only what Era keeps, not anything your bank holds. It can't move money or make a payment. Like banking:read, banking:write is one broad scope. It covers the changes you can make in Era yourself, such as the category, merchant name, note and review status on a transaction; categories, tags and automation rules; the names Era shows for your accounts and which ones you see; and manual accounts with their balances and transactions. It can start a new connection, but you still sign in to your bank yourself, in the provider's flow rather than through the key. It can ask for a fresh sync, which only reads, or disconnect an institution. Disconnecting ends Era's link, and your account at the bank stays open and unchanged.
- Scopes don't update
- A key's scopes are fixed when you make it and never change afterwards. That matters for anything a scope covers that isn't switched on yet: grant social:write today and the key still has it when shared views arrive. Grant what you're using now, not what you might use later.
- No approval needed
- Making a key needs no review and no waiting list: nobody at Era approves the request. It's written to your activity log the moment you make it, so an unfamiliar key is easy to spot.
- Bank login stays out of reach
- A key can't reach your bank login, because Era never has it. You enter it in the connection flow run by the data provider, not on an Era screen — what Era keeps afterwards is a per-connection access token, encrypted at rest with AES-256, that you can throw away by disconnecting the institution.
- Era keeps only a hash of your key
- Your key is 256 bits of random data. Era hashes it with SHA-256 and keeps only the hash, so if you lose it, revoke it and make a new one.
Core resources
Accounts
Every account you can see, across every connected institution, with the count of the ones left out beside them: excludedAccountCount, split into tierExcludedAccountCount for the accounts your plan's account limit leaves out and userExcludedAccountCount for the ones you've hidden. accountLimit is how many accounts your plan shows at once, across all your connections. accountLimitLift names the cheapest plan with room for every account you haven't hidden, and it's null when your plan leaves nothing out. Each account carries its accountGroupKey — the value the balance endpoint takes in its path — and the connectionId it belongs to, so this is the call to make first. Takes connectionId to narrow to a single connection (the counts narrow with it; accountLimit and accountLimitLift don't), and includeExcluded to bring in the accounts your plan leaves out and the ones you've hidden.
- connectionIdoptional
- Narrow the list to one connection's accounts.
- includeExcludedoptional
- Include the accounts your plan leaves out and the ones you've hidden, with their balances withheld. Each row's visibility says which: tierExcluded or userExcluded, and visible for the rest. The balance endpoint spells these differently, so don't share one parser between them. excludedAccountCount still comes back when this is true, and those accounts are already in the list, so don't add the two. Defaults to false.
{
"accounts": [
{
"accountGroupKey": "uagr_7f3c9a21",
"connectionId": "ucon_4b19e02c",
"name": "Everyday Checking",
"currentBalance": 4820.16,
"supportsTransactions": true,
…
}
],
"excludedAccountCount": 1
}On an account you've hidden or your plan excludes, the balance fields come back null rather than zero — null means withheld, not empty. supportsTransactions is null in the same spirit: it means Era can't say, and never that the answer is no. The same goes for the plan fields: if Era couldn't read your plan on that call, tierExcludedAccountCount, userExcludedAccountCount, accountLimit and accountLimitLift all come back null. accountLimit is also null on a plan with no account limit, and accountLimitLift when no plan has more room or when your plan leaves nothing out.
Account balance
One account's balance, with the credit fields filled in when the account is a liability. The path takes that account's accountGroupKey — the same value /banking/accounts returns for it. The key is not checked for shape before the lookup, so a malformed key and an unknown one answer the same way.
{
"accountGroupKey": "uagr_7f3c9a21",
"currentBalance": 4820.16,
"availableBalance": 4712.03,
"creditLimit": null,
"currencyCode": "USD",
"availableCredit": null,
"asOf": "2026-08-11T09:32:00Z",
"visibility": null
}A hidden account, or one whose connection was severed, still answers 200 — with the balance fields null. A 404 means the account genuinely isn't there, or the key wasn't shaped like one. Watch the visibility field here: it's null when the account is visible, tier_excluded when your plan's account limit leaves it out, user_excluded when you've hidden it, and connection_severed when its connection was cut. For tier_excluded, accountLimit is your plan's limit and accountLimitLift names the cheapest plan with room for every account you haven't hidden, this one included. accountLimitLift is null for every other state, and on connection_severed accountLimit is null too.
Account summary
Totals across the accounts you can see, live ones by default: totalAssets, totalLiabilities, and netWorthHint, which is the first minus the second. Pass simulationModes to choose which modes count: the accounts list and the totals cover only the ones you name. includedSimulationModes says which modes they cover, on every response. simulationExcludedAccountCount counts the accounts you can see that were left out because of their mode. totalHiddenCount is a separate count: the accounts your plan or your own visibility setting hides, in any mode, and never the ones simulationModes left out.
- simulationModesoptional
- Which kinds of account to total: live for real money, paper for a broker's practice account, scenario for a hypothetical one. Repeat the parameter to ask for more than one, as in simulationModes=live&simulationModes=paper. Leave it out for live accounts only. An empty or unknown value is refused with a 400 that lists the allowed ones.
{
"userId": "7d1c0b93a8e24f60",
"accounts": [ … ],
"totalVisibleCount": 6,
"totalHiddenCount": 2,
"totalAssets": 48210.75,
"totalLiabilities": 9327.40,
"netWorthHint": 38883.35,
"includedSimulationModes": [ "live" ],
"simulationExcludedAccountCount": 0,
"computedAt": "2026-08-11T09:32:00Z"
}netWorthHint counts only the accounts in this response, and two counts tell you what it leaves out. simulationExcludedAccountCount is the accounts outside the modes you asked for. totalHiddenCount is the accounts hidden from you in any mode. It splits in two: tierExcludedAccountCount for the ones your plan's account limit leaves out, and userExcludedAccountCount for the ones you've hidden yourself. Both are null if Era couldn't read your plan on that call. accountLimitLift names the cheapest plan that brings the first kind back, and it's null when there are none. Treat netWorthHint as a starting figure rather than an authoritative net worth.
Transactions
Your transactions, a page at a time, wrapped in an envelope with the paging counts beside them. Takes page and pageSize (100 is the ceiling), plus optional filters for account, date range, applied rules, and assigned tags.
- accountIdoptional
- Narrow to one account's transactions, by its accountGroupKey.
- fromDateoptional
- Only transactions on or after this date.
- toDateoptional
- Only transactions on or before this date.
- pageoptional
- Page number, 1-indexed. Defaults to 1.
- pageSizeoptional
- Rows per page. Defaults to 50, clamped to 100.
- sortByoptional
- Field to sort by: transactionDate, amount, description, category, or merchantName.
- sortDirectionoptional
- asc or desc. Defaults to descending.
- categoryKeysoptional
- Only transactions in these categories, by their fcat_ keys. Takes a list, not a single key, and a transaction matches if its effective category is any one of them. Send the literal "uncategorized" to select the transactions that have no category at all.
- searchoptional
- Full-text search across merchant, description, category, account name, and amount.
- ruleIdsoptional
- Only transactions an automation rule touched, by the rule's key.
- tagKeysoptional
- Only transactions carrying one of these tags.
- reviewStatusesoptional
- needs_review, reviewed, or flagged. Takes a list; a transaction matches if its review status is any one of them.
- includeChildrenoptional
- With a category filter set, also include transactions in the subcategories of every key you passed. Defaults to false.
- includePendingoptional
- Also return pending charges from the last 7 days, marked isPending. Defaults to false. Pending rows are read-only.
{
"transactions": [ … ],
"pagination": {
"currentPage": 1,
"pageSize": 20,
"totalItems": 412,
"totalPages": 21
},
"historyWindowApplied": true,
"historyWindowFloorDate": "2026-06-28",
"historyWindowHiddenCount": 137,
"historyWindowEarliestDate": "2024-03-02",
"historyWindowDegraded": false
}Your plan may apply a history-window floor, which hides transactions older than it. That's why the response carries the historyWindow fields: historyWindowApplied tells you a floor actually hid something, historyWindowFloorDate is where it fell, historyWindowHiddenCount is how many rows are behind it, and historyWindowEarliestDate is how far your history really goes. Without them a short result set is indistinguishable from an account with no older transactions. Two of them change what you write: historyWindowHiddenCount can be null even when a floor applied, so read null as unknown rather than zero; and when historyWindowDegraded is true, Era couldn't confirm your plan on that read, so the floor date is a guess rather than a fact. On a confirmed paid read no floor applies and historyWindowApplied comes back false. Your plan's account limit leaves transactions out too: tierExcludedAccountCount is how many of your accounts it keeps out of this read, and userExcludedAccountCount how many you've hidden — both narrowed to the account you filtered to, if any. Both are null when Era couldn't read your plan on that call.
Paging through a long history spends requests. Your plan caps how fast you can call and, on the free plan, how many calls you get a day; paid plans run on fair use. Limits has the per-plan figures, the rate-limit headers, and what a 429 tells you.
Change one transaction
Four things on a transaction are yours to override: its category, the merchant name, a note of your own, and its review status. Send only the ones you're changing — anything you leave out stays as it is. The id in the path is the transaction's utgr_ key. It needs banking:write rather than banking:read, but it changes only Era's copy of the transaction: the one at your bank stays as it was.
- categoryKeyoptional
- The fcat_ key of the category to assign. Leave it out and the transaction keeps the category it has.
- merchantNameoptional
- A merchant name of your own, up to 1000 characters. Leave it out and the current name stays.
- descriptionoptional
- A note of your own on this transaction, up to 5000 characters. Leave it out and the current note stays.
- clearCategoryoptional
- Drops your category override, so Era's own categorisation takes over again. Defaults to false.
- clearMerchantNameoptional
- Drops your merchant-name override, so the name your bank sent comes back. Defaults to false.
- clearDescriptionoptional
- Drops your description override, so the description your bank sent comes back. Defaults to false.
- reviewStatusoptional
- Mark it needs_review, reviewed, or flagged.
- clearReviewStatusoptional
- Drops your review-status override. Defaults to false.
{
"transaction": { … }
}You get the whole updated transaction back, in the same shape the list above returns — not reprinted here, because it's large and still changing while the API is in beta. Setting a field and clearing it in the same call comes back 400. A transaction that isn't yours, or isn't there at all, comes back 403 — the API doesn't tell those two apart. And if something else changed the same row while you were writing, you get 409: read it again and send it again.
Change up to 100 at once
The same four overrides, applied to a list of transactions in one call. Every id in the list gets the same changes — there is no per-transaction variation. It needs banking:write rather than banking:read, but it changes only Era's copies of those transactions: the ones at your bank stay as they were.
- transactionIds
- The utgr_ keys of the transactions to change. At least one, and no more than 100. Over 100 is refused rather than trimmed — unlike pageSize above, you get a 400 and nothing changes at all.
- categoryKeyoptional
- The fcat_ key of the category to assign. Leave it out and the transaction keeps the category it has.
- merchantNameoptional
- A merchant name of your own, up to 1000 characters. Leave it out and the current name stays.
- descriptionoptional
- A note of your own on this transaction, up to 5000 characters. Leave it out and the current note stays.
- clearCategoryoptional
- Drops your category override, so Era's own categorisation takes over again. Defaults to false.
- clearMerchantNameoptional
- Drops your merchant-name override, so the name your bank sent comes back. Defaults to false.
- clearDescriptionoptional
- Drops your description override, so the description your bank sent comes back. Defaults to false.
- reviewStatusoptional
- Mark it needs_review, reviewed, or flagged.
- clearReviewStatusoptional
- Drops your review-status override. Defaults to false.
{
"transactions": [ … ]
}You get the updated transactions back, in the same shape the list above returns. Setting a field and clearing it in the same call comes back 400, and so does an empty list. A list holding a transaction that isn't yours, or isn't there at all, comes back 403 for the whole call — nothing is changed. If something else changed one of those rows while you were writing, you get 409: read them again and send them again.
Categories
The whole category taxonomy: every set of categories, with its sub-categories nested inside. The taxonomy is shared, not per-account.
{
"packs": [
{
"packSlug": "default",
"packName": "Era default categories",
"isDefault": true,
"categories": [
{
"projectionKey": "fcat_food_dining",
"categoryName": "Food & dining",
"isTopLevel": true,
"children": [ … ]
}
]
}
],
"meterLimit": 25,
"canCreateCustomCategories": true
}Add a category
A user-defined category under an existing parent. It needs banking:write rather than banking:read, but it changes only your categories in Era: nothing at your bank changes.
- slug
- URL-safe identifier — lowercase letters, numbers, and hyphens, 2 to 50 characters.
- parentCategoryKey
- The fcat_ key of the category this one nests under.
- name
- Display name.
- descriptionoptional
- Optional description.
- iconNameoptional
- Optional icon name.
- spendingTypeoptional
- Optional spending classification.
- displayOrderoptional
- Optional sort position among its siblings.
- assignmentEligibilityoptional
- Optional rule for which transactions this category can be assigned to.
- sourceSystemKeysoptional
- Optional list of existing category keys whose transactions should be routed here going forward.
- applyRetroactivelyoptional
- When true, re-evaluates past transactions against the new routing too. Defaults to false.
{
"categoryKey": "fcat_side_hustle_9f2a",
"overlayProjectionKey": "fcov_9f2a1c",
"action": "created",
"isQuotaExceeded": false,
"createdMappingRuleKeys": [ … ],
…
}The response also carries retroactiveAffectedCount, mergeSourcesHiddenCount and mergeSourcesTotalCount, which this page doesn't cover, plus isQuotaExceeded, quotaExceededMessage and meterGate, which on a created category are always false, null and null. A create your plan's quota refuses gets a 402 instead, with none of those fields: its body is statusCode, message, and errors.generalErrors, whose one entry says which limit you hit.
Tags
Every tag on your account, as one list. No paging — one response returns all of them.
- tagTypeoptional
- Filter by tag origin: user, system, or auto.
- includeDeletedoptional
- Include deleted tags. Defaults to false.
{
"tags": [
{
"tagKey": "utag_9c2f01ab",
"name": "business-expense",
"displayName": "Business expense",
"tagType": "user",
"color": "#6DC6BA",
"transactionCount": 42
}
]
}Create a tag
A new tag, canonicalised to lowercase. It needs banking:write rather than banking:read, but it changes only your tags in Era: nothing at your bank changes. System tags can't be created through the API; user and auto tags can.
- name
- The tag's canonical name.
- displayNameoptional
- Optional display name. Defaults to the canonical name.
- tagTypeoptional
- user or auto. Defaults to user. system is refused — it is reserved for tags Era creates itself.
- coloroptional
- Optional hex colour for display.
- iconoptional
- Optional icon name.
{
"tag": {
"tagKey": "utag_9c2f01ab",
"name": "business-expense",
"displayName": "Business expense",
"tagType": "user",
"version": 1,
"createdAt": "2026-08-26T09:15:00Z"
}
}Rules
Every automation rule on your account — the ones you made and the ones Era set up for you from its rule library — each with its conditions (filters), what it does (actions), whether it's switched on, and its version. No paging: one response returns all of them. Switched-off rules are left out unless you ask for them.
- includeInactiveoptional
- Also return the rules that are switched off. Defaults to false.
{
"rules": [
{
"id": "rule_4c1e7a90",
"ruleName": "Coffee shops",
"isActive": true,
"version": 3,
"priority": 10,
"filters": [{ "type": "merchant_match", "pattern": "Blue Bottle", "mode": "contains" }],
"actions": [{ "type": "set_category", "categoryKey": "fcat_coffee_shops", "categoryDisplayName": "Coffee shops" }],
"source": "user",
"matchedTransactionCount": 42,
…
}
]
}filters and actions are lists of typed objects. Each one names its kind in a type field — merchant_match or category among the filters, set_category or add_tag among the actions — and the OpenAPI document linked in the quickstart lists every kind and the fields it takes.
Create a rule
A new automation rule: conditions that pick out transactions, and what happens to each one they match. It runs on every new transaction from then on, and on the ones you already have too if you ask. It needs banking:write rather than banking:read, but it changes only your rules and transactions in Era: nothing at your bank changes.
- ruleName
- A name for the rule.
- descriptionoptional
- An optional note on what the rule is for.
- priorityoptional
- Where the rule runs among your others: higher runs first, and when two rules would set the same thing on one transaction, the higher one wins. Defaults to zero.
- filters
- The conditions, as a list of typed filter objects — each names its kind in a type field. A transaction must match every one; nest and, or and not filters to combine them otherwise. At least one. An account_type filter that lists account types works only on a pattern rule; on a transaction rule it's refused with a 400.
- actions
- What happens to each transaction the rule matches, as a list of typed action objects: set a category, add or remove a tag, rename the merchant, rewrite the description, set a review status, hide it, or mark it as a transfer. At least one, and no more than one of each kind except the tag actions. Each tag must already exist, and can appear once.
- isActiveoptional
- Whether the rule starts switched on. Defaults to true.
- applyRetroactivelyoptional
- Also run the rule over the transactions you already have, not only new ones. Only takes effect when the rule starts switched on. Defaults to false.
- appliesFromoptional
- Only transactions on or after this date.
- appliesTooptional
- Only transactions on or before this date.
- applicationScopeoptional
- When the rule runs: prospective for new transactions only, retroactive when it is run over existing ones, or both. Defaults to both.
- ruleScopeoptional
- transaction, the default, matches transactions one at a time. pattern matches a pattern across several — a recurring charge, a pair of transfers — described by patternSpec.
- triggerModeoptional
- For a pattern rule, when to check for the pattern: onEachTransaction, onBatchComplete, onSchedule or onDemand. Defaults to onEachTransaction.
- patternSpecoptional
- For a pattern rule, the pattern to look for, as a typed object. Required when ruleScope is pattern.
- windowDaysoptional
- For a pattern rule, how many days back to look for the rest of the pattern, between 1 and 365. Required when ruleScope is pattern.
- windowLookaheadDaysoptional
- For a pattern rule, how many days forward to look for the rest of the pattern.
- windowExcludeTriggerAccountoptional
- For a pattern rule, leave the account of the transaction that started the check out of the search. Defaults to false.
- groupoptional
- The group to file the rule under: spendingHabits, problemsAndAnomalies, incomeAndTransfers, subscriptionsAndRecurring or taxAndRecordKeeping. Leave it out to file it under none. categoryMapping is reserved for rules Era makes itself, and is refused with a 400.
{
"rule": { … },
"retroactiveStatus": "completed",
"affectedTransactionCount": 18
}retroactiveStatus is null unless you asked the call to apply the rule to transactions you already have, or to undo what it did. completed means that work finished before the response; in_progress means it's still running, so affectedTransactionCount isn't final yet; queue_full means the queue was full and it didn't start, though the rule change itself was saved; partial_success means only some of it went through — some changes weren't saved, or only part of the work was queued.
Preview a rule
Tries a rule's conditions against your recent transactions and reports what they'd match, without saving anything: how many matched out of how many were checked, up to 50 of the matching keys, and up to 10 samples. A transaction rule checks the last 90 days, at most 5,000 transactions, and its samples carry the merchant, amount and date. A pattern rule's samples carry detectionReason, why the pattern matched (which can be empty), and no merchant, amount or date. Nothing is written, so banking:read is enough.
- filters
- The conditions, as a list of typed filter objects — each names its kind in a type field. A transaction must match every one; nest and, or and not filters to combine them otherwise. At least one. An account_type filter that lists account types works only on a pattern rule; on a transaction rule it's refused with a 400.
- ruleScopeoptional
- transaction, the default, matches transactions one at a time. pattern matches a pattern across several — a recurring charge, a pair of transfers — described by patternSpec.
- patternSpecoptional
- For a pattern rule, the pattern to look for, as a typed object. Required when ruleScope is pattern.
- windowDaysoptional
- For a pattern rule, how many days back to look for the rest of the pattern, between 1 and 365. Required when ruleScope is pattern.
{
"matchCount": 12,
"totalEvaluated": 418,
"matchingGroupKeys": ["utgr_5d0e2b77", … ],
"sampleMatches": [
{
"groupKey": "utgr_5d0e2b77",
"merchantName": "Blue Bottle Coffee",
"amount": -6.25,
"currency": "USD",
"transactionDate": "2026-09-12"
},
…
]
}Change a rule
Replaces a rule's definition with the one you send, so send every field you want to keep — not only the ones you're changing. The ruleId in the path is the id the rules list returns, and version must be the one you last read. On a rule from Era's library, only userOverrides can change: send everything else back as the list returned it. It needs banking:write rather than banking:read, but it changes only your rules and transactions in Era: nothing at your bank changes.
- versionoptional
- The rule's version as you last read it. It must match the rule's current version, or the call is refused with a 409 — so leaving it out doesn't skip the check, it fails it.
- ruleName
- A name for the rule.
- descriptionoptional
- An optional note on what the rule is for.
- priorityoptional
- Where the rule runs among your others: higher runs first, and when two rules would set the same thing on one transaction, the higher one wins. Defaults to zero.
- filters
- The conditions, as a list of typed filter objects — each names its kind in a type field. A transaction must match every one; nest and, or and not filters to combine them otherwise. At least one. An account_type filter that lists account types works only on a pattern rule; on a transaction rule it's refused with a 400.
- actions
- What happens to each transaction the rule matches, as a list of typed action objects: set a category, add or remove a tag, rename the merchant, rewrite the description, set a review status, hide it, or mark it as a transfer. At least one, and no more than one of each kind except the tag actions. Each tag must already exist, and can appear once.
- applyRetroactivelyoptional
- Re-run the changed rule over the transactions you already have, undoing what the old version did to them first. Defaults to false.
- appliesFromoptional
- Only transactions on or after this date.
- appliesTooptional
- Only transactions on or before this date.
- applicationScopeoptional
- When the rule runs: prospective for new transactions only, retroactive when it is run over existing ones, or both. Defaults to both.
- userOverridesoptional
- On a rule from Era's library, your own values for the settings it lets you tune, as a typed object of the same kind as the rule's pattern. Ignored on rules you made.
- groupoptional
- The group to file the rule under, from the same list create takes. The update replaces the whole definition, so leaving it out clears the rule's group — send its current group to keep it. On a rule from Era's library, leave it out or send the group it already has.
{
"rule": { … },
"retroactiveStatus": null,
"affectedTransactionCount": 0
}retroactiveStatus is null unless you asked the call to apply the rule to transactions you already have, or to undo what it did. completed means that work finished before the response; in_progress means it's still running, so affectedTransactionCount isn't final yet; queue_full means the queue was full and it didn't start, though the rule change itself was saved; partial_success means only some of it went through — some changes weren't saved, or only part of the work was queued.
Turn a rule on
Switches a rule back on, so it runs on new transactions again. Turning on a rule that's already on changes nothing. Rules from Era's library can be switched on and off like your own. It needs banking:write rather than banking:read, but it changes only your rules and transactions in Era: nothing at your bank changes.
- versionoptional
- The rule's version as you last read it. Leave it out to skip the check; send it, and the call is refused with a 409 if the rule changed since.
{
"rule": { … },
"retroactiveStatus": null,
"affectedTransactionCount": 0
}Turn a rule off
Switches a rule off, so it stops running on new transactions. It keeps its definition, and what it already did stays unless you ask for it back with revert_retroactively. Turning off a rule that's already off leaves it off. It needs banking:write rather than banking:read, but it changes only your rules and transactions in Era: nothing at your bank changes.
- versionoptional
- The rule's version as you last read it. Leave it out to skip the check; send it, and the call is refused with a 409 if the rule changed since.
- revert_retroactivelyoptional
- Also undo what the rule already did to your transactions — the categories, tags and other changes it applied. Defaults to false, which leaves those changes in place.
{
"rule": { … },
"retroactiveStatus": null,
"affectedTransactionCount": 0
}retroactiveStatus is null unless you asked the call to apply the rule to transactions you already have, or to undo what it did. completed means that work finished before the response; in_progress means it's still running, so affectedTransactionCount isn't final yet; queue_full means the queue was full and it didn't start, though the rule change itself was saved; partial_success means only some of it went through — some changes weren't saved, or only part of the work was queued.
Delete a rule
Deletes a rule for good. To stop a rule without losing it, disable it instead. The ruleId in the path is the id the rules list returns, and version must be the one you last read. Rules from Era's library can't be deleted, only disabled. It needs banking:write rather than banking:read, but it changes only your rules and transactions in Era: nothing at your bank changes. Asking a delete to revert_retroactively is a single try: if it comes back queue_full, the rule is already gone and there's nothing left to retry against. To delete a rule and undo what it did, disable it with revert_retroactively first — you can repeat that until it's accepted — then delete it.
- versionoptional
- The rule's version as you last read it. It must match the rule's current version, or the call is refused with a 409 — so leaving it out doesn't skip the check, it fails it.
- revert_retroactivelyoptional
- Also undo what the rule already did to your transactions — the categories, tags and other changes it applied. Defaults to false, which leaves those changes in place.
{
"success": true,
"retroactiveStatus": null,
"affectedTransactionCount": 0
}retroactiveStatus is null unless you asked the call to apply the rule to transactions you already have, or to undo what it did. completed means that work finished before the response; in_progress means it's still running, so affectedTransactionCount isn't final yet; queue_full means the queue was full and it didn't start, though the rule change itself was saved; partial_success means only some of it went through — some changes weren't saved, or only part of the work was queued.