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.

Still have questions? We’re happy to help!

Contact Us