ReferenceReference by module

Budgets API

Every Budgets action (6) and data model (2): endpoints, scopes, input, preconditions, events and examples.

A spending plan per expense category, with alerts when you go over.

Module code budgets · 6 actions · 2 data models · 5 events. A key or token never does more here than the person behind it can; row-level security applies exactly as in the app. Scopes: budgets (everything), budgets:read (read only) or budgets:<capability>.

Resources

ResourceTableList and readPOSTPATCHDELETE
budget_linesbudget_line/api/v1/budget_lines
budgetsbudget/api/v1/budgetsbudgets.createbudgets.update

Lists take limit (up to 200), cursor, order (e.g. -created_at), fields and q; every other query parameter is a column filter: ?status=paid, ?total[gte]=100, ?id[in]=a,b, ?x=null.

Actions

ActionNeedsWhat it does
budgets.check_alertsbudgets:writeCheck budget alerts
budgets.closebudgets:manageClose a budget
budgets.createbudgets:writePlan a budget
budgets.remove_linebudgets:writeRemove a category
budgets.set_linebudgets:writePlan a category
budgets.updatebudgets:writeEdit a budget

budgets.check_alerts

Check budget alerts — Compares actual spending with the plan and raises each threshold once.

Needs budgets:write · changes data

POSThttps://app.getera.io/api/v1/budget_lines/actions/check_alerts
API key or OAuth token · budgets:write
  • Canonical address: POST /api/v1/actions/budgets.check_alerts

Input

FieldTypeRequiredNotes
expenseIduuidExpense · id of expense
budgetIduuidBudget · id of budget

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
expense_not_foundA named expense must exist.

Events it publishes: budgets.threshold_reached, budgets.exceeded

What it writes: budget_line (update) — Stamps the alert level

curl -X POST https://app.getera.io/api/v1/budget_lines/actions/check_alerts \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"expenseId":"EXPENSE_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "budgets.threshold_reached",
    "budgets.exceeded"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.budgetLines.checkAlerts({
  expenseId: "EXPENSE_ID"
})
era budget_lines check_alerts \
  --expense-id "EXPENSE_ID"
tools/call
{
  "name": "era__budgets__check_alerts",
  "arguments": {
    "expenseId": "EXPENSE_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold budgets:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 expense_not_found · 429 slow down

budgets.close

Close a budget — Freezes the plan; it stops raising alerts.

Needs budgets:manage · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/budgets/{id}/actions/close
API key or OAuth token · budgets:manage
  • Canonical address: POST /api/v1/actions/budgets.close

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Budget · id of budget

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
budget_not_foundThe budget must exist.
budget_closedA budget is closed once.

Events it publishes: budgets.closed

What it writes: budget (update) — Closes it

curl -X POST https://app.getera.io/api/v1/budgets/BUDGET_ID/actions/close \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "budgets.closed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.budgets.close("BUDGET_ID")
era budgets close BUDGET_ID
tools/call
{
  "name": "era__budgets__close",
  "arguments": {
    "id": "BUDGET_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold budgets:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 budget_not_found, budget_closed · 429 slow down

budgets.create

Plan a budget — A period, a currency and a planned amount per expense category.

Needs budgets:write · changes data

POSThttps://app.getera.io/api/v1/budgets/actions/create
API key or OAuth token · budgets:write
  • REST alias: POST /api/v1/budgets
  • Canonical address: POST /api/v1/actions/budgets.create

Input

FieldTypeRequiredNotes
namestringyesName · max 255
periodStartdateyesFrom
periodEnddateyesTo
currencystringCurrency · max 3
alertThresholdPercentnumberWarn at (%) · min 1 · max 100
notesstringNotes · max 4000
linesobject / arrayLines

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
budget_period_invalidA period cannot end before it starts.
invalid_currencyThe currency must be an ISO-4217 code.
invalid_budget_linesEvery planned amount is zero or more.
expense_category_not_availableEach line names an active expense category.

Events it publishes: budgets.created

What it writes: budget (insert) — Plans the budget; budget_line (insert) — Writes its lines

curl -X POST https://app.getera.io/api/v1/budgets/actions/create \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name":"Name","periodStart":"2026-11-02","periodEnd":"2026-11-02"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "budgets.created"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.budgets.create({
  name: "Name",
  periodStart: "2026-11-02",
  periodEnd: "2026-11-02"
})
era budgets create \
  --name "Name" \
  --period-start "2026-11-02" \
  --period-end "2026-11-02"
tools/call
{
  "name": "era__budgets__create",
  "arguments": {
    "name": "Name",
    "periodStart": "2026-11-02",
    "periodEnd": "2026-11-02"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold budgets:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 budget_period_invalid, invalid_currency, invalid_budget_lines, expense_category_not_available · 429 slow down

budgets.remove_line

Remove a category — Takes a category out of the plan.

Needs budgets:write · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/budget_lines/{id}/actions/remove_line
API key or OAuth token · budgets:write
  • Canonical address: POST /api/v1/actions/budgets.remove_line

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Line · id of budget_line

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
budget_line_not_foundThe line must exist.
budget_closedA closed budget is not edited.

Events it publishes: budgets.updated

What it writes: budget_line (delete) — Removes the line

curl -X POST https://app.getera.io/api/v1/budget_lines/BUDGET_LINE_ID/actions/remove_line \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "budgets.updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.budgetLines.removeLine("BUDGET_LINE_ID")
era budget_lines remove_line BUDGET_LINE_ID
tools/call
{
  "name": "era__budgets__remove_line",
  "arguments": {
    "id": "BUDGET_LINE_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold budgets:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 budget_line_not_found, budget_closed · 429 slow down

budgets.set_line

Plan a category — Sets the planned amount for one expense category.

Needs budgets:write · changes data

POSThttps://app.getera.io/api/v1/budget_lines/actions/set_line
API key or OAuth token · budgets:write
  • Canonical address: POST /api/v1/actions/budgets.set_line

Input

FieldTypeRequiredNotes
budgetIduuidyesBudget · id of budget
categoryIduuidyesCategory · id of expense_category
plannedAmountnumber (money)yesPlanned · min 0

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
budget_not_foundThe budget must exist.
budget_closedA closed budget is not edited.
invalid_budget_linesA planned amount is zero or more.
expense_category_not_availableThe category must be active.

Events it publishes: budgets.updated

What it writes: budget_line (upsert) — Writes the line

curl -X POST https://app.getera.io/api/v1/budget_lines/actions/set_line \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"budgetId":"BUDGET_ID","categoryId":"EXPENSE_CATEGORY_ID","plannedAmount":1}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "budgets.updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.budgetLines.setLine({
  budgetId: "BUDGET_ID",
  categoryId: "EXPENSE_CATEGORY_ID",
  plannedAmount: 1
})
era budget_lines set_line \
  --budget-id "BUDGET_ID" \
  --category-id "EXPENSE_CATEGORY_ID" \
  --planned-amount 1
tools/call
{
  "name": "era__budgets__set_line",
  "arguments": {
    "budgetId": "BUDGET_ID",
    "categoryId": "EXPENSE_CATEGORY_ID",
    "plannedAmount": 1
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold budgets:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 budget_not_found, budget_closed, invalid_budget_lines, expense_category_not_available · 429 slow down

budgets.update

Edit a budget — Renames it, moves its warning threshold, or switches it between draft and active.

Needs budgets:write · changes data

POSThttps://app.getera.io/api/v1/budgets/{id}/actions/update
API key or OAuth token · budgets:write
  • REST alias: PATCH /api/v1/budgets/{id}
  • Canonical address: POST /api/v1/actions/budgets.update

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Budget · id of budget
namestringName · max 255
alertThresholdPercentnumberWarn at (%) · min 1 · max 100
notesstringNotes · max 4000
statusstring (enum)Status · Allowed values: draft, active

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
budget_not_foundThe budget must exist.
budget_closedA closed budget is not edited.

Events it publishes: budgets.updated

What it writes: budget (update) — Updates the budget; budget_line (update) — Clears alerts when the threshold moves

curl -X POST https://app.getera.io/api/v1/budgets/BUDGET_ID/actions/update \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name":"Name"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "budgets.updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.budgets.update("BUDGET_ID", {
  name: "Name"
})
era budgets update BUDGET_ID \
  --name "Name"
tools/call
{
  "name": "era__budgets__update",
  "arguments": {
    "id": "BUDGET_ID",
    "name": "Name"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold budgets:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 budget_not_found, budget_closed · 429 slow down

Data models

BudgetLine

Table budget_line · Resource budget_lines

  • GET /api/v1/budget_lines
  • GET /api/v1/budget_lines/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
budget_idstring (uuid) · uuidbudget
expense_category_idstring (uuid) · uuidexpense_category
planned_amountstring (int64) · int8
alert_levelstring · textyes
alerted_atstring (date-time) · timestamptzyes
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

Unique: budget_id, expense_category_id

Checks:

  • CHECK (((alert_level IS NULL) OR (alert_level = ANY (ARRAY['warning'::text, 'exceeded'::text]))))
  • CHECK ((planned_amount >= 0))

Budget

Table budget · Resource budgets

ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
namestring · text
period_startstring (date) · date
period_endstring (date) · date
currencystring · bpcharmax 3
statusstring · textdraft, active, closed
alert_threshold_percentstring (decimal) · numeric
notesstring · textyes
created_by_user_idstring (uuid) · uuidyesapp_user
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

Checks:

  • CHECK (((alert_threshold_percent > (0)::numeric) AND (alert_threshold_percent <= (100)::numeric)))
  • CHECK ((currency ~ '^[A-Z]{3}$'::text))
  • CHECK ((length(TRIM(BOTH FROM name)) > 0))
  • CHECK ((period_end >= period_start))

Events

Workflows and webhooks react to these events.

EventWhat happensPayload
budgets.closedA budget was closed.id
budgets.createdA budget was planned.id
budgets.exceededA category went over budget.id, lineId, category, plannedAmount, actualAmount, currency
budgets.threshold_reachedA category crossed its warning threshold.id, lineId, category, plannedAmount, actualAmount, currency
budgets.updatedA budget changed.id
Was this page helpful?