Service Catalog
Catalog item definition, request fields, fulfillment workflow, approval chains, and the self-service portal.
Overview
The service catalog is the end-user-facing menu of IT services and products that employees can request. Each catalog item defines what can be requested, who can request it, what information to collect, and how to fulfill it. Requests are tracked as catalog request records with their own lifecycle.
Catalog Item Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | string (cat_...) | required | auto | Unique identifier. |
| name | string | required | Display name shown in the catalog browser. | |
| slug | string | required | auto | URL-safe identifier. Auto-derived from name. |
| description | text | optional | Full description shown to requesters. Markdown supported. | |
| shortDescription | string | optional | One-line summary shown in catalog grid view. | |
| category | string | optional | Groups items in the catalog browser. Examples: Hardware, Software, Access, HR. | |
| icon | string | optional | Icon name (Lucide icon slug) or URL to a custom icon image. | |
| status | enum | required | draft | draft · active · retired. Only active items appear in the catalog. |
| visibility | enum | optional | all_users | all_users · groups_only · hidden. Groups restrict to specific AD/IdP groups. |
| allowedGroups | string[] | optional | [] | If visibility=groups_only, list of group IDs that can see this item. |
| fulfillmentType | enum | required | workflow | workflow · manual · automated. workflow runs a linked workflow on submission. |
| fulfillmentWorkflow | string (wf_...) | optional | Workflow triggered on request submission (when fulfillmentType=workflow). | |
| approvalRequired | boolean | optional | false | If true, request must be approved before fulfillment begins. |
| approvers | object | optional | {} | { type: "manager"|"group"|"user", value: string }. Who must approve. |
| approvalTimeoutHours | integer | optional | 48 | Auto-reject if approval not given within this many business hours. |
| slaHours | integer | optional | Target fulfillment time in business hours. Drives SLA tracking on requests. | |
| formFields | object[] | optional | [] | Dynamic form definition. See form field schema below. |
| cost | decimal | optional | Optional display cost shown to requester. Informational only. | |
| costCurrency | string | optional | USD | ISO 4217 currency code. |
| tags | string[] | optional | [] | Search tags. |
| order | integer | optional | 0 | Display order within category. Lower = higher. |
| price | decimal | optional | 0 | Chargeback unit price for this item. See Catalog Cost Model below. |
| currency | string | optional | USD | ISO 4217 currency code for price. |
| chargebackEnabled | boolean | optional | false | If true, requests for this item post cost to the requester’s cost center for chargeback reporting. |
| costCenterId | string (ObjectId) | optional | null | Default cost center charged when this item is ordered. |
| pricingNotes | string | optional | null | Free-text note shown alongside price (e.g. billing cadence, proration rules). |
Catalog Cost Model & Chargeback
Catalog items can carry a unit price/currency and an optional default costCenterId. When chargebackEnabled is true, submitted requests for that item are expected to carry cost through to the resulting Service Request via its totalCost/costCurrency fields (see Service Request Fulfillment Fields below), so spend can be attributed back to the requesting cost center.
totalCost across Service Requests, as exposed by the Catalog Spend Analytics endpoint below.Catalog Spend Analytics
Aggregates totalCost across Service Requests over a date range, for chargeback and budget reporting.
/api/itsm/analytics/catalog-spendSpend breakdown and top items for the given date range
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| from | query param (ISO date) | optional | 30 days ago | Start of the reporting window. |
| to | query param (ISO date) | optional | now | End of the reporting window. |
| groupBy | query param | optional | catalogItem | Field to group the breakdown by, e.g. catalogItem, department, or month (buckets by createdAt as YYYY-MM). |
// GET /api/itsm/analytics/catalog-spend?from=2026-06-01&to=2026-07-01&groupBy=month
{
"success": true,
"data": {
"totalSpend": 48250,
"totalRequests": 132,
"avgCostPerRequest": 365.53,
"currency": "USD",
"breakdown": [
{ "key": "2026-06", "label": "2026-06", "totalCost": 48250, "requestCount": 132 }
],
"topItems": [
{ "catalogItemId": "cat_laptop_request", "totalCost": 21000, "requestCount": 30 }
]
}
}Only requests with totalCost > 0 are included. breakdown is limited to the top 20 groups by cost; topItems is always grouped by catalogItemId and limited to the top 10.
Form Field Schema
Each item in formFields defines a question the requester must answer:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | string | required | — | Unique field key. Used as the key in the submitted request fields object. |
| type | enum | required | — | text · textarea · number · email · phone · date · select · multiselect · checkbox · radio · file_upload · table_lookup |
| label | string | required | — | Display label shown above the field. |
| placeholder | string | optional | — | Placeholder text inside the field. |
| helpText | string | optional | — | Hint text shown below the field. |
| required | boolean | optional | — | Whether the field must be filled before submission. |
| options | object[] | optional | — | [{ value, label }] for select, multiselect, radio types. |
| tableLookup | object | optional | — | { tableSlug, labelField, valueField, filters } for table_lookup type. |
| validation | object | optional | — | { min, max, pattern, message } for numeric/text validation. |
| conditions | object[] | optional | — | Show/hide rules: [{ field, operator, value }]. Field appears only when condition is met. |
| order | integer | optional | — | Display order within the form. |
Catalog Request Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | string (creq_...) | required | auto | Unique identifier. |
| number | string | required | auto | Human-readable number (REQ-NNNN). |
| catalogItemId | string (cat_...) | required | The catalog item being requested. | |
| requestedBy | string (usr_...) | required | actor | User submitting the request. |
| requestedFor | string (usr_...) | optional | requestedBy | User the request is being made on behalf of (e.g. manager ordering for new hire). |
| status | enum | required | submitted | submitted · pending_approval · approved · rejected · fulfilling · fulfilled · cancelled |
| fields | object | required | {} | Key-value map of form field responses. Keys match catalog item formFields[].id. |
| approvalStatus | enum (read-only) | optional | not_required | not_required · pending · approved · rejected |
| approvers | object[] (read-only) | optional | computed | Approval records generated from the catalog item approvers config. |
| workflowRunId | string (run_...) | optional | The fulfillment workflow run ID. Set when fulfillment begins. | |
| fulfillmentNotes | text | optional | Notes from the fulfiller. Visible to the requester. | |
| fulfilledAt | timestamp | optional | When status moved to fulfilled. Auto-set. | |
| sla | object | optional | computed | SLA tracking object, same structure as incident SLA. |
| createdAt | timestamp | optional | auto | Submission timestamp. |
Service Request Fulfillment Fields
Beyond the catalog-facing fields above, the underlying Service Request record (requests collection, shared with the rest of ITSM's task model) carries a set of fulfillment-tracking fields used once a request has been approved and handed off for delivery.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| catalogItemId | string (cat_...) | optional | null | The catalog item this request was submitted for. |
| fulfillmentWorkflowId | string (ObjectId) | optional | null | Workflow Studio workflow run that is fulfilling this request. |
| provisionedAssetIds | string[] (ObjectId) | optional | [] | Install Base / asset records provisioned as a result of this request. |
| deliveryTeamId | string (ObjectId) | optional | null | Group responsible for delivering/fulfilling the request. |
| expectedDelivery | timestamp | optional | null | Target delivery date, distinct from SLA due date. |
| actualDelivery | timestamp | optional | null | When the request was actually delivered. |
| onBehalfOf | string (usr_...) | optional | null | User the request was placed for, when different from the requester (e.g. a manager ordering for a new hire). |
| businessJustification | text | optional | null | Requester-supplied justification for the request. |
| department | string | optional | null | Requester’s department at time of submission. |
| costCenter | string | optional | null | Cost center to charge for this request. |
| approvalChainId | string (ObjectId) | optional | null | Approval chain governing this request, if one applies. |
| orderNumber | string | optional | null | External order/PO number, when fulfillment involves procurement. |
| watcherIds | string[] (usr_...) | optional | [] | Users watching this request for updates. |
| escalatedAt | timestamp | optional | null | When the request was escalated. |
| escalatedIncidentIds | string[] (inc_...) | optional | [] | Incidents created as a result of escalating this request. |
| totalCost | decimal | optional | 0 | Total cost of this request, used by Catalog Spend Analytics. |
| costCurrency | string | optional | USD | ISO 4217 currency code for totalCost. |
| notes | text | optional | null | Internal fulfillment notes. |
| category | string | optional | null | Request category. |
| subcategory | string | optional | null | Request subcategory. |
App Builder Form Integration
Each catalog item can have a linked App Builder app that serves as its request form. This lets admins design rich, custom forms with full App Builder capabilities (conditional fields, data sources, custom layouts) instead of the default variable list.
Admin workflow
- •Open the catalog item in the builder at /itsm/catalog/builder/[id].
- •Go to the Form App tab.
- •Click "Create Form App" — a new App Builder app is created with a starter TSX form and the builder opens automatically.
- •Customise the form: add fields, change layout, wire a REST API data source, etc. The starter template already posts to /api/itsm/requests with the catalogItemId pre-filled.
- •Save in App Builder. The form is now live.
End-user experience
When a user opens the catalog item at /itsm/catalog/[id] and the item has a linked form app, the App Builder app is embedded as an iframe. The form submits directly to POST /api/itsm/requests and shows the resulting request number on success.
Catalog items without a form app continue to use the built-in variable-based form (backward compatible).
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| formAppId | string (ObjectId) | optional | null | ID of the linked App Builder app. Set automatically by POST /api/itsm/catalog/:id/create-form-app. Can be unlinked via PATCH with formAppId: null. |
/api/itsm/catalog/:id/create-form-appCreate (or return existing) an App Builder app linked to this catalog item. Idempotent. Returns { appId, builderUrl }.
Workflow Trigger on Submission
Any workflow in Workflow Studio can be configured to fire automatically when a request is submitted. No per-item configuration is needed — the trigger is event-driven and applies globally.
Setup
- •Go to /workflows/new and create a workflow.
- •Set trigger type to Event.
- •Set event name to request.created.
- •Activate the workflow.
Every time a catalog request is submitted (via the built-in form or via the App Builder form app), the platform emits a request.created event. All active workflows matching that trigger fire automatically with the following payload:
{
"requestId": "...",
"requestNumber": "REQ-0001",
"catalogItemId": "...",
"requestedBy": "...",
"workspaceId": "...",
"title": "Request: Laptop for new hire",
"variables": {
"urgency": "high",
"seat_count": 250,
"department": "engineering"
}
}The variables key holds all form field responses submitted with the request. Access them in workflow nodes as ctx.payload.variables.fieldName.
API Quick Reference
/api/itsm/catalogList catalog items (respects visibility)
/api/itsm/catalogCreate a catalog item (admin/developer)
/api/itsm/catalog/:idGet catalog item with form definition (includes formAppId)
/api/itsm/catalog/:idUpdate catalog item (accepts formAppId: null to unlink form app)
/api/itsm/catalog/:id/create-form-appCreate or return existing App Builder form app linked to this item
/api/itsm/requestsSubmit a catalog request
/api/itsm/requestsList requests (filtered by requester unless admin/manager)
/api/itsm/requests/:idGet request detail
/api/itsm/requests/:idUpdate a request, including status transitions
/api/itsm/analytics/catalog-spendChargeback spend breakdown by catalog item, department, or month
POST /api/approvals/:id/approve, POST /api/approvals/:id/reject) against approval records created with contextType: "request".Example: Submit a request
POST /api/itsm/requests
{
"catalogItemId": "cat_laptop_request",
"requestedFor": "usr_new_hire_bob",
"fields": {
"laptopModel": "MacBook Pro 14 M4",
"ramSize": "32GB",
"storageSize": "1TB",
"accessories": ["magic_mouse", "usb_hub"],
"deliveryAddress": "123 Main St, Floor 4, Desk 4B",
"urgency": "medium",
"justification": "New hire starting 2026-07-01"
}
}