Detailed Reports
Detailed reports return entry-level time and expense rows for a report’s filters, together with the report’s own totals. Each response contains an object with a results property that contains an array of up to per_page rows. If no more rows are available, the resulting array will be empty. Several additional pagination properties are included in the response to simplify paginating your results.
An entry that has recorded no hours is not a row: it is absent from results and total_entries, and its zero is not in totals. A running timer records zero hours until it stops, so it is not a row either. An entry whose project, task, client or user no longer resolves is likewise not a row, and the totals are summed over exactly the rows.
The time report row object
| Attribute | Type | Description |
|---|---|---|
id |
integer | Unique ID for the time entry. |
spent_date |
date | Date the time was tracked. |
hours |
decimal | Hours recorded on the entry. A running timer is not included. totals.total_hours sums this field. |
billable |
boolean | Whether the entry is billable. |
is_billed |
boolean | Whether the entry has been invoiced. |
approval_status |
string | Approval status of the entry: unsubmitted, submitted, or approved. |
notes |
string | Notes on the entry. |
client_id |
integer | The ID of the client associated with the entry. |
client_name |
string | The name of the client associated with the entry. |
project_id |
integer | The ID of the project associated with the entry. |
project_name |
string | The name of the project associated with the entry. Includes the project code. |
task_id |
integer | The ID of the task associated with the entry. |
task_name |
string | The name of the task associated with the entry. |
user_id |
integer | The ID of the user associated with the entry. |
user_name |
string | The name of the user associated with the entry. |
The expense report row object
| Attribute | Type | Description |
|---|---|---|
id |
integer | Unique ID for the expense. |
spent_date |
date | Date the expense was incurred. |
total_cost |
decimal | Total cost of the expense. |
units |
decimal | Number of units on the expense. |
currency |
string | Currency code of the expense. |
billable |
boolean | Whether the expense is billable. |
is_billed |
boolean | Whether the expense has been invoiced. |
approval_status |
string | Approval status of the expense: unsubmitted, submitted, or approved. |
notes |
string | Notes on the expense. |
client_id |
integer | The ID of the client associated with the expense. |
client_name |
string | The name of the client associated with the expense. |
project_id |
integer | The ID of the project associated with the expense. |
project_name |
string | The name of the project associated with the expense. Includes the project code. |
expense_category_id |
integer | The ID of the expense category associated with the expense. |
expense_category_name |
string | The name of the expense category associated with the expense. |
user_id |
integer | The ID of the user associated with the expense. |
user_name |
string | The name of the user associated with the expense. |
Required permissions
For the time report, Administrators see all time entries for the account. Managers see their own entries, plus entries for the projects and people they manage. Members see only their own entries.
For the expense report, Administrators see all expenses for the account. Managers see their own expenses, plus expenses for the projects and people they manage. Members see only their own expenses.
Filtering by is_billed requires permission to read billed state: the time report requires permission to read any invoices, and the expense report requires permission to read all expenses. A caller without that permission receives a 403 Forbidden response code when they use the filter.
Time Detailed Report
Beta endpoint. Fully available, but the response shape may still change. Until it is frozen, every response includes
Harvest-Api-Stability: beta.
GET /v2/reports/time/detailed
| Parameter | Type | Description |
|---|---|---|
from |
date | Required. Only report on time entries with a spent_date on or after the given date. |
to |
date | Required. Only report on time entries with a spent_date on or before the given date. The timeframe cannot exceed 365 days. |
client_id |
integer | Only report on time entries for the given client. |
project_id |
integer | Only report on time entries for the given project. |
task_id |
integer | Only report on time entries for the given task. |
user_id |
integer | Only report on time entries for the given user. |
is_billed |
string | Pass true for invoiced entries and false for uninvoiced entries. |
approval_status |
string | Only report on entries with the given approval status: unsubmitted, submitted, or approved. |
group_by |
string | The grouping to order rows by: dates (default), clients, projects, tasks, or users. |
page |
integer | The page number to use in pagination. (Default: 1) |
per_page |
integer | The number of records to return per page. Can range between 1 and 2000. (Default: 2000) |
Example Request:
curl "https://api.harvestapp.com/v2/reports/time/detailed?from=2026-09-01&to=2026-09-30&group_by=projects" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Harvest-Account-Id: $ACCOUNT_ID" \
-H "User-Agent: MyApp ([email protected])"
Example Response:
{
"results": [
{
"id": 636709355,
"spent_date": "2026-09-22",
"hours": 3.0,
"billable": true,
"is_billed": false,
"approval_status": "approved",
"notes": "Design review",
"client_id": 5735774,
"client_name": "ABC Corp",
"project_id": 14307913,
"project_name": "[MW] Marketing Website",
"task_id": 8083365,
"task_name": "Graphic Design",
"user_id": 1782959,
"user_name": "Kim Allen"
},
{
"id": 636709360,
"spent_date": "2026-09-24",
"hours": 5.5,
"billable": false,
"is_billed": false,
"approval_status": "submitted",
"notes": "Team meeting",
"client_id": 5735774,
"client_name": "ABC Corp",
"project_id": 14307913,
"project_name": "[MW] Marketing Website",
"task_id": 8083368,
"task_name": "Project Management",
"user_id": 1782884,
"user_name": "Bob Powell"
}
],
"totals": {
"total_hours": 8.5,
"billable_hours": 3.0
},
"per_page": 2000,
"total_pages": 1,
"total_entries": 2,
"next_page": null,
"previous_page": null,
"page": 1,
"links": {
"first": "https://api.harvestapp.com/v2/reports/time/detailed?from=2026-09-01&group_by=projects&page=1&per_page=2000&to=2026-09-30",
"next": null,
"previous": null,
"last": "https://api.harvestapp.com/v2/reports/time/detailed?from=2026-09-01&group_by=projects&page=1&per_page=2000&to=2026-09-30"
}
}Expense Detailed Report
Beta endpoint. Fully available, but the response shape may still change. Until it is frozen, every response includes
Harvest-Api-Stability: beta.
GET /v2/reports/expenses/detailed
| Parameter | Type | Description |
|---|---|---|
from |
date | Required. Only report on expenses with a spent_date on or after the given date. |
to |
date | Required. Only report on expenses with a spent_date on or before the given date. The timeframe cannot exceed 365 days. |
client_id |
integer | Only report on expenses for the given client. |
project_id |
integer | Only report on expenses for the given project. |
expense_category_id |
integer | Only report on expenses for the given expense category. |
user_id |
integer | Only report on expenses for the given user. |
is_billed |
string | Pass true for invoiced expenses and false for uninvoiced expenses. |
approval_status |
string | Only report on expenses with the given approval status: unsubmitted, submitted, or approved. |
group_by |
string | The grouping to order rows by: dates (default), clients, projects, categories, or users. |
page |
integer | The page number to use in pagination. (Default: 1) |
per_page |
integer | The number of records to return per page. Can range between 1 and 2000. (Default: 2000) |
Example Request:
curl "https://api.harvestapp.com/v2/reports/expenses/detailed?from=2026-09-01&to=2026-09-30&group_by=categories" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Harvest-Account-Id: $ACCOUNT_ID" \
-H "User-Agent: MyApp ([email protected])"
Example Response:
{
"results": [
{
"id": 15296442,
"spent_date": "2026-09-18",
"total_cost": 133.35,
"units": 1.0,
"currency": "USD",
"billable": true,
"is_billed": false,
"approval_status": "submitted",
"notes": "Client dinner",
"client_id": 5735774,
"client_name": "ABC Corp",
"project_id": 14307913,
"project_name": "[MW] Marketing Website",
"expense_category_id": 4195926,
"expense_category_name": "Meals",
"user_id": 1782959,
"user_name": "Kim Allen"
}
],
"totals": [
{
"currency": "USD",
"total_amount": 133.35
}
],
"per_page": 2000,
"total_pages": 1,
"total_entries": 1,
"next_page": null,
"previous_page": null,
"page": 1,
"links": {
"first": "https://api.harvestapp.com/v2/reports/expenses/detailed?from=2026-09-01&group_by=categories&page=1&per_page=2000&to=2026-09-30",
"next": null,
"previous": null,
"last": "https://api.harvestapp.com/v2/reports/expenses/detailed?from=2026-09-01&group_by=categories&page=1&per_page=2000&to=2026-09-30"
}
}The expense totals are returned per currency and are never summed across currencies.
Errors
| Status | When |
|---|---|
400 |
from or to is missing: Cannot complete your request. Parameter is missing or the value is empty: from. |
400 |
A date is malformed: Invalid from date provided: "...". |
400 |
The timeframe exceeds 365 days: Invalid timeframe: must not exceed 1 year. |
400 |
An unknown approval_status: Invalid approval_status: must be one of unsubmitted, submitted, approved. |
400 |
An unknown group_by: Invalid group_by: must be one of dates, clients, projects, tasks, users (the expense report lists ... projects, categories, users). |
400 |
An invalid is_billed: Invalid is_billed: must be true or false. |
400 |
An ID is not an integer, for example Invalid client_id integer provided: "any". |
400 |
An ID is <= 0, for example Invalid project_id: must be greater than 0. |
400 |
page or per_page is malformed or out of range. |
401 |
Missing or invalid credentials. |
403 |
The caller cannot filter by billed state: You do not have permission to filter by billed status. |
403 |
The caller cannot read reports: Not authorized!. |
403 |
The account is inactive: Account inactive. |