Skip to content

Projects API

The Projects API covers the full project lifecycle: listing and creating projects, reading a project and its details, updating fields, statuses, budgets, and colours, working with milestones and the project history feed, creating and scheduling tasks inside a project, and deleting a project with all of its related data.

Authentication: every endpoint requires an API key. See API Keys & Authentication. All examples use the Authorization: Bearer rnk_your_key header.

All requests run inside the organisation your key belongs to. Successful responses use the standard envelope described in the REST API Overview, for example { "success": true, "project": { ... } }. Validation failures return 400 with details of the invalid fields.

These values appear throughout this page:

FieldValid values
Project statusdraft, active, review_internal, client_review, done, on_hold, completed, cancelled, archived (read only in generic updates, see Archiving a project)
Project stagediscovery, planning, design, development, testing, review, deployment, maintenance
Project workflowStage.categorydraft, active, on_hold, done, cancelled
Project colorguava, mandarin, matcha, acai, coffee, guava-dark, mandarin-dark, matcha-dark, acai-dark, coffee-dark
Task prioritylow, medium, high, critical
Task statuspending, scheduled, in_progress, completed, cancelled

Dates are ISO 8601 strings. IDs are UUIDs.

Project responses also carry workflowStageId and a workflowStage object (id, name, category): the project’s Kanban workflow stage. stage is the separate legacy lifecycle field and is unrelated to boards.

Archived projects additionally carry archivedAt, archivedBy (null when archived automatically), preArchiveStatus, and preArchiveWorkflowStageId. These are null on every other project.

List the projects in your organisation, newest first, with pagination.

Any organisation member can call this. Admins and owners see every project. Other members see internal projects, projects for clients whose staffing list includes them (or uses the all-staff default), and any project where they’re an explicit project member.

Query parameters

NameTypeRequiredDescription
statusstringNoFilter by project status. See the field reference above.
stagestringNoFilter by project stage. See the field reference above.
limitintegerNoPage size, 1 to 100. Defaults to 50.
offsetintegerNoNumber of projects to skip. Defaults to 0.
includeArchivedbooleanNoArchived projects are excluded by default. Set true to include them, or filter with status=archived to list only archived projects.

Example request

Terminal window
curl 'https://api.runnit.io/api/v1/projects?status=active&limit=20' \
-H 'Authorization: Bearer rnk_your_key'

Response

{
"success": true,
"projects": [
{
"id": "1f0e9a52-...",
"organizationId": "8c2d41b7-...",
"jobNumber": "RB-2026-014",
"name": "Winter Launch",
"description": "Delivery workspace for the winter campaign.",
"status": "active",
"stage": "planning",
"ownerId": "d3a6f0c1-...",
"budget": 75000,
"budgetCurrency": "AUD",
"startDate": "2026-07-01T00:00:00.000Z",
"targetCompletionDate": "2026-08-31T00:00:00.000Z",
"clientOrganizationId": "b91c77e4-...",
"color": "matcha",
"tags": [],
"createdAt": "2026-06-20T02:14:09.000Z",
"updatedAt": "2026-07-01T04:33:52.000Z"
}
],
"pagination": { "total": 42, "limit": 20, "offset": 0, "hasMore": true }
}

budget, startDate, targetCompletionDate, clientOrganizationId, color, summary, and description can be null or absent. Projects also carry other metadata fields not shown here.

Errors

  • 400 if no active organisation can be resolved for your account, or a query parameter is invalid.

Create a project directly, without the Brief Builder workflow. The project is created in draft status with you as the owner.

You need project creation access, and your organisation must have direct project creation enabled in its settings.

Request body

NameTypeRequiredDescription
namestringYesProject name. Must not be empty.
descriptionstringNoLonger description of the work.
clientOrganizationIdUUIDNoClient the project is for. Omit for an internal project.
budgetnumberNoProject budget amount.
budgetCurrencystringNoCurrency code. Defaults to your organisation’s currency, then AUD.
startDateISO 8601 dateNoPlanned start date.
targetCompletionDateISO 8601 dateNoTarget delivery date.
jobNumberstringNo1 to 100 characters. Runnit generates one if omitted.
colorstringNoProject colour key. See the field reference above. A random colour is used if omitted.
workflowStageIdUUIDNoEntry workflow stage, for example a sales pipeline’s Enquiry stage. Must be a draft-category stage in your organisation (400 otherwise); the project then appears on every board whose columns map that stage. Omit for the standard Planning (Draft) stage.

Example request

Terminal window
curl -X POST https://api.runnit.io/api/v1/projects \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{
"name": "Winter Launch",
"description": "Delivery workspace for the winter campaign.",
"clientOrganizationId": "<client-uuid>",
"budget": 75000,
"budgetCurrency": "AUD",
"startDate": "2026-07-01",
"targetCompletionDate": "2026-08-31"
}'

Response

Returns 201 with the created project:

{
"success": true,
"project": {
"id": "1f0e9a52-...",
"name": "Winter Launch",
"jobNumber": "RB-2026-014",
"status": "draft",
"budget": 75000,
"budgetCurrency": "AUD"
}
}

Errors

  • 400 if name is missing or a field fails validation.
  • 400 if no active organisation can be resolved for your account.
  • 403 with code DIRECT_PROJECT_CREATION_DISABLED if the organisation has not enabled direct project creation.
  • 404 if the organisation record cannot be found.

Get a single project by ID.

Any member of the project’s organisation can call this.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.

Example request

Terminal window
curl https://api.runnit.io/api/v1/projects/<project-id> \
-H 'Authorization: Bearer rnk_your_key'

Response

{ "success": true, "project": { "id": "1f0e9a52-...", "name": "Winter Launch", "status": "active" } }

The project object has the same shape as in the list response.

Errors

  • 404 if the project does not exist.
  • 403 if the project belongs to an organisation you are not a member of.

Get a project together with its team members, tasks, milestones, and brief in one call.

Any member of the project’s organisation can call this.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.

Example request

Terminal window
curl https://api.runnit.io/api/v1/projects/<project-id>/details \
-H 'Authorization: Bearer rnk_your_key'

Response

{
"success": true,
"project": { "id": "1f0e9a52-...", "name": "Winter Launch", "status": "active" },
"members": [
{
"id": "77a1c9d0-...",
"userId": "d3a6f0c1-...",
"isAdmin": true,
"roleTitle": "Creative Director",
"user": { "id": "d3a6f0c1-...", "name": "Alex Chen", "email": "alex@example.com" }
}
],
"tasks": [
{
"id": "4be2d871-...",
"name": "Draft homepage copy",
"status": "scheduled",
"priority": "high",
"assignedToId": "d3a6f0c1-...",
"estimatedHours": 6,
"plannedStartDate": "2026-07-13T00:00:00.000Z",
"plannedEndDate": "2026-07-14T00:00:00.000Z"
}
],
"milestones": [
{
"id": "0a4f6c33-...",
"name": "Client sign-off",
"type": "custom",
"targetDate": "2026-08-15T00:00:00.000Z",
"isFirm": true,
"orderIndex": 0
}
],
"brief": { "...": "..." }
}

brief is null or absent for projects created without the Brief Builder. Member, task, and milestone objects include further metadata fields.

Errors

  • 404 if the project does not exist.
  • 403 if the project belongs to an organisation you are not a member of.

Get the project’s history feed: status changes, brief edits, task activity, comments, milestone changes, and more.

Any member of the project’s organisation can call this.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.

Query parameters

NameTypeRequiredDescription
limitintegerNoPage size, 1 to 200. Defaults to 100.
offsetintegerNoNumber of events to skip. Defaults to 0.
qstringNoFree-text search across event titles and descriptions.
eventTypesstringNoComma-separated list of event types to include, for example task.created,task.status.updated.
categoriesstringNoComma-separated list of categories. Valid values: project, brief, task, comment, asset, team. Unknown values are ignored.
taskIdUUIDNoOnly return events linked to this task.
significantOnlybooleanNoWhen true, excludes routine automatic events (AI column fills and dependency cascade reschedules). The in-app dashboard uses this; the full feed includes them.

Example request

Terminal window
curl 'https://api.runnit.io/api/v1/projects/<project-id>/history?categories=task,comment&limit=50' \
-H 'Authorization: Bearer rnk_your_key'

Response

{
"success": true,
"events": [
{
"id": "9d21e07b-...",
"projectId": "1f0e9a52-...",
"taskId": "4be2d871-...",
"actorUserId": "d3a6f0c1-...",
"actor": { "id": "d3a6f0c1-...", "name": "Alex Chen", "email": "alex@example.com" },
"category": "task",
"eventType": "task.status.updated",
"title": "Task status updated: Draft homepage copy",
"description": "Status changed from scheduled to in_progress.",
"metadata": { "previousStatus": "scheduled", "nextStatus": "in_progress" },
"createdAt": "2026-07-06T23:41:12.000Z"
}
],
"filters": {
"eventTypes": ["task.created", "task.status.updated", "project.updated"],
"categories": ["project", "task", "comment"]
},
"pagination": { "total": 128, "limit": 50, "offset": 0, "hasMore": true }
}

taskId, actorUserId, actor, and description can be null. filters lists the event types and categories that actually appear in this project’s history, which is useful for building filter controls.

Errors

  • 404 if the project does not exist.
  • 403 if the project belongs to an organisation you are not a member of.

Update a project’s common editable fields in one call. Send only the fields you want to change.

Requires the project owner, a project admin, or an organisation admin or owner.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.

Request body (all fields optional, at least one required)

NameTypeRequiredDescription
namestringNoNew project name. Must not be empty.
descriptionstring or nullNoProject description. Null clears it.
summarystring or nullNoProject summary. Null clears it.
statusstringNoNew status. archived is not accepted here: use POST /:id/archive. See the field reference above.
jobNumberstringNo1 to 100 characters.
startDateISO 8601 date or nullNoPlanned start date. Null clears it.
targetCompletionDateISO 8601 date or nullNoTarget delivery date. Null clears it.
budgetnumber or nullNoBudget amount, zero or greater. Null clears it.
budgetCurrencystringNoExactly 3 letters, for example AUD. Stored uppercase.
clientOrganizationIdUUID or nullNoClient to link. Must be an active client of the organisation, otherwise 400. Null removes the client link and makes the project internal. Changing the client clears any pinned client rate card.
colorstringNoProject colour key. See the field reference above.
tagsarray of stringsNoReplaces the full tag list.

Example request

Terminal window
curl -X PATCH https://api.runnit.io/api/v1/projects/<project-id> \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{
"status": "active",
"targetCompletionDate": "2026-09-30",
"budget": 80000,
"budgetCurrency": "AUD"
}'

Response

{ "success": true, "project": { "id": "1f0e9a52-...", "status": "active", "budget": 80000 } }

Returns the full updated project. If the status changed to a completed state, Runnit also records the work in each contributor’s portfolio history.

Errors

  • 400 if no updatable fields are supplied (“No updates provided”).
  • 400 if jobNumber is supplied but empty after trimming.
  • 404 if the project does not exist.
  • 403 if you are not a member of the project’s organisation, or you are not the project owner, a project admin, or an organisation admin or owner.
  • 409 with code RATE_CARD_CONFIRMATION_REQUIRED or PROJECT_BUDGET_REQUIRED when status would activate a draft that still needs its rate card confirmed or a required budget set. The same activation checks as the status endpoint apply, and no other fields are changed when the check fails.
  • 409 with code PROJECT_INCOMPLETE_TASKS when status is a terminal value (done, completed, cancelled) and incomplete tasks remain. See Incomplete task checks.
  • 409 with code PROJECT_ARCHIVED if the project is archived. Unarchive it first.

Update the project’s brief content (description and summary).

Only the project owner can call this.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.

Request body

NameTypeRequiredDescription
descriptionstringNoBrief description text.
summarystringNoBrief summary text.

Example request

Terminal window
curl -X PATCH https://api.runnit.io/api/v1/projects/<project-id>/brief \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{ "summary": "Repositioned for a spring release window." }'

Response

{ "success": true, "project": { "id": "1f0e9a52-...", "summary": "Repositioned for a spring release window." } }

Errors

  • 404 if the project does not exist.
  • 403 if you are not a member of the project’s organisation.
  • 403 if you are not the project owner (“Only project owner can update the brief”).

Update only the project’s status.

Organisation admins and owners can always call this. Other members can update the status of internal projects, projects they are a team member of, and client projects for clients they can access, unless their per-member permission to update project statuses has been switched off.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.

Request body

NameTypeRequiredDescription
statusstringYesNew status. archived is not accepted here: use POST /:id/archive.
acknowledgeIncompleteTasksbooleanNoConfirms a terminal status change that would leave incomplete tasks behind. See Incomplete task checks.
incompleteTaskHandlingstringNoleave, complete_remaining, or cancel_remaining.

Example request

Terminal window
curl -X PATCH https://api.runnit.io/api/v1/projects/<project-id>/status \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{ "status": "completed" }'

Response

{ "success": true, "project": { "id": "1f0e9a52-...", "status": "completed" } }

Moving a project to a completed status also records the work in each contributor’s portfolio history.

Schedule bookings follow the status change. Moving a draft project to an active or on-hold status confirms all schedule entries for its tasks. Moving a project back to draft makes those reservations tentative again, except entries that were individually held. Moving a draft straight to done or cancelled leaves its tentative reservations unchanged.

The status also keeps the project’s Kanban workflow stage in step: the project moves to the stage that matches the new status. A status update that matches the project’s current stage leaves a custom stage in place.

Errors

  • 400 if status is missing, not a valid value, or archived.
  • 409 with code RATE_CARD_CONFIRMATION_REQUIRED when an AI-created draft requires its project rate card to be confirmed before activation. Select the card through the organisation project-rate-card endpoint, then retry.
  • 409 with code PROJECT_BUDGET_REQUIRED when the draft’s planning policy requires a budget and none is set.
  • 409 with code PROJECT_INCOMPLETE_TASKS when moving to a terminal status while incomplete tasks remain. See Incomplete task checks.
  • 409 with code USE_ARCHIVE_ENDPOINT if the project is archived. Status changes on archived projects go through POST /:id/unarchive.
  • 404 if the project does not exist.
  • 403 if you are not a member of the project’s organisation, your status permission is switched off, or the project is for a client you cannot access.

Move a project to a workflow stage (the same action as dragging its card on a Kanban board). The project’s status syncs to the stage automatically, with the same permission rules, activation checks, and schedule booking behaviour as the status endpoint.

Moving a project onto a stage whose status is archived archives it: this requires the archive permission (project:archive) and behaves exactly like POST /:id/archive, including the eligibility rule and the incomplete task check. Moving a project out of an archived stage is rejected; unarchive it instead.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.

Request body

NameTypeRequiredDescription
workflowStageIdUUIDYesTarget workflow stage. List stages with the organisation workflow-stages endpoint.
acknowledgeIncompleteTasksbooleanNoSee Incomplete task checks.
incompleteTaskHandlingstringNoleave, complete_remaining, or cancel_remaining.

Example request

Terminal window
curl -X PATCH https://api.runnit.io/api/v1/projects/<project-id>/stage \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{ "workflowStageId": "<stage-id>" }'

Response

{ "success": true, "project": { "id": "1f0e9a52-...", "status": "active", "workflowStageId": "<stage-id>" } }

Errors

  • 400 if workflowStageId is missing or not a UUID.
  • 404 if the project or the stage does not exist in your organisation.
  • 409 with code RATE_CARD_CONFIRMATION_REQUIRED or PROJECT_BUDGET_REQUIRED when moving a draft into an active-category stage and the activation checks fail.
  • 409 with codes PROJECT_INCOMPLETE_TASKS, PROJECT_NOT_ARCHIVABLE, or USE_ARCHIVE_ENDPOINT under the archive rules described above.
  • 403 under the same permission rules as the status endpoint, or without the archive permission when the target stage is archived.

Archive a project. Archiving hides the project from project lists, boards, and pickers, and makes it fully read-only until it’s unarchived. The project’s current status and workflow stage are saved so unarchiving can restore it exactly where it left off.

Requires the archive permission (project:archive). By default only organisation admins and owners have it; it can be granted to other roles in the permissions matrix.

Only done, completed, or cancelled projects can be archived. Complete or cancel the project first.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.

Request body (optional)

NameTypeRequiredDescription
acknowledgeIncompleteTasksbooleanNoConfirms archiving even though incomplete tasks remain.
incompleteTaskHandlingstringNoleave, complete_remaining, or cancel_remaining.

Example request

Terminal window
curl -X POST https://api.runnit.io/api/v1/projects/<project-id>/archive \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{ "incompleteTaskHandling": "complete_remaining" }'

Response

{ "success": true, "project": { "id": "1f0e9a52-...", "status": "archived", "archivedAt": "2026-07-18T04:12:00.000Z", "preArchiveStatus": "completed" } }

Archiving raises a project.archived event for automations, webhooks, and notifications. The check for incomplete tasks always runs when archiving, whatever the project’s previous status.

Errors

  • 403 without the archive permission.
  • 404 if the project does not exist.
  • 409 with code PROJECT_NOT_ARCHIVABLE if the project is not done, completed, or cancelled.
  • 409 with code PROJECT_INCOMPLETE_TASKS when incomplete tasks remain. See Incomplete task checks.

Restore an archived project. The project returns to the status and workflow stage it had when it was archived. If that stage no longer exists, it falls back to the standard stage for its previous status.

Requires the same archive permission as archiving.

Example request

Terminal window
curl -X POST https://api.runnit.io/api/v1/projects/<project-id>/unarchive \
-H 'Authorization: Bearer rnk_your_key'

Response

{ "success": true, "project": { "id": "1f0e9a52-...", "status": "completed", "archivedAt": null } }

Unarchiving raises a project.unarchived event.

Errors

  • 403 without the archive permission.
  • 404 if the project does not exist.
  • 409 with code PROJECT_NOT_ARCHIVED if the project is not archived.

While a project is archived, every write to it or anything inside it is rejected with 409 and code PROJECT_ARCHIVED. That includes project fields, budget, colour, brief, milestones, team membership, tasks and their dates, estimates, allocations, dependencies, assignees and table values, project views, comments on its conversations, and attaching new assets. Reads keep working, and deleting an archived project is still allowed.

When a project moves to a terminal status (done, completed, cancelled), or is archived from any status, Runnit checks for tasks that are not yet completed or cancelled. The organisation’s project lifecycle settings control what happens:

  • Warn (default): the request fails with 409, code PROJECT_INCOMPLETE_TASKS, and a details object:

    {
    "error": "This project still has 3 incomplete task(s).",
    "code": "PROJECT_INCOMPLETE_TASKS",
    "details": {
    "incompleteTaskCount": 3,
    "byStatus": { "pending": 2, "in_progress": 1 },
    "policy": "warn",
    "targetStatus": "completed"
    }
    }

    Retry with acknowledgeIncompleteTasks: true (or incompleteTaskHandling: "leave") to proceed and leave the tasks as they are, or send incompleteTaskHandling set to complete_remaining or cancel_remaining to resolve them as part of the same change.

  • Block: only complete_remaining or cancel_remaining is accepted. Acknowledging or leaving tasks unresolved is rejected.

  • Off: no check runs.

complete_remaining marks every remaining task completed (progress 100%). cancel_remaining cancels them. Each resolved task gets its own history entry and event, exactly as if it had been updated individually.

Update the project’s colour identifier.

Organisation admins and owners can always call this. Other members can update internal projects, projects they are a team member of, and client projects for clients they can access.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.

Request body

NameTypeRequiredDescription
colorstringYesColour key. See the field reference above.

Example request

Terminal window
curl -X PATCH https://api.runnit.io/api/v1/projects/<project-id>/color \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{ "color": "guava" }'

Response

{ "success": true, "project": { "id": "1f0e9a52-...", "color": "guava" } }

Errors

  • 400 if color is not one of the valid colour keys.
  • 404 if the project does not exist.
  • 403 if you are not a member of the project’s organisation or cannot access the project’s client.

Update the project’s budget amount, currency, or both.

Requires the project owner, a project admin, or an organisation admin or owner.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.

Request body (at least one field required)

NameTypeRequiredDescription
budgetnumber or nullNoBudget amount, zero or greater. Null clears the budget.
budgetCurrencystringNoExactly 3 letters. Stored uppercase.

Example request

Terminal window
curl -X PATCH https://api.runnit.io/api/v1/projects/<project-id>/budget \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{ "budget": 90000, "budgetCurrency": "AUD" }'

Response

{ "success": true, "project": { "id": "1f0e9a52-...", "budget": 90000, "budgetCurrency": "AUD" } }

Errors

  • 400 if neither budget nor budgetCurrency is supplied (“No budget updates provided”), the budget is negative, or the currency is not 3 letters.
  • 404 if the project does not exist.
  • 403 if you are not the project owner, a project admin, or an organisation admin or owner.

List the project’s milestones (key dates).

Any member of the project’s organisation can call this.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.

Example request

Terminal window
curl https://api.runnit.io/api/v1/projects/<project-id>/milestones \
-H 'Authorization: Bearer rnk_your_key'

Response

{
"success": true,
"milestones": [
{
"id": "0a4f6c33-...",
"projectId": "1f0e9a52-...",
"name": "Client sign-off",
"type": "custom",
"targetDate": "2026-08-15T00:00:00.000Z",
"isFirm": true,
"description": "Final approval from the client team.",
"sourceKey": null,
"orderIndex": 0,
"achievedAt": null,
"achievedBy": null,
"metadata": {},
"createdAt": "2026-07-01T04:12:00.000Z",
"updatedAt": "2026-07-01T04:12:00.000Z"
}
]
}

description and sourceKey can be null or absent. achievedAt is the timestamp when the milestone was marked achieved and achievedBy is the user who marked it; both are null while the milestone is open. See Mark a milestone achieved.

Errors

  • 404 if the project does not exist.
  • 403 if you are not a member of the project’s organisation.

Create a milestone on the project.

Requires the project owner, a project admin, or an organisation admin or owner.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.

Request body

NameTypeRequiredDescription
namestringYesMilestone name. Must not be empty.
targetDatestringYesDate in YYYY-MM-DD format.
typestringNoMilestone type label. Defaults to custom.
isFirmbooleanNoWhether the date is firm. Defaults to true.
descriptionstringNoOptional detail.
sourceKeystring or nullNoOptional key linking the milestone to its source.
orderIndexintegerNoSort position, zero or greater. Defaults to 0.
metadataobjectNoFree-form metadata.

Example request

Terminal window
curl -X POST https://api.runnit.io/api/v1/projects/<project-id>/milestones \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{ "name": "Client sign-off", "targetDate": "2026-08-15", "isFirm": true }'

Response

Returns 201 with the created milestone:

{ "success": true, "milestone": { "id": "0a4f6c33-...", "name": "Client sign-off", "targetDate": "2026-08-15T00:00:00.000Z" } }

Errors

  • 400 if name is missing, targetDate is missing or not in YYYY-MM-DD format, or the date is invalid.
  • 404 if the project does not exist.
  • 403 if you are not the project owner, a project admin, or an organisation admin or owner.

PATCH /api/v1/projects/:id/milestones/:milestoneId

Section titled “PATCH /api/v1/projects/:id/milestones/:milestoneId”

Update a milestone. Send only the fields you want to change.

Requires the project owner, a project admin, or an organisation admin or owner.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.
milestoneIdUUIDYesMilestone ID. Must belong to the project.

Request body (all optional)

NameTypeRequiredDescription
namestringNoNew name. Must not be empty.
targetDatestringNoDate in YYYY-MM-DD format.
typestringNoMilestone type label.
isFirmbooleanNoWhether the date is firm.
descriptionstring or nullNoDetail text. Null or empty clears it.
sourceKeystring or nullNoSource key. Null clears it.
orderIndexintegerNoSort position, zero or greater.
metadataobjectNoReplaces the metadata object.

Example request

Terminal window
curl -X PATCH https://api.runnit.io/api/v1/projects/<project-id>/milestones/<milestone-id> \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{ "targetDate": "2026-08-22" }'

Response

{ "success": true, "milestone": { "id": "0a4f6c33-...", "targetDate": "2026-08-22T00:00:00.000Z" } }

Errors

  • 400 if targetDate is not a valid YYYY-MM-DD date.
  • 404 if the project or milestone does not exist, or the milestone belongs to a different project.
  • 403 if you are not the project owner, a project admin, or an organisation admin or owner.

DELETE /api/v1/projects/:id/milestones/:milestoneId

Section titled “DELETE /api/v1/projects/:id/milestones/:milestoneId”

Delete a milestone from the project.

Requires the project owner, a project admin, or an organisation admin or owner.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.
milestoneIdUUIDYesMilestone ID. Must belong to the project.

Example request

Terminal window
curl -X DELETE https://api.runnit.io/api/v1/projects/<project-id>/milestones/<milestone-id> \
-H 'Authorization: Bearer rnk_your_key'

Response

{ "success": true }

Errors

  • 404 if the project or milestone does not exist, or the milestone belongs to a different project.
  • 403 if you are not the project owner, a project admin, or an organisation admin or owner.

POST /api/v1/projects/:id/milestones/:milestoneId/achieve

Section titled “POST /api/v1/projects/:id/milestones/:milestoneId/achieve”

Mark a milestone as achieved. This records who marked it and when, and stops any further “milestone due” or “milestone overdue” notifications for it.

The call is idempotent: marking an already achieved milestone keeps the original timestamp and sends no duplicate notifications.

Requires the project owner, a project admin, or an organisation admin or owner.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.
milestoneIdUUIDYesMilestone ID. Must belong to the project.

Example request

Terminal window
curl -X POST https://api.runnit.io/api/v1/projects/<project-id>/milestones/<milestone-id>/achieve \
-H 'Authorization: Bearer rnk_your_key'

Response

{ "success": true, "milestone": { "id": "0a4f6c33-...", "achievedAt": "2026-08-14T02:10:00.000Z", "achievedBy": "9b1d2e40-..." } }

Errors

  • 404 if the project or milestone does not exist, or the milestone belongs to a different project.
  • 403 if you are not the project owner, a project admin, or an organisation admin or owner.

DELETE /api/v1/projects/:id/milestones/:milestoneId/achieve

Section titled “DELETE /api/v1/projects/:id/milestones/:milestoneId/achieve”

Reopen an achieved milestone. This clears achievedAt and achievedBy, and the milestone becomes eligible for due and overdue notifications again.

Requires the project owner, a project admin, or an organisation admin or owner.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.
milestoneIdUUIDYesMilestone ID. Must belong to the project.

Example request

Terminal window
curl -X DELETE https://api.runnit.io/api/v1/projects/<project-id>/milestones/<milestone-id>/achieve \
-H 'Authorization: Bearer rnk_your_key'

Response

{ "success": true, "milestone": { "id": "0a4f6c33-...", "achievedAt": null, "achievedBy": null } }

Errors

  • 404 if the project or milestone does not exist, or the milestone belongs to a different project.
  • 403 if you are not the project owner, a project admin, or an organisation admin or owner.

Create a task in the project. Only the name is required. Without a planned start, the task is created as pending backlog work with null planned dates and no schedule segments. Supplying a planned start creates status scheduled and builds the working-day schedule from the start and estimate.

Runnit validates initial dependencies before it saves the task. If dependency validation, schedule creation, or the dependency cascade fails, the request returns an error and the new task is not retained. You can retry the request without creating a duplicate task.

Requires task:create, or a project owner or project admin override. Supplying assignedToId also requires task:assign unless the override applies. Managers have both capabilities by default.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.

Request body

NameTypeRequiredDescription
namestringYesTask name. Must not be empty.
descriptionstringNoTask description.
prioritystringNolow, medium, high, or critical. Defaults to medium.
assignedToIdUUID or nullNoUser to assign. Must be an active organisation member. On a client project they must be on the client staffing list or the explicit project team.
plannedStartDateISO 8601 dateNoSchedule start. Omit it to keep the task unscheduled.
estimatedHoursnumberNoGreater than 0 and less than 1000. Without a start, the estimate is stored without creating a schedule. With a start, it defaults to the organisation’s dailyCapacityHours value, or 8 when that setting is missing or invalid.
dependsOnUUID[]NoInitial finish-to-start dependencies from the same project. Requires task-wide update and schedule-management access unless a project override applies.

Example request

Capture a name-only backlog task:

Terminal window
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": "Confirm meeting follow-up" }'

This returns a pending task with null assignment, estimate, and planned dates, plus empty scheduleSegments and scheduleConflicts arrays.

Create and schedule a planned task:

Terminal window
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",
"description": "First pass for internal review.",
"priority": "high",
"assignedToId": "<user-uuid>",
"plannedStartDate": "2026-07-13",
"estimatedHours": 6
}'

Response

Returns 201 with the created task, its schedule segments, and any clashes with the assignee’s existing schedule. Unscheduled tasks return empty arrays:

{
"success": true,
"task": {
"id": "4be2d871-...",
"name": "Draft homepage copy",
"status": "scheduled",
"priority": "high",
"assignedToId": "d3a6f0c1-...",
"estimatedHours": 6,
"plannedStartDate": "2026-07-13T00:00:00.000Z",
"plannedEndDate": "2026-07-13T23:59:59.000Z"
},
"scheduleSegments": [
{
"startTime": "2026-07-13T09:00:00.000Z",
"endTime": "2026-07-13T15:30:00.000Z",
"hours": 6,
"isOverdue": false,
"daysOverdue": 0
}
],
"scheduleConflicts": [
{
"taskId": "77c0d1aa-...",
"startTime": "2026-07-13T09:00:00.000Z",
"endTime": "2026-07-13T12:00:00.000Z",
"durationMinutes": 180
}
]
}

scheduleConflicts is empty when the task has no assignee or the assignee is free. Conflicts are informational only; the task is still created.

Errors

  • 400 if name is missing, estimatedHours is out of range, or plannedStartDate is invalid.
  • 400 if the assignee is not an active member of the organisation, or is neither on the client staffing list nor the explicit project team.
  • 404 if the project or the assigned user does not exist.
  • 403 if you are not the project owner, a project admin, or an organisation manager, admin, or owner.

PATCH /api/v1/projects/:id/tasks/:taskId/dates

Section titled “PATCH /api/v1/projects/:id/tasks/:taskId/dates”

Move a task to a new start date and rebuild its schedule. The end date is recalculated from the task’s estimated hours across working days. Scheduling a pending backlog task this way also sets its status to scheduled, matching a task created with a planned start. Work that is already in progress, completed, or cancelled keeps its status.

Requires the project owner, a project admin, or an organisation manager, admin, or owner.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.
taskIdUUIDYesTask ID.

Request body

NameTypeRequiredDescription
plannedStartDateISO 8601 dateYesNew schedule start.
plannedEndDateISO 8601 dateNoAccepted but not used; the end date is always recalculated from the estimate.
allowWeekendsbooleanNoAllow scheduling on weekends. An explicit value updates the task’s saved weekend policy. When omitted, the current policy is preserved; tasks without a saved policy default to weekdays only.

If the task has no usable estimate, Runnit derives one from its existing schedule entries, then from its current planned date span, and finally uses the organisation’s dailyCapacityHours, with 8 hours as the fallback.

The response schedule is also stored as the task’s canonical daily allocation. Later estimate changes and dependency cascades preserve the saved weekend policy. Organisation closure dates are never scheduled.

Example request

Terminal window
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", "allowWeekends": false }'

Response

{
"success": true,
"task": { "id": "4be2d871-...", "plannedStartDate": "2026-07-20T00:00:00.000Z", "plannedEndDate": "2026-07-21T00:00:00.000Z" },
"scheduleSegments": [
{ "startTime": "2026-07-20T09:00:00.000Z", "endTime": "2026-07-20T17:00:00.000Z", "hours": 8, "isOverdue": false, "daysOverdue": 0 }
]
}

Errors

  • 400 if plannedStartDate is missing or invalid.
  • 400 if the schedule cannot be rebuilt for the requested dates.
  • 404 if the project or task does not exist.
  • 403 if you are not the project owner, a project admin, or an organisation manager, admin, or owner.

PATCH /api/v1/projects/:id/tasks/:taskId/estimate

Section titled “PATCH /api/v1/projects/:id/tasks/:taskId/estimate”

Change a task’s estimated hours. An undated task stores the estimate but remains unscheduled. A dated task with a day-by-day breakdown (manual schedule mode) keeps its planned window when the new total fits that window at the organisation’s daily capacity: each day’s hours are rescaled in proportion so they sum to the new estimate. When the new total does not fit, or the task has an automatic schedule, the schedule rebuilds from the task’s current start and the planned end is recalculated.

Requires task:update:any plus schedule:manage, or a project owner or project admin override. Managers have both capabilities by default.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.
taskIdUUIDYesTask ID.

Request body

NameTypeRequiredDescription
estimatedHoursnumberYesGreater than 0 and less than 1000.
scheduleModestringNoOnly auto is accepted. Discards any day-by-day breakdown and rebuilds the schedule automatically, even when the new total fits the pinned window.

The schedule keeps the task’s existing start date. If the task has no planned start and no existing schedule entry, the response has null planned dates and an empty scheduleSegments array. Use the dates endpoint when you are ready to place it on the calendar.

Example request

Terminal window
curl -X PATCH https://api.runnit.io/api/v1/projects/<project-id>/tasks/<task-id>/estimate \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{ "estimatedHours": 12 }'

Response

{
"success": true,
"task": { "id": "4be2d871-...", "estimatedHours": 12 },
"scheduleSegments": [
{ "startTime": "2026-07-13T09:00:00.000Z", "endTime": "2026-07-13T17:00:00.000Z", "hours": 8, "isOverdue": false, "daysOverdue": 0 },
{ "startTime": "2026-07-14T09:00:00.000Z", "endTime": "2026-07-14T13:00:00.000Z", "hours": 4, "isOverdue": false, "daysOverdue": 0 }
]
}

Errors

  • 400 if estimatedHours is missing or out of range.
  • 400 if the schedule cannot be rebuilt.
  • 404 if the project or task does not exist.
  • 403 if you do not have both task-wide update and schedule-management access, and no project owner/admin override applies.

PATCH /api/v1/projects/:id/tasks/:taskId/allocations

Section titled “PATCH /api/v1/projects/:id/tasks/:taskId/allocations”

Set an explicit per-day hour breakdown for a task (manual schedule mode). This replaces the task’s automatic schedule with the exact days and hours you supply.

Requires task:update:any plus schedule:manage, or a project owner or project admin override. Managers have both capabilities by default.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.
taskIdUUIDYesTask ID. Must belong to the project.

Request body

NameTypeRequiredDescription
allocationsarrayYesAt least one entry.
allocations[].dateISO 8601 dateYesThe day to allocate hours to.
allocations[].hoursnumberYesGreater than 0 and less than 24.

Example request

Terminal window
curl -X PATCH https://api.runnit.io/api/v1/projects/<project-id>/tasks/<task-id>/allocations \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{
"allocations": [
{ "date": "2026-07-13", "hours": 4 },
{ "date": "2026-07-15", "hours": 3.5 }
]
}'

Response

{
"success": true,
"task": { "id": "4be2d871-...", "estimatedHours": 7.5 },
"scheduleSegments": [
{ "startTime": "2026-07-13T09:00:00.000Z", "endTime": "2026-07-13T13:00:00.000Z", "hours": 4, "isOverdue": false, "daysOverdue": 0 },
{ "startTime": "2026-07-15T09:00:00.000Z", "endTime": "2026-07-15T12:30:00.000Z", "hours": 3.5, "isOverdue": false, "daysOverdue": 0 }
]
}

Errors

  • 400 if allocations is empty, a date is invalid, or hours are out of range.
  • 400 if the manual schedule cannot be applied.
  • 404 if the project or task does not exist, or the task belongs to a different project.
  • 403 if you do not have both task-wide update and schedule-management access, and no project owner/admin override applies.

PATCH /api/v1/projects/:id/tasks/:taskId/assignee

Section titled “PATCH /api/v1/projects/:id/tasks/:taskId/assignee”

Assign a task to a user, or unassign it. The task’s schedule entries move to the new assignee.

Requires the project owner, a project admin, or an organisation manager, admin, or owner.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.
taskIdUUIDYesTask ID. Must belong to the project.

Request body

NameTypeRequiredDescription
assignedToIdUUID or nullNoUser to assign. Null (or omitted) removes the assignment. Must be an active organisation member. On a client project they must be on the client staffing list or the explicit project team.

Example request

Terminal window
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>" }'

Response

{
"success": true,
"task": {
"id": "4be2d871-...",
"name": "Draft homepage copy",
"assignedToId": "d3a6f0c1-...",
"assignee": { "id": "d3a6f0c1-...", "name": "Alex Chen", "email": "alex@example.com" }
},
"scheduleConflicts": []
}

scheduleConflicts lists the new assignee’s overlapping schedule entries in the task’s window, in the same shape as the task creation endpoint. It is informational only; the assignment still happens.

Errors

  • 400 if assignedToId is not a UUID.
  • 400 if the assignee is not an active member of the organisation, or is neither on the client staffing list nor the explicit project team.
  • 404 if the project, task, or assigned user does not exist.
  • 403 if you are not the project owner, a project admin, or an organisation manager, admin, or owner.

List all active organisation staff and their staffing eligibility for this project. This single response supports both task assignment and the project team selector. On a client project, the response distinguishes the client staffing list from an explicit project-only override.

Requires the project owner, a project admin, or an organisation manager, admin, or owner.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.

Example request

Terminal window
curl https://api.runnit.io/api/v1/projects/<project-id>/assignee-options \
-H 'Authorization: Bearer rnk_your_key'

Response

{
"success": true,
"hasClientRestrictions": true,
"options": [
{
"id": "d3a6f0c1-...",
"name": "Alex Chen",
"email": "alex@example.com",
"avatarUrl": "https://...",
"roleTitle": "Creative Director",
"inProjectTeam": true,
"clientEligible": false,
"eligibleForAssignment": true,
"staffingSource": "project_member"
}
]
}

avatarUrl and roleTitle can be absent. Use eligibleForAssignment to build an assignee control. A false value means the person must first be added to the project team. staffingSource is one of client_team, open_client, project_member, or organization.

Errors

  • 404 if the project does not exist.
  • 403 if you are not the project owner, a project admin, or an organisation manager, admin, or owner.

Projects can carry custom columns for the task table view: text, long text, number, dropdown, date, checkbox, link, and rating. A column is defined at one of three scopes:

  • project: exists on this project only;
  • client: appears on every project for one client organisation; and
  • organization: appears on every project in the organisation.

A project resolves its effective columns with project definitions taking precedence over client definitions, which take precedence over organisation definitions, matched by key. A project-scope column with the same key as an inherited one overrides it on that project only, and an override keeps the inherited key and type so values stay comparable across projects.

Column values live on each task in metadata.tableValues, keyed by the column’s key.

List the effective custom columns for a project.

Any member of the project’s organisation can call this. Pass includeArchived=true to also receive archived and hidden columns; this only takes effect when you can manage columns on the project.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.

Example request

Terminal window
curl https://api.runnit.io/api/v1/projects/<project-id>/table-columns \
-H 'Authorization: Bearer rnk_your_key'

Response

{
"success": true,
"columns": [
{
"id": "7c2d91aa-...",
"projectId": null,
"organizationId": "9b1f2e04-...",
"clientOrganizationId": null,
"scope": "organization",
"key": "reviewStage",
"title": "Review stage",
"type": "dropdown",
"settings": {
"options": [
{ "key": "onTrack", "label": "On Track", "color": "matcha" },
{ "key": "atRisk", "label": "At Risk", "color": "mandarin" }
]
},
"orderIndex": 0,
"isArchived": false,
"isOverride": false,
"isHidden": false,
"createdAt": "2026-07-09T10:00:00.000Z",
"updatedAt": "2026-07-09T10:00:00.000Z"
}
],
"canManage": true,
"canManageOrg": true,
"defaultColumns": ["status", "assignee", "hours"],
"defaultColumnsSource": "organization"
}
  • defaultColumns is the configured default visible column set for this project’s task table, in display order, or null when nothing is configured. defaultColumnsSource names where it came from: board when the project’s kanban board sets its own columns, organization when the organisation setting applies, or null. These are defaults only: each person’s own saved layout and saved views still take precedence in the app.
  • scope tells you where the definition lives. isOverride is true when a project-scope column shadows an inherited one, and overridesScope then names the shadowed scope.
  • canManage reports whether you can manage project-scope columns and task values. canManageOrg reports whether you hold the shared table columns permission, which controls client- and organisation-scope definitions.
  • settings varies by type: dropdowns carry options (each with key, label, and a color from guava, acai, matcha, coffee, mandarin, neutral), numbers can carry unit and precision, and ratings carry max.
  • Any type except link can also carry settings.ai: { "enabled": true, "prompt": "...", "sourceColumns": ["otherKey"], "autoFill": true, "includeFinancials": true }. When enabled, the fill endpoint below writes AI-generated values of the column’s type, validated exactly like manual input. Enabled AI columns always refresh automatically, so autoFill is retained for compatibility and is always returned as true. With includeFinancials, each task’s computed financial snapshot (assignee bill rate, planned cost, actual logged hours/cost, cost variance) is added to the model context; running such fills requires the financial reports permission, and the generated values are visible to everyone who can see the table.

Errors

  • 404 if the project does not exist.
  • 403 if you are not a member of the project’s organisation.

Create a custom column.

Project-scope columns require the project owner, a project admin, or an organisation manager, admin, or owner. Client- and organisation-scope columns require the shared table columns permission, which organisation admins and owners hold by default and can grant to other roles or people.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.

Request body

NameTypeRequiredDescription
titlestringYesColumn name, up to 255 characters. The stable key is generated from it.
typestringYesOne of text, long_text, number, dropdown, date, checkbox, link, rating.
scopestringNoOne of project (default), client, organization. client requires the project to have a client organisation.
settingsobjectNoType-specific settings. Dropdowns require at least one option.

Example request

Terminal window
curl -X POST https://api.runnit.io/api/v1/projects/<project-id>/table-columns \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{
"title": "Review stage",
"type": "dropdown",
"scope": "organization",
"settings": {
"options": [
{ "label": "On Track", "color": "matcha" },
{ "label": "At Risk", "color": "mandarin" }
]
}
}'

Response

Returns 201 with the created column in the same shape as the list endpoint. When the column has AI enabled, Runnit starts filling its existing tasks in the background and the response returns immediately. Track the run with the fill status endpoint below. A provider failure does not undo the column creation; a later automatic or manual refresh can retry. The response’s aiRefresh field is deprecated and always null.

Errors

  • 400 if the title is empty, the type is unsupported, the settings are invalid for the type, the scope limit of 50 columns is reached, or client scope is requested on a project without a client organisation.
  • 404 if the project does not exist.
  • 403 if you lack manage rights for the requested scope (shared scopes need the shared table columns permission).

POST /api/v1/projects/:id/table-columns/overrides

Section titled “POST /api/v1/projects/:id/table-columns/overrides”

Create a project-scope shadow of an inherited column: an override you can rename and reconfigure on this project, or a per-project hide.

Requires the project owner, a project admin, or an organisation manager, admin, or owner.

Request body

NameTypeRequiredDescription
keystringYesKey of the inherited client- or organisation-scope column.
hiddenbooleanNoWhen true, the column is hidden on this project instead of overridden. Defaults to false.

Example request

Terminal window
curl -X POST https://api.runnit.io/api/v1/projects/<project-id>/table-columns/overrides \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{ "key": "reviewStage", "hidden": false }'

Response

Returns 201 with the created project-scope column. Delete this column to restore the inherited definition.

Errors

  • 400 if no inherited column has that key, or the project already overrides it.
  • 404 if the project does not exist.
  • 403 if you cannot manage columns on this project.

PATCH /api/v1/projects/:id/table-columns/:columnId

Section titled “PATCH /api/v1/projects/:id/table-columns/:columnId”

Update a column’s title, settings, order, or archive state. Send only the fields you want to change. The column’s type and key never change.

Project-scope columns require project manage rights. Client- and organisation-scope columns require the shared table columns permission, and changes apply to every project that inherits the column.

Request body

NameTypeRequiredDescription
titlestringNoNew column name.
settingsobjectNoReplacement settings, validated for the column’s type.
orderIndexintegerNoSort position, zero or greater.
isArchivedbooleanNoArchive or restore the column. Archived columns keep their values but stop appearing.

Response

Returns the updated column. When the update changes an AI column’s settings and the column is not archived, Runnit refreshes its cells in the background and the response returns immediately. Track the run with the fill status endpoint below. The response’s aiRefresh field is deprecated and always null.

Errors

  • 400 if the settings are invalid for the column’s type.
  • 404 if the column does not exist or is not visible from this project.
  • 403 if you lack manage rights for the column’s scope.

DELETE /api/v1/projects/:id/table-columns/:columnId

Section titled “DELETE /api/v1/projects/:id/table-columns/:columnId”

Delete a column definition. Task values stored under the column’s key remain in task metadata but are no longer shown. Deleting a project-scope override or hide restores the inherited definition.

The same scope-based permissions as the update endpoint apply.

Errors

  • 404 if the column does not exist or is not visible from this project.
  • 403 if you lack manage rights for the column’s scope.

POST /api/v1/projects/:id/table-columns/refresh

Section titled “POST /api/v1/projects/:id/table-columns/refresh”

Refresh stale cells across every visible AI-enabled column in a project. A cell is stale when it has no generated result, its task or prompt inputs changed, or its generation time is older than staleBefore. Cells that are still current are skipped before an AI request is made.

Runnit’s task table calls this automatically on load with the start of the user’s current local day. You can also call it for an integration that updates or displays project tasks. Any member who can view the project can request the refresh. The automatic run uses each column’s owner and existing permissions for its AI work.

Request body

NameTypeRequiredDescription
taskIdsUUID[]NoRestrict the refresh to up to 100 tasks in this project. Omit it to check every task.
staleBeforeISO 8601 timestampNoRefresh values generated before this time, even when their inputs have not changed.
asyncbooleanNoWhen true, start the refresh in the background and return 202 with { "success": true, "started": true } immediately. Track progress with the fill status endpoint. Defaults to false, which waits and returns the summary below.

Example request

Terminal window
curl -X POST https://api.runnit.io/api/v1/projects/<project-id>/table-columns/refresh \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{ "staleBefore": "2026-07-17T00:00:00.000+10:00" }'

Response

{
"success": true,
"summary": {
"filled": 4,
"skipped": 11,
"failed": 0,
"columns": [
{
"columnKey": "deliveryRisk",
"filled": 4,
"skipped": 11,
"failed": 0,
"results": [
{ "taskId": "8174ae2c-...", "status": "filled", "value": "high" }
]
}
]
}
}

Large projects are checked in groups of 100 tasks. Matching refreshes already in progress are reused, and rapid task edits are grouped before automatic refresh, which limits duplicate AI calls.

Errors

  • 400 if taskIds contains more than 100 entries, or if staleBefore is not a valid timestamp.
  • 404 if the project does not exist or a requested task is outside it.
  • 403 if you cannot view the project.
  • 503 if an AI column needs a refresh but no provider is configured.

GET /api/v1/projects/:id/table-columns/fill-status

Section titled “GET /api/v1/projects/:id/table-columns/fill-status”

Live progress of the project’s background AI fill runs: the runs started by creating or updating an AI column, by an async refresh, or by a manual fill. Any member who can view the project can read it. Finished runs stay in the feed for about a minute so a final poll can read the outcome.

Example request

Terminal window
curl https://api.runnit.io/api/v1/projects/<project-id>/table-columns/fill-status \
-H 'Authorization: Bearer rnk_your_key'

Response

{
"success": true,
"runs": [
{
"columnKey": "deliveryRisk",
"total": 88,
"done": 30,
"filled": 28,
"skipped": 0,
"failed": 2,
"startedAt": "2026-07-31T00:12:35.429Z",
"finishedAt": null
}
]
}

runs is empty when nothing is running and nothing finished recently. Progress is tracked in memory, so a server restart clears the feed; the cells themselves are recovered by the next automatic refresh.

Errors

  • 404 if the project does not exist.
  • 403 if you cannot view the project.

POST /api/v1/projects/:id/table-columns/preview

Section titled “POST /api/v1/projects/:id/table-columns/preview”

Try a column draft’s AI instruction on a small sample of the project’s tasks without saving anything. No cell values, provenance, or project history are written. The task table’s column editor uses this for its Test on sample tasks action, and you can call it while designing a column through the API.

The draft is validated exactly like the create endpoint (dropdowns need at least one option, AI needs a prompt, link columns cannot use AI). Requires the same manage rights as the fill endpoint. Previewing a draft that reads financial data requires the financial reports permission.

Request body

NameTypeRequiredDescription
columnobjectYesThe draft: title, type, and optional settings in the same shape as the create endpoint, including the ai block.
columnIdUUIDNoWhen previewing changes to an existing column, its id, so source-column context resolves against the saved key.
sampleSizeintegerNoNumber of sample tasks, 1 to 5. Defaults to 3. Samples are spread evenly across the task list.

Example request

Terminal window
curl -X POST https://api.runnit.io/api/v1/projects/<project-id>/table-columns/preview \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{
"column": {
"title": "Delivery risk",
"type": "dropdown",
"settings": {
"options": [
{ "label": "High", "color": "mandarin" },
{ "label": "Low", "color": "matcha" }
],
"ai": { "enabled": true, "prompt": "Classify this task'\''s delivery risk." }
}
},
"sampleSize": 2
}'

Response

{
"success": true,
"items": [
{
"taskId": "8174ae2c-...",
"taskName": "Stakeholder alignment and governance",
"status": "ok",
"value": "low",
"valueLabel": "Low",
"model": "gpt-5.6-luna"
}
]
}

Dropdown values are option keys; valueLabel carries the matching option label. A status of failed includes a safe error message, for example when the model’s value fails the column’s validation.

Errors

  • 400 if the draft is invalid or AI is not enabled on it.
  • 404 if the project or columnId does not exist.
  • 403 if you cannot manage columns, or the draft reads financial data without the financial reports permission.
  • 503 if no AI provider is configured for the environment.

POST /api/v1/projects/:id/table-columns/:columnId/fill

Section titled “POST /api/v1/projects/:id/table-columns/:columnId/fill”

Run the AI fill for a column whose settings include an enabled ai block. The model reads each task’s built-in fields (name, description, status, priority, tags, dates) plus any configured source columns, and writes a value of the column’s type. Every value passes the same validation as manual input, and provenance (model, timestamp) is stored per cell.

Requires the project owner, a project admin, or an organisation manager, admin, or owner.

Request body

NameTypeRequiredDescription
taskIdsUUID[]NoRestrict the fill to specific tasks. Defaults to every task in the project (up to 100).
onlyEmptybooleanNoSkip tasks that already have a value for this column. Defaults to false.
onlyStalebooleanNoSkip cells whose task inputs and instruction are unchanged since they were generated. Defaults to false.

Example request

Terminal window
curl -X POST https://api.runnit.io/api/v1/projects/<project-id>/table-columns/<column-id>/fill \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{ "onlyEmpty": true }'

Response

{
"success": true,
"summary": {
"columnKey": "deliveryRisk",
"filled": 15,
"skipped": 2,
"failed": 0,
"results": [
{ "taskId": "8174ae2c-...", "status": "filled", "value": "high" }
]
}
}

Failed cells report status: "failed" with an error message and leave the existing value untouched.

Errors

  • 400 if the column has no AI configuration, a task id is not in this project, or more than 100 tasks are requested.
  • 404 if the column does not exist or is hidden on this project.
  • 403 if you cannot edit task fields on this project.
  • 503 if no AI provider is configured for the environment.

PATCH /api/v1/projects/:id/tasks/:taskId/table-values

Section titled “PATCH /api/v1/projects/:id/tasks/:taskId/table-values”

Set custom-column values on a task. Values are validated against the project’s effective columns and merged into the task’s metadata.tableValues; send null to clear a value.

Requires the project owner, a project admin, or an organisation manager, admin, or owner.

Request body

NameTypeRequiredDescription
valuesobjectYesKeyed by column key. Text, long text, date (YYYY-MM-DD), and dropdown option keys are strings; number and rating are numbers; checkbox is a boolean; link is { "url": "https://...", "label": "optional" }.

Example request

Terminal window
curl -X PATCH https://api.runnit.io/api/v1/projects/<project-id>/tasks/<task-id>/table-values \
-H 'Authorization: Bearer rnk_your_key' \
-H 'Content-Type: application/json' \
-d '{ "values": { "reviewStage": "atRisk", "poNumber": "PO-1044", "approved": true } }'

Response

Returns the updated task. The stored values are in task.metadata.tableValues.

Errors

  • 400 if a key does not match any effective column, a value fails the column’s validation, or the column is hidden on this project.
  • 404 if the project or task does not exist.
  • 403 if you cannot edit task fields on this project.

Per-task rollup of logged time costs, priced with the rate snapshots captured on each time entry (the same maths as the cost tools): actualCost uses the actual cost rate, billableCost uses the billable rate and only counts billable entries.

Requires the financial reports permission (organisation managers and above by default). Unlike most read endpoints this is hard-enforced: callers without the permission receive 403 regardless of the organisation’s permission enforcement mode.

Response

{
"success": true,
"taskCosts": [
{
"taskId": "8174ae2c-...",
"hours": 3,
"billableHours": 2,
"actualCost": 360,
"billableCost": 400,
"currency": "AUD"
}
],
"assigneeRates": [
{ "userId": "213ae491-...", "rate": 290, "currency": "AUD" }
]
}

Tasks with no logged time are omitted from taskCosts. assigneeRates carries the resolved bill rate for every task assignee on the project, including contributors who are not formal project members (resolution: project member rate snapshot, then project member rate, then organisation membership rate, then user rate). The app uses this endpoint for the view-only financial columns on the project task table: Rate, Actual hours, Planned billable, Actual billable, and Billable variance (actual minus planned). Those columns only exist for users holding the same permission.

Tasks support finish-to-start dependencies (dependsOn, an array of task ids in the same project). Dependencies are validated on write: self-references, tasks from other projects, cycles (including transitive ones), and lists over 25 entries are rejected with 400.

Dependencies are self-maintaining. Whenever a task’s dates change (date edits, allocation edits, hour changes, or a dependency edit), every dependent that would now start before its latest predecessor ends is pushed forward through the real scheduler: hours re-spread across working days, weekends and organisation closure dates skipped, manual day-by-day hour patterns preserved, completed and cancelled tasks never moved, and tasks with slack left alone (the cascade is push-only). The affected endpoints return a cascade object:

{
"cascade": {
"shifted": [
{ "taskId": "...", "taskName": "Design", "fromStart": "2026-07-15T00:00:00.000Z", "toStart": "2026-07-23", "fromEnd": "2026-07-17T00:00:00.000Z", "toEnd": "2026-07-24" }
],
"skipped": [],
"evaluated": 2
}
}

skipped lists dependents that could not be moved automatically (reason unscheduled or rebuild_failed) with the earliest start they should have. When a task completes and it was the last thing blocking a dependent, the dependent’s assignee receives a task unblocked notification.

PUT /api/v1/projects/:id/tasks/:taskId/dependencies

Section titled “PUT /api/v1/projects/:id/tasks/:taskId/dependencies”

Replace a task’s dependency list. Requires the project owner, a project admin, or an organisation manager, admin, or owner.

Request body

NameTypeRequiredDescription
dependsOnUUID[]YesPredecessor task ids (same project, max 25, acyclic). Empty array clears.

Response

Returns the updated task and the cascade summary described above.

POST /api/v1/projects/:id/tasks also accepts dependsOn, applying the same validation and shifting the new task after its predecessors when needed. Invalid dependencies are rejected before the task is saved.

The task table’s layout (visible columns, order, widths, grouping, sorts, and filters) can be saved as a named view. Personal views belong to one user; shared views (created with "shared": true) are visible to everyone on the project. One personal and one shared view can be flagged as the default; the personal default wins when both exist.

List the views visible to you: your personal views plus the project’s shared views. Any member of the project’s organisation can call this.

Response

{
"success": true,
"views": [
{
"id": "4c1d22aa-...",
"projectId": "1f0e9a52-...",
"userId": null,
"name": "Team standard",
"isDefault": true,
"config": { "groupBy": "priority", "sorts": [], "columns": [], "filters": {} },
"createdAt": "2026-07-10T00:00:00.000Z",
"updatedAt": "2026-07-10T00:00:00.000Z"
}
],
"canManageShared": true
}

userId is null for shared views. config is the stored layout object; the app treats it as opaque.

Create a saved view.

Personal views can be created by any member with project access. Shared views ("shared": true) require the project owner, a project admin, or an organisation manager, admin, or owner.

Request body

NameTypeRequiredDescription
namestringYesView name, up to 120 characters.
configobjectYesThe layout to store (up to 20 KB).
sharedbooleanNoCreate as a shared project view. Defaults to false.
isDefaultbooleanNoFlag as the default in its scope. Clears any previous default.

Response

Returns 201 with the created view.

Errors

  • 400 if the config is not an object, is too large, or the 30-views-per-scope limit is reached.
  • 403 if shared is requested without manage rights.
  • 404 if the project does not exist.

Update a view’s name, config, or default flag. You can modify your own views; shared views also require project manage rights.

Errors

  • 403 if the view is not yours (personal views of other users cannot be modified by anyone, including managers).
  • 404 if the view does not exist on this project.

Delete a view. The same ownership rules as the update endpoint apply.

Delete a project and everything attached to it.

Requires the project owner, a project admin, or an organisation admin or owner.

Deleting a project removes, in one operation:

  • the project itself;
  • all of its tasks;
  • all schedule entries for those tasks;
  • all project team memberships;
  • the asset collections linked to the project; and
  • every asset in those collections, including stored files and file versions.

Path parameters

NameTypeRequiredDescription
idUUIDYesProject ID.

Example request

Terminal window
curl -X DELETE https://api.runnit.io/api/v1/projects/<project-id> \
-H 'Authorization: Bearer rnk_your_key'

Response

Returns a summary of what was removed:

{
"success": true,
"projectId": "1f0e9a52-...",
"tasksSoftDeleted": 14,
"scheduleEntriesDeleted": 31,
"membersRemoved": 5,
"collectionsDeleted": 2,
"assetsDeleted": 27
}

Errors

  • 404 if the project does not exist or was already deleted.
  • 403 if you are not the project owner, a project admin, or an organisation admin or owner.

Work with individual tasks through the Tasks API, or group projects with the Master Projects API.