Project Groups
Optional folders for organising projects — additive, and never required.
Project groups are optional folders for projects. Nothing in the API requires one: a project with no group behaves exactly as every project did before groups existed, and filing one away never hides it from GET /projects. All endpoints require the api-key header and are free.
A project joins a group through groupId on Create Project or Update Project, and you filter by group with GET /projects?groupId=<id> (or ?groupId=none for the ungrouped ones).
A group is a label, not a container: the link is stored on the project, not as a list of project ids on the group. That is why deleting a group is a cheap operation that releases its members, and why a group can hold 100,000 projects without any document growing.
Every lookup is scoped to the calling account, and a group owned by somebody else is indistinguishable from one that does not exist — you get 404 PROJECT_GROUP_NOT_FOUND either way.
Limits: 3,000 groups per account · 100,000 projects per group · name up to 80 characters · description up to 500 characters.
#Create Project Group
/api/v1/vcaas/project-groupsFreeCreate a folder for projects.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Trimmed, up to 80 characters. Required — an empty name is rejected |
description | string | No | Trimmed and truncated to 500 characters rather than rejected |
Response fields
| Field | Type | Description |
|---|---|---|
data.groupId | string | The new group's ID — use this as groupId elsewhere |
data.name | string | The group name |
data.description | string | The description, or an empty string when none was given |
data.createdAt | string | ISO 8601 creation date |
data.updatedAt | string | ISO 8601 last-modified date |
Example request
curl -X POST \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"name":"Client work","description":"Everything billed to clients"}' \
https://api-accounts.totalum.app/api/v1/vcaas/project-groups{
"errors": null,
"data": {
"groupId": "65f1a2b3c4d5e6f7a8b9c0d1",
"name": "Client work",
"description": "Everything billed to clients",
"createdAt": "2026-08-04T10:00:00.000Z",
"updatedAt": "2026-08-04T10:00:00.000Z"
}
}{
"errors": {
"errorCode": "CREATE_PROJECT_GROUP_ERROR",
"errorMessage": "Group name is required"
},
"data": null
}Error codes
| Code | HTTP | Meaning |
|---|---|---|
CREATE_PROJECT_GROUP_ERROR | 400 | Name missing or longer than 80 characters, or the account already has 3,000 groups |
#List Project Groups
/api/v1/vcaas/project-groupsFreeYour groups, newest first, each with the number of projects currently filed under it.
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
search | string | No | Case-insensitive match on the group name only |
limit | number | No | Page size, default 100, maximum 200 |
skip | number | No | Groups to skip, for paging (default: 0) |
Response headers
| Header | Type | Description |
|---|---|---|
X-Total-Count | number | Total groups matching the filters, across all pages |
X-Limit | number | Page size actually applied |
X-Skip | number | Offset actually applied |
Response fields
| Field | Type | Description |
|---|---|---|
data | array | Array of group objects, newest first |
data[].groupId | string | The group ID |
data[].name | string | The group name |
data[].description | string | The description, or an empty string |
data[].createdAt | string | ISO 8601 creation date |
data[].updatedAt | string | ISO 8601 last-modified date |
data[].projectCount | number | How many of your projects are filed under this group |
Example request
curl -H "api-key: tlm_sk_your_key" \
"https://api-accounts.totalum.app/api/v1/vcaas/project-groups?limit=100"{
"errors": null,
"data": [
{
"groupId": "65f1a2b3c4d5e6f7a8b9c0d1",
"name": "Client work",
"description": "Everything billed to clients",
"createdAt": "2026-08-04T10:00:00.000Z",
"updatedAt": "2026-08-04T10:00:00.000Z",
"projectCount": 12
}
]
}{
"errors": {
"errorCode": "LIST_PROJECT_GROUPS_ERROR",
"errorMessage": "Error listing project groups"
},
"data": null
}Error codes
| Code | HTTP | Meaning |
|---|---|---|
LIST_PROJECT_GROUPS_ERROR | 400 | Internal error listing project groups |
#Get Project Group
/api/v1/vcaas/project-groups/:groupIdFreeOne group, with its current project count.
Path parameters
| Parameter | Type | Description |
|---|---|---|
groupId | string | The group ID |
Response fields
| Field | Type | Description |
|---|---|---|
data.groupId | string | The group ID |
data.name | string | The group name |
data.description | string | The description, or an empty string |
data.createdAt | string | ISO 8601 creation date |
data.updatedAt | string | ISO 8601 last-modified date |
data.projectCount | number | How many of your projects are filed under this group |
Example request
curl -H "api-key: tlm_sk_your_key" \
https://api-accounts.totalum.app/api/v1/vcaas/project-groups/65f1a2b3c4d5e6f7a8b9c0d1{
"errors": null,
"data": {
"groupId": "65f1a2b3c4d5e6f7a8b9c0d1",
"name": "Client work",
"description": "",
"createdAt": "2026-08-04T10:00:00.000Z",
"updatedAt": "2026-08-04T10:00:00.000Z",
"projectCount": 12
}
}{
"errors": {
"errorCode": "PROJECT_GROUP_NOT_FOUND",
"errorMessage": "Project group not found"
},
"data": null
}Error codes
| Code | HTTP | Meaning |
|---|---|---|
PROJECT_GROUP_NOT_FOUND | 404 | No such group, the id is malformed, or it is not yours |
GET_PROJECT_GROUP_ERROR | 400 | Internal error getting the project group |
#Update Project Group
/api/v1/vcaas/project-groups/:groupIdFreeRename a group or change its description. Keys you do not send are left alone, so renaming a group cannot blank its description.
Path parameters
| Parameter | Type | Description |
|---|---|---|
groupId | string | The group ID |
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Trimmed, up to 80 characters. Sending an empty name is an error — omit the key instead |
description | string | No | Trimmed and truncated to 500 characters. Send an empty string to clear it |
Response fields
| Field | Type | Description |
|---|---|---|
data.groupId | string | The group ID |
data.name | string | The name after the update |
data.description | string | The description after the update |
data.createdAt | string | ISO 8601 creation date |
data.updatedAt | string | ISO 8601 last-modified date |
data.projectCount | number | How many of your projects are filed under this group |
Example request
curl -X PATCH \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"name":"Client work 2026"}' \
https://api-accounts.totalum.app/api/v1/vcaas/project-groups/65f1a2b3c4d5e6f7a8b9c0d1{
"errors": null,
"data": {
"groupId": "65f1a2b3c4d5e6f7a8b9c0d1",
"name": "Client work 2026",
"description": "Everything billed to clients",
"createdAt": "2026-08-04T10:00:00.000Z",
"updatedAt": "2026-08-04T12:30:00.000Z",
"projectCount": 12
}
}{
"errors": {
"errorCode": "PROJECT_GROUP_NOT_FOUND",
"errorMessage": "Project group not found"
},
"data": null
}Error codes
| Code | HTTP | Meaning |
|---|---|---|
PROJECT_GROUP_NOT_FOUND | 404 | No such group, the id is malformed, or it is not yours |
UPDATE_PROJECT_GROUP_ERROR | 400 | Empty name, name longer than 80 characters, or an internal error |
#Delete Project Group
/api/v1/vcaas/project-groups/:groupIdFreeDelete the folder only.
Its members simply become ungrouped — the same state every project is in by default — and releasedProjects tells you how many were released. There is deliberately no cascade: a folder is not an owner. To delete the projects themselves, call Delete Project for each one.
Path parameters
| Parameter | Type | Description |
|---|---|---|
groupId | string | The group ID |
Response fields
| Field | Type | Description |
|---|---|---|
data.deleted | boolean | true on successful deletion |
data.releasedProjects | number | How many projects became ungrouped. None were deleted |
Example request
curl -X DELETE -H "api-key: tlm_sk_your_key" \
https://api-accounts.totalum.app/api/v1/vcaas/project-groups/65f1a2b3c4d5e6f7a8b9c0d1{ "errors": null, "data": { "deleted": true, "releasedProjects": 12 } }{
"errors": {
"errorCode": "PROJECT_GROUP_NOT_FOUND",
"errorMessage": "Project group not found"
},
"data": null
}Error codes
| Code | HTTP | Meaning |
|---|---|---|
PROJECT_GROUP_NOT_FOUND | 404 | No such group, the id is malformed, or it is not yours |
DELETE_PROJECT_GROUP_ERROR | 400 | Internal error deleting the project group |
