---
title: "Update a project budget"
method: PATCH
path: "/public-api/v1/projects/{project_id}/budget"
tags: ["Projects"]
---

# Update a project budget

`PATCH /public-api/v1/projects/{project_id}/budget`

## Request

- Base URL: `https://app.hellobonsai.com`
- URL: `https://app.hellobonsai.com/public-api/v1/projects/{project_id}/budget`
- Auth: one of:
  - OAuth 2
    - Authorization code: `https://app.hellobonsai.com/oauth/authorize`
    - Token URL: `https://app.hellobonsai.com/oauth/token`
  - HTTP bearer

Changes the budget a project already tracks. Every field is optional,
and anything you leave out keeps its current value. None of them can be
cleared, so `null` or an empty string returns `422`; to stop tracking
the budget, use `DELETE /public-api/v1/projects/{id}/budget`. Keys other
than the ones below are ignored.

`calculation_mode` sets the unit of every number in the budget: `fee`
counts money in the project's currency, `time` counts hours. Changing it
does not convert `amount` — `"40"` stays 40, read in the new unit — so
send the new `amount` in the same request.

`calculation_base` has to be one the budget's mode can count. The check
runs on the budget as it will be saved, not only on what you sent, so
moving a `fee` budget based on `expenses` to `time` means sending a time
base with it:

| `calculation_mode` | `calculation_base` |
| --- | --- |
| `fee` | `billable_time`, `billable_time_and_expenses`, `expenses` |
| `time` | `billable_time`, `non_billable_time`, `billable_and_non_billable_time` |

Changing `reset_frequency` rebuilds the budget's cycles from the project
start date, and `current_cycle` in the response is the rebuilt one. Any
other change keeps the cycles and recounts them in the background, so
`current_cycle` can still show the figures from before the change; read
the project again to see the new ones.

A request that changes nothing returns `200` with the budget as it is.
The project needs a `start_date`; set one with
`PATCH /public-api/v1/projects/{id}` first.

A project the token cannot see returns `404`, and so does one with no
budget; start one with `POST /public-api/v1/projects/{id}/budget`. A
project it can see returns `403` when the token may not change the
project or see its budget, or when the plan does not include budgets.

## Request body

- object
  - `budget` object, required
    - `calculation_mode` 'fee' | 'time': What the budget counts: `fee` for money in the project currency, `time` for hours. `amount` is kept as it is and read in the new unit. Cannot be cleared.
    - `amount` string: The budget ceiling as a decimal string above zero: money in the project currency for `fee`, hours for `time`. Not cents. Cannot be cleared.
    - `calculation_base` 'billable_time' | 'non_billable_time' | 'billable_and_non_billable_time' | 'billable_time_and_expenses' | 'expenses': What counts against the budget. Must be one the resulting `calculation_mode` can count; see the table above. Cannot be cleared.
    - `expenses_calculation_base` 'billable' | 'non_billable' | 'all': Which expenses count. Only read when `calculation_base` includes expenses, but stored either way. Cannot be cleared.
    - `reset_frequency` 'never' | 'weekly' | 'monthly' | 'quarterly': How often the budget starts over. `never` runs one cycle for the life of the project; the others open a new cycle every period from the project start date. Changing it rebuilds the cycles. Cannot be cleared.
    - `over_budget_notification` 'enabled' | 'disabled': Whether Bonsai alerts the project team when spending passes `over_budget_notification_threshold`. Cannot be cleared.
    - `over_budget_notification_threshold` string: Percentage of `amount` that triggers the alert, as a decimal string above zero. It can exceed 100, and is saved even while the alert is disabled. Cannot be cleared.

## Response `200`

The budget was updated, or already had these values.

- object
  - `data` ProjectBudget, required: A ceiling on what a project may consume — money or hours — and how often it resets.
    - `id` integer, required: Unique identifier for the budget.
    - `amount` string, required: The budget ceiling as a decimal string — an amount in the project currency when `calculation_mode` is `fee`, a number of hours when it is `time`.
    - `over_budget_notification_threshold` string, required: Percentage of the budget that triggers the alert, as a decimal string.
    - `calculation_mode` 'fee' | 'time', required: What the budget is measured in: `fee` for money, `time` for hours.
    - `calculation_base` 'billable_time' | 'non_billable_time' | 'billable_and_non_billable_time' | 'billable_time_and_expenses' | 'expenses', required: What counts against the budget: logged time (`billable_time`, `non_billable_time`, `billable_and_non_billable_time`), billable time plus expenses (`billable_time_and_expenses`), or expenses alone (`expenses`).
    - `expenses_calculation_base` 'billable' | 'non_billable' | 'all', required: Which expenses count, when `calculation_base` includes them: `billable`, `non_billable` or `all`.
    - `reset_frequency` 'never' | 'weekly' | 'monthly' | 'quarterly', required: How often the budget starts over. `never` runs one cycle for the life of the project; the rest open a fresh cycle every `weekly`, `monthly` or `quarterly` period from the project start date.
    - `over_budget_notification` 'enabled' | 'disabled', required: Whether Bonsai alerts the project team when spending passes `over_budget_notification_threshold`.
    - `current_cycle` ProjectBudgetCycle, required: One run of a budget: the window it covers and how much of the budget it has used.
      - `opening_date` string, date, required: First day the cycle counts.
      - `closing_date` string, date, nullable, required: Last day the cycle counts, or `null` when the budget never resets and the project is open-ended.
      - `consumed_amount` string, required: How much of the budget this cycle has used, as a decimal string, in the same unit as the budget `amount`.
      - `consumed_percentage` integer, required: How much of the budget this cycle has used, as a whole percentage. Exceeds 100 once the budget is over.
  - `meta` Meta, required: Metadata returned alongside every single-resource response.
    - `request_id` string, required: Identifier for this request. Quote it when contacting support about a specific call.

## Other responses

- `400`: The request body is missing its `budget` wrapper, or the wrapper is empty.
- `401`: The access token is missing, expired or invalid.
- `403`: This token can see the project but may not change it or see its budget, or the plan does not include budgets.
- `404`: No project with that `id` that this token can see, or the project has no budget. Sample projects return `404` too.
- `422`: The budget could not be updated. `blank` means a field was sent as `null` or an empty string; none can be cleared. `invalid_value` on a field means it is not one of the listed values, not a number above zero, or a `calculation_base` the resulting mode cannot count. `invalid_value` at `/data/attributes` means the project has no `start_date`.
- `429`: Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.

---

[API](https://skmtc.dev/hellobonsai/apis/bonsai-api.md) - [All operations](https://skmtc.dev/hellobonsai/apis/bonsai-api/llms.txt) - [OpenAPI document](https://skmtc.dev/hellobonsai/apis/bonsai-api/revisions/abf12ed6a8d3?raw)
