Custom Roles

Custom roles are the custom profiles referenced on the Users page. They are unrelated to the descriptive user roles managed at /v2/roles.

The custom role object

Attribute Type Description
id integer Unique ID for the custom role.
name string The name of the custom role.
permission_ids array of integers The IDs of the permissions the custom role grants, deduplicated and sorted ascending. Permission IDs the account cannot see are stored on the custom role but omitted from the response.
created_at datetime Date and time the custom role was created. Use the ISO 8601 Format.
updated_at datetime Date and time the custom role was last updated. Use the ISO 8601 Format.

Required permissions

The account must have custom profiles enabled. You must have permission to manage all users in order to interact with the /v2/custom_roles endpoint. Insufficient permissions will result in a 403 Forbidden status code.

List all custom roles

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 the account’s custom roles, sorted by descending ID.

The response contains an object with a custom_roles property that contains an array of up to per_page custom roles. Each entry in the array is a separate custom role object. If no more custom roles are available, the resulting array will be empty. Several additional pagination properties are included in the response to simplify paginating your custom roles.

GET /v2/custom_roles
Parameter Type Description
updated_since datetime Only return custom roles whose updated_at is later than this timestamp.
cursor string A cursor from a previous response’s links or next_page value. Takes precedence over page.
page integer The page number to use in pagination. Omitting this parameter, or passing 1, uses cursor-based pagination.
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/custom_roles" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Harvest-Account-Id: $ACCOUNT_ID" \
  -H "User-Agent: MyApp ([email protected])"

Example Response:

{
  "custom_roles": [
    {
      "id": 901,
      "name": "Team Lead",
      "permission_ids": [3, 7, 12],
      "created_at": "2026-10-16T10:00:00Z",
      "updated_at": "2026-10-16T10:00:00Z"
    },
    {
      "id": 900,
      "name": "Contractor",
      "permission_ids": [3],
      "created_at": "2026-10-15T09:30:00Z",
      "updated_at": "2026-10-15T09:30:00Z"
    }
  ],
  "per_page": 2000,
  "total_pages": 1,
  "total_entries": 2,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://api.harvestapp.com/v2/custom_roles?page=1&per_page=2000&ref=first",
    "next": null,
    "previous": null,
    "last": "https://api.harvestapp.com/v2/custom_roles?page=1&per_page=2000&ref=last"
  }
}

Retrieve a custom role

Beta endpoint. Fully available, but the response shape may still change. Until it is frozen, every response includes Harvest-Api-Stability: beta.

Retrieves the custom role with the given ID. Returns a custom role object and a 200 OK response code if a valid identifier was provided.

GET /v2/custom_roles/{CUSTOM_ROLE_ID}

Example Request:

curl "https://api.harvestapp.com/v2/custom_roles/901" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Harvest-Account-Id: $ACCOUNT_ID" \
  -H "User-Agent: MyApp ([email protected])"

Example Response:

{
  "id": 901,
  "name": "Team Lead",
  "permission_ids": [3, 7, 12],
  "created_at": "2026-10-16T10:00:00Z",
  "updated_at": "2026-10-16T10:00:00Z"
}

Create a custom role

Beta endpoint. Fully available, but the response shape may still change. Until it is frozen, every response includes Harvest-Api-Stability: beta.

Creates a custom role. An account can have a maximum of 50 custom roles. Returns a custom role object and a 201 Created response code if the call succeeded. The response includes a Location header pointing to the created custom role.

POST /v2/custom_roles
Parameter Type Required Description
name string required The name of the custom role. Maximum 100 characters, unique within the account, compared case-insensitively.
permission_ids array of integers required The complete set of permission IDs the custom role grants. Must contain at least one ID; null and an empty array are rejected.

The permission_ids values are the IDs published by GET /v2/permissions. Numeric strings are accepted.

Example Request:

curl -X POST "https://api.harvestapp.com/v2/custom_roles" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Harvest-Account-Id: $ACCOUNT_ID" \
  -H "Content-Type: application/json" \
  -H "User-Agent: MyApp ([email protected])" \
  -d '{"name": "Team Lead", "permission_ids": [3, 7, 12]}'

Example Response:

{
  "id": 901,
  "name": "Team Lead",
  "permission_ids": [3, 7, 12],
  "created_at": "2026-10-16T10:00:00Z",
  "updated_at": "2026-10-16T10:00:00Z"
}

Update a custom role

Beta endpoint. Fully available, but the response shape may still change. Until it is frozen, every response includes Harvest-Api-Stability: beta.

Updates the custom role with the given ID by setting the values of the parameters passed. Any parameters not provided are left unchanged. Returns a custom role object and a 200 OK response code if the call succeeded.

PATCH /v2/custom_roles/{CUSTOM_ROLE_ID}
Parameter Type Description
name string The name of the custom role. Maximum 100 characters, unique within the account, compared case-insensitively.
permission_ids array of integers The complete set of permission IDs the custom role grants. Must contain at least one ID; null and an empty array are rejected. Omitting the parameter leaves the permission set unchanged.

Changing permission_ids updates the permissions of every user on the custom role, which can remove a user as project manager from the projects they manage. Renaming the custom role does not change anyone’s permissions.

Example Request:

curl -X PATCH "https://api.harvestapp.com/v2/custom_roles/901" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Harvest-Account-Id: $ACCOUNT_ID" \
  -H "Content-Type: application/json" \
  -H "User-Agent: MyApp ([email protected])" \
  -d '{"permission_ids": [3, 7, 12, 18]}'

Example Response:

{
  "id": 901,
  "name": "Team Lead",
  "permission_ids": [3, 7, 12, 18],
  "created_at": "2026-10-16T10:00:00Z",
  "updated_at": "2026-10-16T14:30:00Z"
}

Delete a custom role

Beta endpoint. Fully available, but the response shape may still change. Until it is frozen, every response includes Harvest-Api-Stability: beta.

Deletes the custom role with the given ID. Returns the deleted custom role object and a 200 OK response code if the call succeeded.

DELETE /v2/custom_roles/{CUSTOM_ROLE_ID}

Users on the custom role are moved to the member access role and their direct permissions are cleared.

Example Request:

curl -X DELETE "https://api.harvestapp.com/v2/custom_roles/900" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Harvest-Account-Id: $ACCOUNT_ID" \
  -H "User-Agent: MyApp ([email protected])"

Example Response:

{
  "id": 900,
  "name": "Contractor",
  "permission_ids": [3],
  "created_at": "2026-10-15T09:30:00Z",
  "updated_at": "2026-10-15T09:30:00Z"
}

Errors

The following status codes are returned for the custom role endpoints:

Status Description
400 A required User-Agent header is missing (User-Agent header required), permission_ids is not an array of permission IDs (Invalid permission_ids provided: must be an array of permission ids), or updated_since is not a valid datetime (Invalid updated_since datetime provided: "…").
401 Authentication failed: Authentication failed for API request.
403 The account is inactive (Account inactive), the account does not have custom profiles enabled (Custom profiles feature is not enabled for this account), or the caller is not authorized (Not authorized!).
404 The custom role does not exist, was deleted, or belongs to another account: Record not found.
415 A Content-Type: application/json header is required when sending a body: Content-Type header required to be application/json, got ….
422 A validation failed. See the messages below.
500 An unexpected error occurred.

The 422 responses use these messages:

  • Name has already been taken
  • Permission ids can't be blank
  • Permission ids contain invalid permissions
  • Permission ids contain unavailable permissions
  • Permission ids can’t include Forecast permissions
  • A custom profile with these exact permissions already exists ("…")., naming the existing custom role
  • These permissions match the "…" base profile. Add at least one extra permission to create a custom profile. (ends with to save a custom profile. when updating)
  • Maximum of 50 custom role templates per account
  • You cannot give someone permissions that you do not have yourself. Ask an Administrator to do it. You do not have: …
  • You cannot change your own permissions. Ask another Administrator to do it.
  • Invalid cursor parameter …

Still have questions? We’re happy to help!

Contact Us