Skip to content

Entity lifecycle and reference candidates

All /api/workspace/... routes below resolve against WORKSPACE_API_BASE_URL, the installation API origin supplied by the administrator. They do not resolve against moltaro.com. Workspace UI code uses its authenticated Moltaro API client.

An administrator with existing access can read GET /api/workspace/admin/agent-integration. Record Data.ProductVersion and Data.BuildId, then use Data.PublicOpenApiUrl and Data.ConfigurationOpenApiUrl for the installation’s Runtime and Configuration schemas. A source build without package identity returns null version/build values; older installations may omit these properties. Neither case identifies a release. Ordinary users do not gain administrative access through this endpoint.

Without administrative access, any active user or Service user can read GET /api/workspace/error-catalog: its ProductVersion and BuildId identify the answering build, and its entries describe the error codes of the mutations below and of the composite Entity mutation for that build. See Installed error catalog.

Data.ReleaseNotesVersionUrl, when present, links the installed release note. The public documentation and /docs/llms-manifest.json describe the documentation deployment, not the answering API or independently updated WebApp/Worker. These examples require a build containing the documented contract; confirm its local schema before applying them to an older installation. Do not provision new access merely to run these examples.

To check a lost write response with a handle saved before sending, use Read a mutation confirmation. This GET needs no original body and adds no write. Absence of a confirmed receipt is never permission to retry the mutation.

OperationMethod and suffix after /api/workspace/entity/{entityIdOrKey}/instances/{instanceId}Required state
ArchiveDELETE /archiveActive record
RestorePOST /restoreArchived record
Permanent deleteDELETEArchived record; referencing Restrict relationships may block deletion

Each operation takes this JSON body. Replace the illustrative identities and versions with values discovered from the installation:

{
"OperationKey": "b84c10ed-6824-4a59-989f-f2f8117cf0c1",
"EntityDefinitionKey": "SupportTicket",
"EntityInstanceId": "record-id-from-get",
"RowVersion": "53cedb7e-dad6-425c-86c1-edd23dacfd59",
"Reason": "Duplicate confirmed by the record owner"
}

Reason is required for all three operations, including privileged callers. Missing, null, empty, and whitespace-only values fail. After trimming leading and trailing whitespace, the reason must contain 1–1000 characters. The length rule applies to the trimmed value; it is not a raw-JSON maxLength rule. A reason does not grant permission or bypass lifecycle, relation, or concurrency checks. The trimmed reason is stored with the operation; the record change feed returns it as Lifecycle.Reason on archive and restore entries. See Lifecycle reasons.

Read the record first and send its current RowVersion. Use a new OperationKey for each new logical operation. Reuse a key only for an exact retry of the same request after an uncertain outcome: for seven days after the first execution the retry confirms the committed operation with IsIdempotentReplay: true and writes nothing again. Reusing a key with a different request fails with HTTP 409 moltaro.security.idempotency.keyReuse. After archive or restore, use the new authorized row version before the next operation. An AuthorizedEntityDetail result contains Data.Data.RowVersion; a committed Receipt after loss of read access contains no record values or row version. A retry after you lost read access to the record, or after it was permanently deleted, also answers a Receipt. Do not treat that receipt as a failed write or invent a version. A retry after the operation was disabled for the record type answers HTTP 409 moltaro.instances.operation.disabled instead of the stored result. Permanent delete returns a receipt. See mutation results.

Create, update, archive, restore, and permanent delete check the request in one order: the body, OperationKey, the definition and record of the route, then the members of the operation. The first failure returns HTTP 400 with Success: false and one structured error whose Field is the public request member:

Errors[].CodeFieldAction
moltaro.request.requiredRequestSend a JSON body
moltaro.security.operationKey.requiredOperationKeySend a new UUID for each logical operation
moltaro.request.requiredEntityDefinitionKey, EntityInstanceId, RowVersion, Patch, Values, ResponsibilityCandidates, ExpectedDefinitionRowVersionSupply the member; a missing or empty UUID counts as missing
moltaro.request.invalidEntityDefinitionKey, EntityInstanceIdSend the definition and record of the route
moltaro.instances.reason.requiredReasonSupply a nonblank reason
moltaro.instances.reason.tooLongReasonReduce the trimmed reason to 1000 characters
moltaro.request.binding.invalidSafe request field, else RequestCorrect malformed JSON, unsupported properties, or value types using local OpenAPI

JSON that cannot be bound is answered before the route is resolved, so it can precede a 404 for an unknown entity; field keys inside Values and Patch are reported on their member and never echoed. A malformed Guid is a binding failure; a well-formed stale version is a concurrency conflict (HTTP 409 moltaro.security.concurrency.staleRowVersion with Field RowVersion), not an instruction to force the write: read the record again. HTTP 409 moltaro.instances.conflict means that a concurrent write held the record; nothing was written, and the identical request can be repeated after a delay. Server authorization remains authoritative even when a menu previously showed the action as allowed. The installed error catalog lists these codes with their Field rules and reactions. A composite Entity mutation that creates or updates several records in one transaction checks its steps with its own request rules and names the failing step in Field.

The prefix in this table is /api/workspace/entity/{entityIdOrKey}/ui. For Form/Table routes it identifies the source entity; for the generic picker it identifies the target entity. IDs and revision tokens come from the current saved runtime surface, not guessed field names or an unsaved Constructor draft.

ContextRead route after prefixContext and confirmation
Saved Form List or LongListPOST /form/{formSurfaceId}/items/{formItemId}/reference-candidates/queryExpectedFormSurfaceRowVersion, SourceInstanceId, allowed CurrentValues; confirm through the same prefix ending /reference-candidates/selection
Saved Form flat LookupPOST /form/{formSurfaceId}/items/{formItemId}/lookup-table/bootstrapSaved Lookup item and its revision; table preflight identity when supplied; confirm through anchored /reference-candidates/selection
Saved Form Tree LookupSame bootstrap, then /lookup-table/tree/childrenUse the returned tree context and /lookup-table/tree/selection; navigation-only nodes cannot be confirmed
Saved Table reference filterPOST /table/{tableSurfaceId}/filters/{filterItemKey}/reference-candidates/queryExpectedTableSurfaceRowVersion, bounded DraftFilterTree; confirm through sibling /reference-candidates/selection
Generic target pickerPOST /reference-picker/table/bootstrapTarget entity and role-resolved table, optionally tableSurfaceKey; has no source Form field/draft eligibility context

The anchored candidate query/selection also supports a flat Lookup item, but only Lookup input mode supports lookup-table bootstrap. List/LongList cannot consume a Tree table. These are supported-context differences, not interchangeable URLs.

CurrentValues is a direct map of server-declared writable draft fields to values; scalar references use raw IDs. Do not add a Fields wrapper. Use the runtime ReferenceContext.CurrentValueInputs allow-lists for Create/Update and any owned locked-create inputs. Omitted server-owned values are reconstructed by the server. An editable effect target can still be a permitted user-owned draft input: follow the operation’s allow-list and its current field origin, not the mere existence of an effect. Do not submit hidden, non-allowed, or currently server-owned values as user overrides; calculated targets are not draft inputs.

Read the response’s State and, when present, ErrorCode before using Items. A successful HTTP response can describe ContextIncomplete, ConditionNotMet, DependencyUnavailable, or another non-ready state; an empty list alone does not mean that no records exist. Stale surface revisions return a conflict; refresh the surface and rebuild its context. Wrong input mode is a validation failure. Selection is independently revalidated and may no longer be eligible.

Never retry a failed anchored request through the generic picker to bypass the context. Generic visibility is not proof of source-field eligibility. Use the anchored selection check before updating a Form draft, and let the save command validate the final write again.

For the anchored candidate query, send:

{
"ExpectedFormSurfaceRowVersion": "0d834bd9-dc7a-438a-9bf6-005434d3b901",
"SourceInstanceId": null,
"CurrentValues": {},
"Query": {
"Search": "river",
"Page": 1,
"PageSize": 250
}
}

Use the saved Form revision and fill the allowed current-value inputs required by that actual context. SourceInstanceId: null means Create; use the existing source record ID for Update. Query.Search is canonical. Query.SearchText is rejected.

  • Omitted, null, empty, or whitespace-only Search adds no search predicate.
  • Ordinary search accepts 3–256 trimmed characters and uses configured SearchTargets. Matching is case-insensitive substring search; %, _, and \ are literals. Exact #number and #{id} shortcuts follow the existing Entity search rules.
  • Nonblank search on an entity without configured search targets returns moltaro.search.notAvailable. It does not silently search all columns.
  • Search, an optional ordinary Query.Filter, eligibility and access rules are intersected. Candidate filtering occurs before pagination; counts describe the authorized result, and an unmatched valid search returns an empty Ready page.
  • Query.Page defaults to 1 and must be at least 1. Query.PageSize defaults to 25 and must be 1–250. Omission uses the default; zero, negative, null, a wrong type, or a value above 250 is not a request for an unlimited page.
  • A Ready result returns Page, PageSize, TotalCount, and Items. If Page * PageSize < TotalCount, request the next page with the same search, filter, sort and current-value context. Stop at the total; a page beyond the result is empty. Requery after changes: separate requests are not one immutable snapshot. The 250 limit applies to one page, not the entire accessible set.

Out-of-range integers return moltaro.referenceCandidates.page.invalid on Query.Page or moltaro.referenceCandidates.pageSize.invalid on Query.PageSize. Missing/null Query returns moltaro.request.required on Query. Malformed JSON, a wrong type, or an unknown property returns moltaro.request.binding.invalid; error fields are bounded public paths and messages do not echo submitted values. Read codes and fields, not translated text.

These strict rules belong to candidate queries. Ordinary lists and flat lookup bootstraps have their existing page normalization and effective Table Surface defaults; their request shape uses top-level Page, PageSize, and Filter. They do not accept the candidate Query.Search envelope. Express search there with the existing SearchTerm filter in the Entity Instance Query contract.