Custom Forms API
The Custom Forms API manages configurable forms (CustomForm) that back ticket creation with dynamic fields. Forms are used when creating and editing tickets (web and mobile). A form consists of a JSON formSchema with fields, optional assignment (assignedTo) and multilingual labels. Base path: /api/forms.
🔐 Auth: User login only, API keys are not accepted. All management endpoints (list, detail, create, update) require settings.editGeneral; /available requires tickets.create. See RBAC →.
Endpoints
| Method | Endpoint | Description | Permission |
|---|---|---|---|
GET | /api/forms | Admin list (filters, full fields) | settings.editGeneral |
GET | /api/forms/available | Forms for ticket creation (without management data, filtered by assignedTo and field visibility) | tickets.create |
GET | /api/forms/:id | Single form | settings.editGeneral |
POST | /api/forms | Create (201) | settings.editGeneral |
PATCH | /api/forms/:id | Update (deactivate = isActive:false, archive = isArchived:true) | settings.editGeneral |
Admin list vs. /available
GET /api/forms is the admin endpoint (settings.editGeneral, all fields). GET /api/forms/available is the endpoint for ticket creation (tickets.create): the server filters by assignedTo (allRoles/roles/users), returns the forms without management data and removes fields according to visibleToRoles / hiddenForEndUsers. hiddenForEndUsers applies to users WITHOUT tickets.viewAll/editAll (so custom end-user roles count too).
List Filters (GET /api/forms)
| Parameter | Values |
|---|---|
isActive | true | false | all |
isArchived | true | false | all |
includeWorkflowTemplates | true | false (default false) |
Create Form
POST /api/forms
{
"name": "Hardware Request",
"description": "Form for new hardware tickets",
"isActive": true,
"isDefault": false,
"formSchema": {
"fields": [
{
"id": "subject",
"type": "subject",
"label": "Subject",
"order": 0,
"required": true,
"labels": { "de": "Betreff", "en": "Subject" }
},
{
"id": "device",
"type": "text",
"label": "Device",
"order": 1,
"required": true,
"maxLength": 100,
"visibleToRoles": ["AGENT", "ADMIN"],
"hiddenForEndUsers": false
}
],
"assignedTo": { "allRoles": false, "roles": ["clx-role-enduser"], "users": [] },
"names": { "de": "Hardware-Anfrage", "en": "Hardware Request" }
}
}
Fields: name (3–100, unique), description? (max 500), formSchema (required), isActive (default true), isDefault (default false; there is at most one default form, a new one automatically replaces the previous one). formSchema.fields: 1–50 entries; assignedTo = { allRoles, roles (role IDs), users (user IDs) }. Unknown fields are rejected with 400.
Field Structure (formSchema.fields[])
| Feld | Description |
|---|---|
id, type, label, order | Required. type is free-form (subject, category, description, text, number, email, …) |
required | Mandatory field (default false) — applies in the masks AND in the API; cannot be set on an attachment field |
placeholder, description, defaultValue | Optional UI helpers |
minLength, maxLength, validation | Validation bounds (validation = free object) |
visibleToRoles | Only these roles see the field (empty = all) |
hiddenForEndUsers | Hide field for end users (default false) |
labels, placeholders, descriptions, optionLabels | Multilingual variants (Record<lang, …>) |
Where the requirement applies: The field rule applies in the input masks AND in the ticket API: anyone creating with formId or changing custom fields is validated server-side — required, minimum and maximum length, email, URL and phone format, plus membership in the option list. Violations are 400 FORM_SUBMISSION_INVALID; details.issues names fieldId, fieldType and a code per field (REQUIRED, MIN_LENGTH, MAX_LENGTH, INVALID_EMAIL, INVALID_URL, INVALID_PHONE, INVALID_OPTION). Only what the caller may see is checked. Values for invisible fields and unknown keys are discarded without an error.
Attachment fields do NOT take part in the validation — files are uploaded only after creation, so at creation time the requirement is not checkable. That is also why an attachment field cannot be saved as mandatory (400). Default values apply as long as nothing is entered: they appear in the response and are stored; a field with a default cannot be set to empty.
Relation to Workflows
A CustomForm can be assigned to one or more workflow templates (workflowTemplates relation). The WorkflowTemplate holds the rendered FormSchema as triggerSchema; submitted values land in the WorkflowInstance under data.trigger.*. DATA_COLLECTION steps use the same field structure and write to data.stepOutputs[stepName]. So the form supplies the FIELDS — a workflow is started through the catalog (user) or the API trigger (API key); submitting a form does not by itself start a workflow.
⚙️ Trigger types, step types and the instance data model: Workflows API →.
start dialog (triggerSchema), DATA_COLLECTION steps
Ticket creation uses /forms/available
settings.editGeneral grants form management
Permission matrix