Stitchflow
Harvest logo

Harvest User Management API Guide

API workflow

How to automate user lifecycle operations through APIs with caveats that matter in production.

UpdatedMar 11, 2026

Summary and recommendation

Harvest exposes a REST API at https://api.harvestapp.com/v2 supporting OAuth 2.0 and Personal Access Tokens. All requests require both an Authorization: Bearer {token} header and a Harvest-Account-Id header - omitting the latter returns a 401 even with a valid token.

The API covers full user CRUD, project assignments, and billable rate management, but has no native SCIM 2.0 endpoint and no webhook support for user events. Admin-level credentials are required to create, update, or delete users; non-admin tokens are scoped to GET /v2/users/me only.

API quick reference

Has user APIYes
Auth methodOAuth 2.0 or Personal Access Token (Bearer token in Authorization header)
Base URLOfficial docs
SCIM availableNo
SCIM plan requiredNot available on any plan

Authentication

Auth method: OAuth 2.0 or Personal Access Token (Bearer token in Authorization header)

Setup steps

  1. Navigate to https://id.getharvest.com/developers to create an OAuth2 application or generate a Personal Access Token (PAT).
  2. For OAuth 2.0: register your app, obtain client_id and client_secret, redirect users to https://id.getharvest.com/oauth2/authorize, exchange the authorization code for an access token at https://id.getharvest.com/api/v2/oauth2/token.
  3. For PAT: copy the token from the developer portal; no OAuth flow required.
  4. Include the token in all requests as: Authorization: Bearer {token}
  5. Include the required Harvest-Account-Id header: Harvest-Account-Id: {account_id}
  6. Set Content-Type: application/json for POST/PATCH requests.

Required scopes

Scope Description Required for
all:read Read access to all resources in the account Listing and retrieving users
all:edit Read and write access to all resources in the account Creating, updating, and deleting users

User object / data model

Field Type Description On create On update Notes
id integer Unique user ID auto-assigned immutable Used in URL path for user-specific operations
first_name string User's first name required optional
last_name string User's last name required optional
email string User's email address; used for login required optional Must be unique within the account
telephone string User's telephone number optional optional
timezone string User's timezone (e.g., 'Eastern Time (US & Canada)') optional optional Defaults to account timezone if not set
has_access_to_all_future_projects boolean Whether user is auto-added to new projects optional optional
is_contractor boolean Whether user is a contractor optional optional
is_admin boolean Whether user has admin privileges optional optional Admins can manage account settings and all users
is_project_manager boolean Whether user can manage projects optional optional
can_see_rates boolean Whether user can view billing rates optional optional
can_create_projects boolean Whether user can create projects optional optional
can_create_invoices boolean Whether user can create invoices optional optional
is_active boolean Whether user account is active optional optional Set to false to deactivate without deleting
weekly_capacity integer User's weekly capacity in seconds optional optional Used for capacity planning
default_hourly_rate decimal User's default billing rate optional optional
cost_rate decimal User's internal cost rate optional optional
roles array[string] List of role names assigned to the user optional optional
avatar_url string URL of user's avatar image auto-assigned immutable Read-only
created_at datetime ISO 8601 timestamp of user creation auto-assigned immutable Read-only

Core endpoints

List all users

  • Method: GET
  • URL: https://api.harvestapp.com/v2/users
  • Watch out for: Returns both active and inactive users by default; use ?is_active=true to filter active only.

Request example

GET /v2/users?is_active=true&page=1
Authorization: Bearer {token}
Harvest-Account-Id: {account_id}

Response example

{
  "users": [{"id":1234,"first_name":"Jane","last_name":"Doe","email":"jane@example.com","is_active":true}],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "page": 1
}

Retrieve a user

  • Method: GET
  • URL: https://api.harvestapp.com/v2/users/{userId}

Request example

GET /v2/users/1234
Authorization: Bearer {token}
Harvest-Account-Id: {account_id}

Response example

{
  "id": 1234,
  "first_name": "Jane",
  "last_name": "Doe",
  "email": "jane@example.com",
  "is_admin": false,
  "is_active": true
}

Retrieve the currently authenticated user

  • Method: GET
  • URL: https://api.harvestapp.com/v2/users/me
  • Watch out for: Useful for resolving the authenticated user's ID from a PAT or OAuth token.

Request example

GET /v2/users/me
Authorization: Bearer {token}
Harvest-Account-Id: {account_id}

Response example

{
  "id": 1234,
  "first_name": "Jane",
  "email": "jane@example.com",
  "is_admin": true
}

Create a user

  • Method: POST
  • URL: https://api.harvestapp.com/v2/users
  • Watch out for: Creating a user sends an invitation email to the provided address. The user counts against the account's seat limit immediately.

Request example

POST /v2/users
Content-Type: application/json

{"first_name":"John","last_name":"Smith","email":"john@example.com","is_admin":false}

Response example

{
  "id": 5678,
  "first_name": "John",
  "last_name": "Smith",
  "email": "john@example.com",
  "is_active": true
}

Update a user

  • Method: PATCH
  • URL: https://api.harvestapp.com/v2/users/{userId}
  • Watch out for: Only include fields you want to change; omitted fields retain their current values.

Request example

PATCH /v2/users/5678
Content-Type: application/json

{"is_admin":true,"weekly_capacity":144000}

Response example

{
  "id": 5678,
  "is_admin": true,
  "weekly_capacity": 144000,
  "updated_at": "2024-01-15T10:00:00Z"
}

Delete a user

  • Method: DELETE
  • URL: https://api.harvestapp.com/v2/users/{userId}
  • Watch out for: Harvest recommends deactivating users (PATCH is_active=false) rather than deleting to preserve historical time entry data. Deletion is permanent.

Request example

DELETE /v2/users/5678
Authorization: Bearer {token}
Harvest-Account-Id: {account_id}

Response example

HTTP 200 OK
{}

List billable rates for a user

  • Method: GET
  • URL: https://api.harvestapp.com/v2/users/{userId}/billable_rates
  • Watch out for: Requires admin or project manager access; returns rate history with effective date ranges.

Request example

GET /v2/users/1234/billable_rates
Authorization: Bearer {token}
Harvest-Account-Id: {account_id}

Response example

{
  "billable_rates": [{"id":1,"amount":150.0,"start_date":"2024-01-01","end_date":null}]
}

List project assignments for a user

  • Method: GET
  • URL: https://api.harvestapp.com/v2/users/{userId}/project_assignments
  • Watch out for: Returns only active project assignments by default; use ?is_active=false to include inactive ones.

Request example

GET /v2/users/1234/project_assignments
Authorization: Bearer {token}
Harvest-Account-Id: {account_id}

Response example

{
  "project_assignments": [{"id":99,"project":{"id":10,"name":"Website Redesign"},"is_active":true}]
}

Rate limits, pagination, and events

  • Rate limits: Harvest enforces a rate limit of 100 requests per 15 seconds per access token.
  • Rate-limit headers: Yes
  • Retry-After header: Yes
  • Rate-limit notes: When the limit is exceeded, the API returns HTTP 429. The Retry-After header indicates when requests can resume. Headers X-RateLimit-Limit and X-RateLimit-Remaining are included in responses.
  • Pagination method: offset
  • Default page size: 100
  • Max page size: 100
  • Pagination pointer: page
Plan Limit Concurrent
All plans 100 requests per 15 seconds 0
  • Webhooks available: No
  • Webhook notes: Harvest does not offer native webhooks for user management events. The API must be polled to detect user changes.
  • Alternative event strategy: Poll GET /v2/users with updated_since query parameter to detect changes since a given timestamp.

SCIM API status

  • SCIM available: No
  • SCIM version: Not documented
  • Plan required: Not available on any plan
  • Endpoint: Not documented

Limitations:

  • Harvest has no native SCIM 2.0 endpoint.
  • User provisioning via SCIM is only available through third-party IdP connectors (e.g., OneLogin provisioning connector).
  • SAML SSO is available on the Premium plan but does not include SCIM provisioning.
  • Deprovisioning must be handled via the REST API (PATCH is_active=false or DELETE).

Common scenarios

Three core automation scenarios are well-supported by the API. First, provisioning: POST /v2/users with first_name, last_name, email, is_admin, and weekly_capacity (expressed in seconds - 40 hrs = 144000) creates the user and immediately triggers an invitation email; there is no silent provisioning path.

Second, deprovisioning: PATCH /v2/users/{userId} with {"is_active": false} deactivates the account and preserves all historical time entries; DELETE /v2/users/{userId} is permanent and irreversible, so deactivation is strongly preferred. Third, roster sync: GET /v2/users?

is_active=true with page-based pagination (max 100 records per page) returns the active user list; use ? updated_since={ISO8601_timestamp} on subsequent syncs to fetch only changed records, and add a 60-second buffer to account for clock skew.

Feeding this sync output into an identity graph allows downstream systems to maintain a consistent, current view of Harvest's active user state without full re-fetches on every cycle.

Provision a new employee

  1. POST /v2/users with first_name, last_name, email, is_admin, weekly_capacity, and default_hourly_rate.
  2. Capture the returned user id.
  3. POST /v2/projects/{projectId}/user_assignments with the new user_id to assign them to relevant projects.
  4. Optionally POST /v2/users/{userId}/billable_rates to set a project-specific billing rate.

Watch out for: Step 1 triggers an invitation email immediately. Ensure the email address is correct before calling the endpoint.

Deactivate a departing employee

  1. GET /v2/users?is_active=true to find the user's id by email.
  2. PATCH /v2/users/{userId} with {"is_active": false} to deactivate the account.
  3. Verify the user no longer appears in active user lists by calling GET /v2/users?is_active=true.

Watch out for: Deactivation preserves all historical time entries. Deletion (DELETE /v2/users/{userId}) is irreversible and removes the user's data associations.

Sync active user roster to an external directory

  1. GET /v2/users?is_active=true&page=1 and iterate through all pages using the next_page field.
  2. Store each user's id, email, first_name, last_name, is_admin, and roles.
  3. On subsequent syncs, use GET /v2/users?updated_since={last_sync_timestamp} to fetch only changed records.
  4. Compare results against the external directory and apply PATCH or deactivation as needed.

Watch out for: The updated_since parameter uses ISO 8601 format. Clock skew between systems can cause missed updates; add a 60-second buffer to the timestamp.

Why building this yourself is a trap

The absence of native SCIM and webhooks creates two structural gaps worth flagging explicitly. Without SCIM, any IdP-driven provisioning flow must be custom-built against the REST API or routed through a third-party connector such as OneLogin; neither path is zero-maintenance.

Without webhooks, detecting user state changes requires polling - GET /v2/users?updated_since= is the only supported mechanism, and missed updates are possible if the polling interval or timestamp buffer is misconfigured.

The rate limit of 100 requests per 15 seconds per access token is generous for most use cases, but high-frequency sync jobs should implement backoff on HTTP 429 responses and respect the Retry-After header. OAuth 2.0 access tokens expire and require refresh token handling; PATs do not expire but carry broader scope risk if leaked.

No public documentation confirms the OAuth token expiry duration - verify against the Harvest developer portal before implementing.

Automate Harvest workflows without one-off scripts

Stitchflow builds and maintains end-to-end IT automation across your SaaS stack, including apps without APIs. Built for exactly how your company works, with human approvals where they matter.

Every app coverage, including apps without APIs
60+ app integrations plus browser automation for apps without APIs
IT graph reconciliation across apps and your IdP
Less than a week to launch, maintained as APIs and admin consoles change
SOC 2 Type II. ~2 hours of your team's time

UpdatedMar 11, 2026

* Details sourced from official product documentation and admin references.

Keep exploring

Related apps

Abnormal Security logo

Abnormal Security

API Only
AutomationAPI only
Last updatedMar 2026

Abnormal Security is an enterprise email security platform focused on detecting and investigating threats such as phishing, account takeover (ATO), and vendor email compromise. It does not support SCIM provisioning, which means every app in your stack

ActiveCampaign logo

ActiveCampaign

API Only
AutomationAPI only
Last updatedFeb 2026

ActiveCampaign uses a group-based permission model: every user belongs to exactly one group, and all feature-area access (Contacts, Campaigns, Automations, Deals, Reports, Templates) is configured at the group level, not per individual. The default Adm

ADP logo

ADP

API Only
AutomationAPI only
Last updatedFeb 2026

ADP Workforce Now is a mid-market to enterprise HCM platform that serves as the HR source of record for employee data — payroll, benefits, time, and talent. User access is governed by a hybrid permission model: predefined security roles (Security Maste