add_task_to_project
ChatGPTAdd a task to a project so time can be logged against it. Reactivates the task on the project if it was previously removed. Both the project and the task must be active — reactivate an archived project with update_project (is_active: true) first. Requires permission to edit the project.
assign_user_to_project
ChatGPTAssign a user to a project so they can log time against it. Reactivates the assignment if it was previously removed. Requires permission to edit the project and to manage the user.
create_client
ChatGPTCreate a client in the user's Harvest account. name is required. currency_code defaults to the company currency when omitted.
create_expense
ChatGPTCreate an expense for the authenticated user (or, for managers/admins, another user via user_id). Provide total_cost for amount-based categories, or units for unit-based categories (see list_expense_categories). Defaults to billable and to today in the user's timezone. Receipt upload is not supported.
create_invoice
ChatGPTCreate a free-form draft invoice for a client. client_id is required; supply line_items for the invoice content. The invoice number is assigned automatically and the invoice is created as a draft (not sent). The response is the same shape get_invoice returns, including estimate_id, retainer_id, and recurring_invoice_id (all null for a free-form invoice created this way). To bill tracked time or expenses instead, use create_invoice_from_tracked_time.
create_invoice_from_tracked_time
ChatGPTCreate a draft invoice for a client by importing uninvoiced billable tracked time and/or expenses for the given projects. client_id and project_ids are required, plus at least one of time or expenses. When a time/expenses block omits from/to, ALL uninvoiced billable activity for those projects is pulled. The invoice number is assigned automatically and the invoice is created as a draft (not sent). The response is the same shape get_invoice returns, including the estimate_id, retainer_id, and recurring_invoice_id linkage ids.
create_project
ChatGPTCreate a project for an existing client. name and client_id are required. Set hourly_rate only when the project bills hourly; it is ignored without billable-rate permission, and implies bill_by "Project" unless you pass a different bill_by. Pass bill_by explicitly to bill by Tasks or People (then set those rates separately). The response echoes billable_rate and rate_status so you can confirm whether the rate was applied — a rate dropped for lack of permission, or because the project does not bill at the project level, is reported rather than silently ignored. Set currency (an ISO code such as "EUR") only to bill this project in a currency other than its client's; costs always stay in the account currency, and the response echoes the resolved currency. Omit it, or pass the client's own code, to leave the project inheriting the client's — a value equal to the client's is not stored as the project's own, so it follows the client if that changes. Also sets the project's starts_on/ends_on dates, a fixed-fee fee, saved invoice defaults (invoice_due_date, invoice_due_date_custom_days, purchase_order, tax, tax2, discount) when invoices are enabled, and project tags by name (tags) when project tags are enabled. A fee implies is_fixed_fee and is_billable true and is refused without billable-rate permission; an unsupported field is refused rather than silently ignored.
create_task
ChatGPTCreate a new task in the Harvest account. Tasks are the kinds of work that get assigned to projects (e.g. "Design", "Development"). Set add_to_all_projects to also assign the new task to every active project. Requires task-management permission.
delete_time_entry
ChatGPTPermanently delete a time entry — your own, or a teammate's whose time you can edit in Harvest (an admin, or a manager of them or of an active project they're assigned to). The server enforces the same permissions as the web timesheet — you'll get a not_found error for an entry you're not allowed to delete. Entries on an invoiced or approval-locked period cannot be deleted and return a validation_failed error — unless you hold company-wide timer-write permission (an Administrator), which bypasses those locks here exactly as it does on the web timesheet. To zero out hours, delete the entry rather than updating it to 0.
get_account_settings
ChatGPTReturn the authenticated account's capabilities and tracking conventions so a caller can pre-validate before attempting an action instead of probing blind. Includes: approval_required (does logged time need approval), notes_required (every time entry must carry notes), time_rounding (whether hours are rounded and how), wants_timestamp_timers (timers track clock-in/out times vs raw durations), week_start_day, time_format, and whether the invoice and expense features are enabled. Account-level read — every authenticated user sees the same values; no per-row authorization applies.
get_expense
ChatGPTReturn a single expense with full detail: amounts, units, category (name, unit metadata), project and client names, currency, billable/invoiced/locked status, and whether a receipt is attached. Invoiced status is is_invoiced (true once the expense is on an invoice); the legacy is_billed key is a deprecated alias of it — prefer is_invoiced. Use list_expenses first to find the id. An expense the caller is not permitted to read returns not_found, identical to an id that does not exist.
get_invoice
ChatGPTReturn a single invoice with its header (client, state, amounts, taxes, dates) and full line_items. Use list_invoices first to find the id. An invoice the caller is not permitted to read returns not_found, identical to an id that does not exist. The header also reports where the invoice came from via estimate_id, retainer_id, and recurring_invoice_id — each is the linked record's id, or null when the invoice is not linked (a non-null recurring_invoice_id means it was generated by a recurring schedule; retainer_id marks a retainer deposit).
get_project_budget
ChatGPTReturn budget configuration and current usage for a specific project. scope states whether hours_spent/money_spent cover the current_month (monthly-reset budgets) or lifetime — read it before comparing usage to a budget (period carries the same value and is retained for backward compatibility). For money budgets (budget_by: project_cost|task_fees) money_spent is populated; money_spent_status says why it is null when it is (not_applicable = hours-only budget, hidden = rate-gated, visible = present). Rate fields (billable_rate, cost_budget, money_spent, and a fees budget) follow the caller's billable-rate permission. An hours budget instead follows project-budget access, so it matches what the caller sees in the Harvest UI. For a budget_by: person project this budget is the project-wide hours total across everyone assigned; read the per-person breakdown from list_project_assignments (users roster), where each currently-active member you're permitted to see carries their own budget (those active rows need not sum exactly to this total, which can also include people no longer active). billing_currency_code is the project's resolved billing currency (override → client → company) that the money amounts (money_spent, billable_rate, and a money budget) are expressed in; cost_budget is in the company currency. Each field is present when visible, null when not.
get_running_timer
ChatGPTReturn the time entry whose timer is currently running for the authenticated user, or — for managers/admins — another team member via user_id. Returns null if no timer is active. Authorization is enforced server-side: a role with no cross-user timer access gets forbidden for any user_id other than its own. A manager who can read only their managed scope may see null for a user_id outside that scope — same as list_time_entries may return no entries — rather than forbidden. When that happens scope_limited is true: a null may mean "you cannot see this user's timer" rather than "no timer is running" — do not report it as the user being idle.
get_time_report
ChatGPTAggregate time entries over a date range, grouped by project, client, or user. Each row carries total, billable, and non-billable hours, plus invoiced and uninvoiced hours, plus the group's id and display name (project_name/client_name/user_name). The billable split (billable/non-billable) and the invoiced split (invoiced/uninvoiced) are independent: invoiced_hours is what has been put on an invoice (invoice_id set), uninvoiced_hours is the rest — use these to answer "how many hours are (not) invoiced", e.g. with group_by=client. A totals object sums every hours field across all rows. The server scopes results to what the caller is allowed to see — call the tool and let the server decide; only an explicit forbidden error indicates a true denial.
list_clients
ChatGPTList clients in the user's Harvest account, ordered by name. Returns up to 100 clients per page; total_count is how many clients match the filter in full, so when truncated is true you can tell how many are missing — refine search or is_active to narrow the result.
list_expense_categories
ChatGPTList the expense categories in the user's Harvest account, ordered by name. Use this to find the expense_category_id for create_expense. Unit-based categories report is_unit_based: true along with their unit_name and unit_price. Returns up to limit categories; truncated is true when more exist.
list_expenses
ChatGPTList expenses, most recent first. Filter by project_id, user_id, a spent_at date range (from/to), or billable. Defaults to the last 30 days and, when user_id is omitted, to everyone you're allowed to see — your own expenses as a regular member, but a manager's managed teammates or an admin's whole account. Results are scoped server-side to what the caller may read. Each row carries is_invoiced (true once the expense is on an invoice; the legacy is_billed key is a deprecated alias — prefer is_invoiced). Returns limit expenses per page (default 100, max 500); when truncated is true, paginate by passing next_cursor from the prior response back via cursor — keep all other parameters identical and treat the cursor as opaque (do not construct one yourself). Use get_expense for full detail (category, project, lock status).
list_invoices
ChatGPTList invoices, most recent first. Filter by status (draft/approved/open/sent/paid/late), client_id, issue-date range (issued_from/issued_to), an exact due_date, or a search term matched against invoice number, subject, and PO. sent means open and not yet past due; late means open and past due. Results are scoped server-side to what the caller may read. Returns limit invoices per page (default 100, max 500); when truncated is true, paginate by passing next_cursor from the prior response back via cursor — keep all other parameters identical and treat the cursor as opaque (do not construct one yourself). Each row reports where the invoice came from via estimate_id, retainer_id, and recurring_invoice_id — the linked record's id, or null when not linked — so you can tell recurring-generated invoices and retainer deposits apart without a second call. Use get_invoice for full line-item detail.
list_project_assignments
ChatGPTList who and what is assigned to a project, with billable rates — the data needed to compute revenue/margin (pair it with get_project_budget for cost). assignment_type selects one roster per call: users returns the assigned team members (with is_project_manager, uses_default_rate, the effective billable_rate, and — for a project that budgets per person — each member's own hours budget); tasks returns the assigned tasks (with is_billable and the task billable_rate). Call once per type when you need both. The per-person budget is populated only when the project budgets by person (get_project_budget reports budget_by: person); it is null for every other budget method (the budget then lives on the task or the whole project). You always see your own budget; you see a teammate's only when you can view all tracked time on the project (an admin or the project's manager), matching the project's Team analysis view — otherwise a teammate's budget is null. get_project_budget's budget is the project-wide hours total for such a project — broadly the sum of these per-person budgets, but it can also include people no longer active on the project, so the active rows here need not add up to it exactly. This lists ASSIGNED members/tasks, which is distinct from who has logged time — an assigned user with no entries still appears. billable_rate follows your role: present when you can view rates on the project, null otherwise. billing_currency_code is the project's resolved billing currency that every billable_rate in the response is expressed in. Returns limit rows per page (default 100, max 500); when truncated is true, paginate by passing next_cursor back via cursor with all other parameters identical, treating the cursor as opaque.
list_projects
ChatGPTList projects in the user's Harvest account, ordered by project name unless sort is passed; each project includes its starts_on and ends_on dates (YYYY-MM-DD, or null when unset). Pass sort: start_date_asc/start_date_desc or end_date_asc/end_date_desc to order by those dates, with projects that have no date last. Each project's billing_currency_code is its resolved billing currency (override → client → company). Returns limit projects per page (default 100, max 500); total_count is how many projects match the filter in full. When truncated is true, page through the rest by passing next_cursor from the prior response back via cursor with all other parameters identical, treating the cursor as opaque — refine search, client_ids, or is_active only when you want a narrower result, not because the list ran out.
list_tasks
ChatGPTList tasks in the user's Harvest account, ordered by name. Returns up to 100 tasks per page; total_count is how many tasks match the filter in full, so when truncated is true you can tell how many are missing — refine search or is_active to narrow the result.
list_time_entries
ChatGPTList time entries, most recent first. Defaults to the last 30 days when from/to are omitted. Each entry includes started_time/ended_time (24h "HH:MM" clock-in/out, null for duration entries). hours is the raw tracked duration. Each entry also carries two independent status fields: billable (whether the work can be billed) and is_invoiced (whether it has been invoiced — true once the entry is on an invoice). Neither is the same as is_closed, which is timesheet-approval status and says nothing about invoicing. To answer invoiced vs. uninvoiced hours, read each entry's is_invoiced or pass the is_invoiced filter — never infer invoicing from is_closed or billable. When the account configures hour rounding, each entry also includes rounded_hours — the same duration under the account's rounding rule, matching what shows on their Harvest reports and invoices; prefer it when the user asks "how many hours" for a rounding-configured account (accounts with no rounding configured omit rounded_hours, since it would equal hours). Returns limit entries per page (default 100, max 500); when truncated is true, paginate by passing next_cursor from the prior response back via cursor — keep all other parameters identical and treat the cursor as opaque (do not construct one yourself). Authorization is enforced server-side: a role with no cross-user read access gets forbidden for any user_ids other than its own. A manager who can read only their managed scope receives just the entries they can see for user_ids outside that scope (often none), rather than forbidden. In that case scope_limited is true: the page may be empty or only partial because of your permissions, not because the user logged nothing — do not treat absent or zero hours as authoritative.
list_users
ChatGPTList team members in the user's Harvest account, ordered by id. Returns up to 100 users per page; total_count is how many users match the filter in full on every page, not how many are left. Fetch the rest by calling again with the returned next_cursor and the same filters, until truncated is false. Field availability follows the caller's role — populated values reflect what's visible to you.
log_time
ChatGPTLog a completed time entry for the authenticated user, or — for managers/admins — another team member via user_id. Use this for past time you already know the duration of. To track time as it elapses, use start_timer instead. Provide EITHER hours (use at least 0.01, the smallest stored increment; positive values that would be stored as 0 are rejected) OR a started_time and ended_time pair (e.g. "9:00am" to "11:00am"), from which the duration is derived. Accounts that track timestamped timers also record the interval on the entry; accounts that track decimal hours keep only the derived duration.
remove_task_from_project
ChatGPTRemove a task from a project. Historical time entries are preserved. Returns removed: false if the task was not assigned to the project. Requires permission to edit the project.
search_help_center
ChatGPTSearch the Harvest Help Center for product documentation: how a feature works, setup steps, integrations, billing, permissions, and troubleshooting. Returns article excerpts with the article title, the section heading, and the canonical Help Center URL to cite back to the user. Relevance-ranked semantic search, so wording matters and there is no relevance floor: a response always carries up to top_k excerpts even when none are related, and relevance_score only orders them within that one response — read the excerpts, and rephrase and search again before concluding a topic is not documented. Documentation only: it never reads the account's own data, so answer questions about the caller's clients, projects, time, or invoices with the tools that read those instead. When available is false nothing is indexed and no search ran — say so and point the user at help.getharvest.com rather than retrying.
start_timer
ChatGPTStart a new running timer on the given project + task, for the authenticated user or — for managers/admins — another team member via user_id. If that user already has a running timer for the same day, it is stopped first (cross-day timers are not auto-stopped — use stop_timer + start_timer in that case). Returns the running time entry.
stop_timer
ChatGPTStop the currently running timer for the authenticated user. If time_entry_id is provided, stops that specific entry; otherwise stops whichever timer is running. Returns the stopped time entry, or an error if no timer is running.
submit_feedback
ChatGPTSend feedback to the Harvest team. Call this only after the user agrees to send it; summarize their words and do not include data they did not choose to share. After calling, tell the user it was sent.
submit_timesheet
ChatGPTSubmit the authenticated user's timesheet for approval over a date range (or, for managers/admins, another user's via user_id). Stop any running timer in the range first — submission is rejected while a timer is running. Only available when timesheet approvals are enabled for the account.
unassign_user_from_project
ChatGPTRemove a user's assignment from a project. Historical time entries are preserved. Returns removed: false if the user was not assigned. Requires permission to edit the project and to manage the user.
update_client
ChatGPTUpdate a client's name, address, currency, or active state. Only the supplied fields change; omit a field to leave it unchanged. Set is_active to false to archive the client (this is blocked while the client still has active projects — archive or reassign them first) or true to reactivate an archived one. Requires client-management permission.
update_expense
ChatGPTUpdate an existing expense's amount, units, notes, billable flag, project, category, or date. Pass delete_receipt: true to remove an attached receipt. Uploading a receipt is not supported. The server enforces access — you'll get a not_found error if the expense isn't visible to you.
update_project
ChatGPTUpdate a project's name, code, billing setup, active state, or billing currency — the fields are: name, project code, type (hourly, fixed_fee, or non_billable), bill-by method, hourly rate, is_active, and currency. Only the supplied fields change. Set is_active to false to archive the project or true to reactivate an archived one — archiving a project with a running timer fails until the timer is stopped, and reactivation is blocked if the account is at its active-projects plan limit. Changing rates requires billable-rate permission. The response echoes code, billable_rate, rate_status, and the resolved currency so you can confirm what took effect — a rate that does not bill (the project is not billed at the project level) is reported as ignored_bill_by rather than silently kept inert. currency is stored as a relabel and does not convert existing amounts; costs always stay in the account currency.
update_task
ChatGPTUpdate a task: rename it, change its default billable rate, and/or archive/reactivate it via is_active. Only the supplied fields change. A new name must be unique among active and archived tasks in the account. Set is_active to false to archive the task (a task with a running timer can't be archived until the timer is stopped) or true to reactivate an archived one. Requires task-management permission. default_hourly_rate changes the task's default billable rate. IMPORTANT: this is retroactive and account-wide — it re-prices EVERY existing time entry on this task, with no date limit, on projects that bill by task where the assignment follows the task's default rate. Past billing data is rewritten and the previous per-entry rates are not recoverable, so confirm with the user before changing a rate on a task that already has tracked time. The rate is ignored without billable-rate permission; check the top-level rate_status in the response (applied or ignored_no_permission) to confirm what happened, since the echoed default_hourly_rate is null for callers who cannot view rates.
update_time_entry
ChatGPTUpdate an existing time entry's hours, notes, project, task, date, or clock-in/clock-out times — your own, or a teammate's whose time you can edit in Harvest (an admin, or a manager of them or of an active project they're assigned to). The server enforces the same permissions as the web timesheet — you'll get a not_found error for an entry you're not allowed to edit. Set the duration with EITHER hours (use at least 0.01, the smallest stored increment; positive values that would be stored as 0 are rejected) OR a started_time and ended_time pair (e.g. "9:00am" to "11:00am"), which is derived into hours. Accounts that track timestamped timers also record the interval on the entry; accounts that track decimal hours keep only the derived duration. To remove an entry use delete_time_entry, which enforces the same permissions.