Export Harvest to JSON: API Time Entries vs OCR for Timesheet PDFs
← All posts
GuideOct 12, 2026· 8 min read

Export Harvest to JSON: API Time Entries vs OCR for Timesheet PDFs

If you searched for export Harvest to JSON, you probably want typed time or expense records you can store, sync to payroll, or load into a warehouse. Short answer: for a live Harvest account, list time entries with GET /v2/time_entries (native JSON) and use the Reports API for aggregated hours by client, project, task, or team. Use the product’s CSV/Excel exports when a spreadsheet is the real deliverable. When the input is a timesheet screenshot, PDF detailed-time report, printed sheet, or invoice PDF with no Harvest token to call, that is when OCRskill’s structured path (POST /ocr.json?fields=...) turns the page into named JSON.

This post is the Harvest-specific decision guide and glue. It is not a full field catalog. For form OCR as a document class, see form data extraction API. For how /ocr.json maps pixels to typed keys, see structured OCR JSON. The sibling Typeform walkthrough is export Typeform to JSON.

Three exports people confuse

“Export Harvest to JSON” usually means one of three jobs:

  1. Row-level time entries: each tracked period as a JSON object (user, client, project, task, hours, notes, approval).
  2. Aggregated time reports: totals by client, project, task, or team for a date window.
  3. Pixel capture: a PDF report printout, UI screenshot, or paper timesheet with no API access to the original account.

Jobs 1 and 2 belong to Harvest’s own API and admin exports. Job 3 belongs to OCR. Mixing them wastes time: you cannot call api.harvestapp.com against a phone photo of a timesheet, and you should not run OCR on data you can already pull as clean JSON from the Time Entries API.

Live account: Harvest API v2 paths to JSON

Harvest documents authentication and timesheets under API v2. Requests need a Bearer token (personal access token or OAuth access token), a Harvest-Account-Id header, and an identifying User-Agent. Personal access tokens come from the Developers section of Harvest ID; each request also needs your account ID because one token can cover multiple accounts. Details live in Harvest’s authentication guide.

Time entries: the native row-level JSON export

GET https://api.harvestapp.com/v2/time_entries returns a time_entries array plus pagination metadata. That is the direct answer when someone wants Harvest time data as JSON for ETL or a custom report.

curl "https://api.harvestapp.com/v2/time_entries?from=2026-01-01&to=2026-01-31" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Harvest-Account-Id: $ACCOUNT_ID" \
  -H "User-Agent: MyApp (ops@example.com)"

Verified behavior from Harvest’s time entries docs:

  • Entries are sorted by spent_date (sort order is not customizable).
  • Useful filters include user_id, client_id, project_id, task_id, is_billed, is_running, approval_status, updated_since, from, and to.
  • per_page ranges from 1 to 2000 (default 2000). Prefer the links.next URL from the response over inventing pagination yourself.
  • Each object can include nested user, client, project, and task summaries, decimal hours / rounded_hours, notes, approval_status, billable flags, and invoice linkage when billed.

That JSON is the native export for developers who searched “export Harvest to JSON” with an active workspace and a token.

Time reports: aggregated JSON for a date window

When you need totals rather than every row, Harvest’s Reports API exposes:

  • GET /v2/reports/time/clients
  • GET /v2/reports/time/projects
  • GET /v2/reports/time/tasks
  • GET /v2/reports/time/team

Each call requires both from and to, and the window cannot exceed 365 days. Responses include a results array with hours (and billable amounts when your permission allows). See the time reports reference for field lists and Forecast-related options.

curl "https://api.harvestapp.com/v2/reports/time/projects?from=2026-01-01&to=2026-03-31" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Harvest-Account-Id: $ACCOUNT_ID" \
  -H "User-Agent: MyApp (ops@example.com)"

Use reports for dashboards and capacity views. Use time entries when you need notes, start/end times, or per-row approval state.

Admin and report UI: CSV, Excel, PDF (not native JSON)

Harvest’s help center covers exporting data from the product UI:

  • Settings → Import/Export (administrators): export all company time as CSV; invoices and estimates as PDF zip archives.
  • Reports → Time and Detailed time: Excel or CSV; detailed time also offers PDF that mirrors the on-screen report.
  • Expenses, invoices, estimates, contractor, profitability, activity log, projects, tasks, people, and clients each have their own Excel/CSV (and sometimes PDF) paths, subject to plan and permission.

Useful for analysts. If your app needs JSON, either call the Time Entries or Reports API directly or convert the spreadsheet in your own code. Do not expect the UI download button to emit the same JSON shape as GET /v2/time_entries.

When OCR is the right path

Use OCRskill when you cannot authenticate to Harvest, or when the artifact is not in Harvest’s store:

  • PDF exports of detailed time or expense reports that someone emailed as an attachment
  • Screenshots from the Timesheet UI, email, or a slide deck
  • Printed weekly timesheets filled by hand or stamped for approval
  • Client-facing invoice PDFs you need indexed when you only have the file, not account API access
  • Partner packets that look like Harvest layouts but live only as images

In those cases, declare the keys your backend already uses and call structured extraction. For a Harvest invoice PDF, invoice-oriented fields often fit:

curl https://api.ocrskill.com/get-key.json

curl "https://api.ocrskill.com/ocr.json?fields=company_name,invoice_date,buyer_name?,seller_name?" \
  -H "Authorization: Bearer sk-your-key-here" \
  -F "file=@harvest-invoice.pdf"

You get trimmed strings and normalized dates as JSON, with required fields failing closed (400) when the page cannot support them. Optional keys use the name? suffix. Discovery mode (omit fields) helps you explore a new layout before you freeze a schema.

There is no dedicated hours, project_name, or task_name field guaranteed for every timesheet layout in the published catalog. For dense time-report tables, start with Markdown via POST /ocr so every row stays searchable, then parse project/task/hours in your own code. Full field lists and error shapes stay on the OCR JSON API reference; the sibling posts above cover buyer framing and pipeline validation so this page stays Harvest-focused.

If you only need searchable text for review or RAG, use Markdown via POST /ocr instead of forcing a schema. See image to text API for when Markdown beats typed fields. For receipt photos that sit next to expense rows, the paperless expense management workflow covers intake through archive.

Glue patterns that stay honest

A practical split for teams that both run Harvest and still receive paper or PDF leftovers:

Source Prefer Output
Live Harvest, need every time row Time Entries API GET /v2/time_entries time_entries JSON
Live Harvest, need totals by client/project/task/team Reports API /v2/reports/time/... results JSON
Live Harvest, need a spreadsheet Reports UI → Excel/CSV Tabular file
Live Harvest, need PDF of what is on screen Detailed time → PDF Visual report PDF
Screenshot / PDF report / paper timesheet OCRskill POST /ocr then parse, or /ocr.json where fields fit Markdown or app-shaped JSON
Invoice PDF only (no account access) OCRskill POST /ocr.json?fields=... Invoice-shaped JSON

Map Harvest nested objects (user.name, project.name, hours) to your internal column names in your own layer. Do not expect Harvest’s nested entry shape to match OCRskill’s flat field object one-for-one. Keep one schema contract in your app (Zod, Pydantic, JSON Schema) and adapt both sources into it.

For multipage PDF report dumps, decide page split vs single upload the way you would for any form packet. Harvest’s API already returns structured entries; OCR should only touch pages that never lived in that API (or that you must re-capture because the account is gone).

Limitations worth planning for

  • Token and account headers. Every API call needs Bearer auth, Harvest-Account-Id, and a real User-Agent. A public timesheet URL alone does not unlock private history.
  • Permissions. Administrators see all entries; managers see managed projects and teammates; members see only themselves. Report JSON respects the same rules.
  • Pagination. Large histories need links.next (or date windows), not a single giant dump. Default page size is already up to 2000.
  • Report window. Time report endpoints require from and to and refuse ranges longer than 365 days.
  • Admin formats. UI export is CSV/Excel/PDF; JSON for time entries is an API concern.
  • OCR quality. Handwriting, stamps, cropped screenshots, and tiny report tables still need a review path. Required-field 400s are a feature for incomplete scans, not a bug to retry blindly.
  • Field catalogs. OCRskill supports a published field list; novel timesheet columns with no analogue still need Markdown OCR plus your own structuring step until you lock keys.

None of this invents a Harvest “Download JSON” button that does not exist in the UI. It routes you to the documented Time Entries API, Reports API, admin export, and OCR when pixels are all you have.

Conclusion

“Export Harvest to JSON” is solved first by Harvest itself: Time Entries API for row-level JSON, Reports API for aggregated hours inside a one-year window, and admin CSV/Excel/PDF when a spreadsheet or printable report is the real deliverable. Keep OCRskill for the leftover world of timesheet screenshots, PDF report printouts, paper sheets, and invoice files where there is no Bearer token and no account ID to call. Wire both sources into one app schema so live Harvest traffic and scanned leftovers land as the same typed records. Grab a free OCRskill key when the next artifact is an image; keep Harvest’s time entries and authentication docs open when the account is still online.