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 thetask.completedworkflow event; moving back to an open status clears it. - Tags:
tagsreplaces 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
/movechanges the status it firestask.status_changed, andtask.completedwhen it lands in a closed status - so the people on the task are notified and workflows run, exactly as forPATCH. A move within the same column changes no status and fires nothing. (Before #266 neither/movenor 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 answers201.revision(optional) - the revision you read fromGET. 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:
422for a missing required answer or a bad value (the Forms validation),404for a requirement that is not on this task,403without 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.