Projects API
Project API access follows the current Keito permissions and active project assignments of the user bound to the credential.
Visibility and Permissions
| Identity | Read | Create | Update, archive, restore | Delete |
|---|---|---|---|---|
| Owner or Administrator | All workspace projects | Yes | Yes | Yes |
| Manager with Edit projects | Actively assigned projects | Yes | Only assigned projects they manage | No |
| Other active internal user | Actively assigned projects | No | No | No |
| Personal read-only sync key | Assigned project list and item reads | No | No | No |
Changing a role, Manager permission, active assignment, or project-manager status takes effect on the next request. A missing, cross-workspace, or unassigned project returns 404, so callers cannot use the API to discover inaccessible records.
Rates, fees, budgets, task budgets, and internal costs are omitted unless the bound user has the corresponding financial visibility in Keito. Collection, item, and mutation responses use the same redaction.
List Projects
GET /api/v2/projects
Supported query parameters are is_active, client_id, updated_since, owner_id, page, and per_page. Personal read-only sync keys support is_active, owner_id, page, and per_page only.
curl "https://app.keito.ai/api/v2/projects?is_active=true&per_page=100" \
-H "Authorization: Bearer $KEITO_API_KEY" \
-H "Keito-Account-Id: $KEITO_ACCOUNT_ID"
Get a Project
GET /api/v2/projects/{id}
A successful item read returns an ETag for the exact permission-filtered representation. Financial-visibility changes therefore change the validator.
Create a Project
POST /api/v2/projects
client_id and name are required. You may also set owner_id, code, notes, dates, task_ids, and consistent billing and budget settings. Commercial properties require the same elevated billable-rate permission as the Keito interface.
curl -X POST "https://app.keito.ai/api/v2/projects" \
-H "Authorization: Bearer $KEITO_API_KEY" \
-H "Keito-Account-Id: $KEITO_ACCOUNT_ID" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: project-create-001" \
--data '{"client_id":"client_id","name":"Example project","code":"EXAMPLE"}'
Omit task_ids to assign workspace default tasks, or pass an empty array to assign none. Cross-workspace or inactive relationship IDs are rejected. A successful create returns 201 Created, Location, and ETag.
Update, Archive, or Restore
PATCH /api/v2/projects/{id}
Owners and Administrators may update any visible project. A Manager needs Edit projects and must currently manage that project. Use is_active: false to archive and is_active: true to restore.
curl -X PATCH "https://app.keito.ai/api/v2/projects/$PROJECT_ID" \
-H "Authorization: Bearer $KEITO_API_KEY" \
-H "Keito-Account-Id: $KEITO_ACCOUNT_ID" \
-H "Content-Type: application/json" \
-H 'If-Match: "etag-from-the-latest-read"' \
-H "Idempotency-Key: project-update-001" \
--data '{"name":"Updated project"}'
If-Match is optional for PATCH but strongly recommended. A stale value returns 412 Precondition Failed.
Permanently Delete a Project
DELETE /api/v2/projects/{id}
Permanent deletion is Owner/Administrator-only. The project must already be archived and the request must include the latest If-Match; missing or stale preconditions return 428 or 412. Keito removes project-bound time, expenses, assignments, tasks, and integration mappings while preserving historical invoice lines. Dependencies that cannot be preserved return 409.
Use a disposable project, archive it first, and confirm invoice-history requirements before testing deletion. Deletion cannot be undone.
Tasks and Project Task Budgets
Tasks are reusable across a workspace, while their budgets belong to a task’s assignment to a project. A workspace-global GET /api/v2/tasks omits budget. To return the active project-task assignment budget, pass project_id:
curl "https://app.keito.ai/api/v2/tasks?project_id=$PROJECT_ID&is_active=true" \
-H "Authorization: Bearer $KEITO_API_KEY" \
-H "Keito-Account-Id: $KEITO_ACCOUNT_ID"
Use project context when choosing a task for a time entry: Keito validates that the task is actively assigned to the selected project and may apply member-specific task restrictions. Personal read-only sync keys cannot call /api/v2/tasks; their fixed allowlist provides project directory reads only.
budget can be 0 or null. Keito omits it when there is no project context, the assignment is inactive or missing, or the caller cannot view that project’s budget. Nested project tasks follow the same rule. Detect support through project-task-budgets in X-Keito-Features.
To change a task’s budget, rate, or billable setting on a project, use Project Task Assignments.
Project Team (User Assignments)
Add, update, and remove the people assigned to a project. These endpoints follow Harvest’s user_assignments shape and the same rules as the project’s Team settings in Keito. Detect support through project-assignments-v1 in X-Keito-Features.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v2/projects/{project_id}/user_assignments |
List the project team. Supports is_active, updated_since, page, and per_page. |
POST |
/api/v2/projects/{project_id}/user_assignments |
Add a person to the project |
GET |
/api/v2/projects/{project_id}/user_assignments/{id} |
Get one assignment |
PATCH |
/api/v2/projects/{project_id}/user_assignments/{id} |
Update an assignment, or restore it with is_active: true |
DELETE |
/api/v2/projects/{project_id}/user_assignments/{id} |
Remove a person from the project |
Each assignment returns id, project, user, is_active, is_project_manager, can_track_time, can_view_project_financials, can_view_internal_costs, created_at, and updated_at. use_default_rates, hourly_rate, and budget are included only for callers who can see billable rates. Email addresses are never returned.
POST requires user_id, an active member of the workspace. You may also send is_project_manager, can_track_time, can_view_project_financials, can_view_internal_costs, and budget (person hour budget). PATCH accepts the same fields plus is_active.
curl -X POST "https://app.keito.ai/api/v2/projects/$PROJECT_ID/user_assignments" \
-H "Authorization: Bearer $KEITO_API_KEY" \
-H "Keito-Account-Id: $KEITO_ACCOUNT_ID" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: team-add-001" \
--data '{"user_id":"user_id"}'
DELETE deactivates the assignment, exactly like Remove in Keito. The person’s logged time and history are kept, and you can restore them with PATCH and is_active: true. Assignments are never permanently deleted through the API.
Owners and Administrators can change any field. A Manager with Edit projects who manages the project can add people with the default settings, remove or restore people who are not project managers, and change is_project_manager on an existing assignment only when the workspace lets project managers assign managers. Managers cannot change their own assignment. Everything else, including budget, the visibility flags, and adding someone as a project manager, is Administrator-only. Person hourly rates are read-only in the API because Keito manages them through rate history.
Project Task Assignments
Manage the tasks on a project and each task’s project-specific settings. These endpoints follow Harvest’s task_assignments shape.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v2/projects/{project_id}/task_assignments |
List the project’s tasks. Supports is_active, updated_since, page, and per_page. |
POST |
/api/v2/projects/{project_id}/task_assignments |
Assign an existing workspace task to the project |
GET |
/api/v2/projects/{project_id}/task_assignments/{id} |
Get one task assignment |
PATCH |
/api/v2/projects/{project_id}/task_assignments/{id} |
Update budget, hourly_rate, billable, or is_active |
DELETE |
/api/v2/projects/{project_id}/task_assignments/{id} |
Remove a task from the project |
Each task assignment returns id, project, task, is_active, created_at, and updated_at. billable and hourly_rate are included when the caller can see the project’s financials, and budget when the caller can see its budget.
curl -X PATCH "https://app.keito.ai/api/v2/projects/$PROJECT_ID/task_assignments/$TASK_ASSIGNMENT_ID" \
-H "Authorization: Bearer $KEITO_API_KEY" \
-H "Keito-Account-Id: $KEITO_ACCOUNT_ID" \
-H "Content-Type: application/json" \
-H 'If-Match: "etag-from-the-latest-read"' \
-H "Idempotency-Key: task-budget-001" \
--data '{"budget":40}'
Send null to clear hourly_rate or budget. A rate change applies to new time only; the API never reprices existing time entries. POST requires task_id and returns 409 if the task is already active on the project. To create a brand-new task on a project, use POST /api/v2/tasks with project_id.
DELETE works like Remove in Keito. If any time was logged against the task on this project, the assignment is archived ("outcome": "archived") and can be restored with PATCH and is_active: true. Only an assignment with no logged time is deleted ("outcome": "deleted").
Any change needs the same access as editing the project: an Owner or Administrator, or a Manager with Edit projects who manages the project. billable, hourly_rate, and budget also require billable-rate permission.
All assignment endpoints return an ETag on item reads and accept If-Match and Idempotency-Key on writes. Projects you cannot see, and assignment IDs from another project or workspace, return 404. Personal read-only sync keys cannot call these endpoints.
Safe Retries and Request Rules
Mutation requests may include an Idempotency-Key containing 8–255 letters, numbers, dots, underscores, or hyphens. The same key, request, precondition, and authorization context replays the stored response for 24 hours. Reusing a key for different input, after a permission change, or while the original request is still running returns 409.
Bodies must be uncompressed UTF-8 application/json, are limited to 128 KiB, and use strict schemas. Unknown properties, empty updates, inconsistent billing settings, invalid date ranges, and out-of-range amounts are rejected. Responses are private, no-store.