REST API Tasks - edmozley/freeitsm GitHub Wiki

โœ… REST API: Tasks

The complete usage guide for the Tasks (kanban) module of the REST API โ€” every URL, method, parameter and response shape, including moving cards on the board. Mirrors the interactive documentation at System โ†’ API โ†’ Documentation (with its live "Try it" tester).

[!NOTE] New to the API? The basics table on REST API: Tickets covers base URL, authentication, the response envelope, error codes, rate limits and PATCH semantics โ€” identical across all modules.

What's distinctive about Tasks:

๐Ÿ“‹ It's a board Tasks live in status columns ordered by board_position. POST /tasks/{id}/move is the kanban drag as an API call โ€” target column + 0-based position, re-packed automatically.
๐Ÿช† Subtasks are tasks A subtask is just a task with parent_task_id set. Lists return top-level tasks by default (each with done/total subtask counts); pass parent_task_id=N to list a task's children.
๐Ÿ”— Links respect company scope A task can link to a ticket, change or contract. Ticket links are validated against the key's company scope, and a linked ticket's details only appear in responses when the key could read that ticket directly โ€” tighter than the UI.
๐Ÿท๏ธ Tags are curated Task tags are a managed list (Tasks โ†’ Settings) โ€” the API accepts existing names or ids and returns 422 for unknown ones rather than creating tags on the fly.
๐Ÿข Company-scoped Tasks belong to a company. A key sees and changes only tasks in its companies. (This page said tasks were install-wide until #181; they have been scoped since tasks gained companies.)
๐Ÿงพ History is recorded, not served Every write through the API is recorded on the task's history (#181) - who, when, what - shown in the task window and the evidence report. The API does not serve the history itself.
๐Ÿ”’ Read only is enforced A key whose analyst has Read only access to Tasks is refused every write (403, code read_only).

๐Ÿš€ Quick start

Create a task from a monitoring runbook and walk it across the board:

B="https://your-server/api/v1"; K="Authorization: Bearer fitsm_โ€ฆ"

# 1. Create (lands at the end of the To Do column)
TASK=$(curl -s -X POST "$B/tasks" -H "$K" -H "Content-Type: application/json" -d '{
  "title": "Renew SSL certificate for portal",
  "priority": "High", "due_date": "2026-07-20",
  "tags": ["Security"]}' | jq -r '.data.id')

# 2. Drag it to the top of In Progress
curl -s -X POST "$B/tasks/$TASK/move" -H "$K" -H "Content-Type: application/json" \
  -d '{"status": "In Progress", "position": 0}'

# 3. Done โ†’ completed_at stamped, task.completed workflow fires
curl -s -X PATCH "$B/tasks/$TASK" -H "$K" -H "Content-Type: application/json" -d '{"status": "Done"}'

โœ… Tasks

๐ŸŸข GET /tasks โ€” list / board feed ย  ๐Ÿ”‘ tasks.read

Top-level tasks in board order by default. Every task carries its tags, subtasks: {total, done} counts and board_position โ€” enough to render a board.

Parameter Description
state open | closed | all (default all)
status / status_id One board column, by name (e.g. In Progress) or id
priority / priority_id By name or id
assigned_analyst_id / assigned_team_id Assignment filters ยท unassigned=true
ticket_id ยท change_id ยท contract_id Tasks linked to a record
parent_task_id List the subtasks of this task (default: top-level only)
tag Only tasks carrying this tag (exact name)
q Search title and description
due_before / due_after YYYY-MM-DD bounds
overdue true = open tasks past their due date โฐ
sort board_position (default), created_at, updated_at, due_date, completed_at, title, id โ€” prefix - for descending
page / per_page Pagination โ€” default 1 / 25, max 100

The task shape:

{ "id": 45, "title": "Renew SSL certificate for portal", "description": null,
  "status":   { "id": 2, "name": "In Progress", "is_closed": false, "colour": "#f59e0b" },
  "priority": { "id": 3, "name": "High", "colour": "#f59e0b" },
  "assigned_analyst": { "id": 1, "name": "Administrator" }, "assigned_team": null,
  "contractor": null, "contractor_contact": null,
  "start_date": null, "due_date": "2026-07-20", "estimate_hours": 4.5,
  "parent_task_id": null, "ticket_id": null, "change_id": null, "contract_id": null,
  "board_position": 0,
  "tags": ["Security"], "subtasks": { "total": 1, "done": 0 },
  "created_by": { "id": 1, "name": "Administrator" },
  "created_at": "2026-07-03T12:40:44Z", "updated_at": "2026-07-03T12:41:12Z",
  "completed_at": null }

estimate_hours (3.3.0) โ€” how long the task should take, in hours (up to two decimals, above 0, at most 9999; null = not estimated). Accepted on create and update, returned on every task.

๐Ÿ”ต POST /tasks โ€” create ย  ๐Ÿ”‘ tasks.create

Only title is required. Defaults To Do / Medium; the card is appended to the end of its status column. Returns 201.

Field Description
title โœ… Task title
description Details
status / status_id ยท priority / priority_id By name or id
assigned_analyst_id / assigned_team_id Assignment (both validated)
assigned_supplier_id / assigned_contact_id (3.3.0) A contractor: a supplier from Contracts doing the work, and optionally a person there. The contact must work for that supplier; a contact alone brings its supplier; null clears. Setting or changing either needs the key's analyst to have Contracts (403 otherwise). Read back as contractor / contractor_contact ({id, name} or null)
start_date / due_date YYYY-MM-DD
parent_task_id Create as a subtask of this task
ticket_id / change_id / contract_id Link to a record โ€” validated; ticket links must be within the key's company scope
tags Array of existing tag names or ids (422 for unknown)

๐ŸŸข GET /tasks/{id} โ€” get one ย  ๐Ÿ”‘ tasks.read

Everything in the list shape plus parent summary, the ordered subtask_list, comments, and linked_ticket / linked_change summaries (linked-ticket details only when the key's scope allows).

๐ŸŸ  PATCH /tasks/{id} โ€” update ย  ๐Ÿ”‘ tasks.update

Everything creatable is patchable; null clears. Two behaviours worth knowing:

  • Closing: moving to a closed status (Done, Cancelled) stamps completed_at (kept if already set) and fires the task.completed workflow event; moving back to an open status clears it.
  • Tags: tags replaces the tag set.

๐Ÿ”ต POST /tasks/{id}/move โ€” move on the board ย  ๐Ÿ”‘ tasks.update

The kanban drag as an API call:

{ "status": "In Progress", "position": 0 }
  • status / status_id โ€” target column (omit to reorder within the current one)
  • position โ€” 0-based slot in the column (omit = end)

The whole column is re-packed transactionally, so positions stay dense and ordered.

[!NOTE] A move is announced like any status change (#266): when /move changes the status it fires task.status_changed, and task.completed when it lands in a closed status - so the people on the task are notified and workflows run, exactly as for PATCH. A move within the same column changes no status and fires nothing. (Before #266 neither /move nor the board's drag fired any event, and this page called that a deliberate parity quirk - it was a gap in both.)

๐Ÿ”ด DELETE /tasks/{id} โ€” delete ย  ๐Ÿ”‘ tasks.delete

[!WARNING] Permanent, no trash. The whole subtask tree, comments and tag links are removed with it. The response reports subtasks_deleted.


๐Ÿ’ฌ Comments

๐ŸŸข GET /tasks/{id}/comments ย  ๐Ÿ”‘ task_comments.read

Comments oldest first, with the author.

๐Ÿ”ต POST /tasks/{id}/comments ย  ๐Ÿ”‘ task_comments.create

Body: {"text": "โ€ฆ"}. Attributed to the analyst the key acts as. Comments can't be edited or deleted (parity with the UI). Returns 201.


๐Ÿ“‹ Forms a task needs (#190)

A task - typically an occurrence of a repeating check - can need forms from the Forms module (Forms on tasks). These two let an integration see them and submit one against that occurrence: no submission numbers to copy, the same validation as the Forms page, and the submission linked to the task in the same transaction.

๐ŸŸข GET /tasks/{id}/forms ย  ๐Ÿ”‘ task_forms.read

Each form the task needs: id (the requirement - use it to submit), form_id (the exact form version), title, required, requirement (block | warn | optional), submission_id, submitted_by, submitted_at, has_draft, revision.

answers ([{field_id, question, answer, exception}]) is included only where the key's analyst may read them - access to Forms, or the role permission Read the form answers on tasks you can see - exactly as in the app. Otherwise the field is absent. exception is true for an answer the form marks as counting as an exception.

๐Ÿ”ต POST /tasks/{id}/forms/{task_form_id}/submissions ย  ๐Ÿ”‘ task_forms.create

{ "data": { "812": "Warning", "813": "Job 4 retried overnight" },
  "client_reference": "backup-2026-10-18" }
  • data - the answers, keyed by form field id (the field ids of the requirement's form version).
  • client_reference (recommended) - your own id for this submit, up to 64 characters. A retry with the same one returns the submission already made: 200, "replayed": true, and no second submission. A first submit answers 201.
  • revision (optional) - the revision you read from GET. If somebody has saved the form since, 409. Without it the current revision is used, which is right for an integration that is the only one filling the form in.
  • Submitting again replaces the earlier submission on the task; the earlier one is kept and recorded.
  • Refusals: 422 for a missing required answer or a bad value (the Forms validation), 404 for a requirement that is not on this task, 403 without the permission, or when the key's analyst has no Forms access or Read only Tasks.

Required forms are part of the task's completion check, so after this, completing the task over the API (PATCH its status, or /move to a closed column) is allowed.


๐Ÿ“š Reference data

Under the shared ๐Ÿ”‘ reference.read permission:

Endpoint Returns
๐ŸŸข GET /task-statuses The board columns with is_closed, colour, display_order (seeded To Do โ†’ In Progress โ†’ Blocked โ†’ Done โ†’ Cancelled)
๐ŸŸข GET /task-priorities Low / Medium / High / Urgent (plus custom)
๐ŸŸข GET /task-tags The curated tag list with colours and usage counts

๐Ÿงช Worked example: sprint-bot standup report

A read-only key (tasks: read) posts a morning summary to your chat tool:

B="https://your-server/api/v1"; K="Authorization: Bearer fitsm_โ€ฆ"

# Overdue tasks by assignee
curl -s -H "$K" "$B/tasks?overdue=true&sort=due_date&per_page=100" \
  | jq -r '.data[] | "\(.due_date)  \(.assigned_analyst.name // "unassigned")  \(.title)"'

# Board totals per column
for s in "To Do" "In Progress" "Blocked"; do
  n=$(curl -s -H "$K" "$B/tasks?status=$(echo $s | sed 's/ /+/g')" | jq '.meta.total')
  echo "$s: $n"
done

And the automation direction โ€” a deployment pipeline creating a follow-up task with a subtask checklist:

P=$(curl -s -X POST "$B/tasks" -H "$K" -H "Content-Type: application/json" \
  -d '{"title":"Post-deploy checks for release 2.4","priority":"High","due_date":"2026-07-05"}' | jq -r '.data.id')
for step in "Smoke-test portal login" "Check error rates" "Confirm backups ran"; do
  curl -s -o /dev/null -X POST "$B/tasks" -H "$K" -H "Content-Type: application/json" \
    -d "{\"title\": \"$step\", \"parent_task_id\": $P}"
done

Under the hood: REST API โ€” How It Works ยท Other modules: Tickets ยท Assets ยท Problems ยท Changes ยท Knowledge ยท Keys & permissions: System โ†’ API (System module) ยท Module docs: Tasks.