REST API Changes - edmozley/freeitsm GitHub Wiki
π REST API: Changes
The complete usage guide for the Change Management module of the REST API β every URL, method, parameter and response shape, including the full CAB approval workflow. 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 Changes:
| π Install-wide | Changes have no company β a key's company scope does not restrict them (matches the UI). |
| π² Risk is computed | Send risk_likelihood and risk_impact_score (1β5 each); the API computes score = likelihood Γ impact and bands the level exactly like the UI: β€4 Low Β· β€9 Medium Β· β€15 High Β· β€20 Very High Β· else Critical. |
| π³οΈ CAB voting works over the API | The key's acts-as analyst can vote, and the auto-transition mechanics are the UI's own: any required Reject sends the change back to Draft; the all/majority threshold of required members flips it to Approved. |
| π§Ύ Strict where it matters | Unknown status/type/priority/impact names are a 422 (the UI silently falls back to defaults β machines deserve an error). |
π Quick start
Raise a change, staff its CAB, submit it, and approve it β entirely over the API:
B="https://your-server/api/v1"; K="Authorization: Bearer fitsm_β¦"
# 1. Create (Draft by default; risk computed server-side)
CHG=$(curl -s -X POST "$B/changes" -H "$K" -H "Content-Type: application/json" -d '{
"title": "Replace SFP on core switch uplink",
"change_type": "Normal", "priority": "High",
"reason_for_change": "Fixes PRB-00005 (recurring VPN drops).",
"risk_likelihood": 2, "risk_impact_score": 4,
"cab_required": true, "cab_approval_type": "all",
"work_start_at": "2026-07-05T06:00:00Z", "work_end_at": "2026-07-05T08:00:00Z"
}' | jq -r '.data.id')
# 2. Staff the CAB
curl -s -X POST "$B/changes/$CHG/cab" -H "$K" -H "Content-Type: application/json" \
-d '{"members": [{"analyst_id": 1, "is_required": true}, {"analyst_id": 2, "is_required": false}]}'
# 3. Submit for approval
curl -s -X PATCH "$B/changes/$CHG" -H "$K" -H "Content-Type: application/json" -d '{"status": "Pending Approval"}'
# 4. A CAB member's key casts the deciding vote β auto-Approved, workflow fires
curl -s -X POST "$B/changes/$CHG/cab/vote" -H "$K" -H "Content-Type: application/json" \
-d '{"vote": "Approve", "comment": "Rollback plan looks solid."}'
Every step lands in the change's audit trail exactly as if it happened in the UI. π§Ύ
π Changes
π’ GET /changes β list / search Β π changes.read
| Parameter | Description |
|---|---|
state |
open | closed | all (default all) β closed = Rejected/Completed/Failed/Cancelled |
status / status_id |
e.g. Pending Approval, by name or id |
change_type / change_type_id |
Standard / Normal / Emergency |
priority / priority_id Β· impact / impact_id |
By name or id |
category_id Β· requester_id Β· assigned_to_id Β· approver_id |
More id filters |
cab_required |
true / false |
risk_level |
Low, Medium, High, Very High, Critical |
q |
Search title β CHG-0042 (or chg42) finds a change by its reference |
work_start_from / work_start_to |
π Scheduled-work window (ISO 8601) β the calendar shape |
created_since / modified_since |
ISO 8601 date-time bounds |
sort |
created_at (default, desc), modified_at, id, title, work_start_at, risk_score, priority, status β prefix - for descending |
page / per_page |
Pagination β default 1 / 25, max 100 |
The change shape (lists; detail adds bodies, PIR, attachments, linked problems):
{ "id": 11, "change_number": "CHG-0011",
"title": "Replace SFP on core switch uplink",
"change_type": { "id": 2, "name": "Normal" },
"status": { "id": 4, "name": "Approved", "is_closed": false },
"priority": { "id": 3, "name": "High" },
"impact": { "id": 2, "name": "Medium" },
"category": null,
"requester": null, "assigned_to": { "id": 1, "name": "Administrator" }, "approver": null,
"approval_at": "2026-07-02T23:30:32Z",
"cab": { "required": true, "approval_type": "all" },
"risk": { "likelihood": 2, "impact": 4, "score": 8, "level": "Medium" },
"schedule": { "work_start_at": "2026-07-05T06:00:00Z", "work_end_at": "2026-07-05T08:00:00Z",
"outage_start_at": null, "outage_end_at": null },
"created_by": { "id": 1, "name": "Administrator" },
"created_at": "2026-07-02T23:30:12Z", "modified_at": "2026-07-02T23:30:32Z" }
π΅ POST /changes β create Β π changes.create
Only title is required. Omitted lookups get the module defaults (Normal / Draft / Medium / Medium); a creation entry is written to the audit trail. Returns 201.
| Field group | Fields |
|---|---|
| Lookups (name or id) | change_type Β· status Β· priority Β· impact Β· category_id |
| People (analyst ids) | requester_id Β· assigned_to_id Β· approver_id |
| Schedule (ISO 8601) | work_start_at Β· work_end_at Β· outage_start_at Β· outage_end_at |
| Plans (long text) | description Β· reason_for_change Β· risk_evaluation Β· test_plan Β· rollback_plan |
| Risk (1β5 each) | risk_likelihood Β· risk_impact_score β score + level computed |
| CAB | cab_required (bool) Β· cab_approval_type (all | majority) |
π’ GET /changes/{id} β get one Β π changes.read
Everything above plus description, reason_for_change, risk.evaluation, test_plan, rollback_plan, the PIR block (review, was_successful, actual_start_at/end_at, lessons_learned, follow_up), the attachments list (name/size/type β files are managed in the UI), and linked_problems (problems this change fixes).
π PATCH /changes/{id} β update Β π changes.update
Everything creatable is patchable, plus the PIR fields (pir_was_successful, pir_actual_start_at, pir_actual_end_at, pir_lessons_learned, pir_follow_up). Send only what changes; null clears; identical PATCHes are idempotent.
Audit parity with the UI: human field labels ("Title", "Status", "Work Start", β¦), display names for lookups, (empty) placeholders, status changes marked status_change β and the long plan bodies update silently (the UI doesn't audit them either). Changing risk inputs recomputes score + level. A genuine transition into Approved fires the change.approved workflow event.
[!TIP] The lifecycle is: Draft β Pending Approval β Approved β Scheduled β In Progress β Completed / Failed (Rejected and Cancelled are terminal). Only the Pending Approval β Approved/Draft leg is automated (by CAB votes); every other transition is an explicit status PATCH, exactly like the UI.
π΄ DELETE /changes/{id} β delete Β π changes.delete
[!WARNING] Permanent. Deletes the change, removes its attachment files from disk, and cascades comments, CAB entries and the audit trail β same as the UI. No restore.
π³οΈ CAB (Change Advisory Board)
π’ GET /changes/{id}/cab Β π change_cab.read
The roster with votes, plus live approval progress:
{ "data": { "cab_required": true, "approval_type": "all",
"members": [ { "analyst_id": 1, "name": "Administrator", "is_required": true,
"vote": "Approve", "vote_comment": "Rollback plan looks solid.",
"voted_at": "2026-07-02T23:30:32Z" } ],
"progress": { "required_total": 1, "required_approved": 1, "required_rejected": 0 } } }
π΅ POST /changes/{id}/cab β set the roster Β π change_cab.manage
Body: {"members": [{"analyst_id": 1, "is_required": true}, β¦]} β replaces the roster. Additions, removals and requiredβoptional switches are diffed and each writes its own audit entry (like the UI). Returns the updated roster.
π΅ POST /changes/{id}/cab/vote β cast a vote Β π change_cab.vote
Body: {"vote": "Approve" | "Reject" | "Abstain", "comment": "β¦"}. The vote is cast as the analyst the key acts as β they must be a CAB member who hasn't voted (403 if not a member, 409 if already voted).
The auto-transition is the UI's exact mechanics, and only runs while the change is in Pending Approval:
- π΄ Any required member votes Reject β the change goes back to Draft.
- π’ The threshold of required members approve (
all= everyone;majority= more than half) β the change becomes Approved,approval_atis stamped, and thechange.approvedworkflow event fires. - βͺ Optional members' votes are recorded but don't count toward the tally; Abstain never transitions anything.
The response tells you what happened: {"vote": "Approve", "status_changed": true, "new_status": "Approved"}.
π¬ Comments & history
π’ GET /changes/{id}/comments Β π change_comments.read
Comments newest first, with the author.
π΅ POST /changes/{id}/comments Β π change_comments.create
Body: {"text": "β¦"}. Always internal, attributed to the acts-as analyst, with a preview written to the audit trail. Comments can't be edited (parity with the UI) β only added and deleted.
π΄ DELETE /changes/{id}/comments/{comment_id} Β π change_comments.delete
Hard-deletes the comment.
π’ GET /changes/{id}/audit Β π change_audit.read
Every audited event β field changes, status changes, CAB roster edits, votes, comment previews β with action (field_change / status_change / cab_vote / comment), old/new values and the analyst, newest first.
π Reference data
Under the shared π reference.read permission:
| Endpoint | Returns |
|---|---|
π’ GET /change-statuses |
Draft, Submitted, Pending Approval, Approved, Rejected, Scheduled, In Progress, Completed, Failed, Cancelled β with is_closed, is_default, colour |
π’ GET /change-types |
Standard, Normal, Emergency (plus custom) |
π’ GET /change-priorities |
Low, Medium, High, Critical |
π’ GET /change-impacts |
Low, Medium, High |
π’ GET /change-categories |
Your configured categories (empty until set up) |
π§ͺ Worked example: change calendar feed
A read-only key (changes: read) feeds an external calendar or maintenance-window banner:
B="https://your-server/api/v1"; K="Authorization: Bearer fitsm_β¦"
# Everything scheduled in the next 14 days, soonest first
curl -s -H "$K" "$B/changes?work_start_from=$(date -u +%Y-%m-%dT%H:%M:%SZ)&work_start_to=$(date -u -d '+14 days' +%Y-%m-%dT%H:%M:%SZ)&sort=work_start_at&per_page=100" \
| jq -r '.data[] | "\(.change_number) \(.schedule.work_start_at) [\(.risk.level // "-")] \(.title)"'
And a pipeline check before deploys β is anything risky approved for tonight?
curl -s -H "$K" "$B/changes?status=Approved&risk_level=High" | jq '.meta.total'
Under the hood: REST API β How It Works Β· Other modules: Tickets Β· Assets Β· Problems Β· Keys & permissions: System β API (System module) Β· Module docs: Change Management.