A REST API with familiar conventions — if you have tooling built against Harvest v2, it mostly ports by changing the base URL and token. Machine-readable spec: /api/v1/openapi.json (imports straight into Postman).
Send Authorization: Bearer <token>. Two token kinds, both created on the in-app Developer page:
user_id.Endpoints marked manager+ or admin refuse lower-grade personal tokens — money (rates, invoices, reports) never reaches member tokens.
per_page, total_pages, total_entries, next_page, previous_page, and a links object.{"message": "…"} with meaningful status codes; business rules refuse with 422.Retry-After).approval_status maps open and rejected weeks to unsubmitted.The authenticated workspace.
GET/v1/companyRetrieve the companyTracked time. Member-grade personal tokens see and modify only their own entries; submitted or approved weeks are locked.
GET/v1/time_entriesList time entriesfrom — Only entries on/after this date (YYYY-MM-DD)to — Only entries on/before this dateuser_id — Filter to one memberclient_id — Filter by clientproject_id — Filter by projecttask_id — Filter by taskbillable — true/falseis_billed — true = already invoicedis_running — true = timer currently runningapproval_status — unsubmitted, submitted, or approvedexternal_reference_id — Match an external referenceupdated_since — ISO 8601 datetimepage — Page number, starting at 1per_page — Items per page (default 100, max 2000)POST/v1/time_entriesCreate a time entryPersonal tokens log as their owner. Weeks already submitted for approval refuse new time (422).
BODYproject_id — UUID, requiredtask_id — UUID, requiredspent_date — YYYY-MM-DD, requiredhours — Decimal hours (rounded per org policy)notes — Entry notesbillable — Defaults trueexternal_reference.id — External system reference (e.g. issue key)user_id — Required with an organization key; ignored for personal tokensGET/v1/time_entries/{id}Retrieve a time entryid (required) — The time entry id (UUID)PATCH/v1/time_entries/{id}Update a time entryLocked (approved/invoiced) and submitted entries return 422.
PARAMETERSid (required) — The time entry id (UUID)project_id — UUIDtask_id — UUIDspent_date — YYYY-MM-DD (target week must be open)hours — Decimal hoursnotes — Entry notesbillable — true/falseexternal_reference.id — Set or replace the external referenceDELETE/v1/time_entries/{id}Delete a time entryid (required) — The time entry id (UUID)PATCH/v1/time_entries/{id}/restartRestart the timer on an entry · personal tokenPersonal tokens only — timers belong to a person. Stops any other running timer.
PARAMETERSid (required) — The time entry id (UUID)PATCH/v1/time_entries/{id}/stopStop the running timer on an entryid (required) — The time entry id (UUID)DELETE/v1/time_entries/{id}/external_referenceRemove an entry’s external referenceid (required) — The time entry id (UUID)Clients and their currencies.
GET/v1/clientsList clientsname — Case-insensitive name fragmentpage — Page number, starting at 1per_page — Items per page (default 100, max 2000)POST/v1/clientsCreate a client · manager+name — Requiredcurrency — 3-letter codeaddress — Postal addressnotes — NotesGET/v1/clients/{id}Retrieve a clientid (required) — The client id (UUID)PATCH/v1/clients/{id}Update a client · manager+id (required) — The client id (UUID)name — Client namecurrency — 3-letter codeaddress — Postal addressnotes — NotesDELETE/v1/clients/{id}Archive a client · manager+Refuses (422) while the client has active projects.
PARAMETERSid (required) — The client id (UUID)Projects, and who is assigned to them.
GET/v1/projectsList projectsclient_id — Filter by clientis_active — true/falsepage — Page number, starting at 1per_page — Items per page (default 100, max 2000)POST/v1/projectsCreate a project · manager+client_id — UUID, requiredname — Requiredcode — Short codehourly_rate — Project hourly ratestarts_on — YYYY-MM-DDends_on — YYYY-MM-DDGET/v1/projects/{id}Retrieve a projectid (required) — The project id (UUID)PATCH/v1/projects/{id}Update a project · manager+id (required) — The project id (UUID)client_id — UUIDname — Project namecode — Short codeis_active — false archives, true restoreshourly_rate — Project hourly ratestarts_on — YYYY-MM-DDends_on — YYYY-MM-DDDELETE/v1/projects/{id}Archive a project · manager+id (required) — The project id (UUID)GET/v1/projects/{id}/user_assignmentsList a project’s peopleid (required) — The project id (UUID)page — Page number, starting at 1per_page — Items per page (default 100, max 2000)POST/v1/projects/{id}/user_assignmentsAssign a person to a project · manager+id (required) — The project id (UUID)user_id — Member UUID, requiredis_project_manager — Grants the PM flaghourly_rate — Per-assignment billable ratePATCH/v1/projects/{id}/user_assignments/{assignmentId}Update an assignment · manager+id (required) — The project id (UUID)assignmentId (required) — Assignment UUIDis_project_manager — PM flaghourly_rate — Per-assignment billable rateDELETE/v1/projects/{id}/user_assignments/{assignmentId}Remove an assignment · manager+id (required) — The project id (UUID)assignmentId (required) — Assignment UUIDGET/v1/projects/{id}/task_assignmentsList a project’s tasks (compatibility view)Tasks belong to projects here, so each task is its own assignment.
PARAMETERSid (required) — The project id (UUID)page — Page number, starting at 1per_page — Items per page (default 100, max 2000)Tasks (each belongs to one project — a deviation from Harvest).
GET/v1/tasksList tasksproject_id — Filter by projectpage — Page number, starting at 1per_page — Items per page (default 100, max 2000)POST/v1/tasksCreate a task · manager+project_id — UUID, requiredname — Requiredbillable_by_default — Defaults truedefault_hourly_rate — Task rateGET/v1/tasks/{id}Retrieve a taskid (required) — The task id (UUID)PATCH/v1/tasks/{id}Update a task · manager+id (required) — The task id (UUID)name — Task namebillable_by_default — true/falsedefault_hourly_rate — Task rateDELETE/v1/tasks/{id}Delete a task · manager+id (required) — The task id (UUID)Workspace members. Rates are money — manager+ tokens only.
GET/v1/usersList usersrole — owner, admin, manager, or memberpage — Page number, starting at 1per_page — Items per page (default 100, max 2000)GET/v1/users/meRetrieve the token’s owner · personal tokenGET/v1/users/{id}Retrieve a userid (required) — The user id (UUID)GET/v1/users/{id}/billable_ratesList a user’s billable rates · manager+id (required) — The user id (UUID)page — Page number, starting at 1per_page — Items per page (default 100, max 2000)GET/v1/users/{id}/cost_ratesList a user’s cost rates · manager+id (required) — The user id (UUID)page — Page number, starting at 1per_page — Items per page (default 100, max 2000)GET/v1/users/{id}/project_assignmentsList a user’s project assignmentsid (required) — The user id (UUID)page — Page number, starting at 1per_page — Items per page (default 100, max 2000)GET/v1/users/{id}/teammatesList a user’s direct reportsid (required) — The user id (UUID)page — Page number, starting at 1per_page — Items per page (default 100, max 2000)Work roles (job functions), assignable to members.
GET/v1/rolesList rolespage — Page number, starting at 1per_page — Items per page (default 100, max 2000)POST/v1/rolesCreate a role · manager+name — RequiredPATCH/v1/roles/{id}Update a role · manager+id (required) — The role id (UUID)name — RequiredDELETE/v1/roles/{id}Delete a role · manager+id (required) — The role id (UUID)Invoices, payments, and sending. Admin tokens or organization keys only.
GET/v1/invoicesList invoicesstate — draft, open, partially_paid, paid, closedclient_id — Filter by clientpage — Page number, starting at 1per_page — Items per page (default 100, max 2000)POST/v1/invoicesCreate an invoice · adminProvide line_items OR period_start+period_end. Generated invoices mark hours billed so nothing is double-invoiced.
BODYclient_id — UUID, requiredline_items — Array of {kind, description, quantity, unit_price} for a free-form invoiceperiod_start — With period_end: generate from uninvoiced time insteadperiod_end — YYYY-MM-DDgrouping — task (default), person, or detailedinclude_expenses — Defaults truesubject — Subject lineissue_date — Defaults todaydue_date — YYYY-MM-DDtax — Percentdiscount — PercentGET/v1/invoices/{id}Retrieve an invoice (lines + payments)id (required) — The invoice id (UUID)PATCH/v1/invoices/{id}Update a draft invoice · adminid (required) — The invoice id (UUID)subject — Subjectnotes — Notesissue_date — YYYY-MM-DDdue_date — YYYY-MM-DDtax — Percentdiscount — PercentDELETE/v1/invoices/{id}Delete a draft invoice · adminid (required) — The invoice id (UUID)POST/v1/invoices/{id}/messagesMark an invoice sent · adminMoves draft → open. Email delivery stays a deliberate in-app action.
PARAMETERSid (required) — The invoice id (UUID)POST/v1/invoices/{id}/paymentsRecord a payment · adminid (required) — The invoice id (UUID)amount — Requiredpaid_date — YYYY-MM-DD, requirednotes — NotesDELETE/v1/invoices/{id}/payments/{paymentId}Delete a payment · adminid (required) — The invoice id (UUID)paymentId (required) — Payment UUIDQuotes that convert to invoices. Admin tokens or organization keys only.
GET/v1/estimatesList estimates · manager+page — Page number, starting at 1per_page — Items per page (default 100, max 2000)POST/v1/estimatesCreate an estimate · adminclient_id — UUID, requiredline_items — Array of {kind, description, quantity, unit_price}, requiredsubject — Subjectissue_date — Defaults todaytax — Percentdiscount — PercentGET/v1/estimates/{id}Retrieve an estimate · manager+id (required) — The estimate id (UUID)PATCH/v1/estimates/{id}Change estimate state · adminid (required) — The estimate id (UUID)state — sent, accepted, or declinedDELETE/v1/estimates/{id}Delete a draft estimate · adminid (required) — The estimate id (UUID)POST/v1/estimates/{id}/convertConvert to a draft invoice · adminid (required) — The estimate id (UUID)Expenses and categories. Member tokens manage their own.
GET/v1/expensesList expensesuser_id — Required with an organization keyfrom — YYYY-MM-DDto — YYYY-MM-DDpage — Page number, starting at 1per_page — Items per page (default 100, max 2000)POST/v1/expensesCreate an expensespent_date — YYYY-MM-DD, requiredtotal_cost — Requiredproject_id — UUIDexpense_category_id — UUIDbillable — true/falseis_reimbursable — true/falsenotes — Notesuser_id — Required with an organization keyDELETE/v1/expenses/{id}Delete an expenseid (required) — The expense id (UUID)GET/v1/expense_categoriesList expense categoriespage — Page number, starting at 1per_page — Items per page (default 100, max 2000)POST/v1/expense_categoriesCreate an expense category · manager+name — Requiredunit_price — For unit-based categoriesAggregations with money — manager+ tokens or organization keys.
GET/v1/reports/time/{dimension}Time report by clients, projects, tasks, or team · manager+dimension (required) — clients, projects, tasks, or teamfrom (required) — YYYY-MM-DDto (required) — YYYY-MM-DDGET/v1/reports/expenses/{dimension}Expense report by categories, projects, or team · manager+dimension (required) — categories, projects, or teamfrom (required) — YYYY-MM-DDto (required) — YYYY-MM-DDGET/v1/reports/project_budgetHours budget vs actual per project · manager+from (required) — YYYY-MM-DDto (required) — YYYY-MM-DDGET/v1/reports/uninvoicedApproved, unbilled hours and amounts per project · manager+page — Page number, starting at 1per_page — Items per page (default 100, max 2000)