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

# Update a project

`PATCH /public-api/v1/projects/{id}`

## Request

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

Updates a project. Every field is optional, and anything you leave out
keeps its current value.

Sending `null` clears `finish_date`, `billing_fee` and `billing_cycle`.
`title`, `start_date`, `billing_type` and `board_group_id` can be
changed but not cleared.

Whenever you touch a billing field the resulting combination is checked,
not just the values you sent — so moving a `retainer` to `time` means
clearing the fee and cycle in the same request:

| `billing_type` | `billing_fee` | `billing_cycle` |
| --- | --- | --- |
| `fixed_fee` | required | must be empty |
| `retainer` | required | required |
| `time` | must be empty | must be empty |
| `not_billable` | must be empty | must be empty |

`billing_type`, `billing_fee` and `billing_cycle` need permission to see
billing; without it the whole request is rejected with `422`
`not_accessible` and nothing is saved. Existing time entries keep the
rate they were logged at — a billing change applies to new ones only.

`finish_date` has to be on or after `start_date`, whether you send both
or only one of them — the other is taken from the project as it stands.

`board_group_id` moves the project to another stage of the project
board it is already on — a stage belonging to a different board is
rejected. Find one with
`GET /public-api/v1/board_groups?filter[resource_type]=Project`. A
project that is on no board yet can be filed onto any of them.

The client company and the currency are both fixed once a project
exists, so `company_id` and `currency` are ignored here. Amounts already
recorded against a project are never converted, so changing its currency
would silently restate them — set it when you create the project.

A project the token cannot see returns `404`, and one it can see but may
not change returns `403`.

## Path parameters

- `id` integer, required

## Request body

- object
  - `project` object, required
    - `title` string: Project title. A project must have one, so it cannot be blank.
    - `start_date` string, date: Date work starts. Cannot be cleared.
    - `finish_date` string, date, nullable: Date work ends. Send `null` to clear it.
    - `billing_type` 'time' | 'fixed_fee' | 'retainer' | 'not_billable': How the project bills. See the table above for which fee fields each type takes.
    - `billing_fee` string, nullable: The agreed fee, as a decimal string. Send `null` to clear it.
    - `billing_cycle` 'weekly' | 'monthly' | 'quarterly' | 'biannually' | 'yearly' | 'null', nullable: How often a retainer bills. Send `null` to clear it.
    - `board_group_id` string, uuid: Id of the stage to move the project to, on the project board it already sits on.

## Response `200`

The project was updated.

- object
  - `data` Project, required: An engagement you deliver for a client. Tasks, logged time and invoices all hang off a project.
    - `id` integer, required: Unique identifier for the project.
    - `title` string, nullable, required: Project title.
    - `public_url_token` string, nullable, required: Token in the project's client-facing URL. The list endpoint accepts it as `filter[public_url_token]`.
    - `currency` string, required: Three-letter ISO 4217 currency code the project bills in.
    - `start_date` string, date, nullable, required: Date work on the project starts.
    - `finish_date` string, date, nullable, required: Date work on the project ends, or `null` when it is open-ended.
    - `sequence` integer, nullable, required: Counter behind `number`, incrementing per client. `null` until a number has been assigned.
    - `number` string, nullable, required: Human-readable project reference. `null` until one has been assigned.
    - `board_group_id` string, uuid, nullable, required: Id of the pipeline stage the project's board card sits in, or `null` when the project is not on a board. Send it to `PATCH /public-api/v1/projects/{id}` to move the project to another stage.
    - `company_name` string, nullable, required: Name of the client the project is for, or `null`.
    - `client_or_company_name` string, required: Deprecated — read `company_name` instead, which carries the same value. This field will be removed in a future release.
    - `billing_type` 'time' | 'fixed_fee' | 'retainer' | 'not_billable' | 'null', nullable: How the project bills: `time` (hourly), `fixed_fee` (a single agreed fee), `retainer` (a recurring fee) or `not_billable`. Absent unless the token is allowed to see project finances.
    - `billing_cycle` 'weekly' | 'monthly' | 'quarterly' | 'biannually' | 'yearly' | 'null', nullable: How often a retainer bills. `null` for every other billing type. Absent unless the token is allowed to see project finances.
    - `billing_fee` string, nullable: The agreed fee as a decimal string, in `currency`. Set on `fixed_fee` and `retainer` projects, `null` otherwise. Absent unless the token is allowed to see project finances.
    - `company_id` integer, nullable, required: Id of the client company the project is for, or `null` when the client is not a company record.
    - `url` string, required: Absolute link to the project in the Bonsai app. Share it to send someone straight to the record.
    - `budget` ProjectBudget: 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 `project` wrapper.
- `401`: The access token is missing, expired or invalid.
- `403`: This token can see the project but is not allowed to change it.
- `404`: No project with that `id` that this token can see. Sample projects return `404` too.
- `422`: The project could not be saved. `blank` or `invalid_value` means a field was empty, `finish_date` falls before `start_date`, or the billing fields disagree with `billing_type`; `not_accessible` means `board_group_id` is not a stage on your project board, or that this token is not allowed to set the billing fields.
- `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)
