The service catalogue API
All under /api/v1/x/itsm/services, 404 while the Service Configuration plugin is off; reading needs itsm:read,
changing itsm:write. You see a service when you can see one of its units and hold service.read there. Guide:
services.
| Endpoint | What |
|---|---|
GET ?status=&type=&tier=&owner=&teamId=&unitId=&orgId=&parent=&q=&updated=&prop=&propValue=&exclude=&sort=&page=&pageSize= | Services, a page at a time: { services, total, page, pageSize } (pageSize up to 500, default 200). status: active (all but retired, the default), all or one of planned, live, retiring, retired. tier: one or several (1,2). owner: me, none or a person's id. teamId: the support team (an offering's own, or its service's). parent: top (no offerings) or a service's id (its offerings). sort: name (the default), code, tier or updated. Each row has inherited: the fields an offering takes from its service. |
GET glance? (the same filters) | { total, live, noOwner, mine, critical } (critical: tier 1). |
GET count | { mine }: services you own that aren't retired. |
GET options?orgId= | For the forms, in the organization: its units, businessHours, slaPolicies, and the orgs you can see. |
GET people?unitIds=a,b&q= | Who may own a service with these units (people working in one of them), by name or email: at most 20. |
GET teams?q= | Teams by name: at most 20. |
POST | Add a service (service.settings in one of its units): code (a letter, then letters, digits or dashes, up to 24; unique in the organization; 409 code_taken), name, unitIds (one organization), optional type (business, technical), status, tier (1–4), ownerId (default: you, when you may own it; null: nobody), supportTeamId, escalationTeamId, businessHoursId, slaPolicyId, retireAt, description, customerDescription ({ en, … }), links ([{ title, url }]), parentId (an offering of that service; one level), dependsOnIds, accountVisible (whether contacts can pick it on forms and see it on the portal; default true for business services, false for technical ones), props. |
GET <id> | The service with its offerings, accountResponsibilities, dependsOn and neededBy (those you can see), impact (the whole chain: affects, everything that needs it all the way up, and reliesOn, everything it needs all the way down, each { id, code, name, status, depth, via } where via is the code of the first service in between; at most 200 each and 10 steps, truncated when cut), links, version, its history (newest first: { version, kind, actor, at, changes: [{ field, from, to }] }) and may: { edit, manage }. |
GET <id>/as-of?at= | The record as it was at that moment (an ISO time in the past): { version, at, by, record }, 404 before it existed. |
PATCH <id> | Change any field, unitIds (the whole list; units you can't see stay), dependsOnIds (no loops: 400 dependency_loop), props. The owner or service.write may; code, status and parentId need service.settings. Each change that changes something raises version and adds a revision. |
DELETE <id> | Delete a service with no offerings (409 has_offerings) that nothing depends on (409 needed); service.settings. |
Accounts using services, and accounts' approvers, under /api/v1/x/itsm/. Accounts and contacts are seen as on
their own pages (an agent sees a contact through their cases); one you can't see answers 404, and lists count the rest
as hidden.
| Endpoint | What |
|---|---|
GET services/<id>/accounts | { accounts, hidden }: each { account, startsOn, endsOn, contractRef, active, approvers } (dates YYYY-MM-DD; approvers: how many may approve for this service). |
POST services/<id>/accounts | { accountId, startsOn, endsOn, contractRef }: the account (in the service's organization) starts using it (409 already_using). Needs the right to edit the service. |
PATCH services/<id>/accounts/<accountId>, DELETE … | Change the dates or contract (400 bad_dates), or take the account off. Each is a change in the service's history (accounts). |
GET accounts/<accountId> | { account, services, approvers, notice, hidden, may: { contacts } }: the services it uses, and its approvers and notice contacts, each { id, contact, role, serviceIds, services, addedAt } (serviceIds empty: all). |
POST accounts/<accountId>/contacts | { contactId, role, serviceIds }: one of the account's contacts as approver or notice (400 not_used for a service it doesn't use, 409 already_listed). Needs service.approvers. |
PATCH accounts/<accountId>/contacts/<id>, DELETE … | { serviceIds }, or take them off. |
GET accounts/<accountId>/history | { events }: every change, newest first, { at, role, action, contact, services, actor } (contact null when erased or not visible to you). |
GET accounts/<accountId>/approvers?at=&serviceId= | Who could approve at that past moment (for that service, or any), from the history. |
The service on cases, and the report, under /api/v1/x/itsm/:
| Endpoint | What |
|---|---|
GET cases/<ref>/service | { service, hidden, setAt, setBy, account, accountServices, required, may: { change } }: the case's service with owner, supportTeam, teamWorksHere and slaPolicy; the services the requester's account uses now; whether its unit needs one. 404 for a case you can't see. |
PUT cases/<ref>/service | { serviceId } (null clears it): a service you can see in the case's organization (400 unknown_service), not retired (400 retired). Needs case.write on the case. |
GET services/pick?q=&ids=&orgId=&retired=1 | For pickers: { items: [{ id, label, hint, owner, supportTeam }] }, at most 25 that aren't retired (retired=1: retired ones too, for the lists' Service filter), or those named by ids. |
GET services/<id>/dashboard?days=&tz= | The service's dashboard, its offerings included, for the last days (30, the default, or 90): { days, dates, offerings, cases, work }. cases: the cases about it you can see: open, unassigned, overdue (past their next target), opened, resolved and openedBefore (the period before), daily: { opened, resolved } (per day of dates), medianResolveMinutes, sla: { met, missed, rate, rateBefore } (resolution targets decided in the period) and csat: { great, okay, bad, rate, rateBefore }. work: per kind of record from the plugins that are on (incident, problem, change), the ones you may see: { type, label, open, opened, closed, openedBefore, daily: { opened, closed }, facts: [{ id, label, value, tone }], highlights } (at most five rows with a note, like a major incident or a change planned in the next 14 days). |
GET services/<id>/work?type=&status=&q=&person=&updated=&page=&pageSize= | The work on the service and its offerings in one list, most recently updated first: { rows, total, page, pageSize, types }. Each row: { type, id, key, title, status, statusLabel, open, priority, priorityRank, person, updatedAt, href, service }. type: case, incident, problem, change (several with commas; all when left out). status: open (the default), done or all. person: me, none or a person's id (the assignee, commander, owner or implementer). updated: 24h, 7d, 30d, 90d. pageSize up to 500; pages reach the latest 2,000 rows. Cases only in your units; other records through their plugin's own access rules. |
GET report?days=&orgId=&tz= | Needs report.read: { days, rows, withoutService, totalCases }, each row { service, cases, resolved, medianResolveMinutes, slaChecked, slaMet, incidents, majorIncidents } (incidents null without Incident Management). |
GET settings/units, PATCH settings/units/<unitId> | Whether cases need a service before an agent resolves them, per unit (covering the units below): { units: [{ unitId, name, path, depth, serviceRequired, inherited, may }] } for anyone signed in (may: whether you may change it); PATCH takes { serviceRequired } and needs unit.settings in that unit. |
Health, windows and freezes, under /api/v1/x/itsm/. Services carry health (operational,
maintenance, degraded, outage), atRiskVia (codes of troubled services it relies on), healthOverride,
public and publicName; the detail adds healthCauses and healthHistory. The list filters by health (one or
several, or at_risk).
| Endpoint | What |
|---|---|
PUT services/<id>/health | { health, reason, hours } (1–168, default 4): set by hand (service.status, or the owner). DELETE goes back. |
GET services/<id>/windows | { windows, freezes, may: { edit, freezeAll } }. |
POST services/<id>/windows, DELETE services/<id>/windows/<windowId> | { kind: weekly or monthly, days (0 Sunday … 6), nth (monthly: 1–4, -1 last), start (HH:MM), minutes, timeZone, label }. |
GET freezes?orgId=&all=, POST freezes, DELETE freezes/<id> | { orgId, serviceId (null: every service), startsAt, endsAt, reason }. |
POST schedule-check | { serviceIds, start, end } → { checks: [{ serviceId, code, hasWindows, inWindow, frozen }] }. |
Evidence and the rest, under /api/v1/x/itsm/:
| Endpoint | What |
|---|---|
GET scopes?orgId=, POST scopes, PATCH scopes/<id> | Audit scopes: { orgId, name, description }, { archived }. Services carry scopes; PATCH services/<id> takes scopeIds (service.settings). |
GET suppliers?orgId=&archived=, GET suppliers.csv?orgId=, POST suppliers, PATCH suppliers/<id>, DELETE suppliers/<id> | The supplier register: { orgId, name, description, website, contact, carvedOut, assurance }. Services carry suppliers; PATCH services/<id> takes suppliers: [{ supplierId, provides }]. |
GET reviews?show=&kind=&orgId=, GET reviews/count, GET reviews.csv?orgId= | Reviews of service records and approver lists (show: mine, pending, all). |
POST reviews | { serviceId } or { accountId }: start one now. |
POST reviews/<id>/decision | { result: confirmed or corrected, note } (a note for corrected; 409 decided). |
GET settings/reviews?orgId=, PATCH … | { reviewMonths, criticalReviewMonths } (0 turns them off). |
GET settings/forms?orgId=, PATCH … | { formTypes }: the service types (business, technical) a form's Service field offers; ["business"] by default. |
GET export?orgId=&asOf=&scopeId=&format= | The catalogue on a date: csv (a file, with X-Checksum-SHA256 and X-Export-Id) or json ({ columns, rows, record }). GET exports?orgId= lists them. |
POST import | { orgId, csv, apply }: { rows: [{ line, code, action, changes, errors }], creates, updates, errors }. |
GET public/<slug>/services | No staff sign-in: the services the portal's signed-in contact's account uses that are visible to accounts. |
| Status | Code | When |
|---|---|---|
| 400 | cannot_resolve | Resolving or closing a case that needs a service and has none. |
| 409 | in_use | Deleting a service that cases or incidents name. |
Events for webhooks
| Event | With |
|---|---|
itsm.service_created | |
itsm.service_changed | changes. |
itsm.service_status_changed | from, to. |
itsm.service_deleted | |
itsm.account_added, itsm.account_removed | |
itsm.approvers_changed | role, action. |
itsm.case_service_changed | caseId, ref, serviceId, code, previousServiceId. |
itsm.service_health_changed | serviceId, code, from, to, atRiskVia, cause. |
itsm.review_decided | reviewId, kind, result, serviceId, accountId, overdue. |
For other plugins. Plugins built on Service Configuration use its services with
ctx.use<ItsmApi>('itsm', 'services') (plugins/itsm/server/itsm-api.ts): services, systemServices, find,
offerings and requirePickable, publicServices, refreshHealth and checkSchedule, and meetings(kind, provider)
with meetingTools(kind, tools) (meetings). It has three hooks:
| Hook | What |
|---|---|
useCheck | A service your plugin names can't be deleted. |
reportCounts | Counts per service for the report. |
healthProvider | How services are, from your plugin's records. |
Meetings
Service Configuration keeps the review meetings (problem reviews and the CAB) other plugins hold, with no page of its own:
Problem Management's problem reviews (problems.review, at /app/x/problems/reviews) and Change Management's CAB
(changes.cab, at /app/x/changes/cab). The API for both is under
/api/v1/x/itsm/meetings, itsm:read to read and itsm:write to change; who sees and holds a kind is that kind's
permission, in a unit of the meeting's organization.
| Endpoint | What |
|---|---|
GET kinds?orgId= | The kinds you may see there, their decisions (outcomes) and whether you may hold them (mayHold). |
GET ?kind=&status=&orgId= | Meetings you may see (status: planned or closed), each with href, its page in its plugin. |
POST | Plan one: { kind, orgId, title, startsAt, location, chairId, attendeeIds } (you chair it unless chairId says otherwise). The agenda starts from what needs attention since the last one, and open actions from earlier ones. |
GET <id>, PATCH <id> | The meeting with its agenda, notes, decisions and actions (items you can't see have no title); change the title, time, place, chair or attendees (the chair, or someone who holds these meetings). |
GET <id>/candidates?q= | What the Add item lookup offers: records of the kind you may see in the organization, by key or title words, not on the agenda yet (at most 20; key, title, status, href). |
POST <id>/refresh, POST <id>/items, DELETE <id>/items/<itemId> | Add what needs attention now; add an item by { ref } (PRB-12, CHG-40; 409 on_agenda); take an undecided item off (the chair). |
PATCH <id>/items/<itemId> | { notes } (attendees), { outcome, decision } (the chair; final, 409 decided). A decision goes on the record's timeline; on a change waiting for the CAB it is the CAB's approval or rejection (403 separation_of_duties for its requester). |
POST <id>/items/<itemId>/actions | { title, assigneeId, dueOn }: a task on the record; the assignee is told. |
POST <id>/close, GET <id>/minutes.md | Close it (the chair): the minutes are locked with their SHA-256 and emailed to the attendees (the kind's plugin's notification kind meeting). The minutes as Markdown, locked or a draft, with X-Checksum-SHA256. |
Everything a meeting does is its plugin's:
| What | How |
|---|---|
| The minutes email | Links to its page and uses its notification kind meeting. |
| The audit log | <plugin>.<name>_created, _closed and _exported. |
| The webhook event | <plugin>.<name>_closed (id, kind, title, sha256): problems.review_meeting_closed and changes.cab_closed. |
| MCP tools (read only) | list_problem_reviews and get_problem_review (problems:read), list_cab_meetings and get_cab_meeting (changes:read). |
A plugin adds a kind with ItsmApi.meetings(kind, provider) once Service Configuration is offered. The
MeetingProvider (plugins/itsm/server/itsm-api.ts) has:
| Part | What |
|---|---|
label | Its label. |
plugin, path, name | Its plugin, the path of its pages and the name its audit entries and event use. |
read, hold | The permissions to see and hold them. |
outcomes | { id, label, needsText }. |
agenda(scope, orgId, since) | Hook: what needs attention since the last meeting, null for the first. |
find(scope, orgId, ref) | Hook: a record added by hand. |
search(scope, orgId, q) | Hook: for the Add item lookup. |
visible(scope, refKind, ids) | Hook. |
decide(user, meeting, item) | Hook: on the record's timeline; throw to refuse. |
action(user, meeting, item, action) | Optional hook: a task on the record, its id. |
openTasks(taskIds) | Optional hook: the ones still open, which come back on the next agenda. |
[!IMPORTANT] Declare the
meetingnotification kind and the<plugin>.<name>_closedevent in your manifest.meetingTools(kind, { scope, list, get })gives the two MCP tools, with your names and words, forctx.mcpTools. The web side shows them withMeetingsListandMeetingPagefromplugins/itsm/web/Meetings.tsx, given aMeetingKindPage.
ctx.onOffer<ItsmApi>('itsm', 'services', (itsm) => {
itsm.meetings('problems.review', this.problemMeetings);
ctx.mcpTools(itsm.meetingTools('problems.review', { scope: 'problems:read', list: { name: 'list_problem_reviews', … }, get: { … } }));
});