Tasks API
The Tasks API works with individual tasks: your personal task overview, reading and updating a task, changing its status (including completion with actual hours), comments, and deletion.
Tasks are created inside a project through
POST /api/v1/projects/:id/tasks,
not through this API. Project-level scheduling operations (dates, estimates,
per-day allocations, and assignees with conflict checks) also live on the
Projects API.
Authentication: every endpoint requires an API key. See API Keys & Authentication.
Requests run in the organisation selected for your API key or MCP connection. Another membership, including an owner role or a different primary organisation, does not change that scope. Task changes record the connected user as the author; supplying an actor ID does not change that identity.
Every response uses the standard { "success": true, ... } envelope.
Multiple assignees
Section titled “Multiple assignees”Use assignedToIds on task creation, PATCH /api/v1/tasks/:taskId, or
PATCH /api/v1/projects/:id/tasks/:taskId/assignee to replace the complete set
of assignees. Supply up to 50 user UUIDs. Duplicate IDs are removed, preserving
their first occurrence. An empty array unassigns everyone. Do not combine this
field with assignedToId; doing so returns 400. The legacy scalar still
replaces the assignment with one person, and null unassigns.
{ "assignedToIds": ["<first-user-uuid>", "<second-user-uuid>"] }The returned task includes assignedToIds, with assignedToId identifying its
first person for older clients. Project details also include an assignees
list. All assignees must be active members of the credential’s organisation and
eligible for that project. Assignment requires task:assign or the project
owner/admin override; ordinary task edits retain their existing requirements.
There is one task ID with shared title, description, status, progress, dates and
discussion. Each person’s schedule row carries that same taskId; use it when
updating the task. Do not use the schedule row’s own id as a task ID.
estimatedHours applies to every assignee, so four hours for two people reserves
eight person-hours. Each schedule entry identifies the person whose capacity
it uses. Removing an assignee removes their allocation; an unscheduled task has
no allocations. Auto schedules respect all assignees’ availability, and manual
allocations validate each person’s leave and working pattern. Actual time is
recorded individually and is never copied to all assignees.
The named MCP tools create_task and update_task accept the same field and
require an unscoped user-generated connection.
call_public_api provides the same operations and requires confirm: true
for writes. See MCP.
Role reservations
Section titled “Role reservations”Use roleIds on POST /api/v1/projects/:id/tasks to reserve unfilled rate-card
roles. Repeat a role UUID to reserve another place. estimatedHours is per
place; plannedStartDate is optional. create_task in MCP accepts the same field.
Named people in assignedToIds can coexist with role places. Use different role
IDs for different skills and repeated IDs for several places with the same role.
For example, create one task with a producer and three unfilled places:
{ "name": "Campaign delivery", "estimatedHours": 4, "assignedToIds": [ "<producer-user-uuid>" ], "roleIds": [ "<designer-role-uuid>", "<designer-role-uuid>", "<writer-role-uuid>" ]}This plans 16 hours: four hours for each named person or unfilled place. Filling a reserved place keeps that total; do not add a second place for the person filling it. The task retains one status, schedule and discussion.
| Operation | Method and path |
|---|---|
| Available role IDs and names | GET /api/v1/projects/:id/task-role-options |
| Read places and revision | GET /api/v1/projects/:id/tasks/:taskId/role-assignments |
| Replace the complete role list | PUT /api/v1/projects/:id/tasks/:taskId/role-assignments |
| Read unfilled demand | GET /api/v1/projects/role-demand?projectId=<uuid> |
Read the role list before writing it. The response contains roleAssignments,
revision, availableRoles and canAssign. Each public place has id, roleId,
roleName and nullable assignedToId. It contains no financial rates.
{ "revision": "<revision-from-read>", "roles": [ { "id": "<existing-place-uuid>", "roleId": "<role-uuid>", "assignedToId": "<person-uuid>" }, { "roleId": "<role-uuid>", "assignedToId": null } ]}Omit id for a new place. Retain it when staffing or reopening an existing
place. Omit a place from the array to remove it. A person can occupy one place
per task; the combined maximum is 50 named people and unfilled places. Invalid
IDs, inactive or ineligible people, duplicate people and submitted price fields
return 400. Stale revisions return 409 with no partial write.
Assignment authority or the project owner/admin override is required. Scheduled
staffing changes also require schedule management authority. A person whose
working pattern, leave or closures cannot accommodate the saved hours is refused
with 409. Other-task overlaps return 409 with code: ROLE_SCHEDULE_CONFLICT
and conflicts. After review, resubmit with allowScheduleConflicts: true to
accept overlaps. This cannot override unavailable working hours.
Filling a place transfers demand to the person and retains the saved role rate.
Removing a person reopens their role. A one-for-one person handover transfers the
role. Estimates, details, dates and status remain shared on the central task.
Unfilled demand rows identify the task, project, place, role and segment hours;
unscheduled rows have null segment times and hours plus estimatedHours.
Completed/cancelled tasks and closed projects are excluded.
Financial readers can use GET /api/v1/projects/:id/task-costs; its
plannedSnapshots map includes each place once at its saved rate. A missing
required FX rate leaves planned cost unknown. Ordinary role reads do not grant
financial access.
get_task_roles and set_task_roles provide named MCP access. The latter
requires confirm: true; call_public_api covers all these operations. Role
operations stay bound to the credential’s organisation. The named role tools
require an unscoped user-generated credential; restricted job/client/tool
sessions cannot use their API bridge.
The task object
Section titled “The task object”Task endpoints return a task with usage metadata:
{ "id": "4be2d871-...", "name": "Draft homepage copy", "description": "First pass for internal review.", "status": "in_progress", "priority": "high", "projectId": "1f0e9a52-...", "projectName": "Winter Launch", "budgetLineId": null, "clientName": "Northwind", "estimatedHours": 6, "actualHours": 5.5, "trackedHours": 4.25, "progress": 60, "plannedStartDate": "2026-07-13T00:00:00.000Z", "plannedEndDate": "2026-07-14T00:00:00.000Z", "actualStartDate": "2026-07-13T01:12:44.000Z", "actualEndDate": null, "completionNotes": null, "commentCount": 3, "tags": ["copy", "homepage"], "metadata": {}, "dueCategory": "today", "dueInDays": 1, "createdAt": "2026-07-10T00:05:31.000Z", "updatedAt": "2026-07-13T02:41:12.000Z"}budgetLineIdis the saved budget line UUID, or null when unmapped. It is included in task detail, personal task lists and project task readbacks. Unrelated task updates preserve it. Use the Finance mapping endpoints to set or clear it; the ID does not grant access to budget amounts.statusis one ofpending,scheduled,in_progress,completed,cancelled.priorityis one oflow,medium,high,critical.dueCategoryis one ofoverdue,today,upcoming,unscheduled, computed from the planned dates. Completed tasks reportupcoming.dueInDaysis the number of days until the planned end date (negative when overdue), or null when the task has no planned end date.trackedHoursis the total of the task’s time entries.actualHoursis the figure recorded when the task was completed.description,clientName,estimatedHours,actualHours, the date fields,completionNotes, andtagscan be null or absent.
GET /api/v1/tasks/my
Section titled “GET /api/v1/tasks/my”Get an overview of the tasks assigned to you: a summary of counts and hours, plus the full task list grouped for due-date display. Cancelled tasks and all tasks in draft projects are excluded, including their counts and hours. This covers custom workflow stages in the draft category, such as Planning. Assignment and scheduling alone do not make draft work actionable.
Project activation makes assigned work visible here, including unscheduled
backlog tasks. Returning the project to draft hides it again. Authorised task
detail and project reads remain available throughout planning. The same behaviour
applies through MCP using call_public_api with GET /tasks/my.
Any organisation member can call this. It only ever returns your own tasks.
Example request
curl https://api.runnit.io/api/v1/tasks/my \ -H 'Authorization: Bearer rnk_your_key'Response
{ "success": true, "data": { "summary": { "total": 12, "overdue": 1, "dueToday": 2, "upcoming": 6, "completed": 3, "inProgress": 2, "scheduledHours": 64.5, "trackedHours": 38.25 }, "tasks": [ { "id": "4be2d871-...", "name": "Draft homepage copy", "dueCategory": "today" } ] }}tasks contains full task objects as described above, ordered by due date
with completed tasks last. The overdue, dueToday, and upcoming counts
only include tasks that are not completed.
Errors
400if no active organisation can be resolved for your account.
GET /api/v1/tasks/:taskId
Section titled “GET /api/v1/tasks/:taskId”Get one of your assigned tasks by ID.
Any organisation member can call this, but it only resolves tasks assigned to
you. A task assigned to someone else (or to nobody) returns 404.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
| taskId | UUID | Yes | Task ID. Must be assigned to you. |
Example request
curl https://api.runnit.io/api/v1/tasks/<task-id> \ -H 'Authorization: Bearer rnk_your_key'Response
{ "success": true, "task": { "id": "4be2d871-...", "name": "Draft homepage copy", "status": "in_progress" } }Errors
404if the task does not exist, is deleted, is in another organisation, or is not assigned to you.
PATCH /api/v1/tasks/:taskId
Section titled “PATCH /api/v1/tasks/:taskId”Update a task’s fields. Send only the fields you want to change. Project owners and project admins have a local override. Organisation capabilities apply on every accessible project, even when the caller is not listed on its team.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
| taskId | UUID | Yes | Task ID. |
Request body (all optional)
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | No | New name. Must not be empty. |
| description | string | No | Task description. |
| priority | string | No | low, medium, high, or critical. |
| progress | integer | No | 0 to 100. |
| estimatedHours | number | No | 0 to 1000. On a dated task with a day-by-day breakdown, keeps the planned window and rescales the daily hours when the new total fits it; otherwise rebuilds from the start. On an undated task, saves the estimate without creating dates. |
| plannedStartDate | ISO 8601 date | No | Planned start. Rebuilds the task schedule and recalculates planned end. |
| dependsOn | UUID[] | No | Replace the task’s finish-to-start dependency list (max 25 task ids from the same project; cycles rejected). Requires task-wide update and schedule-management access. The response includes a cascade summary when downstream tasks were rescheduled. |
| plannedEndDate | ISO 8601 date | No | Calculated output. A value supplied with start or estimate is ignored for backwards compatibility. Sending only this field returns 400. |
| tags | array of strings | No | Replaces the full tag list. |
| assignedToId | string or null | No | New assignee’s user ID. Null unassigns the task. The user must be an active organisation member. On a client project they must be on the client staffing list or the explicit project team. |
A start update creates or rebuilds the same working-day schedule used by the
project dates
endpoint, and moves a pending backlog task to scheduled. An estimate-only
update on a dated task follows the same manual-aware rule as the project
estimate
endpoint: a manual day-by-day plan keeps its window and rescales when the new
total fits, and rebuilds from the start when it does not. It never invents a
start date for backlog work. Changing the assignee also updates the owner on
any existing schedule segments.
General fields require task-wide update access, assigned-task access for the
current assignee, or a project override. Assignment requires task:assign.
Schedule fields and dependencies require both task:update:any and
schedule:manage. A project’s target date is not a write boundary: segments
after it are marked overdue but remain editable.
Example request
curl -X PATCH https://api.runnit.io/api/v1/tasks/<task-id> \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "priority": "critical", "progress": 75 }'Response
{ "success": true, "task": { "id": "4be2d871-...", "priority": "critical", "progress": 75 } }Errors
400if a field fails validation (empty name, progress outside 0 to 100, estimated hours outside 0 to 1000, invalid priority or date), if the new assignee is not an active organisation member, or if the new assignee is neither on the client staffing list nor the explicit project team, or ifplannedEndDateis sent without a start or estimate.404if the task does not exist, is deleted, or is in another organisation.403when the caller lacks the capability required for the fields being changed.
PATCH /api/v1/tasks/:taskId/status
Section titled “PATCH /api/v1/tasks/:taskId/status”Change a task’s status and optional progress atomically. Completing a task can also record the actual hours spent and a completion note.
The task’s assignee or project owner can call this. Users with organisation-wide task update access can also update the task. Assignment changes still use the Projects API.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
| taskId | UUID | Yes | Task ID. |
Request body
| Name | Type | Required | Description |
|---|---|---|---|
| status | string | Yes | One of pending, scheduled, in_progress, completed, cancelled. |
| progress | integer | No | Whole percentage from 0 to 100. |
| actualHours | number | No | 0 to 1000. Recorded with completion. |
| completionNotes | string | No | Up to 2000 characters. |
Completing a task sets its progress to 100 and records the work in your
portfolio history. If you supply actualHours with completion, Runnit also
creates (or updates) a draft time entry for those hours against the task.
The endpoint applies these work-state rules:
- Progress from 1 to 99 changes pending or scheduled work to
in_progress. - Progress at 100 changes the status to
completed. completedalways stores progress at 100.pendingandscheduledstore progress at 0 when progress is omitted or 0.cancelledpreserves the stored progress.- Reopening completed work as
in_progressdefaults to 95 when progress is omitted.
Example request
curl -X PATCH https://api.runnit.io/api/v1/tasks/<task-id>/status \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "status": "completed", "progress": 100, "actualHours": 5.5, "completionNotes": "Copy approved by the internal review." }'Response
{ "success": true, "task": { "id": "4be2d871-...", "status": "completed", "progress": 100, "actualHours": 5.5 } }Errors
400ifstatusis missing or invalid,progressis not a whole number from 0 to 100,actualHoursis out of range, orcompletionNotesexceeds 2000 characters.404if the task does not exist, is deleted, or is in another organisation.403if you are not the task’s assignee or project owner and do not have organisation-wide task update access.
GET /api/v1/tasks/:taskId/comments
Section titled “GET /api/v1/tasks/:taskId/comments”List a task’s comments, including who wrote each one. Comments are the
messages in the task’s chat thread, so entries created by replying or
mentioning someone in the app appear here too. A comment may include a
replyToId referencing the comment it replies to.
Any organisation member can call this for tasks in their organisation.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
| taskId | UUID | Yes | Task ID. |
Example request
curl https://api.runnit.io/api/v1/tasks/<task-id>/comments \ -H 'Authorization: Bearer rnk_your_key'Response
{ "success": true, "comments": [ { "id": "b7e13f90-...", "taskId": "4be2d871-...", "userId": "d3a6f0c1-...", "commentText": "First draft is up for review.", "metadata": {}, "createdAt": "2026-07-13T02:41:12.000Z", "updatedAt": "2026-07-13T02:41:12.000Z", "user": { "id": "d3a6f0c1-...", "name": "Alex Chen", "email": "alex@example.com" } } ]}metadata and the user’s avatarUrl can be absent. userId and user are
null for system-generated entries. replyToId is present on replies. Up to
500 comments are returned.
Errors
404if the task does not exist, is deleted, or is in another organisation.
POST /api/v1/tasks/:taskId/comments
Section titled “POST /api/v1/tasks/:taskId/comments”Add a comment to a task. The comment posts into the task’s chat thread: the task’s assignee, the project owner, and other thread participants are notified in the app and can reply there.
Requires task-comment access for tasks in your organisation.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
| taskId | UUID | Yes | Task ID. |
Request body
| Name | Type | Required | Description |
|---|---|---|---|
| commentText | string | Yes | Comment body. Must not be empty. |
| replyToId | UUID | No | ID of an existing comment on the same task to reply to. |
| mentions | array | No | Structured mentions, e.g. [{ "targetType": "user", "targetId": "<user-id>" }]. Mentioned users must be active organisation members; they are notified directly. |
| metadata | object | No | Free-form metadata stored with the comment. |
Example request
curl -X POST https://api.runnit.io/api/v1/tasks/<task-id>/comments \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "commentText": "First draft is up for review." }'Response
Returns 201 with the task’s full comment list, including the new comment,
in the same shape as the list endpoint above.
Errors
400ifcommentTextis missing or empty, or no active organisation can be resolved for your account.404if the task does not exist, is deleted, or is in another organisation.
DELETE /api/v1/tasks/:taskId
Section titled “DELETE /api/v1/tasks/:taskId”Delete a task. Runnit also removes the task’s schedule entries, including schedule entries for subtasks deleted with the parent. The task no longer appears in task, dashboard, schedule, or capacity results.
The task’s assignee or the project owner can call this.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
| taskId | UUID | Yes | Task ID. |
Example request
curl -X DELETE https://api.runnit.io/api/v1/tasks/<task-id> \ -H 'Authorization: Bearer rnk_your_key'Response
{ "success": true, "message": "Task deleted successfully" }Errors
404if the task does not exist, is already deleted, or is in another organisation.403if you are neither the task’s assignee nor the project owner (“Only the task assignee or project owner can delete tasks”).
Common workflows
Section titled “Common workflows”A typical task lifecycle, end to end. Replace the placeholders with real IDs.
- Create the task in its project (tasks always belong to a project):
curl -X POST https://api.runnit.io/api/v1/projects/<project-id>/tasks \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "name": "Draft homepage copy", "priority": "high", "estimatedHours": 6 }'- Assign it (use the assignee options endpoint to find valid assignees first):
curl -X PATCH https://api.runnit.io/api/v1/projects/<project-id>/tasks/<task-id>/assignee \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "assignedToId": "<user-uuid>" }'- Move its dates when the plan changes (this rebuilds the working-day schedule):
curl -X PATCH https://api.runnit.io/api/v1/projects/<project-id>/tasks/<task-id>/dates \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "plannedStartDate": "2026-07-20" }'- Mark it complete with actual hours (as the assignee):
curl -X PATCH https://api.runnit.io/api/v1/tasks/<task-id>/status \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "status": "completed", "actualHours": 5.5 }'- Comment on the outcome:
curl -X POST https://api.runnit.io/api/v1/tasks/<task-id>/comments \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "commentText": "Done. Final copy is in the shared collection." }'Next steps
Section titled “Next steps”Create and schedule tasks through the Projects API, or read team schedules through the Scheduling API.
Role reservations and unfilled demand follow your current project-view and client staffing access. A project filter cannot widen it. Organisation owners and admins retain visibility across their organisation; other people retain access through eligible clients or their active project membership. A refused request changes no roles or bookings. Conflicts from projects you cannot see show the date and reason without their task name or link.