Saved and Generated Reports
Saved reports store a report definition in the account. Generated reports return the aggregated table for a definition, either one the caller supplies or one that is already saved.
The saved report object
| Attribute | Type | Description |
|---|---|---|
id |
integer | Unique ID for the saved report. |
name |
string | Name of the saved report. |
description |
string | Description of the saved report. May be null. |
configuration |
object | Stored definition of the report. Supports subject, view (summary or detailed), preview_mode (compact or tabular), levels (an array of objects: {"kind": "column", "id": "<column ID>"} or {"kind": "date", "grain": "<grain>"}, where grain is day, week, semimonth, month, quarter, or year), columns, dims, filters, advanced_filters, sort, and include_zero_values. Unknown keys, malformed shapes, unknown dimensions or filters, non-integer dimension IDs, duplicate resolved columns, a non-boolean include_zero_values, a view, preview_mode or subject outside its allowed values, a subject the selected columns and levels rule out, an invalid sort, and a period_start or period_end that is not YYYY-MM-DD are refused. Responses return the full normalized definition: every supported key is present, with defaults filled in for keys the request left out. |
lock_version |
integer | Optimistic-lock token for the report. Send the value you last read when updating. |
owner |
object | An object containing the id and name of the user who owns the report. |
is_owner |
boolean | Whether the calling user owns the report. |
can_edit |
boolean | Whether the calling user can edit the report. Owners and users with an editor share can edit it. |
created_at |
datetime | Date and time the report was created. Use the ISO 8601 Format. |
updated_at |
datetime | Date and time the report was last updated. Use the ISO 8601 Format. |
The dims in a configuration hold quick-filter IDs. On create and on generation, an ID that matches no record in the account is refused. date_kind is stored and passed through as provided.
The generated report object
A generated report response contains a generated_report object with the following fields.
| Attribute | Type | Description |
|---|---|---|
report_id |
integer | ID of the saved report that was run, or null for an ad-hoc generation. |
subject |
string | Subject of the report: expenses, invoices, payments, or time. |
columns |
array | Array of id, label, and type descriptors. type describes the value in the cell and is one of text, number, currency, date, bool, or time. A yes/no or plain-text field used as a metric is number: a 0–100 percentage of true rows, or a distinct count. |
rows |
array | Rows keyed by column ID, each with a zero-based row index. |
row_count |
integer | Number of returned rows. |
overflow |
boolean | Whether the report may have more rows than were returned. |
row_currencies |
array | ISO currency code per row, parallel to rows. Present when the report aggregates money across more than one currency. |
warnings |
array | Warnings for the run, including omitted columns the caller cannot read, group-row notes, and a notice naming limit whenever overflow is true. |
Required permissions
Saved reports are scoped to the reports owned by, or shared with, the calling user. Owners can update and delete a report, users with an editor share can update it, and users with a view-only share can read it.
Running a report uses the calling user’s permissions. Columns the caller cannot read are dropped from the response and reported in warnings.
List all saved reports
Beta endpoint. Fully available, but the response shape may still change. Until it is frozen, every response includes
Harvest-Api-Stability: beta.
Returns a list of saved reports, ordered by updated_at descending, then id descending. The response contains an object with a saved_reports property that contains an array of up to per_page saved report objects.
GET /v2/reports/saved
| Parameter | Type | Description |
|---|---|---|
updated_since |
datetime | Only return saved reports updated at or after this timestamp. |
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/saved?updated_since=2026-10-01T00:00:00Z" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Harvest-Account-Id: $ACCOUNT_ID" \
-H "User-Agent: MyApp ([email protected])"
Example Response:
{
"saved_reports": [
{
"id": 1202,
"name": "Billable time by client",
"description": "Monthly billable hours",
"configuration": {
"subject": "time",
"view": "summary",
"preview_mode": "compact",
"levels": [
{
"kind": "column",
"id": "client"
}
],
"columns": [
"hours",
"billable_amount"
],
"dims": {
"client_ids": [],
"project_ids": [],
"role_ids": [],
"department_ids": [],
"user_ids": [],
"tag_ids": []
},
"filters": {},
"advanced_filters": [],
"sort": null,
"include_zero_values": false
},
"lock_version": 3,
"created_at": "2026-09-15T09:30:00Z",
"updated_at": "2026-10-01T14:05:00Z",
"owner": {
"id": 1782959,
"name": "Kim Allen"
},
"is_owner": true,
"can_edit": true
}
],
"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/saved?page=1&per_page=2000&updated_since=2026-10-01T00%3A00%3A00Z",
"next": null,
"previous": null,
"last": "https://api.harvestapp.com/v2/reports/saved?page=1&per_page=2000&updated_since=2026-10-01T00%3A00%3A00Z"
}
}Create a saved report
Beta endpoint. Fully available, but the response shape may still change. Until it is frozen, every response includes
Harvest-Api-Stability: beta.
Creates a saved report. Returns a saved report object and a 201 Created response code. The Location header points to the new report.
POST /v2/reports/saved
| Parameter | Type | Description |
|---|---|---|
name |
string | Required. Name for the report. A blank name is invalid. |
description |
string | Description for the report. |
configuration |
object | Required. The report definition. An empty definition is invalid. |
Example Request:
curl -X POST "https://api.harvestapp.com/v2/reports/saved" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Harvest-Account-Id: $ACCOUNT_ID" \
-H "Content-Type: application/json" \
-H "User-Agent: MyApp ([email protected])" \
-d '{"name": "Billable time by client", "description": "Monthly billable hours", "configuration": {"subject": "time", "view": "summary", "levels": [{"kind": "column", "id": "client"}], "columns": ["hours", "billable_amount"]}}'
Example Response:
{
"id": 1202,
"name": "Billable time by client",
"description": "Monthly billable hours",
"configuration": {
"subject": "time",
"view": "summary",
"preview_mode": "compact",
"levels": [
{
"kind": "column",
"id": "client"
}
],
"columns": [
"hours",
"billable_amount"
],
"dims": {
"client_ids": [],
"project_ids": [],
"role_ids": [],
"department_ids": [],
"user_ids": [],
"tag_ids": []
},
"filters": {},
"advanced_filters": [],
"sort": null,
"include_zero_values": false
},
"lock_version": 0,
"created_at": "2026-10-16T09:30:00Z",
"updated_at": "2026-10-16T09:30:00Z",
"owner": {
"id": 1782959,
"name": "Kim Allen"
},
"is_owner": true,
"can_edit": true
}Retrieve a saved report
Beta endpoint. Fully available, but the response shape may still change. Until it is frozen, every response includes
Harvest-Api-Stability: beta.
Retrieves a saved report. Returns a saved report object and a 200 OK response code.
GET /v2/reports/saved/{SAVED_REPORT_ID}
Example Request:
curl "https://api.harvestapp.com/v2/reports/saved/1202" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Harvest-Account-Id: $ACCOUNT_ID" \
-H "User-Agent: MyApp ([email protected])"
Example Response:
{
"id": 1202,
"name": "Billable time by client",
"description": "Monthly billable hours",
"configuration": {
"subject": "time",
"view": "summary",
"preview_mode": "compact",
"levels": [
{
"kind": "column",
"id": "client"
}
],
"columns": [
"hours",
"billable_amount"
],
"dims": {
"client_ids": [],
"project_ids": [],
"role_ids": [],
"department_ids": [],
"user_ids": [],
"tag_ids": []
},
"filters": {},
"advanced_filters": [],
"sort": null,
"include_zero_values": false
},
"lock_version": 3,
"created_at": "2026-09-15T09:30:00Z",
"updated_at": "2026-10-01T14:05:00Z",
"owner": {
"id": 1782959,
"name": "Kim Allen"
},
"is_owner": true,
"can_edit": true
}Update a saved report
Beta endpoint. Fully available, but the response shape may still change. Until it is frozen, every response includes
Harvest-Api-Stability: beta.
Updates a saved report. Returns a saved report object and a 200 OK response code. A lock_version that is older than the stored value returns a 409 Conflict response code.
Supplying configuration replaces the stored definition whole: keys left out take their defaults rather than their stored values. To change part of a report, send back the full configuration from a GET with your edits.
PATCH /v2/reports/saved/{SAVED_REPORT_ID}
| Parameter | Type | Description |
|---|---|---|
name |
string | Updated name for the report. |
description |
string | Updated description for the report. |
configuration |
object | Replacement report definition. Omit it to leave the stored definition unchanged. |
lock_version |
integer | The report’s lock_version when you read it. A non-integer is invalid; null is treated as absent. |
Example Request:
curl -X PATCH "https://api.harvestapp.com/v2/reports/saved/1202" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Harvest-Account-Id: $ACCOUNT_ID" \
-H "Content-Type: application/json" \
-H "User-Agent: MyApp ([email protected])" \
-d '{"name": "Billable time by client (monthly)", "lock_version": 3}'
Example Response:
{
"id": 1202,
"name": "Billable time by client (monthly)",
"description": "Monthly billable hours",
"configuration": {
"subject": "time",
"view": "summary",
"preview_mode": "compact",
"levels": [
{
"kind": "column",
"id": "client"
}
],
"columns": [
"hours",
"billable_amount"
],
"dims": {
"client_ids": [],
"project_ids": [],
"role_ids": [],
"department_ids": [],
"user_ids": [],
"tag_ids": []
},
"filters": {},
"advanced_filters": [],
"sort": null,
"include_zero_values": false
},
"lock_version": 4,
"created_at": "2026-09-15T09:30:00Z",
"updated_at": "2026-10-16T10:12:00Z",
"owner": {
"id": 1782959,
"name": "Kim Allen"
},
"is_owner": true,
"can_edit": true
}Delete a saved report
Beta endpoint. Fully available, but the response shape may still change. Until it is frozen, every response includes
Harvest-Api-Stability: beta.
Deletes a saved report. Returns the deleted saved report object and a 200 OK response code. A later request for the same report returns a 404 Not Found response code.
DELETE /v2/reports/saved/{SAVED_REPORT_ID}
Example Request:
curl -X DELETE "https://api.harvestapp.com/v2/reports/saved/1202" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Harvest-Account-Id: $ACCOUNT_ID" \
-H "User-Agent: MyApp ([email protected])"
Example Response:
{
"id": 1202,
"name": "Billable time by client (monthly)",
"description": "Monthly billable hours",
"configuration": {
"subject": "time",
"view": "summary",
"preview_mode": "compact",
"levels": [
{
"kind": "column",
"id": "client"
}
],
"columns": [
"hours",
"billable_amount"
],
"dims": {
"client_ids": [],
"project_ids": [],
"role_ids": [],
"department_ids": [],
"user_ids": [],
"tag_ids": []
},
"filters": {},
"advanced_filters": [],
"sort": null,
"include_zero_values": false
},
"lock_version": 4,
"created_at": "2026-09-15T09:30:00Z",
"updated_at": "2026-10-16T10:12:00Z",
"owner": {
"id": 1782959,
"name": "Kim Allen"
},
"is_owner": true,
"can_edit": true
}Generate a report
Beta endpoint. Fully available, but the response shape may still change. Until it is frozen, every response includes
Harvest-Api-Stability: beta.
Runs a report definition supplied in the request. Returns a generated report object and a 200 OK response code. This action generates only the summary view; a definition that requests another view is refused.
POST /v2/reports/generate
| Parameter | Type | Description |
|---|---|---|
configuration |
object | Required. The report definition to run. An empty definition is invalid. |
limit |
integer | The maximum number of generated rows to return, between 1 and 500. (Default: 100) |
Example Request:
curl -X POST "https://api.harvestapp.com/v2/reports/generate" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Harvest-Account-Id: $ACCOUNT_ID" \
-H "Content-Type: application/json" \
-H "User-Agent: MyApp ([email protected])" \
-d '{"configuration": {"subject": "time", "view": "summary", "levels": [{"kind": "column", "id": "client"}], "columns": ["hours", "billable_amount"]}, "limit": 100}'
Example Response:
{
"generated_report": {
"report_id": null,
"subject": "time",
"columns": [
{
"id": "client",
"label": "Client",
"type": "text"
},
{
"id": "hours",
"label": "Hours",
"type": "number"
},
{
"id": "billable_amount",
"label": "Billable amount",
"type": "currency"
}
],
"rows": [
{
"row": 0,
"client": "ABC Corp",
"hours": 12.5,
"billable_amount": 1250.0
},
{
"row": 1,
"client": "123 Industries",
"hours": 8.0,
"billable_amount": 800.0
}
],
"row_count": 2,
"overflow": false,
"row_currencies": [
"USD",
"EUR"
],
"warnings": []
}
}Run a saved report
Beta endpoint. Fully available, but the response shape may still change. Until it is frozen, every response includes
Harvest-Api-Stability: beta.
Runs the stored definition of a saved report. Returns a generated report object and a 200 OK response code. The report_id in the response is the ID of the saved report. Only saved reports with the summary view can be run; a saved report with the detailed view returns a 422 Unprocessable Entity response code with code unsupported_view.
GET /v2/reports/generated/{SAVED_REPORT_ID}
| Parameter | Type | Description |
|---|---|---|
limit |
integer | The maximum number of generated rows to return, between 1 and 500. (Default: 100) |
Example Request:
curl "https://api.harvestapp.com/v2/reports/generated/1202?limit=100" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Harvest-Account-Id: $ACCOUNT_ID" \
-H "User-Agent: MyApp ([email protected])"
Example Response:
{
"generated_report": {
"report_id": 1202,
"subject": "time",
"columns": [
{
"id": "client",
"label": "Client",
"type": "text"
},
{
"id": "hours",
"label": "Hours",
"type": "number"
}
],
"rows": [
{
"row": 0,
"client": "ABC Corp",
"hours": 12.5
}
],
"row_count": 1,
"overflow": true,
"warnings": [
"This report may have more rows than the limit of 100, so its totals may cover only part of the data. Raise limit (up to 500) or narrow the date range or filters."
]
}
}Errors
| Status | When |
|---|---|
401 |
Missing or invalid credentials. |
403 |
A view-only share member attempts to update a report, or a caller other than the owner (including an editor) attempts to delete it: Not authorized!. |
404 |
The ID is missing, outside the caller’s company, or not owned by or shared with the caller: Record not found. |
409 |
The update’s lock_version is stale: This report was changed by someone else. Reload it and try again. |
422 |
A malformed request, a missing or blank name, an invalid configuration, a limit outside 1–500, an invalid lock version, or a model validation failure. Codes: invalid_configuration, invalid_limit, invalid_lock_version, invalid_report. |
422 |
The definition is invalid to the report engine, for example an unknown column, level, grain or filter ID, a mixed-subject composition, a subject the columns and levels rule out, too many levels, or an unusable sort. Codes include unknown_identifier, column_permission_denied, and column_not_compatible_with_subject. |
422 |
Generation or creation names a quick-filter ID that matches nothing in the account: <dim> has no match in this account for <ids>., code unknown_identifier. |
422 |
The saved-report limit for the account has been reached, code saved_report_limit_reached. |
422 |
Generation requested a view other than summary, or the saved report being run has the detailed view: Only summary reports can be generated through the API, code unsupported_view. |
422 |
The report covers too much data to load: This report covers too much data to load. Narrow the date range or add filters, then try again., code report_too_large. |
500 |
Any other unexpected failure. |