Eviworx
Docs

Changes API

The Changes API manages IT changes following ITIL: a state machine driven by dedicated transition endpoints, approval via the unified approval framework, structured change tasks (implementation/test/rollback) with individual or group assignment, plus calendar invites (.ics) for scheduled work.

🔄
Features
✓ Status changes via 10 transition endpoints
✓ Central approval (/api/approvals/:id/decide)
✓ Four-eyes principle enforced
✓ Change tasks (IMPLEMENTATION/TEST/ROLLBACK)
✓ Task assignment to user or group (XOR)
✓ 4 change types (STANDARD, NORMAL …)
✓ Rollback flow (ROLLING_BACK → BACKED_OUT)
✓ Versioned templates (ITIL standard changes)
✓ Calendar invites for scheduled tasks (.ics)
✓ Field permissions per status (field-permissions)

Endpoints Overview

Method Endpoint Description
GET/api/changesList with filters ({data, pagination}; RBAC-filtered)
GET/api/changes/statsTab counters (all / my / assigned) — accepts the same filters as the list
GET/api/changes/:idGet single change (by ID or number)
POST/api/changesCreate new change (status always DRAFT) → 201
PATCH/api/changes/:idUpdate change fields (NO status, version mandatory)
DELETE/api/changes/:idDelete change (soft-delete, critical action)
POST/api/changes/:id/restoreRestore deleted change (trash listing: ?deleted=1)
PATCH/api/changes/:id/assignSet/clear change manager → {change, assignmentChanged}
GET/api/changes/:id/field-permissionsEditable/locked/required fields + allowed transitions for current status
POST/api/changes/:id/activityAdd comment (the activity log itself comes via GET /:id)

Enum values in upper case: Status, type, priority, risk, impact and urgency are sent and returned in upper case (NORMAL, VERY_HIGH, PENDING_APPROVAL …), including filters and sorting. Lower-case values return 400.

Who sees the change: Besides the requestor (viewOwn) and global visibility (viewAll), involvement also opens access: the assigned change manager, an open approval and whoever holds a change task (directly, via the assigned group or as a substitute) and has changes.viewOwn resp. viewPendingApprovals. This applies to the list, detail, search, reports, attachments and the link cards. Edit and task-management rights are unaffected. Without viewAll, viewOwn or viewPendingApprovals the list and the counters answer 403.

Transition Endpoints (Status Changes)

Important: Statuses are set exclusively through these dedicated endpoints. PATCH /api/changes/:id does NOT accept a status field. Each endpoint has its own permission and required body fields.

Endpoint Transition Permission Body
POST /:id/submitDRAFT → SUBMITTEDchanges.submitchangeManagerId, notes?
POST /:id/route-to-approvalSUBMITTED → PENDING_APPROVAL (or APPROVED if skipApproval)changes.manageWorkflow + assigned change managerskipApproval?, notes?
POST /:id/scheduleAPPROVED → SCHEDULEDchanges.schedulescheduledStartTime, scheduledEndTime
POST /:id/start-implementationSCHEDULED → IN_PROGRESSchanges.startImplementationnotes?
POST /:id/completeIN_PROGRESS → COMPLETEDchanges.markCompletedresolution (≥20), closerId, actualEndTime?
POST /:id/failIN_PROGRESS → FAILEDchanges.markFailedresolution (≥20), closerId, backoutPerformed?
POST /:id/initiate-rollbackIN_PROGRESS → ROLLING_BACKchanges.backoutreason (≥20)
POST /:id/backoutROLLING_BACK → BACKED_OUTchanges.backoutresolution (≥20), closerId
POST /:id/closeCOMPLETED/FAILED/BACKED_OUT → CLOSEDchanges.closereviewNotes (≥20), successCriteriaMet?
POST /:id/return-to-draft* → DRAFTchanges.returnToDraftreason (≥10)

Errors when routing to approval: A STANDARD change needs no approval round; the attempt returns 400 CHANGE_APPROVAL_NOT_REQUIRED. Use skipApproval: true instead. If no eligible approver is found, the route answers 400 CHANGE_NO_APPROVERS_AVAILABLE.

A complete task set as a precondition: The lifecycle transitions require the complete set of change tasks until closure (one task each of kinds IMPLEMENTATION, TEST and ROLLBACK). A change without tasks can neither be started nor closed — the way back is return-to-draft.

Approval Endpoints

Method Endpoint Description
POST/api/changes/:id/approversAssign approver (creates an approval entry)
POST/api/approvals/:id/decideApprover decision (unified approval framework)

Approvers make their decision (approve/reject) via the Approvals API. See Approvals API.

Change Tasks

All routes live under /api/changes/:changeId/tasks:

Method Endpoint Description
GET/List the change's tasks
POST/Create task
POST/reorderReorder (per kind, with version)
POST/bulk-assignBulk-assign tasks
POST/bulk-skipBulk-skip tasks
POST/bulk-deleteBulk-delete tasks
PATCH/:taskIdUpdate task (version required)
DELETE/:taskId?version=NDelete task (version as query param)
POST/:taskId/assignAssign user OR group
POST/:taskId/startStart task (→ IN_PROGRESS)
POST/:taskId/completeComplete task (completionNote required)
POST/:taskId/skipSkip task (skipReason required)
POST/:taskId/failMark task failed (failureReason required, ≥10 characters)
POST/:taskId/retryRetry a failed task (from FAILED only, reason ≥10 characters)
POST/:taskId/commentsComment on task (≥10 characters) → 201

Change Templates /api/changes/templates

Templates model recurring standard changes (ITIL): predefined default values, locked or user-required fields and their own task set. Templates are versioned and run through their own approval workflow. Changes are created from an approved template — the task set is cloned from the approved version snapshot.

Template Status

DRAFT → SUBMITTED(submit) → PENDING_APPROVAL → APPROVED → RETIRED(retire)

Side paths:PENDING_APPROVAL → DRAFT     (withdraw)
APPROVED         → DRAFT      (new-version — new draft version)
DRAFT(discarded) → APPROVED  (discard-draft — revert to last approved version)

Editing (PATCH, tasks) is only possible in status DRAFT.Approval and rejection run through the unified approval framework.

Endpoints

Method Endpoint Description Permission
GET/templatesList (filters + offset/limit pagination)changes.viewTemplates
GET/templates/approvedApproved templates only (for selector)changes.viewTemplates
GET/templates/statisticsTemplate statisticschanges.viewTemplates
GET/templates/:idSingle templatechanges.viewTemplates
GET/templates/:id/versionsVersion historychanges.viewTemplates
GET/templates/:id/versions/:versionSpecific versionchanges.viewTemplates
GET/templates/:id/changesChanges using this template (limit/offset)changes.viewTemplates
GET/templates/:id/activityActivity history (limit)changes.viewTemplates
GET/templates/:id/tasksList template taskschanges.viewTemplates
POST/templatesCreate template (201, status DRAFT)changes.createTemplates
PATCH/templates/:idUpdate template (DRAFT only)changes.editOwnTemplates / editAllTemplates
DELETE/templates/:idDelete template (DRAFT only, 204)changes.deleteTemplates
POST/templates/:id/submitDRAFT → PENDING_APPROVALchanges.submitTemplates
POST/templates/:id/withdrawPENDING_APPROVAL → DRAFT withdrawchanges.submitTemplates
POST/templates/:id/discard-draftDiscard DRAFT → last approved versionchanges.editOwnTemplates / editAllTemplates
POST/templates/:id/retire→ RETIRED (body reason?)changes.retireTemplates
POST/templates/:id/new-versionAPPROVED → new DRAFT version (body changeReason + fields)changes.editOwnTemplates / editAllTemplates
POST/templates/:id/create-changeCreate change from template (201)changes.create
POST/templates/:id/tasksAdd template task (DRAFT only, 201)changes.editOwnTemplates / editAllTemplates
PATCH/templates/:id/tasks/:taskIdUpdate template task (DRAFT only)changes.editOwnTemplates / editAllTemplates
DELETE/templates/:id/tasks/:taskIdDelete template task (DRAFT only, 204)changes.editOwnTemplates / editAllTemplates
POST/templates/:id/tasks/reorderReorder tasks (DRAFT only, 204)changes.editOwnTemplates / editAllTemplates

Write/action routes additionally check ownership (creator OR editAllTemplates). A template in status DRAFT is visible only to its creator — unless they hold changes.viewDraftTemplates OR an approved version already exists: then the template shows its approved revision to every viewTemplates holder. The same rule governs list, detail and the sub-routes (/versions, /changes, /activity, /tasks); a template outside your visibility returns 404 TEMPLATE_NOT_FOUND, just like a non-existent one. Approval/rejection runs exclusively via Approvals API (POST /api/approvals/:id/decide).

Template Fields

Field Type Description
namestringSlug, required (only a–z, 0–9, hyphen)
displayNamestringDisplay name, required
descriptionstringDescription, required
typeenumSTANDARD, NORMAL, EMERGENCY, MAJOR (default STANDARD)
categoryIdstring?Change category
defaultTitle / defaultDescription / defaultJustificationstring?Pre-filled content of the created change
defaultRiskLevel / defaultImpactenumLOW, MEDIUM, HIGH, VERY_HIGH (default LOW)
defaultUrgency / defaultPriorityenumLOW, MEDIUM, HIGH, CRITICAL (default LOW)
defaultRiskAssessmentany?Pre-filled risk assessment
defaultAffectedServices / defaultAffectedAssetsstring[]Pre-filled affected items
defaultPlannedDurationnumber?Pre-filled planned duration (minutes)
lockedFieldsstring[]Fields locked when creating from the template
requiredUserFieldsstring[]Fields the user must fill in when creating

Pagination of the template endpoints: The template list and the list of a template's changes use offset/limit pagination (default 20, max 100), the activity history default 50 / max 100 — unlike the change list (page/per). Enum values are upper case here as everywhere.

Template Tasks

Template tasks define the task set cloned when a change is created. Manageable only while the template is in DRAFT.

Field Values Description
kindIMPLEMENTATION, TEST, ROLLBACKKind (not editable after creation — delete + recreate)
phasePREP, EXECUTE, VALIDATE, POSTCHECKPhase (default EXECUTE)
title / descriptionstringTitle (required) / description
sortOrdernumberOrder
estimatedMinutesnumber?Estimated duration
requiredPermissionstring?Permission required to execute
roleHintstring?Hint for the responsible role
suggestedGroupIdstring?Suggested agent group
dependsOnTemplateTaskIdscuid[]Predecessor template tasks

Create Change from Template

POST /api/changes/templates/:id/create-change
{
  "title": "Upgrade PostgreSQL on cluster-prod-2",
  "scheduledStartTime": "2026-02-01T02:00:00Z",
  "scheduledEndTime": "2026-02-01T04:00:00Z",
  "assignedGroupId": "clx-dba-group-id"
}

All body fields are optional and override the template defaults: title, description, justification, affectedServices, affectedAssets, scheduledStartTime, scheduledEndTime, plannedDuration, categoryId, assignedToId, assignedGroupId (plus requestorId for API-key auth). Locked fields (lockedFields) stay unchanged; the task set is cloned from the approved version snapshot.

Change Categories

Method Endpoint Description
GET/api/changes/categoriesAll categories
POST/api/changes/categoriesCreate category
PUT/api/changes/categories/:idUpdate category
DELETE/api/changes/categories/:idDelete category

State Machine

Changes go through 12 statuses. Each transition happens via a dedicated transition endpoint:

DRAFT → SUBMITTED → PENDING_APPROVAL → APPROVED → SCHEDULED → IN_PROGRESS → COMPLETED → CLOSED

Alternative paths:
PENDING_APPROVAL → REJECTED            (approver rejects)
*                → DRAFT               (via return-to-draft)
IN_PROGRESS      → FAILED              (via fail)
IN_PROGRESS      → ROLLING_BACK → BACKED_OUT  (via initiate-rollback + backout)
COMPLETED/FAILED/BACKED_OUT → CLOSED   (via close)

Status descriptions:
• DRAFT            = Draft, not yet submitted
• SUBMITTED        = Submitted, change manager assigned
• PENDING_APPROVAL = Waiting for approvals (unified approval framework)
• APPROVED         = Approved
• REJECTED         = Rejected
• SCHEDULED        = Scheduled for maintenance window
• IN_PROGRESS      = Implementation in progress (change tasks being executed)
• ROLLING_BACK     = Rollback in progress
• COMPLETED        = Successfully completed
• FAILED           = Failed
• BACKED_OUT       = Rolled back
• CLOSED           = Closed & archived

Change Types

Type Description Approval Requirement
STANDARD Routine changes, low risk (often template-based) Often pre-approved
NORMAL Standard changes, medium risk Regular approval process
EMERGENCY Emergency changes (e.g., urgent security updates) Fast-track, post-implementation review
MAJOR Major changes, high risk Extended approvals

Create Change

Request

POST /api/changes

Requestor (requestorId): For a signed-in user the server sets the requestor to the caller; a value sent in the body is ignored. Only API-key calls must supply requestorId. The status on creation is always DRAFT; everything beyond that runs through the transition endpoints.

{
  "title": "Upgrade PostgreSQL to version 17",
  "description": "Upgrade production database from PostgreSQL 16 to 17 for performance and security.",
  "justification": "Security patches for CVE-2024-xxx. Performance improvements.",
  "type": "NORMAL",
  "categoryId": "clx-category-id",
  "priority": "HIGH",
  "riskLevel": "MEDIUM",
  "impact": "HIGH",
  "urgency": "MEDIUM",
  "scheduledStartTime": "2026-02-01T02:00:00Z",
  "scheduledEndTime": "2026-02-01T04:00:00Z",
  "plannedDuration": 120,
  "affectedServices": ["Database", "API"],
  "affectedAssets": ["clx-asset-id"],
  "riskAssessment": "Standard maintenance-window upgrade, rollback tested.",
  "assignedToId": "clx-change-manager-id",
  "tasks": [
    { "clientKey": "impl-1", "kind": "IMPLEMENTATION", "phase": "EXECUTE", "title": "Run pg_upgrade" },
    { "kind": "TEST", "phase": "VALIDATE", "title": "Run integration tests", "dependsOnTaskKeys": ["impl-1"] },
    { "kind": "ROLLBACK", "phase": "POSTCHECK", "title": "Restore from backup if needed" }
  ]
}

Response (201 Created)

{
  "id": "clx...",
  "number": "CHG-2026-000042",
  "title": "Upgrade PostgreSQL to version 17",
  "status": "DRAFT",
  "type": "NORMAL",
  "priority": "HIGH",
  "riskLevel": "MEDIUM",
  "impact": "HIGH",
  "urgency": "MEDIUM",
  "requestor": { "id": "clx...", "name": "John Doe", "email": "john@example.com" },
  "assignedTo": { "id": "clx...", "name": "Change Manager" },
  "createdAt": "2026-01-27T15:00:00.000Z"
}

Fields Overview

Field Type Required? Description
titlestring✓Short title (5–200 characters)
descriptionstring✓Detailed description (20–5000 characters)
justificationstring✓Justification for the change (20–2000 characters)
typeenum✓STANDARD, NORMAL, EMERGENCY, MAJOR
categoryIdstring✓Change category (ID, required)
priorityenum✓LOW, MEDIUM, HIGH, URGENT, CRITICAL
riskLevelenum✓LOW, MEDIUM, HIGH, VERY_HIGH
impactenum✓LOW, MEDIUM, HIGH, VERY_HIGH
urgencyenum✓LOW, MEDIUM, HIGH, CRITICAL
requestorIdstring(API key)With user auth = logged-in user; required for API-key auth
tasksarrayInline change tasks (see Change Tasks); not combined with templateId
scheduledStartTimeDateTimeScheduled start
scheduledEndTimeDateTimeScheduled end
plannedDurationnumberPlanned duration/downtime (minutes)
affectedServicesstring[]Affected services
affectedAssetsstring[]Affected assets
riskAssessmentstringRisk assessment (free text)
assignedToId / assignedGroupIdstringChange manager (user or group)
relatedTickets / relatedProblemsstring[]Linked tickets/problems — require the linking permission and visibility of the other side, signed-in users only
templateIdstringTemplate reference (tasks cloned from snapshot)
fromProblemIdstringCross-entity creation from a problem

Links to tickets and problems: relatedTickets and relatedProblems require the same permission as the linking endpoint (changes.linkToTickets resp. changes.linkToProblems, otherwise 403), plus visibility of the respective other side. A save that leaves the links unchanged does not require the permission. They are bound to a signed-in user (API key: 400 CHANGE_LINKS_REQUIRE_USER), and no new links can be added to a closed change. Every link writes a timeline entry on BOTH sides.

Approval Workflow

Approvals run through the central unified approval framework. On the change you only assign approvers; the actual decision is made via the Approvals API.

Step 1: Submit (DRAFT → SUBMITTED)

POST /api/changes/:id/submit
{
  "changeManagerId": "clx-change-manager-id",
  "notes": "Ready for review"
}

Step 2: Assign Approvers

POST /api/changes/:id/approvers
{
  "userId": "clx-manager-id"
}

Response (201 Created)

{
  "id": "clx-approval-id",
  "userId": "clx-manager-id",
  "required": true,
  "decision": null,
  "decisionAt": null,
  "comment": null,
  "user": { "id": "clx-manager-id", "name": "Jane Manager", "email": "jane@example.com" },
  "sourceGroup": { "id": "clx-cab-group", "name": "cab", "displayName": "Change Advisory Board" },
  "isManual": true,
  "sequence": 0
}
  • Permission changes.addApprover plus visibility of this change (the same check as for detail and comment).
  • Only possible while the change is in SUBMITTED or PENDING_APPROVAL (otherwise APPROVER_INVALID_STATUS).
  • Four-eyes principle: requestor and assigned change manager cannot be approvers (403 CONFLICT_OF_INTEREST).
  • A manually added approver is attached to the running approval round — sourceGroup is filled accordingly and their decision counts towards its evaluation.

Step 3: Route to approval (SUBMITTED → PENDING_APPROVAL)

POST /api/changes/:id/route-to-approval
{
  "skipApproval": false,
  "notes": "Routing to CAB"
}

With skipApproval: true the change jumps straight to APPROVED (requires changes.manageWorkflow).

Step 4: Approvers Decide

POST /api/approvals/:id/decide

The decision is made via the central approval framework. Once all required approvers have approved, the change automatically moves to APPROVED; on rejection to REJECTED. For details, request/response format and the changes.approve permission see Approvals API.

Change Tasks in Detail

Change tasks model the concrete implementation. Each task has a kind, a phase, a status, and is assigned either to a single user or an agent group.

Field Values Description
kindIMPLEMENTATION, TEST, ROLLBACKType of task
phasePREP, EXECUTE, VALIDATE, POSTCHECKPhase (default: EXECUTE)
statusPENDING, BLOCKED, IN_PROGRESS, DONE, SKIPPED, FAILEDBLOCKED while dependencies are open
assignedToId XOR assignedGroupIdcuidUser OR group – never both
dependsOnTaskIdscuid[]Predecessor tasks (cycle check server-side)
estimatedMinutes / actualMinutesnumberTime tracking
completionNote / skipReason / failureReasonstringRequired on complete / skip / fail
handoverNotesstringHandover to dependent tasks
versionnumberOptimistic lock – required on every mutation

Create Task

POST /api/changes/:changeId/tasks
{
  "kind": "IMPLEMENTATION",
  "phase": "EXECUTE",
  "title": "Run pg_upgrade on primary",
  "description": "Execute pg_upgrade and verify cluster starts",
  "assignedGroupId": "clx-dba-group-id",
  "estimatedMinutes": 45,
  "dependsOnTaskIds": ["clx-prep-task-id"]
}

Task Lifecycle

# Assign user OR group (version required)
POST /api/changes/:changeId/tasks/:taskId/assign
{ "assignedToId": "clx-user-id", "version": 1 }

# Start – on group assignment the starting agent is atomically set as assignedToId
POST /api/changes/:changeId/tasks/:taskId/start
{ "version": 2 }

# Complete (completionNote required)
POST /api/changes/:changeId/tasks/:taskId/complete
{ "completionNote": "pg_upgrade completed, cluster healthy", "actualMinutes": 38, "version": 3 }

# Skip / fail
POST /api/changes/:changeId/tasks/:taskId/skip   { "skipReason": "Not needed, already on v16", "version": 2 }
POST /api/changes/:changeId/tasks/:taskId/fail   { "failureReason": "pg_upgrade aborted: incompatible cluster", "version": 2 }

Permissions: Managing (create/update/delete/reorder/assign/skip/bulk/retry) requires changes.manageTasks OR (changes.editOwn as requestor) OR change-assignee/active group member. Executing (start/complete/fail) requires changes.manageTasks OR (changes.executeTask with involvement). Commenting needs either of the two levels. Note: changes.editAll alone grants NO task rights.

The task structure is frozen during approval: While a change sits in PENDING_APPROVAL, tasks can neither be created, changed, deleted nor reordered — the board assesses an unchangeable proposal. Skipping is only possible in the currently active phase. A failed task can be resumed via retry.

Calendar Invites (.ics)

When a change task is assigned to a person and the change has scheduledStartTime and scheduledEndTime, Eviworx sends a calendar invite (.ics, METHOD:REQUEST) to the assignee. On reassignment/cancellation a CANCEL invite goes to the previous assignee.

  • .ics REQUEST only when the change is in one of the statuses SCHEDULED, APPROVED, IN_PROGRESS and start/end are set.
  • CANCEL is sent regardless of status as soon as schedule data exists.

Update Change

PATCH /api/changes/:id

PATCH updates data fields only – NOT status (transition endpoints handle that). Requires changes.editAll OR changes.editOwn (as requestor). Changing assignedToId/assignedGroupId additionally requires changes.assign. The version field is MANDATORY (optimistic locking, 409 CHANGE_VERSION_CONFLICT on a stale state); null clears a field, an omitted key leaves it unchanged. Which fields are editable/locked/required in the current status is returned by GET /:id/field-permissions — sending a locked field ends with 400 LOCKED_FIELD_MODIFICATION.

List Changes

GET /api/changes?f.status=in:PENDING_APPROVAL,APPROVED&f.type=NORMAL&sort=scheduledStartTime:asc&page=1&per=20

The list is RBAC-filtered (viewAll / viewOwn / viewPendingApprovals) and supports filters and sorting. A filter has the form f.<field>=<operator>:<value>; without an operator prefix equality applies. Response:

{
  "data": [ /* changes */ ],
  "pagination": { "page": 1, "limit": 20, "total": 137, "totalPages": 7, "hasMore": true }
}

Query Parameters

Parameter Description
f.status, f.type, f.priority, f.riskLevel, f.impactEnum filters (eq/neq/in/notIn, values in upper case)
f.number, f.titleText filters (eq/contains/startsWith)
f.requestorId, f.assignedToId, f.categoryIdAssignment filters (eq/in, partly isNull/isNotNull)
f.scheduledStartTime, f.scheduledEndTime, f.createdAt, f.updatedAtTime filters (gt/gte/lt/lte/between/relative)
qSearch across number, title, description
page / per / sortPaging (per defaults to 25, max 200)
deleted=1Trash: ONLY deleted changes (requires changes.viewDeleted; accepts the value 1 only)
includeDeleted=trueMixed list incl. deleted ones (requires changes.viewDeleted)
relatedTicketId, relatedProblemIdFilter by linkage

Error Handling

Self-Approval Blocked (Four-Eyes)

{
  "errorCode": "CONFLICT_OF_INTEREST",
  "message": "Requestor and assigned change manager cannot be an approver of this change."
}

Approver in Invalid Status (400)

{
  "error": "Cannot add an approver while the change is in status SCHEDULED",
  "errorCode": "APPROVER_INVALID_STATUS",
  "details": { "status": "SCHEDULED" }
}

Error Codes of Changes and Change Templates

The change and template routes answer with a dedicated code per case; the parenthesis names what details carries for that code.

errorCodeHTTPMeaning
TEMPLATE_NOT_FOUND404Template unknown — or outside your visibility (details.templateId)
TEMPLATE_VERSION_NOT_FOUND404The template has no such version number (details.templateId, details.version)
TEMPLATE_TASK_NOT_FOUND404Template task unknown — also when it belongs to a different template (details.taskId)
AGENT_GROUP_NOT_FOUND404The agent group named as assignedGroupId does not exist
TEMPLATE_EDIT_DENIED403No permission to edit templates, or not this one (creator or editAllTemplates)
TEMPLATE_MANAGE_DENIED403Submit, withdraw, retire or delete without responsibility for this template
TEMPLATE_NOT_DRAFT_FOR_EDIT · TEMPLATE_NOT_DRAFT_FOR_DELETE · TEMPLATE_NOT_DRAFT_FOR_TASKS403The template is not (or no longer) DRAFT — editing, deleting and task maintenance need a new version (details.status)
TEMPLATE_HAS_CHANGES403The template is used by changes and therefore cannot be deleted (details.count)
TEMPLATE_NOT_DRAFT_FOR_SUBMIT400Submitting works only from DRAFT (details.status)
TEMPLATE_NOT_PENDING_FOR_WITHDRAW400Withdrawing works only from PENDING_APPROVAL (details.status)
TEMPLATE_NOT_DRAFT_FOR_DISCARD400Discarding works only from DRAFT (details.status)
TEMPLATE_NOT_APPROVED_FOR_RETIRE400Retiring works only from APPROVED (details.status)
TEMPLATE_NOT_APPROVED_FOR_VERSION400A new version is created only from APPROVED (details.status)
TEMPLATE_NO_VERSION_FOR_DISCARD400There is no approved version to fall back to — deleting is the way forward here
TEMPLATE_SUBMIT_GATE_FAILED400The template does not meet the submission conditions (mandatory data, missing task kinds); the message names the individual reasons
TEMPLATE_NAME_CONFLICT400The technical name is already taken (details.templateName)
TEMPLATE_CATEGORY_INVALID400The category named in the body does not exist
TEMPLATE_TASK_SELF_REF · TEMPLATE_TASK_CYCLE · TEMPLATE_TASK_DEP_NOT_FOUND · TEMPLATE_TASK_DEP_CROSS_TEMPLATE400The dependency points to itself, forms a cycle, does not exist or belongs to a different template
TEMPLATE_TASK_HAS_DEPENDENTS400Other tasks of the template depend on this one (details.taskTitle, details.count)
TEMPLATE_TASK_REORDER_INVALID400The reorder list names tasks that do not belong to this template
TEMPLATE_UNAVAILABLE400A retired template no longer produces a change; an unknown template ends with 404 TEMPLATE_NOT_FOUND instead
TEMPLATE_NO_VERSION_SNAPSHOT400The template was never approved — submit it and have it approved first
TEMPLATE_SNAPSHOT_EMPTY400The approved version of the template contains no tasks
TEMPLATE_CATEGORY_MISSING400A category is missing — either in the template or as an input when creating
TEMPLATE_REQUIRED_USER_FIELDS_MISSING400The template requires fields the call does not supply (details.fields as a list)
APPROVER_INVALID_STATUS400Approvers can only be added while the change is awaiting sign-off (details.status)
CHANGE_LINKS_REQUIRE_USER400Links on the change form require a signed-in user; an API key uses the linking endpoints
TASK_HAS_DEPENDENTS400A change task cannot be deleted while other tasks depend on it (details.dependents with id, title, kind)
AGENT_GROUP_INACTIVE · AGENT_GROUP_ENTITY_TYPE_MISMATCH400The agent group is inactive or archived, or does not support the CHANGE type
{
  "error": "Template in status \"APPROVED\" cannot be edited directly",
  "errorCode": "TEMPLATE_NOT_DRAFT_FOR_EDIT",
  "details": { "status": "APPROVED" }
}

Required Field Missing (Transition, 400)

{
  "error": "VALIDATION_ERROR",
  "details": [
    { "field": "resolution", "message": "Resolution notes must be at least 20 characters" }
  ]
}

Code Example: Complete Lifecycle (JavaScript)

// ===================================================
// COMPLETE CHANGE LIFECYCLE
// Status NEVER set via PATCH — always dedicated endpoints.
// ===================================================

const API_URL = 'https://your-instance.com/api';
const headers = { 'Content-Type': 'application/json' };
const post = (path, body) => fetch(`${API_URL}${path}`, {
  method: 'POST', credentials: 'include', headers,
  body: body ? JSON.stringify(body) : undefined,
}).then(r => r.json());

// 1. Create change (UPPER enum values, inline tasks)
const change = await post('/changes', {
  title: 'Upgrade PostgreSQL to version 16',
  description: 'Database upgrade for security and performance reasons',
  justification: 'Security patches for CVE-2024-xxx, performance improvements',
  type: 'NORMAL',
  categoryId: 'clx-category-id',
  priority: 'HIGH',
  riskLevel: 'MEDIUM',
  impact: 'HIGH',
  urgency: 'MEDIUM',
  scheduledStartTime: '2026-02-01T02:00:00Z',
  scheduledEndTime: '2026-02-01T04:00:00Z',
  plannedDuration: 120,
  tasks: [
    { clientKey: 'impl', kind: 'IMPLEMENTATION', title: 'Run pg_upgrade' },
    { kind: 'TEST', title: 'Run integration tests', dependsOnTaskKeys: ['impl'] },
    { kind: 'ROLLBACK', title: 'Restore from backup if needed' },
  ],
});
console.log('Created:', change.number); // CHG-2026-000042

// 2. Submit (DRAFT → SUBMITTED), set change manager
await post(`/changes/${change.id}/submit`, { changeManagerId: 'clx-cm-id' });

// 3. Assign an approver (must not be requestor/change manager)
const approver = await post(`/changes/${change.id}/approvers`, { userId: 'clx-cab-member' });

// 4. Route to approval (SUBMITTED → PENDING_APPROVAL)
await post(`/changes/${change.id}/route-to-approval`, { skipApproval: false });

// 5. Approver decides via the unified approvals framework (different user!)
await post(`/approvals/${approver.id}/decide`, { decision: true, comment: 'Looks good' });
// → all required approvers done ⇒ change auto-transitions to APPROVED

// 6. Schedule (APPROVED → SCHEDULED) — .ics invites go out for assigned tasks
await post(`/changes/${change.id}/schedule`, {
  scheduledStartTime: '2026-02-01T02:00:00Z',
  scheduledEndTime: '2026-02-01T04:00:00Z',
});

// 7. Start implementation (SCHEDULED → IN_PROGRESS)
await post(`/changes/${change.id}/start-implementation`, { notes: 'Window opened' });

// 8. Work the tasks (start → complete, version-locked)
const tasks = await fetch(`${API_URL}/changes/${change.id}/tasks`, { credentials: 'include' }).then(r => r.json());
for (const t of tasks) {
  await post(`/changes/${change.id}/tasks/${t.id}/start`, { version: t.version });
  await post(`/changes/${change.id}/tasks/${t.id}/complete`, {
    completionNote: 'Done and verified', version: t.version + 1,
  });
}

// 9. Complete the change (IN_PROGRESS → COMPLETED)
await post(`/changes/${change.id}/complete`, {
  resolution: 'Upgrade completed successfully, all tests green',
  closerId: 'clx-cm-id',
});

// 10. Close (COMPLETED → CLOSED)
await post(`/changes/${change.id}/close`, {
  reviewNotes: 'Post-implementation review passed, no incidents',
  successCriteriaMet: true,
});

Rollback Scenario

If implementation goes wrong, there are two paths:

# A) Controlled rollback: IN_PROGRESS → ROLLING_BACK → BACKED_OUT
POST /api/changes/:id/initiate-rollback
{ "reason": "Integration tests failed, queries timing out — rolling back to v15" }

POST /api/changes/:id/backout
{ "resolution": "Restored from backup, cluster back on v15 and healthy", "closerId": "clx-cm-id" }

# B) Failure without rollback: IN_PROGRESS → FAILED
POST /api/changes/:id/fail
{ "resolution": "pg_upgrade aborted: incompatible cluster versions", "closerId": "clx-cm-id", "backoutPerformed": false }

# Then close: FAILED/BACKED_OUT → CLOSED
POST /api/changes/:id/close
{ "reviewNotes": "Root cause documented, retry planned for next window" }
✅
Core Principles
  • ✓ Status only via transition endpoints
  • ✓ Approval via unified approval framework
  • ✓ Four-eyes principle enforced
  • ✓ Structured change tasks with optimistic lock
  • ✓ Calendar invites (.ics) for scheduled work
🔐
Permissions (RBAC)
  • changes.viewAll / viewOwn / viewPendingApprovals
  • changes.create / editAll / editOwn
  • changes.assign / addApprover
  • changes.submit / manageWorkflow / schedule
  • changes.startImplementation / markCompleted / markFailed
  • changes.backout / close / returnToDraft
  • changes.manageTasks / executeTask
  • changes.delete / restore
  • changes.viewTemplates / viewDraftTemplates / createTemplates
  • changes.editOwnTemplates / editAllTemplates / submitTemplates / retireTemplates / deleteTemplates

Auth/role model: Permissions & RBAC

Next Step

Problems API → Learn more about the Problems API
Entity Linking API → Link changes with tickets/problems/incidents/assets