Skip to main content

Budgets

Overview

The API lets users set spending budgets with configurable limits, periods, and categories. Once created, budgets are automatically populated with matching transactions from linked bank accounts, giving real-time visibility into spending progress. The budget system is:
  • Category-based — budgets target a spending category (e.g. “restaurants”, “groceries”) to track relevant transactions
  • Period-aware — supports weekly, monthly, yearly, and custom date ranges with automatic period calculation
  • Auto-processed — after creation, an asynchronous workflow matches existing transactions to the budget
  • Multi-currency — budgets are denominated in a specific currency, with transaction amounts converted automatically
  • Expandable — fetch budget transactions inline via the expand parameter

Quick Start

1. Create a budget

Set a monthly spending limit for a category:
curl -X POST /v0/budgets/ \
  -H "x-juno-jwt: <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Dining Out",
    "original_request": "Track my restaurant spending this month",
    "category_name": "restaurants",
    "budget_type": "monthly",
    "limit_amount": "500.00",
    "currency": "USD"
  }'
Response: 201 Created
{
  "budget": {
    "id": 42,
    "name": "Dining Out",
    "original_request": "Track my restaurant spending this month",
    "category_name": "restaurants",
    "budget_type": "monthly",
    "limit_amount": "500.00",
    "currency": "USD",
    "spent": "0.00",
    "remaining": "500.00",
    "daily_avg": "0.00",
    "period_start": 1735689600000,
    "period_end": 1738367999999,
    "created_at": 1735700000000,
    "updated_at": 1735700000000,
    "transactions": null
  },
  "_links": {
    "self": { "href": "/v0/budgets/42" },
    "user": { "href": "/v0/user/me" },
    "transactions": { "href": "/v0/budgets/42?expand=transactions" },
    "update": { "href": "/v0/budgets/42", "method": "PATCH" },
    "delete": { "href": "/v0/budgets/42", "method": "DELETE" },
    "category-transactions": { "href": "/v0/transactions?category=restaurants" }
  }
}
A background workflow immediately begins matching existing bank transactions to the budget. The spent, remaining, and daily_avg fields update once processing completes.

2. Check budget progress

curl /v0/budgets/42?expand=transactions \
  -H "x-juno-jwt: <token>"
After processing, the response reflects actual spending:
{
  "budget": {
    "id": 42,
    "name": "Dining Out",
    "category_name": "restaurants",
    "budget_type": "monthly",
    "limit_amount": "500.00",
    "currency": "USD",
    "spent": "127.50",
    "remaining": "372.50",
    "daily_avg": "8.50",
    "period_start": 1735689600000,
    "period_end": 1738367999999,
    "created_at": 1735700000000,
    "updated_at": 1735750000000,
    "transactions": [
      {
        "transaction_id": 301,
        "status": "relevant",
        "amount": "42.50",
        "amount_in_budget_currency": "42.50",
        "timestamp": 1735720000000,
        "running_total": "42.50"
      },
      {
        "transaction_id": 305,
        "status": "relevant",
        "amount": "85.00",
        "amount_in_budget_currency": "85.00",
        "timestamp": 1735740000000,
        "running_total": "127.50"
      }
    ]
  },
  "_links": { "..." : "..." }
}

3. List all budgets

curl "/v0/budgets/?budget_type=monthly&currency=USD" \
  -H "x-juno-jwt: <token>"

4. Update or delete

curl -X PATCH /v0/budgets/42 \
  -H "x-juno-jwt: <token>" \
  -H "Content-Type: application/json" \
  -d '{"limit_amount": "600.00"}'
curl -X DELETE /v0/budgets/42 \
  -H "x-juno-jwt: <token>"

Budget Model

Response Shape

FieldTypeDescription
idintegerBudget identifier
namestringDisplay name (1-100 characters)
original_requeststringThe user’s original natural-language request that created this budget
category_namestringSpending category this budget tracks (e.g. “restaurants”, “groceries”)
budget_typestringPeriod type — see Budget Types
limit_amountdecimalMaximum spending limit for the period
currencystringCurrency code (e.g. USD, GBP)
spentdecimalTotal spent in the current period
remainingdecimalAmount remaining (limit_amount - spent)
daily_avgdecimalAverage daily spend in the current period
period_startintegerPeriod start timestamp (epoch ms)
period_endintegerPeriod end timestamp (epoch ms)
created_atintegerCreation timestamp (epoch ms)
updated_atintegerLast update timestamp (epoch ms)
transactionsarray or nullBudget transactions (only present when expanded)

Budget Types

ValueDescriptionPeriod Calculation
weeklyResets every weekMonday 00:00 to Sunday 23:59:59 UTC
monthlyResets every month1st of month to last day of month UTC
yearlyResets every yearJanuary 1st to December 31st UTC
customUser-defined date rangeRequires explicit period_start and period_end
For weekly, monthly, and yearly types, the API automatically calculates period_start and period_end based on the current date at creation time.

Budget Transactions

When you expand transactions, each entry represents a bank transaction that was matched to the budget during processing.
FieldTypeDescription
transaction_idintegerReference to the source transaction
statusstringProcessing status — see Transaction Statuses
amountdecimal or nullTransaction amount in its original currency
amount_in_budget_currencydecimal or nullAmount converted to the budget’s currency
timestampinteger or nullTransaction timestamp (epoch ms)
running_totaldecimal or nullCumulative spend at this transaction

Categories

Budget categories correspond to the transaction categories assigned during bank sync. Common categories include restaurants, groceries, transport, entertainment, shopping, and utilities. The category_name field on a budget must match the category values on your synced transactions.

Endpoints

Create Budget

POST /v0/budgets/
Request body:
{
  "name": "Groceries",
  "original_request": "Set a $300 weekly grocery budget",
  "category_name": "groceries",
  "budget_type": "weekly",
  "limit_amount": "300.00",
  "currency": "USD"
}
FieldTypeRequiredDescription
namestringYesDisplay name (1-100 characters)
original_requeststringYesNatural-language request that prompted this budget
category_namestringYesSpending category to track
budget_typestringYesOne of: weekly, monthly, yearly, custom
limit_amountstringYesSpending limit as a decimal string (must be positive)
currencystringYesCurrency code (e.g. USD)
period_startintegerCustom onlyPeriod start in epoch ms — required when budget_type is custom
period_endintegerCustom onlyPeriod end in epoch ms — required when budget_type is custom
Response: 201 Created — returns the budget with spent, remaining, and daily_avg initialized to zero. After creation, a background workflow processes existing transactions against the budget. Poll GET /v0/budgets/{id} to see updated spending figures.
Custom budgets require both period_start and period_end. The API returns 400 Bad Request if either is missing or if period_end is not after period_start.

List Budgets

GET /v0/budgets/
Query ParameterTypeDefaultDescription
budget_typestringnullFilter by type: weekly, monthly, yearly, custom
currencystringnullFilter by currency code
period_startstringnullBudgets starting on or after this date. Accepts epoch ms or YYYY-MM-DD
period_endstringnullBudgets ending on or before this date. Accepts epoch ms or YYYY-MM-DD
_searchstringnullFull-text search across name, original request, and category (min 2 characters)
expandstringnoneExpand related data. Options: budget_transactions
offsetinteger0Pagination offset
limitinteger20Results per page (1-100)
Response: 200 OK
{
  "budgets": [
    {
      "id": 42,
      "name": "Dining Out",
      "original_request": "Track my restaurant spending this month",
      "category_name": "restaurants",
      "budget_type": "monthly",
      "limit_amount": "500.00",
      "currency": "USD",
      "spent": "127.50",
      "remaining": "372.50",
      "daily_avg": "8.50",
      "period_start": 1735689600000,
      "period_end": 1738367999999,
      "created_at": 1735700000000,
      "updated_at": 1735750000000,
      "transactions": null
    }
  ],
  "total": 1,
  "offset": 0,
  "limit": 20,
  "_links": {
    "self": { "href": "/v0/budgets/?budget_type=monthly&currency=USD&offset=0&limit=20" },
    "first": { "href": "/v0/budgets/?budget_type=monthly&currency=USD&offset=0&limit=20" },
    "last": { "href": "/v0/budgets/?budget_type=monthly&currency=USD&offset=0&limit=20" },
    "user": { "href": "/v0/user/me" }
  }
}
When using the expand=budget_transactions parameter on the list endpoint, each budget includes up to 10 most recent relevant transactions. For the full transaction history, fetch individual budgets with expand=transactions.

Get Budget

GET /v0/budgets/{budget_id}
Query ParameterTypeDefaultDescription
expandstringnoneExpand related data. Options: transactions
Response: 200 OK — returns the budget with _links. If expand=transactions is set, the transactions array is populated with all relevant budget transactions ordered by timestamp.

Update Budget

PATCH /v0/budgets/{budget_id}
Request body (all fields optional):
{
  "name": "Dining & Takeout",
  "category_name": "restaurants",
  "limit_amount": "600.00",
  "budget_type": "monthly",
  "period_start": 1735689600000,
  "period_end": 1738367999999
}
FieldTypeDescription
namestringUpdated display name
category_namestringUpdated spending category
original_requeststringUpdated original request text
limit_amountstringNew spending limit (adjusts remaining proportionally)
budget_typestringChange the period type
period_startintegerNew start (epoch ms) — only for custom budgets
period_endintegerNew end (epoch ms) — only for custom budgets
Response: 200 OK Behavior:
  • Updating limit_amount recalculates remaining based on the difference: if the old limit was 500 with 372.50 remaining, setting the new limit to 600 gives 472.50 remaining.
  • Changing budget_type resets spent, daily_avg, and remaining to reflect the new period. For non-custom types, the API recalculates period bounds automatically.
  • Changing to custom type requires both period_start and period_end.

Delete Budget

DELETE /v0/budgets/{budget_id}
Response: 204 No Content Soft-deletes the budget. The budget and its transaction associations are no longer returned in queries.

Integration Patterns

Building a Budget Dashboard

  1. Fetch all active budgets with GET /v0/budgets/?expand=budget_transactions
  2. For each budget, calculate progress as spent / limit_amount
  3. Use daily_avg to project whether the user will stay within budget
  4. Show remaining as the amount left to spend in the period
Pseudocode:
budgets = GET /v0/budgets/?expand=budget_transactions

for budget in budgets:
    progress = budget.spent / budget.limit_amount
    days_left = days_until(budget.period_end)
    projected_total = budget.spent + (budget.daily_avg * days_left)
    on_track = projected_total <= budget.limit_amount

    render_budget_card(budget, progress, on_track)

Tracking Spend vs Limit

Budget processing happens asynchronously after creation and after bank transaction syncs. To ensure your dashboard reflects the latest data:
  1. After linking a bank account and triggering a sync, wait for the sync to complete
  2. Budget processing picks up new transactions automatically
  3. Poll GET /v0/budgets/{id} to see updated spent and remaining values
The daily_avg field divides total spend by the number of elapsed days in the period, giving a useful projection baseline.

Creating Budgets After Bank Sync

For the best experience, create budgets after the user has connected a bank account and completed an initial transaction sync. This lets the budget processing workflow immediately populate spending data from existing transactions.
# 1. User links bank account and syncs
POST /v0/user/me/bank/accounts { ... }
POST /v0/user/me/bank/accounts/{id}/sync

# 2. Once sync completes, create budgets
POST /v0/budgets/ {
    "name": "Monthly Groceries",
    "category_name": "groceries",
    "budget_type": "monthly",
    "limit_amount": "400.00",
    "currency": "USD"
}

# 3. Budget processing matches synced transactions
# Poll to see updated figures
GET /v0/budgets/{id}
Use the _search query parameter to find budgets by name, category, or original request:
curl "/v0/budgets/?_search=groceries" \
  -H "x-juno-jwt: <token>"
The search uses full-text matching across name, original_request, and category_name fields. The query must be at least 2 characters.

Error Handling

All errors follow RFC 7807 Problem Details format:
{
  "type": "https://api.sumvin.com/errors/bud-404-001",
  "title": "Budget Not Found",
  "status": 404,
  "detail": "Budget 42 not found",
  "instance": "/v0/budgets/42",
  "error_code": "BUD-404-001"
}
StatusError CodeMeaning
400 Bad RequestBUD-400-001Invalid budget type
400 Bad RequestBUD-400-002Invalid currency
400 Bad RequestBUD-400-003Invalid dates — custom budgets require both period_start and period_end, and end must be after start
400 Bad RequestBUD-400-004Invalid query parameter — see invalid_params array for details
401 UnauthorizedMissing or invalid authentication token
403 ForbiddenBUD-403-001Budget does not belong to the authenticated user
404 Not FoundBUD-404-001Budget not found or has been deleted
409 ConflictBUD-409-001Budget already exists (duplicate)
Query validation errors (BUD-400-004) include an invalid_params array identifying each problematic parameter:
{
  "type": "https://api.sumvin.com/errors/bud-400-004",
  "title": "Invalid Query Parameter",
  "status": 400,
  "detail": "One or more query parameters are invalid",
  "instance": "/v0/budgets",
  "error_code": "BUD-400-004",
  "invalid_params": [
    {
      "name": "period_start",
      "reason": "Must be epoch milliseconds (int) or date string (YYYY-MM-DD)",
      "provided": "not-a-date",
      "valid_example": "2024-01-15 or 1705276800000"
    }
  ]
}

Reference Tables

Budget Types

ValueDescription
weeklyMonday-to-Sunday period, auto-calculated
monthlyFirst-to-last day of month, auto-calculated
yearlyJanuary 1st to December 31st, auto-calculated
customUser-defined start and end timestamps

Budget Transaction Statuses

ValueDescription
pendingTransaction identified, not yet classified
relevantTransaction counts toward the budget’s spend total
irrelevantTransaction excluded from spend calculation

Error Codes

CodeStatusDescription
BUD-400-001400Invalid budget type
BUD-400-002400Invalid currency
BUD-400-003400Invalid period dates
BUD-400-004400Invalid query parameter
BUD-403-001403Unauthorized access to budget
BUD-404-001404Budget not found
BUD-409-001409Budget already exists

Expand Options

EndpointOptionDescription
GET /v0/budgets/budget_transactionsInclude up to 10 recent relevant transactions per budget
GET /v0/budgets/{id}transactionsInclude all relevant transactions for the budget

Next Steps