harvest
Read time-tracking data out of Harvest — the hours that decide utilization, realization and what gets invoiced.
Use case
A consultancy's time tracker is its system of record for revenue. This connector pulls the entries so a workflow can do the arithmetic: utilization-tracker reads it instead of a spreadsheet, and any billing or WIP report can be built on the same call.
Operations
| operation | endpoint | what you get |
|---|---|---|
me |
GET /users/me |
The authenticated user. Cheapest way to check the credential works. |
list-time-entries |
GET /time_entries |
Entries in a date range, optionally filtered by user, client or project. |
list-projects |
GET /projects |
Projects, optionally by client and active state. |
list-clients |
GET /clients |
Clients. |
list-users |
GET /users |
People on the account. |
list-tasks |
GET /tasks |
Task types time is booked against. |
Setup
Credentials — Harvest needs two, not one
Create a personal access token at https://id.getharvest.com/developers. That page shows a token and an Account ID, and Harvest rejects the request unless both are sent:
accessToken→Authorization: Bearer <token>accountId→Harvest-Account-Id: <id>
A valid token with no account id returns a 401 that reads like a bad token. If auth fails, check you supplied both before regenerating anything.
The token is displayed once. Store it in the InTouch credential vault, not in the workflow.
A note on User-Agent
Harvest requires a User-Agent header naming the application with a contact link, and returns 400 Bad Request without one. The connector sends InTouch AI (https://www.blueisle.com). If you fork this, keep a compliant value — an empty or generic agent is a 400, not a warning.
Pagination — and why it publishes truncated
Harvest defaults to per_page=2000, which looks like "everything" right up until a firm's month exceeds it.
This connector follows next_page and returns the whole collection, up to a 25-page stop (50,000 records). It publishes count, pages and truncated alongside the data. If the cap is hit, truncated is true and an error message is set, because a utilization number computed from a silently truncated first page is wrong in the direction nobody checks — it under-reports, and looks reasonable.
If you see truncated: true, narrow the date range or filter by user or client. Do not treat that result as a full period.
Rate limits
100 requests per 15 seconds; Harvest returns 429 with Retry-After. The connector makes one polite 15-second retry on a 429 and then surfaces the failure rather than sitting in a sleep loop — a tool that blocks for minutes inside a scheduled workflow is worse than one that fails and tells you to spread the schedule out.
Published outputs
| key | meaning |
|---|---|
result |
JSON array of records (or the single object for me) |
status |
HTTP status of the last call |
count |
number of records returned |
truncated |
"true" if the page cap stopped the walk — the result is incomplete |
A non-2xx returns whatever was gathered before the failure, with status and an error set, so a partial read is never mistaken for a clean one.
Example — hours for last week
operation: list-time-entries
from: 2026-08-03
to: 2026-08-09
Each entry carries spent_date, hours, billable, billable_rate, and nested user, client, project and task objects — enough to compute utilization and realized revenue without a second call.
Source
harvest.py, tool.iml
Publishes
The keys a later task may reference as {{taskName.key}}. Referencing anything not listed here resolves to nothing at run time.
counterrorpagesresultstatustruncated
Input Properties
Every property this tool accepts, from its own tool.iml.
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
accessToken |
string | yes | — | Harvest personal access token, sent as 'Authorization: Bearer'. Create one at https://id.getharvest.com/developers — it is shown once. |
accountId |
string | yes | — | Harvest Account ID, sent as the 'Harvest-Account-Id' header. Shown next to the token on the same page. Harvest rejects the request without it, even when the token is valid. |
operation |
string | yes | — | One of: me, list-time-entries, list-projects, list-clients, list-users, list-tasks |
from |
string | no | — | Start date for list-time-entries, inclusive, as YYYY-MM-DD. |
to |
string | no | — | End date for list-time-entries, inclusive, as YYYY-MM-DD. |
userId |
string | no | — | Harvest user id. Filters list-time-entries to one person. |
clientId |
string | no | — | Harvest client id. Filters list-time-entries and list-projects. |
projectId |
string | no | — | Harvest project id. Filters list-time-entries. |
isActive |
string | no | — | 'true' or 'false' to filter list-projects, list-clients, list-users and list-tasks by active state. Blank returns both. |
perPage |
string | no | — | Records per page, 1-2000. Defaults to 2000. Pagination is followed regardless, so this only changes how many round trips are made. |