Skip to main content

Era API

Understand the Era API's available endpoints, authentication headers, pagination, and limits.

Last updated 29 September 2026

Still in beta

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

    Sign in to Era and connect an institution, if you haven't already.

  2. 2

    Open your API keys in the dashboard and create a key. Every scope is ticked to start with, so untick the ones you don't need — for these endpoints that leaves banking:read. You also pick an expiry; there's no never-expires option.

  3. 3

    Copy the key. It's shown once, and we can't show it again. Copy it and store it somewhere safe, such as a secrets manager. If you lose a key, you can't view it again. Create a new key instead.

  4. 4

    Send it in a header with your request.

cURL
curl "https://forge.era.app/api/banking/transactions?page=1&pageSize=20" \
  -H "X-API-Key: fmk_your_key_here"
Response · 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
}

Endpoints

Ten endpoints, method and path first. Each row jumps to the section that documents it in full.

Authentication

Send your key either of two ways:

Authentication methods
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

A request id comes back on every response. The rate-limit headers come back on the calls Era metered against a daily budget, and on a 429 only when that daily budget refused the call: a 429 from the per-minute burst cap carries Retry-After alone. For now, only the free plan has a daily budget. If Era can't measure your usage, it serves the call and sends none of them.

Response headers
fly-request-id
A unique identifier for the request. Include it when you contact support about a specific request — see Request ID
X-RateLimit-Limit
Your plan's daily budget. Sent only on plans that have one, and never on a 429 from the per-minute burst cap.
X-RateLimit-Remaining
What's left of your daily budget. Never below zero. Sent only on plans that have one, and never on a 429 from the per-minute burst cap.
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 have one, and never on a 429 from the per-minute burst cap. The per-minute burst cap has no header of its own. See Limits
Retry-Afteron a 429
Seconds to wait before retrying, from whichever limit refused the request. A 429 that also carries X-RateLimit-* was refused by the daily budget; one without them, by the per-minute burst cap. See Limits

Errors

The API returns these error status codes:

  • 400

    Malformed input: a bad parameter, an empty or over-100 bulk update, or a write that sets and clears the same field in one call.

  • 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's category creation only. It's about what you're creating, not how fast you're calling, so waiting doesn't clear it and 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.

  • 404

    An account that isn't there. Only the balance endpoint returns it: an accountGroupKey naming no account, or one that isn't shaped like a key at all, comes back 404 with no body. Transactions never 404 — see 403.

  • 409

    Something else changed the row while you were writing to it. Read it again and send your write again.

  • 429

    Too many requests. You've hit the per-minute burst cap or, on the free plan, spent your daily budget. A 429 with the X-RateLimit-* headers is the daily budget, and one without them is the burst cap. Retry-After says how long to wait, and unlike a 402, waiting frees your next request. See Limits for the per-plan figures.

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 a 404 come back with no body at all, so read the status before you read the body.

Example
{
  "statusCode": 403,
  "message": "One or more errors occurred!",
  "errors": {
    "generalErrors": ["Transaction does not belong to the authenticated user"]
  }
}

Request ID

Every response carries a fly-request-id header. Include it when you contact support about a specific request.

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 (write call)
# 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"}'

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 ten documented endpoints cap how much you can ask for in one call. The other eight don't.

Uncapped per call doesn't mean unlimited. Keys still expire, writes still have field-length limits, creating a category 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, and the single-transaction write have no per-call volume cap. You get the whole set back, or the one row you named.

  • 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

The API enforces 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 daily budget for now, so on a paid plan the burst cap is the only limit. Go over either one 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.

Rate limits by plan
PlanDaily budgetBurst cap
Basic100 a day10 a minute
OrganiseNone30 a minute
AutomateNone60 a minute
OptimiseNone60 a minute
OperateNone120 a minute

For now, paid plans have no daily budget.

Paid plans also run on fair use. That's a policy, not a counter, so the API never refuses a call for it. The API is for scripts, dashboards and integrations on your own financial data, at a volume that fits one person. If we think your use goes beyond that, we won't cut off your account without contacting you first. Pricing calls this “Unlimited (fair use) for now”.

On the free plan, every served response reports your daily budget in the X-RateLimit-* headers described under Response headers. A paid plan's responses carry none of them. No header reports the burst cap on any plan, so pace yourself against the table: on the free plan, Remaining can read well above zero just before a burst comes back 429. If Era can't measure your usage, it serves the call without the headers, so read a free-plan response with none as an unknown count, not an error.

What a 429 tells you

Which limit refused you. A 429 that carries the X-RateLimit-* headers came from the daily budget; one without them came from the burst cap. That holds on every plan. The body is problem details, and its detail field spells out the limit, what it allows, when your next request frees up, 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.

429 response, daily budget
{
  "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, never less than one. It's up to a minute for the burst cap and up to 24 hours for the free plan's daily budget. A refused call doesn't count against either limit, but a retry before Retry-After is up is refused again, so wait it out. Then 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 parameters are case-insensitive, so camelCase works there too — the published spec spells them PascalCase, which is why you'll see both forms around.

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

Everything documented here is covered by this policy.

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.

What we may change without notice

None of these break a client that follows the conventions above.

  • Add an endpoint, or an operation on an existing one.

  • Add a field to a response.

  • Add an optional parameter. Leave it out and nothing changes.

  • Add a value to a fixed set, like a status or a type.

  • Add a response header.

What we won't change without notice

Any of these can break a working client.

  • Remove an endpoint, or change its path or method.

  • Remove or rename a response field.

  • Change a field's type or meaning.

  • Require a parameter that's optional today.

  • Reject input that's accepted today.

  • Change the scope an endpoint needs.

Before any of those, the changelog says so at least 90 days ahead and tells you what to change. What works today keeps working until then.

Changes are announced on the changelog, linked in the Changelog section below. There's no email or feed yet, so check it when you're planning work against the API.

While the API is in beta, the documented set will keep growing. What's already here won't break you without warning.

Changelog

Updates to the Era Developer Platform, including the Era API, newest first. The policy above says what gets warning and how much; the changelog is where those warnings appear.

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 -- Claude

It 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. Even on a write, REST keeps no per-request log: what's recorded is the change, not the call, and the entry lands under your account rather than the key that made it.

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.

More to know before you rely on a key.
Scopes are coarse
banking:read reads more than the six 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 account, not a slice of it. banking:write can change categories, tags, and transaction metadata, manage manual accounts and balances, and connect or disconnect institutions — no scope on this page can move money between your bank accounts.
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
You're already signed in to your own account, so making a key needs nobody else's sign-off — there's no review and no waiting list, and 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.
Keys are hashed, not stored
Your key is 256 bits of random data, hashed with SHA-256 before it's stored. We keep the hash, not the key. Lose it, revoke it, and make a new one.

Core resources

Accounts

GET/banking/accounts
Scope requiredbanking:read

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.

Query parameters
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.
Response · 200
{
  "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

GET/banking/accounts/{accountId}/balance
Scope requiredbanking:read

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.

Response · 200
{
  "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

GET/banking/accounts/summary
Scope requiredbanking:read

Totals across the accounts you can see: totalAssets, totalLiabilities, and netWorthHint, which is the first minus the second. Takes no parameters.

Response · 200
{
  "userId": "7d1c0b93a8e24f60",
  "accounts": [ … ],
  "totalVisibleCount": 6,
  "totalHiddenCount": 2,
  "totalAssets": 48210.75,
  "totalLiabilities": 9327.40,
  "netWorthHint": 38883.35,
  "computedAt": "2026-08-11T09:32:00Z"
}

netWorthHint counts only the accounts in this response, so totalHiddenCount tells you what it's missing: tierExcludedAccountCount of those are left out by your plan's account limit, and userExcludedAccountCount you've hidden yourself. 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

GET/banking/transactions
Scope requiredbanking:read

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.

Query parameters
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.
Response · 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
}

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. 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. Mutating, so it needs banking:write rather than banking:read.

PUT/banking/transactions/{id}
Scope requiredbanking:write
Request body
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.
Response · 200
{
  "transaction": { … }
}

You get the whole updated transaction back, in the same shape the list above returns — not reprinted here, because it's a large object that's still moving. 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. Mutating, so it needs banking:write rather than banking:read.

PUT/banking/transactions/bulk
Scope requiredbanking:write
Request body
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.
Response · 200
{
  "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

GET/banking/categories
Scope requiredbanking:read

The whole category taxonomy: every set of categories, with its sub-categories nested inside. The taxonomy is shared, not per-account.

Response · 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
}

Add a category

A user-defined category under an existing parent. Mutating, so it needs banking:write rather than banking:read.

POST
Scope requiredbanking:write
Request body
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.
Response · 201
{
  "categoryKey": "fcat_side_hustle_9f2a",
  "overlayProjectionKey": "fcov_9f2a1c",
  "action": "created",
  "isQuotaExceeded": false,
  "createdMappingRuleKeys": [ … ],
  …
}

The response also carries retroactiveAffectedCount, mergeSourcesHiddenCount, and mergeSourcesTotalCount — fields this call shares with category merges, not shown here — 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

GET/banking/tags
Scope requiredbanking:read

Every tag on your account, as one list. No paging — one response returns all of them.

Query parameters
tagTypeoptional
Filter by tag origin: user, system, or auto.
includeDeletedoptional
Include deleted tags. Defaults to false.
Response · 200
{
  "tags": [
    {
      "tagKey": "utag_9c2f01ab",
      "name": "business-expense",
      "displayName": "Business expense",
      "tagType": "user",
      "color": "#6DC6BA",
      "transactionCount": 42
    }
  ]
}

Create a tag

A new tag, canonicalized to lowercase. Mutating, so it needs banking:write rather than banking:read. System tags cannot be created through the API; user and auto tags can.

POST
Scope requiredbanking:write
Request body
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.
Response · 201
{
  "tag": {
    "tagKey": "utag_9c2f01ab",
    "name": "business-expense",
    "displayName": "Business expense",
    "tagType": "user",
    "version": 1,
    "createdAt": "2026-08-26T09:15:00Z"
  }
}

Era Financial Advisors LLC is an SEC-registered investment adviser (CRD #334404). Registration does not imply a certain level of skill or training. Investment advisory services are discretionary and AI-assisted; they are not a substitute for personalised financial advice. Brokerage and custodial services are provided by Alpaca Securities LLC, a separate entity and member of FINRA/SIPC. Era Thesis and Era Agency accounts are currently available to US residents only; Era Context connects accounts across the US, the UK, Canada, France, Germany, Spain and 40+ countries in all. Nothing on this website is an offer or solicitation to buy or sell securities. Past performance does not guarantee future results. Please review our Form ADV and Form CRS before investing.

era© 2026 Tinwell Labs Inc. DBA Era