---
title: "Update a time entry"
method: PATCH
path: "/public-api/v1/time_entries/{key}"
tags: ["Time Entries"]
---

# Update a time entry

`PATCH /public-api/v1/time_entries/{key}`

## Request

- Base URL: `https://app.hellobonsai.com`
- URL: `https://app.hellobonsai.com/public-api/v1/time_entries/{key}`
- 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 logged time entry. Every field is optional, and anything you
leave out keeps its current value.

Move the entry between projects or tasks with `project_id` and
`task_uuid`. Sending `task_uuid` also settles the project — it is taken
from the task, and any `project_id` you send is ignored. Send either as
`null` to detach.

Moving the entry to a different project recalculates `rate` and
`non_billable` from the new project unless you set them yourself.

As on creation, `rate` and `non_billable` are only honoured for a token
allowed to set billing, and reassigning `owner_member_id` to somebody
else needs permission to manage the whole team's time.

An entry already billed on a paid or in-progress invoice is frozen:
editing it fails with `422 not_accessible`. Remove the invoice line item
first if you need to change it.

Set `billing_status` to `billed` or `unbilled` to move the entry's
billing state. It is only honoured for a token allowed to set billing,
and a token without that permission gets `422 not_accessible` rather
than a silent no-op. `non_billable` is not a writable value here — use
the `non_billable` flag instead.

Marking an entry `billed` requires it to be on a project and billable,
and fails with `422 invalid_value` otherwise. Moving a billed entry out
of `billed` indirectly — by clearing its project or task, or by moving it
to a non-billable project — needs the same billing permission and fails
with `422 not_accessible` without it. An entry attached to an
invoice cannot be moved in either direction and fails with
`422 not_accessible`. Repeating `billed` re-stamps the entry with the
current time, matching the bulk action in the app; repeating `unbilled`
on an already-unbilled entry changes nothing. A request that only sets
`billing_status` is accepted on a locked timesheet, matching the app's
mark-as-billed action.

`status` and `billing_status` answer different questions. `status`
tracks whether the entry sits on an invoice, so an entry you mark billed
by hand keeps `status: unbilled` while `billing_status` reads `billed`.

## Path parameters

- `key` string, required

## Request body

- object
  - `time_entry` object, required
    - `seconds` integer: How long the work took, in seconds.
    - `date` string, date: The day the work was done.
    - `project_id` integer, nullable: Id of the project to log against, or `null` to detach the entry from the one it is on. Ignored while the entry is linked to a task. Sample projects are not accepted.
    - `task_uuid` string, uuid, nullable: `uuid` of the task to log against — the project comes with it. Send `null` to unlink the task, which also clears the project it brought.
    - `notes` string, nullable: What the time was spent on. Send an empty string to clear it.
    - `rate` string, nullable: Hourly rate to bill at, as a decimal string. Must be zero or greater. Only honoured for a token allowed to set billing.
    - `non_billable` boolean, nullable: Whether to exclude the entry from billing. Only honoured for a token allowed to set billing.
    - `billing_status` 'billed' | 'unbilled': Mark the entry `billed`, or move it back to `unbilled`. Requires a token allowed to set billing — unlike `rate` and `non_billable`, a value here from a token without that permission fails the whole request with `422 not_accessible` rather than being ignored. `non_billable` is not accepted here — use the `non_billable` flag for that.
    - `owner_member_id` union: Who the time is for: a team member id, or `me` for whoever the token belongs to. Reassigning to someone else needs permission to manage the whole team's time.
      - integer, nullable: Team member id of the person the time is for.
      - 'me': Reassigns the time to whoever the token belongs to.

## Response `200`

The time entry was updated.

- object
  - `data` TimeEntry, required: Time logged against a project or task, and the basis for billing hourly work.
    - `key` string, required: Unique identifier for the time entry. This is the id the update endpoint takes in its path.
    - `seconds` integer, required: Duration of the entry in seconds.
    - `date` string, date, required: Date the work was done.
    - `notes` string, nullable, required: What the time was spent on, or `null`.
    - `rate` string, nullable: Hourly rate applied to the entry, as a decimal string in `currency`. `null` when no rate applies. Absent unless the token is allowed to see billing.
    - `non_billable` boolean: Whether the entry is excluded from billing. Absent unless the token is allowed to see billing.
    - `billable_amount` string: What the entry bills for, as a decimal string in `currency`. Absent unless the token is allowed to see billing.
    - `status` 'unbilled' | 'billed', required: Whether the entry has been placed on an invoice. `billing_status` is the fuller picture, and is what the list filter matches on.
    - `project_id` integer, nullable, required: Id of the project the time is logged against, or `null` when the entry is not attached to one.
    - `owner_member_id` integer, nullable, required: Team member id of whoever the time is logged for, or `null`.
    - `created_at` string, date-time, required: When the entry was logged.
    - `billing_status` 'non_billable' | 'unbilled' | 'billed': Effective billing state: `non_billable`, `unbilled` (billable, not on an invoice yet) or `billed` (on an invoice that has been sent, is overdue, is awaiting payment or has been paid, or marked billed by hand). Accepted by `filter[billing_status]` on the list endpoint, and writable as `billed` or `unbilled` on the update endpoint. Absent unless the token is allowed to see billing.
    - `currency` string, required: Three-letter ISO 4217 currency code for `rate` and `billable_amount`.
    - `formatted_time` string, required: `seconds` rendered as `HH:MM:SS`.
    - `task_uuid` string, nullable, required: `uuid` of the task the entry is linked to, or `null` when it is logged against a project only.
  - `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 `time_entry` wrapper.
- `401`: The access token is missing, expired or invalid.
- `403`: This token can see the entry but is not allowed to change it.
- `404`: No time entry with that key that this token can see.
- `422`: The entry could not be saved. `invalid_value` means a field was the wrong shape; `not_accessible` means a referenced record is out of reach, or that the entry is already billed on a paid or in-progress invoice and is frozen.
- `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)
