Skip to content

Read a mutation confirmation

Resolve /api/workspace/... against WORKSPACE_API_BASE_URL, the installation API origin supplied by your administrator, never moltaro.com. Confirm this route in the answering installation’s Runtime OpenAPI before using it.

GET /api/workspace/entity-mutation-outcomes checks a previously saved operation handle. It performs no mutation. Save the handle before the first write and bind it to the installation and signed-in user. A lost handle cannot be reconstructed from this API.

OperationNumeric valueRequired parameters besides OperationKey
Create0EntityDefinitionId
Update1EntityDefinitionId, EntityInstanceId
Archive2EntityDefinitionId, EntityInstanceId
Restore3EntityDefinitionId, EntityInstanceId
Delete4EntityDefinitionId, EntityInstanceId
Composite5None

OperationKey is a nonempty UUID. Definition IDs are stable IDs, not mutable keys. Irrelevant IDs and unknown selector parameters are errors. Actor and workspace are server-derived; a key is not an access token.

For example, a Create needs no generated record ID:

GET ${WORKSPACE_API_BASE_URL}/api/workspace/entity-mutation-outcomes?Operation=0&OperationKey=9da1d307-0469-4319-b1ad-f63c7a28a5f5&EntityDefinitionId=definition-id

The standard success envelope contains:

{
"Success": true, "Errors": [], "Warnings": [],
"Data": {
"SchemaVersion": 1, "State": 1,
"Receipt": {
"CommittedAt": "2026-10-01T12:00:00+00:00",
"ExpiresAt": "2026-10-08T12:00:00+00:00"
},
"Result": {
"Kind": "Single", "EntityDefinitionId": "definition-id",
"EntityInstanceId": "record-id", "RowVersion": "c7207704-d0b5-4a67-826a-5d44a5c741d5"
}
}
}

State 1 is ConfirmedCommitted. The unexpired receipt confirms the operation in the selected scope. Result may be null after access loss, deletion or unreadable stored result data; confirmation remains valid. Composite returns all original ordered StepId/Kind/definition/record/version identities, or a null result if any target is now unreadable. No fields, labels, hidden IDs or hidden step count are returned. A step’s Kind is Create, Update, Archive or Delete; Archive and Delete steps come from a native Form save, whose unchanged root is not reported as a step. A Delete step has a null RowVersion and reports the record identity that was sent; it is not checked for readability, because the record no longer exists. Treat a Kind you do not recognize as an unreadable result, never as permission to write again.

State 0 is NoConfirmedReceipt, with Receipt and Result both null. The write might still be running, commit later or have an expired receipt. It never permits an automatic retry with the old or a new key. It does not mean Failed, Pending, NotApplied or rollback. A failure of this GET is a check error, not either known outcome.

Versions belong to the original operation, even after later edits. Use the ordinary secured record GET for current data before editing. Its failure does not undo the confirmed commit. Receipt timestamps are server-journalled UTC times, not an exact physical COMMIT time or a retry deadline.

The seven-day retention is unchanged; reads are allowed after expiry but cannot provide indefinite confirmation. Never reuse logical operation keys. Without the body this check does not prove equality to an arbitrary payload or resolve keyReuse. Existing exact-replay behavior is unchanged.

The helper takes a typed selector and AbortSignal, makes exactly one safe GET and rejects unknown/malformed states or schemas. It supplies no storage, polling, retries, key generation or record refresh. Storage callbacks below belong to your page and must complete before the first send.

import type { WorkspaceUiEntityMutationHandle } from '@moltaro/workspace-ui'
import { MoltaroApiClient, WorkspaceUiEntityMutationOperationEnum as Operation,
WorkspaceUiEntityMutationOutcomeStateEnum as State } from '@moltaro/workspace-ui'
const api = new MoltaroApiClient()
type SavedHandle = { InstallationId: string, ActorUserId: string, Handle: WorkspaceUiEntityMutationHandle }
export async function firstCreate (
installationId: string, actorUserId: string,
definition: { Id: string, Key: string, RowVersion: string },
values: Record<string, unknown>, persist: (saved: SavedHandle) => Promise<void>,
signal?: AbortSignal,
) {
const handle: WorkspaceUiEntityMutationHandle = {
Operation: Operation.Create, OperationKey: crypto.randomUUID(), EntityDefinitionId: definition.Id,
}
await persist({ InstallationId: installationId, ActorUserId: actorUserId, Handle: handle })
return api.requestData<unknown>(`/api/workspace/entity/${encodeURIComponent(definition.Id)}/instances`, {
method: 'POST', ...(signal ? { signal } : {}),
body: JSON.stringify({ OperationKey: handle.OperationKey, EntityDefinitionKey: definition.Key,
ExpectedDefinitionRowVersion: definition.RowVersion, Values: values, ResponsibilityCandidates: [] }),
})
}
// After reload, supply the saved handle and the current authenticated binding.
export async function checkAfterReload (saved: SavedHandle, installationId: string, actorUserId: string, signal?: AbortSignal) {
if (saved.InstallationId !== installationId || saved.ActorUserId !== actorUserId) {
throw new Error('Handle belongs to another session.')
}
const outcome = await api.readEntityMutationOutcome(saved.Handle, signal)
if (outcome.State === State.ConfirmedCommitted) {
return outcome
}
// Keep the unresolved operation for user investigation. Do not write or generate a new key.
return outcome
}

Workspace UI authoring disallows direct host globals such as window, localStorage and sessionStorage. Supply caller-owned persistence through a supported integration, for example ExternalApiClient to your own separately authenticated and authorized storage service. This helper adds no storage service.

The current-user C# Composite service’s receipts are read through this REST route under the same actor. A system automation receipt does not belong to the interactive initiator. There is no new .NET query gateway.

Regenerate API types from the installation’s OpenAPI. Workspace UI declaration additions change the host fingerprint while keeping HostContractVersion 1: Check -> Build -> Activate -> full reload existing projects. Old artifacts follow the normal incompatible-artifact policy. No receipt migration, retention change or .NET rebuild is required by this addition.

Workspace UI pages can let the host do all of this: Safe Entity writes (useEntityMutation) saves the handle in the browser tab before the first send, checks it once after a reload and never writes again on its own. The record lifecycle dialog uses the same mechanism. Other built-in forms keep their existing flow.