Skip to main content

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.

EndpointWhat
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.
POSTAdd 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.

EndpointWhat
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/:

EndpointWhat
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=1For 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).

EndpointWhat
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/:

EndpointWhat
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>/servicesNo staff sign-in: the services the portal's signed-in contact's account uses that are visible to accounts.
StatusCodeWhen
400cannot_resolveResolving or closing a case that needs a service and has none.
409in_useDeleting a service that cases or incidents name.

Events for webhooks

EventWith
itsm.service_created
itsm.service_changedchanges.
itsm.service_status_changedfrom, to.
itsm.service_deleted
itsm.account_added, itsm.account_removed
itsm.approvers_changedrole, action.
itsm.case_service_changedcaseId, ref, serviceId, code, previousServiceId.
itsm.service_health_changedserviceId, code, from, to, atRiskVia, cause.
itsm.review_decidedreviewId, 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:

HookWhat
useCheckA service your plugin names can't be deleted.
reportCountsCounts per service for the report.
healthProviderHow 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.

EndpointWhat
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.
POSTPlan 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.mdClose 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:

WhatHow
The minutes emailLinks 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:

PartWhat
labelIts label.
plugin, path, nameIts plugin, the path of its pages and the name its audit entries and event use.
read, holdThe 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 meeting notification kind and the <plugin>.<name>_closed event in your manifest. meetingTools(kind, { scope, list, get }) gives the two MCP tools, with your names and words, for ctx.mcpTools. The web side shows them with MeetingsList and MeetingPage from plugins/itsm/web/Meetings.tsx, given a MeetingKindPage.

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: { … } }));
});