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
titlestringShort title (5–200 characters)
descriptionstringDetailed description (20–5000 characters)
justificationstringJustification for the change (20–2000 characters)
typeenumSTANDARD, NORMAL, EMERGENCY, MAJOR
categoryIdstringChange category (ID, required)
priorityenumLOW, MEDIUM, HIGH, URGENT, CRITICAL
riskLevelenumLOW, MEDIUM, HIGH, VERY_HIGH
impactenumLOW, MEDIUM, HIGH, VERY_HIGH
urgencyenumLOW, 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)

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

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