Budget HubAPI
Join the waitlist

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 Read the quick start

Base URL https://www.budget-hub.com/api/v1
Request
curl "https://www.budget-hub.com/api/v1/goals/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/goals/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/goals/", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(await response.json());
200 OK
{
  "count": 2,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 12,
      "name": "Emergency fund (6 months)",
      "description": "",
      "category": {
        "id": 1,
        "name": "Emergency Fund"
      },
      "priority": 1,
      "priority_display": "Highest",
      "is_active": true,
      "target_amount": "72000.00",
      "current_amount": "10000.00",
      "remaining_amount": "62000.00",
      "progress_percentage": "13.89",
      "current_milestone": "10",
      "achievement_level": "bronze",
      "achievement_level_display": "Bronze",
      "streak_count": 0,
      "start_date": "2026-09-01",
      "target_date": "2027-11-17",
      "last_contribution_date": "2026-10-12"
    }
  ]
}

Quick start

Create a key

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.

Request
curl "https://www.budget-hub.com/api/v1/me/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/me/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/me/", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(await response.json());
200 OK
{
  "username": "apidemo",
  "key": {
    "name": "Month-end script",
    "prefix": "bh_nfkUNC",
    "scope": "write"
  },
  "plan": {
    "tier": "free",
    "usage": {
      "goals": {
        "used": 2,
        "limit": 3,
        "can_create": true
      },
      "investments": {
        "used": 2,
        "limit": 3,
        "can_create": true
      },
      "tracked_subscriptions": {
        "used": 3,
        "limit": 5,
        "can_create": true
      }
    }
  },
  "setup_completed": true,
  "consent": {
    "current_version": "2026-05-17",
    "accepted_current_version": true,
    "accept_url": "https://www.budget-hub.com/consent/"
  }
}

Change something

Writes need a Read and write key. This creates a savings goal and returns it, with a Location header pointing at the new goal.

Send a field we do not know, such as a typo, and you get an error instead of a silent no-op.

Request
curl -X POST "https://www.budget-hub.com/api/v1/goals/" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Emergency fund",
  "target_amount": "60000.00",
  "priority": 1,
  "target_date": "2027-10-12"
}'
import requests

response = requests.post(
    "https://www.budget-hub.com/api/v1/goals/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
    json={
        "name": "Emergency fund",
        "target_amount": "60000.00",
        "priority": 1,
        "target_date": "2027-10-12",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/goals/", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "name": "Emergency fund",
    "target_amount": "60000.00",
    "priority": 1,
    "target_date": "2027-10-12"
  }),
});
console.log(await response.json());
201 Created
Location: https://www.budget-hub.com/api/v1/goals/12
{
  "id": 12,
  "name": "Emergency fund (6 months)",
  "description": "",
  "category": {
    "id": 1,
    "name": "Emergency Fund"
  },
  "priority": 1,
  "priority_display": "Highest",
  "is_active": true,
  "target_amount": "72000.00",
  "current_amount": "10000.00",
  "remaining_amount": "62000.00",
  "progress_percentage": "13.89",
  "current_milestone": "10",
  "achievement_level": "bronze",
  "achievement_level_display": "Bronze",
  "streak_count": 0,
  "start_date": "2026-09-01",
  "target_date": "2027-11-17",
  "last_contribution_date": "2026-10-12"
}

Authentication

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_authenticated 401 No API key was sent.
authentication_failed 401 The key is wrong, revoked or expired. These are deliberately indistinguishable.
consent_required 403 The key owner must accept the current Terms and Privacy Policy. The message contains the link.
read_only_key 403 A read-only key was used for a write.
plan_limit_reached 403 Your plan does not allow another of this resource. See error.details.
permission_denied 403 Not allowed.
not_found 404 No such route, or no such object belonging to you.
no_data 404 Income or expenses have never been recorded. PATCH to create them.
method_not_allowed 405 That method is not supported on this route (there is no PUT).
setup_incomplete 409 Finish setting up your Budget Hub profile first.
analytics_unavailable 503 Analytics could not be calculated right now. Try again shortly.
payload_too_large 413 The request body or upload is too large.
invalid_statement 422 The file was readable but is not a usable bank statement: missing date or description columns, no valid rows, or an impossible amount.
unsupported_media_type 415 Send JSON with Content-Type: application/json.
validation_error 400 The request was invalid. See error.details (keyed by field).
parse_error 400 The body is not valid JSON.
throttled 429 Too many requests. See error.retry_after (seconds) and the Retry-After header.
server_error 500 Something 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.

GET /api/v1/

API root

Unauthenticated; confirms the API is reachable.

No key needed

Request
curl "https://www.budget-hub.com/api/v1/"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/",
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/", {

});
console.log(await response.json());
200 OK
{
  "name": "Budget Hub API",
  "version": "v1"
}

GET /api/v1/openapi.json

OpenAPI schema

This API described as OpenAPI 3.1 (machine-readable).

No key needed

Request
curl "https://www.budget-hub.com/api/v1/openapi.json"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/openapi.json",
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/openapi.json", {

});
console.log(await response.json());
200 OK

No body.

GET /api/v1/me/

Who am I

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.

Request
curl "https://www.budget-hub.com/api/v1/me/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/me/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/me/", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(await response.json());
200 OK
{
  "username": "apidemo",
  "key": {
    "name": "Month-end script",
    "prefix": "bh_nfkUNC",
    "scope": "write"
  },
  "plan": {
    "tier": "free",
    "usage": {
      "goals": {
        "used": 2,
        "limit": 3,
        "can_create": true
      },
      "investments": {
        "used": 2,
        "limit": 3,
        "can_create": true
      },
      "tracked_subscriptions": {
        "used": 3,
        "limit": 5,
        "can_create": true
      }
    }
  },
  "setup_completed": true,
  "consent": {
    "current_version": "2026-05-17",
    "accepted_current_version": true,
    "accept_url": "https://www.budget-hub.com/consent/"
  }
}
Response fields
username
string
key
object
plan
object
setup_completed
boolean
consent
object

Income

Your current income snapshot.

GET /api/v1/income/

Get current income

Your income snapshot: the latest row is the figure in effect. 404 no_data if never recorded.

Request
curl "https://www.budget-hub.com/api/v1/income/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/income/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/income/", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(await response.json());
200 OK
{
  "date": "2026-10-12",
  "salary": "32000.00",
  "investments": "350.00",
  "rental": "4500.00",
  "other": "0.00",
  "total": "36850.00"
}
Response fields
date
string (date) Read-only.
salary
money string
investments
money string
rental
money string
other
money string
total
money string Read-only.

PATCH /api/v1/income/

Update income

Merges the given fields into your current snapshot and moves its date to today. The first PATCH creates the snapshot (201). History is never invented.

Needs a write key

Request
curl -X PATCH "https://www.budget-hub.com/api/v1/income/" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "salary": "32000.00",
  "rental": "4500.00"
}'
import requests

response = requests.patch(
    "https://www.budget-hub.com/api/v1/income/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
    json={
        "salary": "32000.00",
        "rental": "4500.00",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/income/", {
  method: "PATCH",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "salary": "32000.00",
    "rental": "4500.00"
  }),
});
console.log(await response.json());
200 OK
{
  "date": "2026-10-12",
  "salary": "32000.00",
  "investments": "350.00",
  "rental": "4500.00",
  "other": "0.00",
  "total": "36850.00"
}

Request body

salary
money string
investments
money string
rental
money string
other
money string

Expenses

Your current expenses, as a total, eight groups and 39 categories.

GET /api/v1/expenses/

Get current expenses

Your expense snapshot: total, eight group subtotals and all 39 categories.

Request
curl "https://www.budget-hub.com/api/v1/expenses/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/expenses/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/expenses/", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(await response.json());
200 OK
{
  "date": "2026-10-12",
  "total": "29005.00",
  "groups": {
    "savings_investments": "2500.00",
    "housing_utilities": "13049.00",
    "transportation": "5780.00",
    "debt_credit": "0.00",
    "insurance_healthcare": "1850.00",
    "personal_development": "0.00",
    "children_family": "0.00",
    "living_personal": "5826.00"
  },
  "books_materials": "0.00",
  "car_insurance": "780.00",
  "car_payment": "3200.00",
  "cell_phone": "450.00",
  "child_care": "0.00",
  "clothing": "0.00",
  "coaching_mentoring": "0.00",
  "credit_cards": "0.00",
  "debt": "0.00",
  "emergency_fund": "0.00",
  "entertainment": "600.00",
  "funeral_cover": "0.00",
  "groceries": "4200.00",
  "gym": "499.00",
  "health_insurance": "1850.00",
  "home_insurance": "0.00",
  "internet": "899.00",
  "investments": "0.00",
  "kids_activities": "0.00",
  "life_insurance": "0.00",
  "maintenance": "0.00",
  "medical_expenses": "0.00",
  "mortgage_rent": "9800.00",
  "online_learning": "0.00",
  "pension": "0.00",
  "personal": "0.00",
  "personal_loans": "0.00",
  "petrol": "1800.00",
  "professional_courses": "0.00",
  "public_transport": "0.00",
  "savings": "2500.00",
  "school_fees": "0.00",
  "security_services": "0.00",
  "store_accounts": "0.00",
  "subscriptions": "527.00",
  "tutoring": "0.00",
  "university_funding": "0.00",
  "utilities": "1900.00",
  "workshops_seminars": "0.00"
}
Response fields
date
string (date) Read-only.
total
money string Read-only.
groups
object Read-only. Subtotals for the eight expense groups shown on the dashboard.
books_materials
money string
car_insurance
money string
car_payment
money string
cell_phone
money string
child_care
money string
clothing
money string
coaching_mentoring
money string
credit_cards
money string
debt
money string
emergency_fund
money string
entertainment
money string
funeral_cover
money string
groceries
money string
gym
money string
health_insurance
money string
home_insurance
money string
internet
money string
investments
money string
kids_activities
money string
life_insurance
money string
maintenance
money string
medical_expenses
money string
mortgage_rent
money string
online_learning
money string
pension
money string
personal
money string
personal_loans
money string
petrol
money string
professional_courses
money string
public_transport
money string
savings
money string
school_fees
money string
security_services
money string
store_accounts
money string
subscriptions
money string
tutoring
money string
university_funding
money string
utilities
money string
workshops_seminars
money string

PATCH /api/v1/expenses/

Update expenses

Merges the given categories into your current snapshot and moves its date to today. The first PATCH creates it (201).

Needs a write key

Request
curl -X PATCH "https://www.budget-hub.com/api/v1/expenses/" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "groceries": "4200.00",
  "petrol": "1800.00"
}'
import requests

response = requests.patch(
    "https://www.budget-hub.com/api/v1/expenses/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
    json={
        "groceries": "4200.00",
        "petrol": "1800.00",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/expenses/", {
  method: "PATCH",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "groceries": "4200.00",
    "petrol": "1800.00"
  }),
});
console.log(await response.json());
200 OK
{
  "date": "2026-10-12",
  "total": "29005.00",
  "groups": {
    "savings_investments": "2500.00",
    "housing_utilities": "13049.00",
    "transportation": "5780.00",
    "debt_credit": "0.00",
    "insurance_healthcare": "1850.00",
    "personal_development": "0.00",
    "children_family": "0.00",
    "living_personal": "5826.00"
  },
  "books_materials": "0.00",
  "car_insurance": "780.00",
  "car_payment": "3200.00",
  "cell_phone": "450.00",
  "child_care": "0.00",
  "clothing": "0.00",
  "coaching_mentoring": "0.00",
  "credit_cards": "0.00",
  "debt": "0.00",
  "emergency_fund": "0.00",
  "entertainment": "600.00",
  "funeral_cover": "0.00",
  "groceries": "4200.00",
  "gym": "499.00",
  "health_insurance": "1850.00",
  "home_insurance": "0.00",
  "internet": "899.00",
  "investments": "0.00",
  "kids_activities": "0.00",
  "life_insurance": "0.00",
  "maintenance": "0.00",
  "medical_expenses": "0.00",
  "mortgage_rent": "9800.00",
  "online_learning": "0.00",
  "pension": "0.00",
  "personal": "0.00",
  "personal_loans": "0.00",
  "petrol": "1800.00",
  "professional_courses": "0.00",
  "public_transport": "0.00",
  "savings": "2500.00",
  "school_fees": "0.00",
  "security_services": "0.00",
  "store_accounts": "0.00",
  "subscriptions": "527.00",
  "tutoring": "0.00",
  "university_funding": "0.00",
  "utilities": "1900.00",
  "workshops_seminars": "0.00"
}

Request body

Send any of these fields. All are optional.

All 39 fields
books_materials
money string
car_insurance
money string
car_payment
money string
cell_phone
money string
child_care
money string
clothing
money string
coaching_mentoring
money string
credit_cards
money string
debt
money string
emergency_fund
money string
entertainment
money string
funeral_cover
money string
groceries
money string
gym
money string
health_insurance
money string
home_insurance
money string
internet
money string
investments
money string
kids_activities
money string
life_insurance
money string
maintenance
money string
medical_expenses
money string
mortgage_rent
money string
online_learning
money string
pension
money string
personal
money string
personal_loans
money string
petrol
money string
professional_courses
money string
public_transport
money string
savings
money string
school_fees
money string
security_services
money string
store_accounts
money string
subscriptions
money string
tutoring
money string
university_funding
money string
utilities
money string
workshops_seminars
money string

Goals

Savings goals and how far along they are.

GET /api/v1/goals/

List goals

Ordered by priority (1 = highest).

Request
curl "https://www.budget-hub.com/api/v1/goals/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/goals/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/goals/", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(await response.json());
200 OK
{
  "count": 2,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 12,
      "name": "Emergency fund (6 months)",
      "description": "",
      "category": {
        "id": 1,
        "name": "Emergency Fund"
      },
      "priority": 1,
      "priority_display": "Highest",
      "is_active": true,
      "target_amount": "72000.00",
      "current_amount": "10000.00",
      "remaining_amount": "62000.00",
      "progress_percentage": "13.89",
      "current_milestone": "10",
      "achievement_level": "bronze",
      "achievement_level_display": "Bronze",
      "streak_count": 0,
      "start_date": "2026-09-01",
      "target_date": "2027-11-17",
      "last_contribution_date": "2026-10-12"
    }
  ]
}

Parameters

active
query Only active (true) or inactive (false) items.
limit
query
offset
query
Response fields
id
integer Read-only.
name
string Read-only.
description
string Read-only.
category
object or null Read-only.
priority
integer: 1, 2, 3, 4, 5 Read-only.
priority_display
string Read-only.
is_active
boolean Read-only.
target_amount
money string Read-only.
current_amount
money string Read-only.
remaining_amount
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.

POST /api/v1/goals/

Create a goal

Counts toward your plan limit while active.

Needs a write key Counts toward your plan limit

Request
curl -X POST "https://www.budget-hub.com/api/v1/goals/" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Emergency fund",
  "target_amount": "60000.00",
  "priority": 1,
  "target_date": "2027-10-12"
}'
import requests

response = requests.post(
    "https://www.budget-hub.com/api/v1/goals/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
    json={
        "name": "Emergency fund",
        "target_amount": "60000.00",
        "priority": 1,
        "target_date": "2027-10-12",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/goals/", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "name": "Emergency fund",
    "target_amount": "60000.00",
    "priority": 1,
    "target_date": "2027-10-12"
  }),
});
console.log(await response.json());
201 Created
Location: https://www.budget-hub.com/api/v1/goals/12
{
  "id": 12,
  "name": "Emergency fund (6 months)",
  "description": "",
  "category": {
    "id": 1,
    "name": "Emergency Fund"
  },
  "priority": 1,
  "priority_display": "Highest",
  "is_active": true,
  "target_amount": "72000.00",
  "current_amount": "10000.00",
  "remaining_amount": "62000.00",
  "progress_percentage": "13.89",
  "current_milestone": "10",
  "achievement_level": "bronze",
  "achievement_level_display": "Bronze",
  "streak_count": 0,
  "start_date": "2026-09-01",
  "target_date": "2027-11-17",
  "last_contribution_date": "2026-10-12"
}

Request body

name
stringrequired Up to 100 characters.
description
string Up to 1000 characters.
category
integer or null Id of the related object.
priority
integer 1 to 5.
is_active
boolean
target_amount
money stringrequired At least 0.01.
target_date
string or null (date)

GET /api/v1/goals/{id}/

Get a goal

Request
curl "https://www.budget-hub.com/api/v1/goals/12/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/goals/12/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/goals/12/", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(await response.json());
200 OK
{
  "id": 12,
  "name": "Emergency fund (6 months)",
  "description": "",
  "category": {
    "id": 1,
    "name": "Emergency Fund"
  },
  "priority": 1,
  "priority_display": "Highest",
  "is_active": true,
  "target_amount": "72000.00",
  "current_amount": "10000.00",
  "remaining_amount": "62000.00",
  "progress_percentage": "13.89",
  "current_milestone": "10",
  "achievement_level": "bronze",
  "achievement_level_display": "Bronze",
  "streak_count": 0,
  "start_date": "2026-09-01",
  "target_date": "2027-11-17",
  "last_contribution_date": "2026-10-12"
}

Parameters

id
path
Response fields
id
integer Read-only.
name
string Read-only.
description
string Read-only.
category
object or null Read-only.
priority
integer: 1, 2, 3, 4, 5 Read-only.
priority_display
string Read-only.
is_active
boolean Read-only.
target_amount
money string Read-only.
current_amount
money string Read-only.
remaining_amount
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.

Needs a write key Counts toward your plan limit

Request
curl -X PATCH "https://www.budget-hub.com/api/v1/goals/12/" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Emergency fund (6 months)",
  "target_amount": "72000.00"
}'
import requests

response = requests.patch(
    "https://www.budget-hub.com/api/v1/goals/12/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
    json={
        "name": "Emergency fund (6 months)",
        "target_amount": "72000.00",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/goals/12/", {
  method: "PATCH",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "name": "Emergency fund (6 months)",
    "target_amount": "72000.00"
  }),
});
console.log(await response.json());
200 OK
{
  "id": 12,
  "name": "Emergency fund (6 months)",
  "description": "",
  "category": {
    "id": 1,
    "name": "Emergency Fund"
  },
  "priority": 1,
  "priority_display": "Highest",
  "is_active": true,
  "target_amount": "72000.00",
  "current_amount": "10000.00",
  "remaining_amount": "62000.00",
  "progress_percentage": "13.89",
  "current_milestone": "10",
  "achievement_level": "bronze",
  "achievement_level_display": "Bronze",
  "streak_count": 0,
  "start_date": "2026-09-01",
  "target_date": "2027-11-17",
  "last_contribution_date": "2026-10-12"
}

Parameters

id
path

Request body

name
string Up to 100 characters.
description
string Up to 1000 characters.
category
integer or null Id of the related object.
priority
integer 1 to 5.
is_active
boolean
target_amount
money string At least 0.01.
target_date
string or null (date)

DELETE /api/v1/goals/{id}/

Delete a goal

Also deletes its contributions.

Needs a write key

Request
curl -X DELETE "https://www.budget-hub.com/api/v1/goals/12/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.delete(
    "https://www.budget-hub.com/api/v1/goals/12/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.status_code)
const response = await fetch("https://www.budget-hub.com/api/v1/goals/12/", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(response.status);
204 No Content

No body.

Parameters

id
path

Contributions

Money added to a goal. A ledger: add or delete, never edit.

GET /api/v1/goals/{id}/contributions/

List contributions

Newest first.

Request
curl "https://www.budget-hub.com/api/v1/goals/12/contributions/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/goals/12/contributions/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/goals/12/contributions/", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(await response.json());
200 OK
{
  "count": 4,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 31,
      "goal": 12,
      "amount": "2500.00",
      "date": "2026-10-12",
      "notes": "October savings",
      "milestone_achieved": ""
    }
  ]
}

Parameters

id
path
limit
query
offset
query
Response fields
id
integer Read-only.
goal
integer Read-only.
amount
money string Read-only.
date
string (date) Read-only.
notes
string Read-only.
milestone_achieved
string Read-only.

POST /api/v1/goals/{id}/contributions/

Add a contribution

Updates the goal's running total and milestone. date may be backdated, never in the future. Contributions cannot be edited: delete and re-add.

Needs a write key

Request
curl -X POST "https://www.budget-hub.com/api/v1/goals/12/contributions/" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "amount": "2500.00",
  "notes": "October savings"
}'
import requests

response = requests.post(
    "https://www.budget-hub.com/api/v1/goals/12/contributions/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
    json={
        "amount": "2500.00",
        "notes": "October savings",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/goals/12/contributions/", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "amount": "2500.00",
    "notes": "October savings"
  }),
});
console.log(await response.json());
201 Created
Location: https://www.budget-hub.com/api/v1/goals/12/contributions/31
{
  "id": 31,
  "goal": 12,
  "amount": "2500.00",
  "date": "2026-10-12",
  "notes": "October savings",
  "milestone_achieved": ""
}

Parameters

id
path

Request body

amount
money stringrequired At least 0.01.
notes
string Up to 500 characters.
date
string (date)

GET /api/v1/goals/{id}/contributions/{contribution_id}/

Get a contribution

Request
curl "https://www.budget-hub.com/api/v1/goals/12/contributions/31/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/goals/12/contributions/31/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/goals/12/contributions/31/", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(await response.json());
200 OK
{
  "id": 31,
  "goal": 12,
  "amount": "2500.00",
  "date": "2026-10-12",
  "notes": "October savings",
  "milestone_achieved": ""
}

Parameters

id
path
contribution_id
path
Response fields
id
integer Read-only.
goal
integer Read-only.
amount
money string Read-only.
date
string (date) Read-only.
notes
string Read-only.
milestone_achieved
string Read-only.

DELETE /api/v1/goals/{id}/contributions/{contribution_id}/

Delete a contribution

Recalculates the goal's progress.

Needs a write key

Request
curl -X DELETE "https://www.budget-hub.com/api/v1/goals/12/contributions/31/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.delete(
    "https://www.budget-hub.com/api/v1/goals/12/contributions/31/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.status_code)
const response = await fetch("https://www.budget-hub.com/api/v1/goals/12/contributions/31/", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(response.status);
204 No Content

No body.

Parameters

id
path
contribution_id
path

Investments

Your holdings and how their value has moved.

GET /api/v1/investments/

List investments

Ordered by name.

Request
curl "https://www.budget-hub.com/api/v1/investments/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/investments/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/investments/", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(await response.json());
200 OK
{
  "count": 2,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 7,
      "name": "Satrix 40 ETF",
      "investment_type": "etf",
      "investment_type_display": "ETFs",
      "is_market_linked": true,
      "purchase_value": "10000.00",
      "current_value": "11250.00",
      "gain_loss": "1250.00",
      "return_percentage": "12.50",
      "purchase_date": "2026-01-10",
      "notes": "Monthly debit order",
      "created_at": "2026-01-10T08:14:03.512Z",
      "updated_at": "2026-10-12T07:02:41.208Z"
    }
  ]
}

Parameters

type
query Only this investment type.
limit
query
offset
query
Response fields
id
integer Read-only.
name
string Read-only.
investment_type
string: stocks, etf, crypto, commodities, retirement_annuity, pension, tfsa, unit_trust, endowment, bonds, mutual_funds, real_estate, other Read-only.
investment_type_display
string Read-only.
is_market_linked
boolean Read-only.
purchase_value
money string Read-only.
current_value
money string Read-only.
gain_loss
money string Read-only.
return_percentage
percent string Read-only.
purchase_date
string (date) Read-only.
notes
string Read-only.
created_at
string (date-time) Read-only.
updated_at
string (date-time) Read-only.

POST /api/v1/investments/

Create an investment

Also records an "Initial investment" history snapshot. Counts toward your plan limit.

Needs a write key Counts toward your plan limit

Request
curl -X POST "https://www.budget-hub.com/api/v1/investments/" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Satrix 40 ETF",
  "investment_type": "etf",
  "purchase_value": "10000.00",
  "current_value": "10850.00",
  "purchase_date": "2026-01-10",
  "notes": "Monthly debit order"
}'
import requests

response = requests.post(
    "https://www.budget-hub.com/api/v1/investments/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
    json={
        "name": "Satrix 40 ETF",
        "investment_type": "etf",
        "purchase_value": "10000.00",
        "current_value": "10850.00",
        "purchase_date": "2026-01-10",
        "notes": "Monthly debit order",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/investments/", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "name": "Satrix 40 ETF",
    "investment_type": "etf",
    "purchase_value": "10000.00",
    "current_value": "10850.00",
    "purchase_date": "2026-01-10",
    "notes": "Monthly debit order"
  }),
});
console.log(await response.json());
201 Created
Location: https://www.budget-hub.com/api/v1/investments/7
{
  "id": 7,
  "name": "Satrix 40 ETF",
  "investment_type": "etf",
  "investment_type_display": "ETFs",
  "is_market_linked": true,
  "purchase_value": "10000.00",
  "current_value": "11250.00",
  "gain_loss": "1250.00",
  "return_percentage": "12.50",
  "purchase_date": "2026-01-10",
  "notes": "Monthly debit order",
  "created_at": "2026-01-10T08:14:03.512Z",
  "updated_at": "2026-10-12T07:02:41.208Z"
}

Request body

name
stringrequired Up to 200 characters.
investment_type
string: stocks, etf, crypto, commodities, retirement_annuity, pension, tfsa, unit_trust, endowment, bonds, mutual_funds, real_estate, otherrequired
purchase_value
money stringrequired At least 0.01.
current_value
money stringrequired
purchase_date
string (date)required
notes
string Up to 1000 characters.

GET /api/v1/investments/{id}/

Get an investment

Request
curl "https://www.budget-hub.com/api/v1/investments/7/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/investments/7/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/investments/7/", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(await response.json());
200 OK
{
  "id": 7,
  "name": "Satrix 40 ETF",
  "investment_type": "etf",
  "investment_type_display": "ETFs",
  "is_market_linked": true,
  "purchase_value": "10000.00",
  "current_value": "11250.00",
  "gain_loss": "1250.00",
  "return_percentage": "12.50",
  "purchase_date": "2026-01-10",
  "notes": "Monthly debit order",
  "created_at": "2026-01-10T08:14:03.512Z",
  "updated_at": "2026-10-12T07:02:41.208Z"
}

Parameters

id
path
Response fields
id
integer Read-only.
name
string Read-only.
investment_type
string: stocks, etf, crypto, commodities, retirement_annuity, pension, tfsa, unit_trust, endowment, bonds, mutual_funds, real_estate, other Read-only.
investment_type_display
string Read-only.
is_market_linked
boolean Read-only.
purchase_value
money string Read-only.
current_value
money string Read-only.
gain_loss
money string Read-only.
return_percentage
percent string Read-only.
purchase_date
string (date) Read-only.
notes
string Read-only.
created_at
string (date-time) Read-only.
updated_at
string (date-time) Read-only.

PATCH /api/v1/investments/{id}/

Update an investment

Changing current_value to a new number records a "Value updated" history snapshot.

Needs a write key

Request
curl -X PATCH "https://www.budget-hub.com/api/v1/investments/7/" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "current_value": "11200.00"
}'
import requests

response = requests.patch(
    "https://www.budget-hub.com/api/v1/investments/7/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
    json={
        "current_value": "11200.00",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/investments/7/", {
  method: "PATCH",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "current_value": "11200.00"
  }),
});
console.log(await response.json());
200 OK
{
  "id": 7,
  "name": "Satrix 40 ETF",
  "investment_type": "etf",
  "investment_type_display": "ETFs",
  "is_market_linked": true,
  "purchase_value": "10000.00",
  "current_value": "11250.00",
  "gain_loss": "1250.00",
  "return_percentage": "12.50",
  "purchase_date": "2026-01-10",
  "notes": "Monthly debit order",
  "created_at": "2026-01-10T08:14:03.512Z",
  "updated_at": "2026-10-12T07:02:41.208Z"
}

Parameters

id
path

Request body

name
string Up to 200 characters.
investment_type
string: stocks, etf, crypto, commodities, retirement_annuity, pension, tfsa, unit_trust, endowment, bonds, mutual_funds, real_estate, other
purchase_value
money string At least 0.01.
current_value
money string
purchase_date
string (date)
notes
string Up to 1000 characters.

DELETE /api/v1/investments/{id}/

Delete an investment

Also deletes its history.

Needs a write key

Request
curl -X DELETE "https://www.budget-hub.com/api/v1/investments/7/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.delete(
    "https://www.budget-hub.com/api/v1/investments/7/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.status_code)
const response = await fetch("https://www.budget-hub.com/api/v1/investments/7/", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(response.status);
204 No Content

No body.

Parameters

id
path

GET /api/v1/investments/{id}/history/

List value history

Dated value snapshots, newest first. Read-only: written when you create an investment or change its value.

Request
curl "https://www.budget-hub.com/api/v1/investments/7/history/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/investments/7/history/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/investments/7/history/", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(await response.json());
200 OK
{
  "count": 3,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 52,
      "investment": 7,
      "date": "2026-10-12",
      "value": "11250.00",
      "profit_loss": "1250.00",
      "notes": "Value updated"
    }
  ]
}

Parameters

id
path
limit
query
offset
query
Response fields
id
integer Read-only.
investment
integer Read-only.
date
string (date) Read-only.
value
money string Read-only.
profit_loss
money string Read-only.
notes
string Read-only.

Subscriptions

Your own recurring payments, like Netflix or the gym.

GET /api/v1/subscriptions/

List tracked subscriptions

Your own recurring payments (Netflix, gym...), not your Budget Hub plan. Ordered by next due date.

Request
curl "https://www.budget-hub.com/api/v1/subscriptions/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/subscriptions/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/subscriptions/", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(await response.json());
200 OK
{
  "count": 3,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 3,
      "name": "Netflix",
      "amount": "199.00",
      "billing_cycle": "monthly",
      "billing_cycle_display": "Monthly",
      "monthly_equivalent": "199.00",
      "category": "video",
      "category_display": "Video Streaming",
      "next_due_date": "2026-10-18",
      "urgency_status": "due_soon",
      "notes": "",
      "is_active": true,
      "created_at": "2026-03-02T10:21:55.004Z",
      "updated_at": "2026-10-01T06:00:12.730Z"
    }
  ]
}

Parameters

active
query Only active (true) or inactive (false) items.
limit
query
offset
query
Response fields
id
integer Read-only.
name
string Read-only.
amount
money string Read-only.
billing_cycle
string: weekly, monthly, quarterly, biannually, annually Read-only.
billing_cycle_display
string Read-only.
monthly_equivalent
money string Read-only.
category
string: entertainment, music, video, gaming, news, productivity, cloud, fitness, education, insurance, utilities, telecom, finance, other Read-only.
category_display
string Read-only.
next_due_date
string (date) Read-only.
urgency_status
string: overdue, due_soon, safe Read-only. overdue: due date has passed; due_soon: within 7 days.
notes
string Read-only.
is_active
boolean Read-only.
created_at
string (date-time) Read-only.
updated_at
string (date-time) Read-only.

POST /api/v1/subscriptions/

Track a subscription

Counts toward your plan limit while active.

Needs a write key Counts toward your plan limit

Request
curl -X POST "https://www.budget-hub.com/api/v1/subscriptions/" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Netflix",
  "amount": "199.00",
  "billing_cycle": "monthly",
  "next_due_date": "2026-10-22",
  "category": "video"
}'
import requests

response = requests.post(
    "https://www.budget-hub.com/api/v1/subscriptions/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
    json={
        "name": "Netflix",
        "amount": "199.00",
        "billing_cycle": "monthly",
        "next_due_date": "2026-10-22",
        "category": "video",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/subscriptions/", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "name": "Netflix",
    "amount": "199.00",
    "billing_cycle": "monthly",
    "next_due_date": "2026-10-22",
    "category": "video"
  }),
});
console.log(await response.json());
201 Created
Location: https://www.budget-hub.com/api/v1/subscriptions/3
{
  "id": 3,
  "name": "Netflix",
  "amount": "199.00",
  "billing_cycle": "monthly",
  "billing_cycle_display": "Monthly",
  "monthly_equivalent": "199.00",
  "category": "video",
  "category_display": "Video Streaming",
  "next_due_date": "2026-10-18",
  "urgency_status": "due_soon",
  "notes": "",
  "is_active": true,
  "created_at": "2026-03-02T10:21:55.004Z",
  "updated_at": "2026-10-01T06:00:12.730Z"
}

Request body

name
stringrequired Up to 200 characters.
amount
money stringrequired At least 0.01.
billing_cycle
string: weekly, monthly, quarterly, biannually, annually
next_due_date
string (date)required
category
string: entertainment, music, video, gaming, news, productivity, cloud, fitness, education, insurance, utilities, telecom, finance, other
notes
string Up to 500 characters.
is_active
boolean

GET /api/v1/subscriptions/{id}/

Get a tracked subscription

Request
curl "https://www.budget-hub.com/api/v1/subscriptions/3/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/subscriptions/3/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/subscriptions/3/", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(await response.json());
200 OK
{
  "id": 3,
  "name": "Netflix",
  "amount": "199.00",
  "billing_cycle": "monthly",
  "billing_cycle_display": "Monthly",
  "monthly_equivalent": "199.00",
  "category": "video",
  "category_display": "Video Streaming",
  "next_due_date": "2026-10-18",
  "urgency_status": "due_soon",
  "notes": "",
  "is_active": true,
  "created_at": "2026-03-02T10:21:55.004Z",
  "updated_at": "2026-10-01T06:00:12.730Z"
}

Parameters

id
path
Response fields
id
integer Read-only.
name
string Read-only.
amount
money string Read-only.
billing_cycle
string: weekly, monthly, quarterly, biannually, annually Read-only.
billing_cycle_display
string Read-only.
monthly_equivalent
money string Read-only.
category
string: entertainment, music, video, gaming, news, productivity, cloud, fitness, education, insurance, utilities, telecom, finance, other Read-only.
category_display
string Read-only.
next_due_date
string (date) Read-only.
urgency_status
string: overdue, due_soon, safe Read-only. overdue: due date has passed; due_soon: within 7 days.
notes
string Read-only.
is_active
boolean Read-only.
created_at
string (date-time) Read-only.
updated_at
string (date-time) Read-only.

PATCH /api/v1/subscriptions/{id}/

Update a tracked subscription

Reactivating counts toward your plan limit.

Needs a write key Counts toward your plan limit

Request
curl -X PATCH "https://www.budget-hub.com/api/v1/subscriptions/3/" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "amount": "229.00"
}'
import requests

response = requests.patch(
    "https://www.budget-hub.com/api/v1/subscriptions/3/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
    json={
        "amount": "229.00",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/subscriptions/3/", {
  method: "PATCH",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "amount": "229.00"
  }),
});
console.log(await response.json());
200 OK
{
  "id": 3,
  "name": "Netflix",
  "amount": "199.00",
  "billing_cycle": "monthly",
  "billing_cycle_display": "Monthly",
  "monthly_equivalent": "199.00",
  "category": "video",
  "category_display": "Video Streaming",
  "next_due_date": "2026-10-18",
  "urgency_status": "due_soon",
  "notes": "",
  "is_active": true,
  "created_at": "2026-03-02T10:21:55.004Z",
  "updated_at": "2026-10-01T06:00:12.730Z"
}

Parameters

id
path

Request body

name
string Up to 200 characters.
amount
money string At least 0.01.
billing_cycle
string: weekly, monthly, quarterly, biannually, annually
next_due_date
string (date)
category
string: entertainment, music, video, gaming, news, productivity, cloud, fitness, education, insurance, utilities, telecom, finance, other
notes
string Up to 500 characters.
is_active
boolean

DELETE /api/v1/subscriptions/{id}/

Delete a tracked subscription

Needs a write key

Request
curl -X DELETE "https://www.budget-hub.com/api/v1/subscriptions/3/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.delete(
    "https://www.budget-hub.com/api/v1/subscriptions/3/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.status_code)
const response = await fetch("https://www.budget-hub.com/api/v1/subscriptions/3/", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(response.status);
204 No Content

No body.

Parameters

id
path

Reports

Month-end numbers, the same as your PDF report.

GET /api/v1/reports/monthly/

Monthly report

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.

Request
curl "https://www.budget-hub.com/api/v1/reports/monthly/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/reports/monthly/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/reports/monthly/", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(await response.json());
200 OK
{
  "report_month": "2026-10-01",
  "period_label": "October 2026",
  "prev_label": "September 2026",
  "income": {
    "salary": "32000.00",
    "investments": "350.00",
    "rental": "4500.00",
    "other": "0.00",
    "total": "36850.00"
  },
  "income_as_of": null,
  "expenses": {
    "savings_investments": "2500.00",
    "housing_utilities": "13049.00",
    "transportation": "5780.00",
    "debt_credit": "0.00",
    "insurance_healthcare": "1850.00",
    "personal_development": "0.00",
    "children_family": "0.00",
    "living_personal": "5826.00",
    "total": "29005.00"
  },
  "expenses_as_of": null,
  "months_with_data": 1,
  "prev_income": {
    "salary": "0.00",
    "investments": "0.00",
    "rental": "0.00",
    "other": "0.00",
    "total": "0.00"
  },
  "prev_expenses": {
    "savings_investments": "0.00",
    "housing_utilities": "0.00",
    "transportation": "0.00",
    "debt_credit": "0.00",
    "insurance_healthcare": "0.00",
    "personal_development": "0.00",
    "children_family": "0.00",
    "living_personal": "0.00",
    "total": "0.00"
  },
  "net_savings": "7845.00",
  "prev_net_savings": "0.00",
  "savings_rate": "21.30",
  "prev_savings_rate": "0.00",
  "budget": {
    "amount": "24000.00",
    "spent": "29005.00",
    "remaining": "-5005.00",
    "used_percent": "120.90"
  },
  "trend": [
    {
      "month": "May 2026",
      "income": "0.00",
      "expenses": "0.00"
    },
    {
      "month": "Jun 2026",
      "income": "0.00",
      "expenses": "0.00"
    },
    {
      "month": "Jul 2026",
      "income": "0.00",
      "expenses": "0.00"
    },
    {
      "month": "Aug 2026",
      "income": "0.00",
      "expenses": "0.00"
    },
    {
      "month": "Sep 2026",
      "income": "0.00",
      "expenses": "0.00"
    },
    {
      "month": "Oct 2026",
      "income": "36850.00",
      "expenses": "29005.00"
    }
  ],
  "goals": [
    {
      "name": "Emergency fund (6 months)",
      "target": "72000.00",
      "current": "10000.00",
      "progress": "13.90",
      "contributed_this_month": "2500.00",
      "achievement": "Bronze"
    }
  ],
  "investments": {
    "holdings": [
      {
        "name": "Satrix 40 ETF",
        "type": "ETFs",
        "invested": "10000.00",
        "value": "11250.00",
        "return_percent": "12.50"
      }
    ],
    "total_value": "11250.00",
    "total_invested": "10000.00",
    "total_gain": "1250.00",
    "return_percent": "12.50"
  },
  "subscriptions": {
    "items": [
      {
        "name": "Netflix",
        "category": "Video Streaming",
        "cycle": "Monthly",
        "monthly": "199.00"
      }
    ],
    "count": 1,
    "monthly_total": "199.00",
    "annual_total": "2388.00"
  }
}
Response fields
report_month
string (date)
period_label
string
prev_label
string
income
object
income_as_of
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.

Request
curl "https://www.budget-hub.com/api/v1/analytics/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/analytics/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/analytics/", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(await response.json());
200 OK
{
  "range": {
    "key": "6m",
    "months": 6
  },
  "as_of": {
    "data_month": "October 2026",
    "is_stale": false
  },
  "totals": {
    "income": "36850.00",
    "expenses": "29005.00",
    "net_savings": "7845.00",
    "savings_rate": "21.30"
  },
  "health": {
    "score": 47,
    "category": "MODERATE",
    "components": [
      {
        "key": "savings_rate",
        "label": "Savings rate",
        "score": 43,
        "weight": 50,
        "detail": "21.3% of income saved"
      },
      {
        "key": "budget",
        "label": "Budget adherence",
        "score": 18,
        "weight": 30,
        "detail": "121% of monthly budget used"
      },
      {
        "key": "debt",
        "label": "Debt burden",
        "score": 100,
        "weight": 20,
        "detail": "0% of income goes to debt"
      }
    ]
  },
  "month_over_month": {
    "income_percent": null,
    "expenses_percent": null,
    "savings_percent": null
  },
  "income_breakdown": [
    {
      "source": "salary",
      "label": "Salary",
      "amount": "32000.00",
      "share_percent": "86.84"
    },
    {
      "source": "investments",
      "label": "Investments",
      "amount": "350.00",
      "share_percent": "0.95"
    },
    {
      "source": "rental",
      "label": "Rental",
      "amount": "4500.00",
      "share_percent": "12.21"
    },
    {
      "source": "other",
      "label": "Other",
      "amount": "0.00",
      "share_percent": "0.00"
    }
  ],
  "expense_breakdown": [
    {
      "group": "savings_investments",
      "label": "Savings & Investments",
      "amount": "2500.00",
      "share_percent": "8.62"
    },
    {
      "group": "housing_utilities",
      "label": "Housing & Utilities",
      "amount": "13049.00",
      "share_percent": "44.99"
    },
    {
      "group": "transportation",
      "label": "Transportation",
      "amount": "5780.00",
      "share_percent": "19.93"
    },
    {
      "group": "debt_credit",
      "label": "Debt & Credit",
      "amount": "0.00",
      "share_percent": "0.00"
    },
    {
      "group": "insurance_healthcare",
      "label": "Insurance & Healthcare",
      "amount": "1850.00",
      "share_percent": "6.38"
    },
    {
      "group": "personal_development",
      "label": "Personal Development & Training",
      "amount": "0.00",
      "share_percent": "0.00"
    },
    {
      "group": "children_family",
      "label": "Children & Family",
      "amount": "0.00",
      "share_percent": "0.00"
    },
    {
      "group": "living_personal",
      "label": "Living & Personal",
      "amount": "5826.00",
      "share_percent": "20.09"
    }
  ],
  "insights": {
    "largest_expense_group": {
      "group": "Housing & Utilities",
      "amount": "13049.00",
      "share_percent": "44.99",
      "advice": "Your housing costs are above the recommended 30% of income. Consider ways to reduce utility bills or explore more affordable housing options."
    },
    "primary_income_source": {
      "source": "Salary",
      "share_percent": "86.84",
      "advice": "Most of your income comes from one source. Building additional income streams could provide more financial security."
    }
  },
  "months": [
    {
      "month": "2026-10-01",
      "label": "Oct 2026",
      "income": "36850.00",
      "expenses": "29005.00",
      "savings": "7845.00",
      "savings_rate": "21.29"
    }
  ],
  "profile": {
    "monthly_budget": "24000.00",
    "savings_goal": "100000.00"
  },
  "projections": {
    "based_on": "Your current monthly net savings, continued unchanged.",
    "has_savings_goal": true,
    "savings_goal": "100000.00",
    "yearly": {
      "amount": "94140.00",
      "progress_percent": "94.10"
    },
    "three_year": {
      "amount": "282420.00",
      "progress_percent": "100.00"
    },
    "five_year": {
      "amount": "470700.00",
      "progress_percent": "100.00"
    }
  },
  "goals": [
    {
      "id": 1,
      "name": "Emergency fund (6 months)",
      "category": "Emergency Fund",
      "priority": 1,
      "target_amount": "72000.00",
      "current_amount": "10000.00",
      "remaining_amount": "62000.00",
      "progress_percentage": "13.89",
      "achievement_level": "bronze",
      "current_milestone": "10",
      "start_date": "2026-10-12",
      "target_date": "2027-11-17",
      "contribution_count": 3,
      "days_since_last_contribution": 2,
      "pace": {
        "window_days": 30,
        "daily": "166.67",
        "weekly": "1166.67",
        "monthly": "5000.00",
        "total_amount": "5000.00",
        "contribution_count": 2
      },
      "prediction": {
        "window_days": 60,
        "predicted_days": 372,
        "predicted_date": "2027-10-19",
        "confidence": "low",
        "contributions_analyzed": 3
      },
      "difficulty": {
        "level": "moderate",
        "percentage_of_income": "13.57"
      },
      "is_on_track": true,
      "has_prediction_data": true,
      "months_left": "13.2",
      "required_per_month": "4706.05"
    },
    {
      "id": 2,
      "name": "BMW",
      "category": null,
      "priority": 2,
      "target_amount": "350000.00",
      "current_amount": "1000.00",
      "remaining_amount": "349000.00",
      "progress_percentage": "0.29",
      "achievement_level": "bronze",
      "current_milestone": "start",
      "start_date": "2026-10-12",
      "target_date": "2027-12-31",
      "contribution_count": 1,
      "days_since_last_contribution": 11,
      "pace": {
        "window_days": 30,
        "daily": "33.33",
        "weekly": "233.33",
        "monthly": "1000.00",
        "total_amount": "1000.00",
        "contribution_count": 1
      },
      "prediction": {
        "window_days": 60,
        "predicted_days": 20939,
        "predicted_date": "2084-02-09",
        "confidence": "low",
        "contributions_analyzed": 1
      },
      "difficulty": {
        "level": "easy",
        "percentage_of_income": "2.71"
      },
      "is_on_track": false,
      "has_prediction_data": true,
      "months_left": "14.6",
      "required_per_month": "23871.21"
    }
  ],
  "banks": {
    "rates_updated": "2026-07",
    "note": "Indicative top rates, curated by hand and not live. Projected interest is what your current monthly savings would earn in a year at each bank's top rate.",
    "monthly_savings_used": "7845.00",
    "items": [
      {
        "key": "african_bank",
        "name": "African Bank",
        "account": "Fixed Deposit (3-60 months)",
        "rate_range": "8.5% - 10.4%",
        "top_rate_percent": "10.40",
        "projected_year_interest": "4619.54"
      },
      {
        "key": "capitec_bank",
        "name": "Capitec Bank",
        "account": "32-day Notice Account",
        "rate_range": "7.75% - 8.30%",
        "top_rate_percent": "8.30",
        "projected_year_interest": "3665.11"
      },
      {
        "key": "fnb_bank",
        "name": "FNB",
        "account": "First Savings Account",
        "rate_range": "7.80%",
        "top_rate_percent": "7.80",
        "projected_year_interest": "3439.50"
      },
      {
        "key": "investec_bank",
        "name": "Investec",
        "account": "Savings Account",
        "rate_range": "4.75% - 9.52%",
        "top_rate_percent": "9.52",
        "projected_year_interest": "4218.23"
      },
      {
        "key": "nedbank",
        "name": "Nedbank",
        "account": "Save & Invest Account",
        "rate_range": "4.5% - 10.06%",
        "top_rate_percent": "10.06",
        "projected_year_interest": "4464.25"
      },
      {
        "key": "standard_bank",
        "name": "Standard Bank",
        "account": "Savings Account",
        "rate_range": "3.5% - 9.5%",
        "top_rate_percent": "9.50",
        "projected_year_interest": "4209.13"
      },
      {
        "key": "tyme_bank",
        "name": "TymeBank",
        "account": "GoalSave Account",
        "rate_range": "4% - 10%",
        "top_rate_percent": "10.00",
        "projected_year_interest": "4436.88"
      }
    ]
  }
}

Parameters

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

Request
curl "https://www.budget-hub.com/api/v1/statements/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/statements/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/statements/", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(await response.json());
200 OK
{
  "count": 2,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 5,
      "bank": "capitec",
      "bank_display": "Capitec",
      "file_name": "capitec_october.csv",
      "uploaded_at": "2026-10-12T12:20:55.288755+02:00",
      "start_date": "2026-10-01",
      "end_date": "2026-10-09",
      "transaction_count": 9,
      "total_income": "16300.00",
      "total_expenses": "2272.65",
      "net_flow": "14027.35",
      "status": "completed"
    }
  ]
}

Parameters

limit
query
offset
query
Response fields
id
integer Read-only.
bank
string Read-only.
bank_display
string Read-only.
file_name
string Read-only.
uploaded_at
string (date-time) Read-only.
start_date
string (date) Read-only.
end_date
string (date) Read-only.
transaction_count
integer Read-only.
total_income
money string Read-only.
total_expenses
money string Read-only. Money out. Always positive.
net_flow
money string Read-only. Money in minus money out.
status
string Read-only.

POST /api/v1/statements/

Upload a statement

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

Needs a write key Counts toward your plan limit

Request
curl -X POST "https://www.budget-hub.com/api/v1/statements/" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: text/csv" \
  --data-binary @statement.csv
import requests

with open("statement.csv", "rb") as statement:
    response = requests.post(
        "https://www.budget-hub.com/api/v1/statements/",
        headers={
            "Authorization": "Bearer YOUR_API_KEY",
        },
        files={"file": statement},
    )
print(response.json())
import { readFile } from "node:fs/promises";

const csv = await readFile("statement.csv");
const response = await fetch("https://www.budget-hub.com/api/v1/statements/", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "text/csv",
  },
  body: csv,
});
console.log(await response.json());
201 Created
Location: https://www.budget-hub.com/api/v1/statements/5
{
  "id": 5,
  "bank": "capitec",
  "bank_display": "Capitec",
  "file_name": "capitec_october.csv",
  "uploaded_at": "2026-10-12T12:20:55.288755+02:00",
  "start_date": "2026-10-01",
  "end_date": "2026-10-09",
  "transaction_count": 9,
  "total_income": "16300.00",
  "total_expenses": "2272.65",
  "net_flow": "14027.35",
  "status": "completed",
  "skipped_rows": 0,
  "warnings": []
}

Parameters

file_name
query Name for the statement when you send a raw text/csv body. Must end in .csv. Default statement.csv.

Request body

file
file (.csv)required As a multipart form part, or send the CSV as the raw text/csv body.

Example file

Date,Description,Money Out,Money In,Balance
2026-10-01,SALARY ACME PTY LTD,,14600.00,15210.00
2026-10-02,CHECKERS HYPER SANDTON,842.35,,14367.65
2026-10-03,ESKOM PREPAID,500.00,,13867.65
2026-10-04,UBER TRIP CAPE TOWN,96.50,,13771.15
2026-10-05,NETFLIX.COM,199.00,,13572.15
2026-10-06,RENTAL INCOME - UNIT 4,,1700.00,15272.15
2026-10-07,WOOLWORTHS FOOD,315.80,,14956.35
2026-10-08,MONTHLY ACCOUNT FEE,69.00,,14887.35
2026-10-09,SOMEONE UNKNOWN PTY,250.00,,14637.35

GET /api/v1/statements/{id}/

Get a statement

Request
curl "https://www.budget-hub.com/api/v1/statements/5/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/statements/5/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/statements/5/", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(await response.json());
200 OK
{
  "id": 5,
  "bank": "capitec",
  "bank_display": "Capitec",
  "file_name": "capitec_october.csv",
  "uploaded_at": "2026-10-12T12:20:55.288755+02:00",
  "start_date": "2026-10-01",
  "end_date": "2026-10-09",
  "transaction_count": 9,
  "total_income": "16300.00",
  "total_expenses": "2272.65",
  "net_flow": "14027.35",
  "status": "completed"
}

Parameters

id
path
Response fields
id
integer Read-only.
bank
string Read-only.
bank_display
string Read-only.
file_name
string Read-only.
uploaded_at
string (date-time) Read-only.
start_date
string (date) Read-only.
end_date
string (date) Read-only.
transaction_count
integer Read-only.
total_income
money string Read-only.
total_expenses
money string Read-only. Money out. Always positive.
net_flow
money string Read-only. Money in minus money out.
status
string Read-only.

DELETE /api/v1/statements/{id}/

Delete a statement

Also deletes all of its transactions.

Needs a write key

Request
curl -X DELETE "https://www.budget-hub.com/api/v1/statements/5/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.delete(
    "https://www.budget-hub.com/api/v1/statements/5/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.status_code)
const response = await fetch("https://www.budget-hub.com/api/v1/statements/5/", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(response.status);
204 No Content

No body.

Parameters

id
path

Transactions

The transactions from your statements, and fixing a category the app got wrong.

GET /api/v1/statements/{id}/transactions/

List the transactions in a statement

Newest first. Amounts are encrypted, so you can filter by category, type, date and description, not by amount.

Request
curl "https://www.budget-hub.com/api/v1/statements/5/transactions/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/statements/5/transactions/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/statements/5/transactions/", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(await response.json());
200 OK
{
  "count": 9,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 120,
      "statement": 5,
      "date": "2026-10-02",
      "description": "CHECKERS HYPER SANDTON",
      "amount": "-842.35",
      "type": "debit",
      "category": "groceries",
      "category_display": "Groceries",
      "auto_categorized": true,
      "user_corrected": false
    }
  ]
}

Parameters

id
path
category
query Only this category, for example groceries, salary or uncategorized.
type
query credit (money in) or debit (money out).
from
query On or after this date, YYYY-MM-DD.
to
query On or before this date, YYYY-MM-DD.
q
query Words in the description, case-insensitive. Up to 100 characters.
limit
query
offset
query
Response fields
id
integer Read-only.
statement
integer Read-only.
date
string (date) Read-only.
description
string Read-only.
amount
money string Read-only.
type
string: credit, debit Read-only.
category
string: groceries, utilities, internet_phone, transport, food_delivery, entertainment, alcohol, healthcare, shopping, restaurants, bnpl, investments, loan_repayment, transfers, atm, insurance, banking_fees, gambling, subscriptions, hardware, uncategorized, salary, other_income Read-only.
category_display
string Read-only.
auto_categorized
boolean Read-only.
user_corrected
boolean Read-only.

GET /api/v1/transactions/

List all transactions

Every transaction across all your statements, newest first. Same filters as a single statement.

Request
curl "https://www.budget-hub.com/api/v1/transactions/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/transactions/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/transactions/", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(await response.json());
200 OK
{
  "count": 9,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 120,
      "statement": 5,
      "date": "2026-10-02",
      "description": "CHECKERS HYPER SANDTON",
      "amount": "-842.35",
      "type": "debit",
      "category": "groceries",
      "category_display": "Groceries",
      "auto_categorized": true,
      "user_corrected": false
    }
  ]
}

Parameters

category
query Only this category, for example groceries, salary or uncategorized.
type
query credit (money in) or debit (money out).
from
query On or after this date, YYYY-MM-DD.
to
query On or before this date, YYYY-MM-DD.
q
query Words in the description, case-insensitive. Up to 100 characters.
limit
query
offset
query

GET /api/v1/transactions/{id}/

Get a transaction

Request
curl "https://www.budget-hub.com/api/v1/transactions/120/" \
  -H "Authorization: Bearer YOUR_API_KEY"
import requests

response = requests.get(
    "https://www.budget-hub.com/api/v1/transactions/120/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/transactions/120/", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
  },
});
console.log(await response.json());
200 OK
{
  "id": 120,
  "statement": 5,
  "date": "2026-10-02",
  "description": "CHECKERS HYPER SANDTON",
  "amount": "-842.35",
  "type": "debit",
  "category": "groceries",
  "category_display": "Groceries",
  "auto_categorized": true,
  "user_corrected": false
}

Parameters

id
path
Response fields
id
integer Read-only.
statement
integer Read-only.
date
string (date) Read-only.
description
string Read-only.
amount
money string Read-only.
type
string: credit, debit Read-only.
category
string: groceries, utilities, internet_phone, transport, food_delivery, entertainment, alcohol, healthcare, shopping, restaurants, bnpl, investments, loan_repayment, transfers, atm, insurance, banking_fees, gambling, subscriptions, hardware, uncategorized, salary, other_income Read-only.
category_display
string Read-only.
auto_categorized
boolean Read-only.
user_corrected
boolean Read-only.

PATCH /api/v1/transactions/{id}/

Correct a category

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.

Needs a write key

Request
curl -X PATCH "https://www.budget-hub.com/api/v1/transactions/120/" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "category": "groceries"
}'
import requests

response = requests.patch(
    "https://www.budget-hub.com/api/v1/transactions/120/",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
    },
    json={
        "category": "groceries",
    },
)
print(response.json())
const response = await fetch("https://www.budget-hub.com/api/v1/transactions/120/", {
  method: "PATCH",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "category": "groceries"
  }),
});
console.log(await response.json());
200 OK
{
  "id": 120,
  "statement": 5,
  "date": "2026-10-02",
  "description": "CHECKERS HYPER SANDTON",
  "amount": "-842.35",
  "type": "debit",
  "category": "groceries",
  "category_display": "Groceries",
  "auto_categorized": true,
  "user_corrected": false
}

Parameters

id
path

Request body

category
string: groceries, utilities, internet_phone, transport, food_delivery, entertainment, alcohol, healthcare, shopping, restaurants, bnpl, investments, loan_repayment, transfers, atm, insurance, banking_fees, gambling, subscriptions, hardware, uncategorized, salary, other_income