Configuration quickstart
The Integration quickstart starts
from a workspace that already has a record type and shows how to discover and
use it. This page is the other half of the pair: it starts from an empty
workspace and creates that same SupportTicket record type through the
Configuration API — definition first, then schema fields, explicit search
targets, and a usable user-facing table, then the first record. Together the
two pages cover the full loop an AI agent needs: shape the data model and its
query surface, then operate on it.
All examples below were executed against a real Moltaro installation; response
bodies are real, trimmed for length. The complete administrative contract is in
the Configuration API reference
and in the raw OpenAPI document
(moltaro-config-v1.json, also
served by every installation at /openapi/moltaro-config-v1.json).
For an agent, that handoff must already contain the guide, API base URL, key, and required role. If it does not, ask the user to complete Agent integration in the Portal or administrative interface; do not create credentials or grant permissions through the API. Apply the conflict and destructive-change rules from Reliable API automation.
After completing the flat vertical slice here, use Configuring Parent Tree View for the self-Reference, index, default or explicitly selected Table Surface, Entity List selector, Lookup picker, and read-back workflow.
Prerequisites
Section titled “Prerequisites”- The caller needs the Admin or Configurator role. For unattended work use a service-account API key whose service user carries one of those roles; the examples below send it as a normal bearer token.
- Everything else from the
Integration quickstart conventions
applies: the
/api/workspace/base path, the{ "Data": …, "Errors": [], "Warnings": [], "Success": true }envelope, PascalCase property names, and numeric enums withx-enum-varnamesin the OpenAPI schema. - The business schema is provisioned immediately, but a new Entity Definition
is intentionally locked until its Security pages contain a valid enabled
Security Statement assigned to the root
ViewPermission. Destructive schema edits (field type changes, deletions) go through explicitschema-change-planpreview endpoints instead of applying silently.
Step 1 — Create the entity definition
Section titled “Step 1 — Create the entity definition”POST /api/workspace/admin/entity-definitions
(operation)
creates the record type. Name is the stable internal name that instance
endpoints later accept as {entityIdOrKey}; the display names are what users
see:
curl -s -X POST https://ops.example.com/api/workspace/admin/entity-definitions \ -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \ -d '{ "Name": "SupportTicket", "DisplayNameSingular": "Support ticket", "DisplayNamePlural": "Support tickets", "Description": "Demo record type for the integration quickstart." }'{ "Data": { "Id": "pYZqCLZWb0K1", "Name": "SupportTicket", "DisplayNameSingular": "Support ticket", "DisplayNamePlural": "Support tickets", "CommentsEnabled": true, "AttachmentsEnabled": true, "TagsEnabled": true, "SearchTargetCount": 0, "SearchTargets": [], "AuditTrailEnabled": true, "AuditTrailRetentionMode": 1, "Tables": [ { "Id": "yBxr7wo1xsNo", "SchemaName": "op", "PhysicalTableName": "SupportTicket", "IsPrimary": true, "Fields": [] } ], "RowVersion": "a244a235-1dc6-4e8b-81cf-2db6b972cd55" }, "Success": true}The response is the full definition detail, including the tables that were
provisioned with it. Keep the id of the table with "IsPrimary": true — that
is where schema fields are created in the next step. Note what the platform
turned on by default: comments, attachments, tags, and the audit trail with
unlimited retention (see
Record history and audit). Responsibilities
and assignment authority are authored in Entity Security rather than
created as Entity relations.
Search becomes available after explicit target paths are saved on the
definition’s Search tab.
Step 2 — Add schema fields
Section titled “Step 2 — Add schema fields”POST /api/workspace/admin/entity-definition-tables/{entityDefinitionTableId}/fields
(operation)
adds one field per call. Key, DisplayName, and FieldType are required;
everything else is per-type configuration. The Select field is the most
involved shape, so here it is in full:
curl -s -X POST https://ops.example.com/api/workspace/admin/entity-definition-tables/yBxr7wo1xsNo/fields \ -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \ -d '{ "Key": "Priority", "DisplayName": "Priority", "FieldType": 9, "RequiredWhen": "TRUE", "Options": [ { "Key": "low", "Value": "Low" }, { "Key": "medium", "Value": "Medium" }, { "Key": "high", "Value": "High" } ] }'{ "Data": { "Id": "l1eqb13B03NG", "EntityDefinitionId": "pYZqCLZWb0K1", "EntityDefinitionTableId": "yBxr7wo1xsNo", "Key": "Priority", "DisplayName": "Priority", "FieldType": 9, "RequiredWhen": "TRUE", "Options": [ { "Id": "3pflNS8LtMQ0", "Key": "low", "Value": "Low", "SortOrder": 0 }, { "Id": "1CK3fc4xlwuK", "Key": "medium", "Value": "Medium", "SortOrder": 1 }, { "Id": "6ZZoc0FtzRWA", "Key": "high", "Value": "High", "SortOrder": 2 } ], "RowVersion": "9fe783cb-b821-4252-800d-63245850b04d" }, "Success": true}The remaining three quickstart fields are one call each with the same shape:
| Field | Request body |
|---|---|
| Title (required string, max 200) | { "Key": "Title", "DisplayName": "Title", "FieldType": 10, "RequiredWhen": "TRUE", "StringMaxLength": 200 } |
| DueDate (date) | { "Key": "DueDate", "DisplayName": "Due date", "FieldType": 1 } |
| EstimatedHours (decimal 6,2) | { "Key": "EstimatedHours", "DisplayName": "Estimated hours", "FieldType": 6, "DecimalPrecision": 6, "DecimalScale": 2 } |
| ScheduledAt (exact instant) | { "Key": "ScheduledAt", "DisplayName": "Scheduled at", "FieldType": 17 } |
Relational and typed fields
Section titled “Relational and typed fields”Reference, Money, and Classifier fields each carry one extra key that points at an existing target — read the target definition or catalog id from the API first, never guess it:
| Field | Request body |
|---|---|
| Customer (single Reference to another definition) | { "Key": "Customer", "DisplayName": "Customer", "FieldType": 13, "ReferenceToEntityDefinitionId": "<customerDefinitionId>", "ReferenceDeleteBehavior": 2 } |
| Budget (Money in a base currency) | { "Key": "Budget", "DisplayName": "Budget", "FieldType": 15, "BaseCurrencyCode": "EUR" } |
| Category (Classifier from a catalog) | { "Key": "Category", "DisplayName": "Category", "FieldType": 16, "ClassifierCatalogDefinitionId": "<catalogDefinitionId>" } |
To expose the inverse side of a Reference on the target record (its
“referenced-by” list), create an Inverse reference field (FieldType 14) on
the target definition’s primary table. Both relation identifiers are required:
ReferenceToEntityDefinitionId is the id of the source (or child) definition
that owns the forward Reference field, and PairedReferenceFieldId is the id
of that forward field.
For example, if SupportTicket.Customer is a Reference to Customer, add the
inverse field to the Customer primary table with this complete request:
curl -s -X POST https://ops.example.com/api/workspace/admin/entity-definition-tables/<customerPrimaryTableId>/fields \ -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \ -d '{ "Key": "SupportTickets", "DisplayName": "Support tickets", "FieldType": 14, "ReferenceToEntityDefinitionId": "<supportTicketDefinitionId>", "PairedReferenceFieldId": "<customerReferenceFieldId>" }'Do not set ReferenceToEntityDefinitionId to the Customer definition where
the inverse field is being created. It points back to SupportTicket, the
definition that contains the forward Reference. Omitting it returns
moltaro.fields.reference.target.required; omitting
PairedReferenceFieldId returns
moltaro.fields.inverseReference.pairedField.required.
ReferenceDeleteBehavior is 0 No action, 1 Cascade, or 2 Restrict.
Omitting it while creating a Reference, or while changing another field to
Reference, defaults to Restrict. Omitting it from an ordinary field update
preserves the stored value. Restrict applies to primary- and child-table
References and returns HTTP 409 with
moltaro.instances.delete.referenced when a permanent delete is blocked.
Cascade is available only on a primary-table Reference. Archive behavior is a
separate setting.
YAML import follows the same omission rules: an omitted delete behavior on a new Reference becomes Restrict, while omission on a matched existing Reference preserves its stored value. YAML export writes the effective value explicitly. Blueprint-created and Package SDK Reference fields also default to Restrict; package-authored child-table references cannot use Cascade.
FieldType is the numeric EntityFieldTypeEnum — the same numbers the read
endpoints return: 10 String, 11 Text, 5 Integer, 6 Decimal, 0
Yes/No, 1 Date, 2 Time, 9 Select, 13 Reference, 14 Inverse reference,
3 User, 15 Money, 16 Classifier, 17 Date and time, 8 Address. A Date
and time value must be an ISO 8601 timestamp with an explicit Z or numeric
offset, for example "2026-07-27T10:15:42.123456+02:00". The API returns the
equivalent UTC instant; it does not preserve the submitted offset. The full
list with per-type semantics and write shapes is in
Fields and schema.
RequiredWhen: "TRUE" means always required; any other value is a conditional
expression.
Typed defaults for new records
Section titled “Typed defaults for new records”DefaultValue is schema metadata, not a DSL expression. It is applied only when
the field key is absent from an Entity Instance create request. For example:
{ "Key": "Active", "DisplayName": "Active", "FieldType": 0, "DefaultValue": { "Kind": 0, "Value": true } }The Kind values are 0 Constant, 1 CurrentDate, and 2
CurrentDateTime. CurrentDate is valid only for Date; CurrentDateTime is valid
only for Date and time. For a dynamic default, Value must be omitted or
null; every non-null value is invalid.
On a create form, CurrentDate is presented as an editable workspace-local
preview. The preview does not change the API contract: an untouched field is
still omitted and is resolved authoritatively when create is processed. A user
change or clear is sent explicitly. CurrentDateTime has no editable preview
because the exact instant remains server-resolved at create time.
Constant defaults support Yes/No, Whole number, Decimal, String, Text, Money, Select (one configured option key, or a non-empty array of unique configured keys for multi-select), scalar Reference, Date, Time, and Date and time. A Reference constant uses one target entity-instance ID string:
"DefaultValue": { "Kind": 0, "Value": "<entity-instance-id>" }The ID must identify an active actor-readable record in the field’s configured
target definition. Number and Display Name are UI-only resolved data. Runtime
create/new-child application rechecks current access, active state, Reference
Integrity, and Reference Eligibility and fails without partial writes when the
target is unavailable. Money uses the ordinary
{ "Amount": "12.34", "CurrencyCode": "EUR" }
shape. Its currency must be active in the workspace; when CurrencyCode is
omitted, null, or blank, the field’s active BaseCurrencyCode is used. Other
non-string values are invalid.
String and Text constants are trimmed like ordinary runtime values and cannot
be empty or whitespace-only.
Decimal accepts a JSON number or an invariant decimal string; JavaScript
clients should use the string form for values that exceed safe numeric
precision. The Entity Instance create/patch API accepts the same invariant
string form, so the browser can submit a high-precision Decimal without
rounding it through a JavaScript number. Entity Instance field envelopes and
form-evaluation patches return Decimal as a canonical invariant string padded
to the field’s configured scale.
Date and time constants require an explicit offset and are canonicalized to
UTC. Calculated and Line number fields cannot have defaults.
Omitting a field key during record creation asks the server to use the default.
Sending the key with null, false, 0, an empty string, or an empty array is
explicit caller input and suppresses it. Defaults also apply to missing fields
on newly created child rows, including rows added by a patch request. It never
defaults primary fields during patch/update and never changes existing child
rows or existing data.
Field updates are full configuration writes. Read and round-trip the current
DefaultValue when changing another property; send "DefaultValue": null to
remove it. Definition YAML carries the same shape as DefaultValue, using enum
names such as Constant or CurrentDate. A Reference constant remains
workspace-local: YAML and Package apply
preserve its exact ID and fail before mutation when the record is missing,
archived, or belongs to another target definition. Interactive import also
honors the current actor’s access; Number and Display Name are never portable.
See
Fields and schema
for the configurator behavior.
Step 3 — Read the definition back
Section titled “Step 3 — Read the definition back”The definition is immediately visible through the public API — this is the exact read that the Integration quickstart uses for discovery:
curl -s https://ops.example.com/api/workspace/entity-definitions/pYZqCLZWb0K1 \ -H "Authorization: Bearer <token>"{ "Data": { "Id": "pYZqCLZWb0K1", "Name": "SupportTicket", "Tables": [ { "Id": "yBxr7wo1xsNo", "IsPrimary": true, "Fields": [ { "Key": "Priority", "FieldType": 9, "RequiredWhen": "TRUE", "Options": [ { "Key": "low", "Value": "Low" }, { "Key": "medium", "Value": "Medium" }, { "Key": "high", "Value": "High" } ] }, { "Key": "Title", "FieldType": 10, "RequiredWhen": "TRUE", "StringMaxLength": 200 }, { "Key": "DueDate", "FieldType": 1 }, { "Key": "EstimatedHours", "FieldType": 6, "DecimalPrecision": 6, "DecimalScale": 2 } ] } ] }, "Success": true}Step 4 — Give records a display name
Section titled “Step 4 — Give records a display name”A field named Title does not automatically become the record label. Until a
display-name presentation rule is set, every record shows as its Number — note
the "DisplayName": "1" in the responses here. Configure the label with a
presentation rule through the same Configuration API:
PUT /api/workspace/admin/entity-definitions/{entityDefinitionId}/display-settings
The request carries PresentationRules (the display-name expression — for
example one that returns the Title field), an optional NumberPrefix,
DefaultSort, and the current RowVersion. See
Presentation rules for the expression grammar
and Display fields
for the concept.
Step 5 — Configure record search
Section titled “Step 5 — Configure record search”Fields do not become searchable merely because they contain text. Read the
latest administrative Entity Definition detail, keep its RowVersion, and
replace the complete set of search targets:
curl -s -X PUT https://ops.example.com/api/workspace/admin/entity-definitions/pYZqCLZWb0K1/search-targets \ -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \ -d '{ "SearchTargets": [ { "Path": [ { "FieldKey": "Title" } ] }, { "Path": [ { "SystemTarget": 0 } ] } ], "RowVersion": "<latest-row-version>" }'This makes the runtime search box match the Title field or computed display
name. The request replaces every existing target, and an empty list disables
search. Use Entity search for relation and
Table paths, system-target values, exact runtime SearchTerm payloads,
concurrency, strict Entity YAML v8, and the contracts that replaced the old full-text flags
and rebuild operations.
Step 6 — Configure the user-facing table query surface
Section titled “Step 6 — Configure the user-facing table query surface”Creating the schema does not complete a user-facing record type. Before writing the table configuration, read the installed release’s supported targets:
GET /api/workspace/admin/entity-definitions/{entityDefinitionId}/ui/table-surfaces/configuration/optionsGET /api/workspace/admin/entity-definitions/{entityDefinitionId}/ui/table-surfaces/{surfaceKey}/configurationSee the generated operations for
configuration options
and the
current table configuration.
Choose the ordered FilterItems, SortItems, and DefaultSort from the typed
targets returned by the options response, then replace the configuration with
PUT table configuration.
Do not guess field paths or infer query behavior from the column renderer.
For complete related-path examples and the distinction between query targets
and lookup breadcrumbs, read
Table filters and sorting.
Every visible business dimension that users reasonably need to segment or order
by—especially a status or another reference—should normally be present in both
FilterItems and SortItems. A root reference target such as Status is
distinct from its displayed value. Use Status/Name when the intended behavior
is label ordering. Use a business target such as Status/SortOrder only when
the referenced records actually contain meaningful values. Every selected path
must come from the options response.
After the PUT, GET the configuration again and verify localized labels,
visibility, VisibleByDefault, item ordering, typed sort paths, and
DefaultSort. Then apply at least one representative filter and one
representative sort in the Web Application. Check the returned total, every
visible row value, and visible ordering; appearing in the Filters or Sorting
drawer alone is not sufficient. Clear the temporary filter and restore the
intended default sort after testing.
Filters and sorts do not grant access. Repeat the applied-query check as an ordinary reader when possible, and confirm that server-side entity and record Security Statements still protect inaccessible fields, records, and result counts. Keep technical keys and paths in English, localize workspace-authored labels, and do not put credentials, secrets, or copied customer values in examples.
Step 7 — Configure Entity Security
Section titled “Step 7 — Configure Entity Security”Before any runtime record request, an Owner, Admin, or Configurator opens the Entity Definition and configures its three Security pages:
- create the required Security Statements;
- assign root
View, actions, and field Permissions to those Statements; - create Responsibility Definitions and assignment rules when the record type uses responsibilities.
Each successful save is atomic and immediately effective. There is no separate publish step. Owner/Admin/Configurator may edit this configuration but do not receive Entity Instance access unless the configured Statements grant it.
Step 8 — Create the first record
Section titled “Step 8 — Create the first record”Record creation now uses the replay-safe command contract. Send a new UUID as
OperationKey, the definition key, the latest definition RowVersion, the
caller-supplied values, and any StageOnCreate responsibility candidates:
curl -s -X POST https://ops.example.com/api/workspace/entity/SupportTicket/instances \ -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \ -d '{ "OperationKey": "154e7dc2-9f61-4ba4-a4ab-e1bd94183dfa", "EntityDefinitionKey": "SupportTicket", "ExpectedDefinitionRowVersion": "a244a235-1dc6-4e8b-81cf-2db6b972cd55", "Values": { "Title": "Printer in hall B is jammed", "DueDate": "2026-07-25", "Priority": "high", "EstimatedHours": 1.5 }, "ResponsibilityCandidates": [] }'All five properties are required:
OperationKeyis a non-empty caller-generated UUID for one logical create. Preserve it only for an exact retry after an unknown outcome. Reusing it with different input returns an idempotency conflict.EntityDefinitionKeymust identify the same definition as the route, by id or normalized key.Valuesis the field-key-to-JSON-value map. Send{}when the record has no caller-supplied values; field defaults and governed mutation rules remain server-owned.ResponsibilityCandidatesis the complete caller proposal for StageOnCreate assignment. Send[]when no candidates are proposed. It is required and must not be omitted ornull.ExpectedDefinitionRowVersionis the latest Entity DefinitionRowVersion. It protects the create from running against a schema that changed after the caller read it. On a conflict, re-read the definition and rebuild the request.
Clients written for the pre-0.0.20 payload must regenerate from the current
Runtime OpenAPI document. Replace Fields with Values, and replace the
arbitrary string MutationIdempotencyKey with the required UUID
OperationKey. Missing or null required members return HTTP 400 with a stable
error Code and the exact PascalCase property in Field; branch on those
values rather than localized messages.
{ "Data": { "Kind": "AuthorizedEntityDetail", "Data": { "EntityDefinitionId": "pYZqCLZWb0K1", "EntityInstanceId": "98520ecb203d4188abf10298a5a638e2", "RowVersion": "daefc569-83f4-4ca2-aa5d-071e7cfa3942", "AuthorizedFields": { "Priority": "high", "Title": "Printer in hall B is jammed", "DueDate": "2026-07-25", "EstimatedHours": 1.50 } }, "CanViewResult": true }, "Success": true}From here the Integration quickstart covers querying, updating with optimistic concurrency, and error handling.
Configure the workspace file-type policy
Section titled “Configure the workspace file-type policy”Workspace Owners and Administrators can read and update the same FileSystem
settings used by the Web Application through the Configuration API. A service
user with the Administrator role has the same access; other roles receive
403 Forbidden.
curl -s https://ops.example.com/api/workspace/filesystem/settings \ -H "Authorization: Bearer <token>"The authoritative FileTypeRules pair extensions with declared MIME types. A
file is accepted only when its longest matching extension and normalized
declared MIME appear in the same rule. Keep the RowVersion from GET and send
it back on PUT. PUT replaces the complete policy, so start with the rules from
GET, make the intended addition or edit, and send the full resulting array.
The shortened array below illustrates the request shape and would deliberately
restrict the workspace to only these three rules:
curl -s -X PUT https://ops.example.com/api/workspace/filesystem/settings \ -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \ -d '{ "FileTypeRules": [ { "Extensions": [".pdf"], "MimeTypes": ["application/pdf"] }, { "Extensions": [".dwg"], "MimeTypes": ["application/octet-stream", "application/acad", "application/x-dwg"] }, { "Extensions": [".fbd"], "MimeTypes": ["application/octet-stream"] } ], "TrashRetentionTimeHours": 720, "RowVersion": "<row-version-from-get>" }'The policy cannot be empty. Administrators may deliberately allow any
extension, including executable formats, and own that configuration decision.
This mechanism checks only the requested file name and declared MIME type. It
is not antivirus or malware inspection and cannot detect executable content
renamed to an allowed ambiguous format. AllowedMimeTypes in responses is a
deprecated compatibility summary and is not authoritative for new clients.
An upload rejected with filesystem.file.type.notAllowed exposes only the
normalized, bounded Extension and DeclaredMimeType plus the effective
FileTypePolicyVersion and FileTypePolicyHash in error metadata. The same
version/hash are returned by settings and upload preflight so operators can
correlate a decision across API and Worker processes. This metadata does not
expose file content or storage-provider details.
When configuration fails
Section titled “When configuration fails”Configuration errors come back in the same envelope with stable machine-readable codes. Creating a second definition with the same name:
{ "Data": null, "Errors": [ { "Code": "moltaro.definitions.name.exists", "Message": "An entity definition with this name already exists.", "Field": "Name", "Type": 1, "Severity": 2 } ], "Success": false}Branch on Code and Field, not on Message. The configuration error
catalog is part of the
error code reference.
Where to go next
Section titled “Where to go next”- Boards configuration walkthrough — put the new record type on a governed process board.
- Record history and audit — audit settings and the record change feed.
- Validation rules and the expression language — declarative guards on top of the schema.
- Entity Security Statements — the definition-scoped
Security Administration operations
control
View, actions, fields, responsibilities, and Assignment Rules. - Data transfer — bulk import/export once the schema is in place.