Versus Desk

Request Fulfillment and Catalog

How Versus Desk turns a self-service catalog order into fulfilled work — the Request → Requested Item → Fulfillment Task hierarchy, catalog items backed by App Builder forms, the native fulfillment provider, the request state machine, and the REST API that drives it all.

Request fulfillment is the "order something and have it delivered" side of Versus Desk. A user picks an item from the service catalog, fills in a form, and submits it; Versus creates the ticket records that track the work, seeds the tasks that carry it out, and drives them through a fulfillment lifecycle to closure. Because the catalog form is an App Builder form and the fulfillment engine is the same workflow engine that runs your network changes, a catalog request can do real work — provision an F5 virtual server, open a firewall rule, issue a certificate — not just file paperwork.

This page covers the record model, how a submission becomes fulfilled work under the hood, and the API you use to build and drive it.


The fulfillment hierarchy

Versus Desk implements the standard ITIL request pattern natively — no bolt-on, and no second engine:

LevelTicket typePrefixWhat it is
RequestrequestREQThe envelope a requester submits. Holds one or more requested items.
Requested Itemrequested_itemITEMA single catalog line-item (the RITM). Carries the catalog item and the form submission.
Fulfillment Task— (RequestTask)FTASKA work-item on an item, often auto-generated from a workflow step.

A REQ is the container; each Requested Item is one thing ordered; each item is fulfilled by one or more Fulfillment Tasks. All three are numbered monotonically per prefix in the form REQ-000001, ITEM-000042, FTASK-000108, allocated under a row lock so numbers never collide. (Historical rows may carry the legacy RTASK- prefix for fulfillment tasks.)

Requests and requested items are Ticket rows like every other Versus Desk record, so they share the same assignment groups, comments, watchers, SLA engine, and hash-chained audit trail as incidents, problems, and changes. See the Versus Desk Overview for the shared ticket model.


Catalog items

A catalog item is a thing users can order. It is deliberately thin — it points at a form and a workflow rather than re-implementing either:

FieldMeaning
name, description, iconCard presentation in the catalog.
categoryGroups items under a CatalogCategory (name + sort order).
app_builder_form_idThe App Builder FormDefinition the requester fills in.
workflow_binding_idThe App Builder WorkflowBinding that runs on submit.
visibilityJSON rules for who sees the item.
request_sla_hintAn SLA definition to apply to requests for this item.
change_gate_modeWhether fulfillment routes through change control (default inherit).
archived_atSoft-delete; archived items drop out of the catalog.

Catalog items and categories are managed through their own endpoints:

GET    /api/itsm/catalog/categories/
POST   /api/itsm/catalog/categories/        { "name": "Network Services", "sort": 10 }

GET    /api/itsm/catalog/items/
POST   /api/itsm/catalog/items/             { "name": "New F5 Virtual Server", "category": 3,
                                              "app_builder_form_id": "…", "workflow_binding_id": "…" }

Reading the catalog requires the itsm.view permission; creating or editing items and categories requires itsm.manage. The item list returns only live items — anything with archived_at set is filtered out.

No second form engine

Catalog forms are App Builder forms, not a parallel form system. The item's app_builder_form_id is a FormDefinition; its fields are designed in the Form Designer. This is a deliberate architectural choice — one form engine, one field catalog, one place to design an intake screen.

For teams migrating from ServiceNow, Versus ships a canonical crosswalk from ServiceNow catalog variables (item_option_new) to App Builder field types, so every variable type has a defined landing spot:

ServiceNow variableApp Builder field
Single Line Text, Wide Single Line Texttext
Multi Line Text, HTMLtextarea
Select Box, Multiple Choice, Lookup Select Boxselect
List Collector, Lookup Multiple Choicemultiselect
Checkbox, Yes/No (True-False)toggle
Referencedevice_picker
Date, Date/Timedate, datetime
Numeric Scalenumber
Attachmentfile
Container / Label / Macrolayout element (not a field)

Lossy mappings are surfaced honestly: HTML collapses to a plain text area (App Builder has no rich-text field type), and a Masked/Password variable maps to text because its secret value is resolved from the Versus Vault at fulfillment time rather than stored on the form.


From submission to fulfilled work

Ordering an item is a two-step handoff. The requester submits the App Builder form through App Builder's own submission endpoint, which validates inputs against the form definition and records a Submission. That submission's id is then handed to Versus Desk, which mints the request records:

POST /api/itsm/catalog/items/{id}/requests/
Authorization: Bearer …
Content-Type: application/json

{
  "app_builder_submission_id": "9f1c…",   // required — submit the form via App Builder first
  "quantity": 1,                            // optional, default 1
  "tasks": [                                // optional; overrides the default single task
    { "short_description": "Build VS on lab pair", "order": 0,
      "workflow_step_ref": "f5.sitebuild" }
  ]
}
201 Created
{ "number": "REQ-000123", "ticket_type": "request", "state": "New",
  "requested_items": [ { "number": "ITEM-000124", "catalog_item_name": "New F5 Virtual Server",
                         "request_tasks": [ { "number": "FTASK-000125", "state": "open" } ] } ] }

Under the hood the native fulfillment provider does this in a single transaction:

  1. Creates the REQ envelope (origin: catalog), with a summary derived from the ordered items.
  2. For each line, creates an ITEM (requested item) linked to its catalog item, the App Builder submission id, the quantity, and its parent REQ.
  3. Seeds the item's FTASK fulfillment tasks. If you pass a tasks list, each entry becomes a task with its own order and optional workflow_step_ref; otherwise a single default task (Fulfill: <item>) is created.

The submission id is stored on the requested item's extension record (RequestExt.app_builder_submission_id) — the form's binding_snapshot and resolved inputs live on the App Builder Submission, never duplicated into the ticket. That keeps one source of truth for what was entered and how it was bound.

The workflow binding

The item's workflow_binding_id connects the intake form to automation. A WorkflowBinding names the workflow to run (workflow_ref), maps form fields onto workflow inputs (input_map), and declares change-control behavior (gates, change_type, approval_policy_id). A binding can run its workflow directly (direct_run) or route through the Versus Desk change gate — which is where change_gate_mode on the catalog item comes in. When fulfillment is gated, the run mints an approved, evidence-carrying Change record before it touches anything; see Change Management for how the gate works. As the workflow progresses, the App Builder Submission advances through its own lifecycle states (SUBMITTED → QUEUED → … → IMPLEMENTING → VERIFYING → COMPLETED), which the fulfillment tasks reflect.


Driving fulfillment

The request lifecycle

Requests and requested items share one small state machine, defined declaratively rather than hardcoded in the UI:

New ──start──▶ In Progress ──fulfill──▶ Fulfilled ──close──▶ Closed
  └────────────────── fulfill ──────────────────▶ Fulfilled

Both request and requested_item tickets use this same lifecycle. The New → fulfill shortcut exists for items that need no interim "in progress" stage. Transitions are applied through the ticket's transition action:

POST /api/itsm/tickets/{number}/transition/
{ "event": "fulfill", "note": "VS built and verified", "fields": { } }

The engine takes a row lock on the ticket, re-checks the current state, applies the transition, and records it — an illegal transition (an event the current state has no rule for) is rejected with 409 Conflict, so the UI never has to guess which buttons are valid. It asks the API instead: each ticket serializes its available_events, and the frontend renders exactly those actions.

Transitions also drive the surrounding machinery automatically:

  • start stops the response-SLA clock; fulfill stops the resolution-SLA clock.
  • close on a request sends a CSAT survey to the requester via a tokenized public link.
  • Every create and transition writes an ItsmAuditEvent into the tamper-evident, hash-chained audit log (verifiable at GET /api/itsm/audit/verify/).

Fulfillment tasks

Fulfillment tasks (FTASK) are the unit of work. They carry an assignment group, an assignee, an order, a state, and — when auto-generated from automation — a workflow_step_ref linking them back to the orchestration step that created them. Manage them through their own endpoint:

GET    /api/itsm/request-tasks/?ticket={item_id}&state=open
POST   /api/itsm/request-tasks/       { "ticket": "…", "short_description": "Manual DNS entry" }
PATCH  /api/itsm/request-tasks/{id}/  { "state": "closed" }

Reading tasks requires itsm.view; creating or updating one requires itsm.agent. Closing a task stamps its closed_at. New tasks are numbered FTASK- automatically on create.


SLAs on requests

A catalog item can name a request_sla_hint, and an SlaDefinition can scope itself to a specific catalog item (its applies_to supports catalog_item_id, alongside ticket type, priority, group, and CI tags). When a request is created, matching SLAs attach automatically, so fulfillment targets — "standard access requests fulfilled within one business day" — are measured on business-calendar time and paused when a ticket is waiting on the user. The SLA engine is shared with the rest of Versus Desk; see the Overview for how calendars and SLA definitions work.


Self-service and external requesters

A request's requester is either an internal user or a portal identity (requester_kind: portal) — an external, self-service requester who reaches Versus only through the catalog and never through the agent console. The ticket records origin: catalog for anything ordered this way, distinguishing catalog-driven work from tickets raised manually, by email, by the API, or automatically from an Observe alert. This lets you report on the catalog as its own intake channel.


Where it fits

Request fulfillment is the front door to Versus automation for people who don't build workflows. A network engineer authors a workflow once in the Workflow Builder; an operator wraps it in a catalog item with an intake form; and anyone in the business can then order it safely, with the request tracked, SLA'd, audited, and — where the change gate applies — approved as a formal change. Everything runs on one system of record, so the ticket, the automation run, the change record, and the audit trail are the same data, not four systems stitched together.

For teams standardizing on Versus after ServiceNow, the native catalog is designed to receive imported items with the variable crosswalk above; see ServiceNow Integration for the coexistence and migration path.


Related