Early access. The API is not open to everyone yet, so the examples below will not work for you until your access is ready. Join the waitlist and we will email you.
Use your Budget Hub data from your own code
A personal API key gives your scripts the same access you have. Read your budget, goals and investments, or update them. Everything is JSON, amounts are exact, and you only ever see your own data.
Join the waitlist. We are opening access gradually and will email you when yours is ready. Then create a key from Developer API in your dashboard. Choose Read only unless your script needs to change data, and copy the key straight away: it is shown once, and we keep only a fingerprint.
Make your first call
Send the key as a Bearer token. If you get your username and plan back, you are connected.
If it says consent_required, log in to Budget Hub once and accept the current Terms and Privacy Policy. Then try again.
Send your key in the Authorization header on every request:
Authorization: Bearer bh_...
Read only keys can use GET. Read and write keys can also POST, PATCH and DELETE.
Keys can expire and you can revoke them at any time from your dashboard. You can hold a limited number of active keys.
A key is a secret: anyone holding it can act as you, within its access level. Keep it out of source control and client-side code. If one leaks, revoke it.
A browser login cookie does not authenticate the API.
Conventions
JSON only
Send Content-Type: application/json. Responses are always JSON.
Money
Rand amounts are decimal strings with two places, like "1234.50", never floats. You can send a string or a JSON number. More than two decimal places is an error: nothing is silently rounded. Amounts run from 0 to 999,999,999.99.
Dates
ISO 8601: 2026-10-31 for dates, UTC timestamps for date-times.
Strict input
Unknown and read-only fields are rejected with validation_error instead of ignored, so a typo can never look like a success. An empty PATCH is an error too.
Changing data
POST creates and returns 201 with a Location header. PATCH changes only the fields you send and returns the updated object. DELETE returns 204. There is no PUT.
Income and expenses
These are your current snapshot, not a list of monthly records. GET returns the latest figures. PATCH updates them in place and moves their date to today, exactly like the website. Monthly reports carry the latest snapshot forward and tell you which date it is from.
Pagination
List endpoints take ?limit= (default 50, max 200) and ?offset=, and return count, next, previous and results.
Your data only
You can only see and change your own data. Asking for someone else's object returns 404, the same as one that does not exist.
Trailing slashes
Optional on every route.
Versioning
This is v1. We may add fields and endpoints without notice, so ignore fields you do not recognise. A breaking change would ship as v2.
Errors
Every error has the same shape. Branch on code. message is for people and may change.
not_authenticated401No API key was sent.
authentication_failed401The key is wrong, revoked or expired. These are deliberately indistinguishable.
consent_required403The key owner must accept the current Terms and Privacy Policy. The message contains the link.
read_only_key403A read-only key was used for a write.
plan_limit_reached403Your plan does not allow another of this resource. See error.details.
permission_denied403Not allowed.
not_found404No such route, or no such object belonging to you.
no_data404Income or expenses have never been recorded. PATCH to create them.
method_not_allowed405That method is not supported on this route (there is no PUT).
setup_incomplete409Finish setting up your Budget Hub profile first.
analytics_unavailable503Analytics could not be calculated right now. Try again shortly.
payload_too_large413The request body or upload is too large.
invalid_statement422The file was readable but is not a usable bank statement: missing date or description columns, no valid rows, or an impossible amount.
unsupported_media_type415Send JSON with Content-Type: application/json.
validation_error400The request was invalid. See error.details (keyed by field).
parse_error400The body is not valid JSON.
throttled429Too many requests. See error.retry_after (seconds) and the Retry-After header.
server_error500Something went wrong on our side.
validation_error
{
"error": {
"code": "validation_error",
"message": "The request was invalid.",
"details": {
"target_amount": [
"Ensure this value is greater than or equal to 0.01."
]
}
}
}
plan_limit_reached
{
"error": {
"code": "plan_limit_reached",
"message": "You have reached the limit of 3 goals on your plan. Upgrade to add more.",
"details": {
"resource": "goals",
"used": 3,
"limit": 3,
"upgrade_url": "https://www.budget-hub.com/select-plan/individual/"
}
}
}
throttled
{
"error": {
"code": "throttled",
"message": "Too many requests. Try again in 12 seconds.",
"retry_after": 12
}
}
Limits
Rate limits
Limits apply per key, plus a total across all your keys and a per-address limit. Go over and you get 429 with a Retry-After header, in seconds.
Per key: 120/min, and for writes 30/min
Per user, all keys together: 3000/hour
Per IP address: 300/min
Statement uploads: 10/hour per key
Plan limits
The API enforces the same limits as the website. On the Free plan you can have 3 active goals, 3 investments and 5 active tracked subscriptions. Going over returns 403 plan_limit_reached with the numbers in error.details. Paid plans are unlimited.
Statement uploads on the Free plan: 1 a month, importing the newest 50 transactions. Any plan: up to 4 MB and 5,000 rows per file.
GET /me/ shows what you have used.
Endpoints
Account
Check that your key works, and see your plan and how much of it you have used.
The key owner, the key in use, your plan and how much of each limited resource you have used, and your consent status. This is the one endpoint that works before you have accepted the current Terms, so a client can tell you why everything else returns consent_required.
money string Read-only. Target minus current, never below zero.
progress_percentage
percent string Read-only.
current_milestone
string: start, 10, 25, 50, 75, 100 Read-only.
achievement_level
string: bronze, silver, gold, platinum Read-only.
achievement_level_display
string Read-only.
streak_count
integer Read-only.
start_date
string (date) Read-only.
target_date
string or null (date) Read-only.
last_contribution_date
string (date) Read-only.
PATCH/api/v1/goals/{id}/
Update a goal
Changing target_amount recalculates progress. Progress, milestone and current amount are read-only: they change only through contributions. Reactivating counts toward your plan limit.
The current month with the same numbers as the PDF report. If income or expenses were last saved in an earlier month they are carried forward and income_as_of / expenses_as_of say when.
string or null (date) null when income was saved this month; otherwise the date of the earlier snapshot carried forward.
expenses
object The eight expense group subtotals plus total.
expenses_as_of
string or null (date)
months_with_data
integer Months in the trend that have real dated rows.
prev_income
object Previous month, real rows only (no carry-forward).
prev_expenses
object
net_savings
money string
prev_net_savings
money string
savings_rate
string Net savings as a percent of income.
prev_savings_rate
string
budget
object
trend
array Six months ending this month, from genuinely dated rows only.
goals
array
investments
object
subscriptions
object
Analytics
The Analytics page as data: health score, breakdowns, trend, goal predictions and bank comparison.
GET/api/v1/analytics/
Analytics
Everything on the Analytics page, calculated the same way: totals and savings rate, the financial health score and what it is made of, income and spending breakdowns, the insight cards with their advice, a monthly trend, your goals with pace, predicted finish date and whether you are on track, and what your savings would earn at each bank. For each goal it also adds months_left and required_per_month, which the page does not show.
query How far back the monthly trend goes: 3m, 6m, 12m, ytd (ytd = since January). Default 6m.
Response fields
range
object The trend window you asked for.
as_of
object Which month your income and expense snapshot belongs to, and whether that is before this month.
totals
object From your current snapshot. savings_rate is rounded to one decimal, as on the page.
health
object The financial health score (0-100) and what it is made of. The budget and debt parts only count when you have a budget set and income.
month_over_month
object Percent change versus last month. null when there is no earlier month to compare.
income_breakdown
array
expense_breakdown
array
insights
object The page's insight cards, with their advice text. null when there is no spending or income to analyse.
months
array Oldest first. Unlike the monthly report, a month with no edit carries your latest earlier snapshot forward, so this is a smooth series, not a record of what you saved each month. The page's month table is this list reversed.
profile
object The two figures from your profile that the score and projections use.
projections
object
goals
array Up to 50 goals, furthest along first.
banks
object What your monthly savings would earn in a year at each bank's top savings rate. The rates are curated by hand, not live.
Statements
Upload a bank statement CSV. Budget Hub detects the bank and categorises every transaction.
GET/api/v1/statements/
List statements
Your uploaded bank statements, newest upload first.
Upload a bank statement as a CSV. Send it as multipart/form-data with a file part, or as a raw text/csv body (name it with ?file_name=). The bank is detected from the column headers (Capitec, FNB, Standard Bank, Nedbank, Absa, or a generic CSV with date, description and amount or debit/credit columns), and every transaction is categorised automatically. Limits: 4 MB and 5,000 rows per file; the Free plan allows one upload a month and imports the newest 50 transactions. Rows that cannot be imported are counted in skipped_rows, and uploading a period you already have from the same bank returns an overlaps_existing_statement warning (it would double-count).
Changes the category and marks the transaction as corrected by you (user_corrected true, auto_categorized false). Nothing else about a transaction can be edited: it is a record of what the bank reported.