Skip to content

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.

  • 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 with x-enum-varnames in 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 View Permission. Destructive schema edits (field type changes, deletions) go through explicit schema-change-plan preview endpoints instead of applying silently.

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:

Terminal window
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.

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:

Terminal window
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:

FieldRequest 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 }

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:

FieldRequest 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:

Terminal window
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.

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.

The definition is immediately visible through the public API — this is the exact read that the Integration quickstart uses for discovery:

Terminal window
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
}

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.

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:

Terminal window
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/options
GET /api/workspace/admin/entity-definitions/{entityDefinitionId}/ui/table-surfaces/{surfaceKey}/configuration

See 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.

Before any runtime record request, an Owner, Admin, or Configurator opens the Entity Definition and configures its three Security pages:

  1. create the required Security Statements;
  2. assign root View, actions, and field Permissions to those Statements;
  3. 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.

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:

Terminal window
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:

  • OperationKey is 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.
  • EntityDefinitionKey must identify the same definition as the route, by id or normalized key.
  • Values is 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.
  • ResponsibilityCandidates is the complete caller proposal for StageOnCreate assignment. Send [] when no candidates are proposed. It is required and must not be omitted or null.
  • ExpectedDefinitionRowVersion is the latest Entity Definition RowVersion. 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.

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.

Terminal window
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:

Terminal window
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.

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.