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¤cy=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
| Field | Type | Description |
|---|
id | integer | Budget identifier |
name | string | Display name (1-100 characters) |
original_request | string | The user’s original natural-language request that created this budget |
category_name | string | Spending category this budget tracks (e.g. “restaurants”, “groceries”) |
budget_type | string | Period type — see Budget Types |
limit_amount | decimal | Maximum spending limit for the period |
currency | string | Currency code (e.g. USD, GBP) |
spent | decimal | Total spent in the current period |
remaining | decimal | Amount remaining (limit_amount - spent) |
daily_avg | decimal | Average daily spend in the current period |
period_start | integer | Period start timestamp (epoch ms) |
period_end | integer | Period end timestamp (epoch ms) |
created_at | integer | Creation timestamp (epoch ms) |
updated_at | integer | Last update timestamp (epoch ms) |
transactions | array or null | Budget transactions (only present when expanded) |
Budget Types
| Value | Description | Period Calculation |
|---|
weekly | Resets every week | Monday 00:00 to Sunday 23:59:59 UTC |
monthly | Resets every month | 1st of month to last day of month UTC |
yearly | Resets every year | January 1st to December 31st UTC |
custom | User-defined date range | Requires 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.
| Field | Type | Description |
|---|
transaction_id | integer | Reference to the source transaction |
status | string | Processing status — see Transaction Statuses |
amount | decimal or null | Transaction amount in its original currency |
amount_in_budget_currency | decimal or null | Amount converted to the budget’s currency |
timestamp | integer or null | Transaction timestamp (epoch ms) |
running_total | decimal or null | Cumulative 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
Request body:
{
"name": "Groceries",
"original_request": "Set a $300 weekly grocery budget",
"category_name": "groceries",
"budget_type": "weekly",
"limit_amount": "300.00",
"currency": "USD"
}
| Field | Type | Required | Description |
|---|
name | string | Yes | Display name (1-100 characters) |
original_request | string | Yes | Natural-language request that prompted this budget |
category_name | string | Yes | Spending category to track |
budget_type | string | Yes | One of: weekly, monthly, yearly, custom |
limit_amount | string | Yes | Spending limit as a decimal string (must be positive) |
currency | string | Yes | Currency code (e.g. USD) |
period_start | integer | Custom only | Period start in epoch ms — required when budget_type is custom |
period_end | integer | Custom only | Period 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
| Query Parameter | Type | Default | Description |
|---|
budget_type | string | null | Filter by type: weekly, monthly, yearly, custom |
currency | string | null | Filter by currency code |
period_start | string | null | Budgets starting on or after this date. Accepts epoch ms or YYYY-MM-DD |
period_end | string | null | Budgets ending on or before this date. Accepts epoch ms or YYYY-MM-DD |
_search | string | null | Full-text search across name, original request, and category (min 2 characters) |
expand | string | none | Expand related data. Options: budget_transactions |
offset | integer | 0 | Pagination offset |
limit | integer | 20 | Results 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¤cy=USD&offset=0&limit=20" },
"first": { "href": "/v0/budgets/?budget_type=monthly¤cy=USD&offset=0&limit=20" },
"last": { "href": "/v0/budgets/?budget_type=monthly¤cy=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 Parameter | Type | Default | Description |
|---|
expand | string | none | Expand 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
}
| Field | Type | Description |
|---|
name | string | Updated display name |
category_name | string | Updated spending category |
original_request | string | Updated original request text |
limit_amount | string | New spending limit (adjusts remaining proportionally) |
budget_type | string | Change the period type |
period_start | integer | New start (epoch ms) — only for custom budgets |
period_end | integer | New 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
- Fetch all active budgets with
GET /v0/budgets/?expand=budget_transactions
- For each budget, calculate progress as
spent / limit_amount
- Use
daily_avg to project whether the user will stay within budget
- 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:
- After linking a bank account and triggering a sync, wait for the sync to complete
- Budget processing picks up new transactions automatically
- 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}
Full-Text Search
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"
}
| Status | Error Code | Meaning |
|---|
| 400 Bad Request | BUD-400-001 | Invalid budget type |
| 400 Bad Request | BUD-400-002 | Invalid currency |
| 400 Bad Request | BUD-400-003 | Invalid dates — custom budgets require both period_start and period_end, and end must be after start |
| 400 Bad Request | BUD-400-004 | Invalid query parameter — see invalid_params array for details |
| 401 Unauthorized | — | Missing or invalid authentication token |
| 403 Forbidden | BUD-403-001 | Budget does not belong to the authenticated user |
| 404 Not Found | BUD-404-001 | Budget not found or has been deleted |
| 409 Conflict | BUD-409-001 | Budget 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
| Value | Description |
|---|
weekly | Monday-to-Sunday period, auto-calculated |
monthly | First-to-last day of month, auto-calculated |
yearly | January 1st to December 31st, auto-calculated |
custom | User-defined start and end timestamps |
Budget Transaction Statuses
| Value | Description |
|---|
pending | Transaction identified, not yet classified |
relevant | Transaction counts toward the budget’s spend total |
irrelevant | Transaction excluded from spend calculation |
Error Codes
| Code | Status | Description |
|---|
BUD-400-001 | 400 | Invalid budget type |
BUD-400-002 | 400 | Invalid currency |
BUD-400-003 | 400 | Invalid period dates |
BUD-400-004 | 400 | Invalid query parameter |
BUD-403-001 | 403 | Unauthorized access to budget |
BUD-404-001 | 404 | Budget not found |
BUD-409-001 | 409 | Budget already exists |
Expand Options
| Endpoint | Option | Description |
|---|
GET /v0/budgets/ | budget_transactions | Include up to 10 recent relevant transactions per budget |
GET /v0/budgets/{id} | transactions | Include all relevant transactions for the budget |
Next Steps