Report Exports
Report exports generate a file, such as a CSV, spreadsheet, PDF or zip, from a report and make it available to download. An export is created and then polled until it is complete.
The export object
| Attribute | Type | Description |
|---|---|---|
id |
integer | Unique ID for the export. |
type |
string | The requested export type. |
format |
string | The requested generation format. |
status |
string | Status of the export: pending or complete. |
created_at |
datetime | Date and time the export was requested, in ISO 8601 UTC. |
completed_at |
datetime | Date and time the export completed, in ISO 8601 UTC, or null while it is pending. |
download_url |
string | A bearer URL valid for one hour, signed on each response, or null while the export is pending. For gdoc, this downloads the intermediate CSV; the completion email carries the Google Doc link. |
Required permissions
Exports are scoped to the calling user and account. Creating, reading and downloading an export each recheck the caller’s access to it, including after a role change. A dashboard export requires a dashboard the caller can view.
Create a report export
Beta endpoint. Fully available, but the response shape may still change. Until it is frozen, every response includes
Harvest-Api-Stability: beta.
Creates a report export. Returns an export object and a 201 Created response code. The Location header is an absolute URL for the new export.
POST /v2/reports/exports
| Parameter | Type | Description |
|---|---|---|
type |
string | Required. One of the report-export types the API supports. Account-lifecycle, internal-finance and Activity Log export types are not requestable. |
export_format |
string | Required. One of csv, xlsx, gdoc, iif, pdf, or zip. gdoc requires permission to export to Google. zip is accepted for invoice_pack and estimate_pack only, pdf only for report types that render one, and expense_receipt accepts pdf alone. |
params |
object | Optional filter keys consumed by the export. An omitted or empty object uses the default active_projects: "false"; a non-empty object replaces that default. Unknown keys, an explicit null object, and keys Harvest sets itself are refused. |
params.dashboard_id |
integer | For a dashboard export, a dashboard the caller can view. A missing, foreign or inaccessible dashboard returns a 403 Forbidden response code. |
params.chart_images |
object | For a dashboard PDF, the base64 PNG images for the dashboard’s charts, keyed by chart. A chart with no image renders a placeholder. |
params.filter_override |
object | Optional for a dashboard export. A complete set of dashboard filters rather than a partial patch: date (date_kind, period_start, period_end), the ID arrays client_ids, project_ids, role_ids, department_ids and user_ids, and their five excluded_* counterparts. An invalid shape returns a 422 Unprocessable Entity response code. Omitted or null uses the dashboard’s saved filters. |
A known filter placed at the top level of the request rather than inside params is refused. Inherited query parameters such as page are accepted and ignored rather than becoming report filters.
Example Request:
curl -X POST "https://api.harvestapp.com/v2/reports/exports" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Harvest-Account-Id: $ACCOUNT_ID" \
-H "Content-Type: application/json" \
-H "User-Agent: MyApp ([email protected])" \
-d '{"type": "detailed_time_report", "export_format": "csv", "params": {"from": "2026-09-01", "till": "2026-09-30"}}'
Example Response:
{
"id": 42,
"type": "detailed_time_report",
"format": "csv",
"status": "pending",
"created_at": "2026-10-16T09:30:00Z",
"completed_at": null,
"download_url": null
}Retrieve a report export
Beta endpoint. Fully available, but the response shape may still change. Until it is frozen, every response includes
Harvest-Api-Stability: beta.
Retrieves the status of an export. Returns an export object and a 200 OK response code. Each response signs a fresh one-hour download_url when the export is complete.
GET /v2/reports/exports/{EXPORT_ID}
Example Request:
curl "https://api.harvestapp.com/v2/reports/exports/42" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Harvest-Account-Id: $ACCOUNT_ID" \
-H "User-Agent: MyApp ([email protected])"
Example Response:
{
"id": 42,
"type": "detailed_time_report",
"format": "csv",
"status": "complete",
"created_at": "2026-10-16T09:30:00Z",
"completed_at": "2026-10-16T09:31:12Z",
"download_url": "https://harvest-export.s3.amazonaws.com/exports/42/detailed_time_report.csv?X-Amz-Expires=3600&X-Amz-Signature=EXAMPLE"
}Download a report export
Beta endpoint. Fully available, but the response shape may still change. Until it is frozen, every response includes
Harvest-Api-Stability: beta.
Serves the completed export file with attachment headers, and returns a 200 OK response code. Access is rechecked on each request.
GET /v2/reports/exports/{EXPORT_ID}/download
Example Request:
curl -OJ "https://api.harvestapp.com/v2/reports/exports/42/download" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Harvest-Account-Id: $ACCOUNT_ID" \
-H "User-Agent: MyApp ([email protected])"
Errors
| Status | When |
|---|---|
400 |
The User-Agent header is missing, or an inherited query parameter is malformed. |
401 |
Missing or invalid credentials. |
403 |
The account is inactive or on hold, or the export type’s policy denies the caller: You are not allowed to export this report. |
404 |
The export ID is missing, malformed, out of range or belongs to another account: Record not found. |
409 |
Download is requested before a file exists: This export is still being generated. Poll GET /v2/reports/exports/:id until its status is complete. |
410 |
Status or download is requested after the export’s access has lapsed: This export is no longer available. |
415 |
The body media type is not accepted. |
422 |
An unsupported type or format, malformed or null params, an unknown or server-set filter, a filter placed at the top level, a malformed dashboard configuration, an export validation failure, an oversized PDF, or a request that the export would split into multiple files. Unavailable Google export: Google Drive export isn’t available for your account. |
429 |
A sixth dashboard export is requested within a minute: Too many dashboard exports were requested. Wait a minute and try again. The response carries Retry-After: 60. |
500 |
Unexpected generation failure: An error occurred while attempting to process your export. If the problem persists, please contact support. |