Integration quickstart
This page shows the shortest reliable path from “I have a Moltaro URL and a user” to reading and changing records through the public API. It is written for developers and for AI agents: every Moltaro workspace describes itself through the same endpoints, so one flow works against any installation — discover the schema first, then act on it.
All examples below were executed against a real Moltaro installation; response
bodies are real, trimmed for length. The complete contract is in the
API reference and in the raw OpenAPI document
(moltaro-public-v1.json, also
served by every installation at /openapi/moltaro-public-v1.json).
Conventions
Section titled “Conventions”WORKSPACE_API_BASE_URLis the API host of the deployed installation, for examplehttps://ops.example.com; all endpoints live under its/api/workspace/path. It is nothttps://moltaro.com.- Authentication is a bearer token:
Authorization: Bearer <token>. Tokens come from the login endpoint or from an administrator-issued API key (see Service accounts and API keys). - JSON responses use an envelope:
{ "Data": …, "Errors": [], "Warnings": [], "Success": true }. CheckSuccess, then readData. The exceptions are file downloads (raw file content) and low-level protocol failures such as malformed JSON or an unknown route, which return a plain HTTP problem response instead of the envelope. - JSON property names are PascalCase. Enums are numeric in JSON; the OpenAPI
schema carries the member names as
x-enum-varnames(and, where provided, member descriptions asx-enum-descriptions). - User docs say record type and record; the API says entity definition and entity instance. See the terminology bridge.
Service accounts and API keys
Section titled “Service accounts and API keys”Unattended integrations and AI agents should not run on a person’s login. The credential handoff is a one-time administrator action in the Moltaro Portal or administrative Web Application interface. Installing or registering the Web Application and provisioning access are not part of an agent’s API workflow.
For a trusted AI agent, the administrator opens Administration > Agent
integration, selects Create service user for agent, and confirms the
high-privilege handoff. Moltaro creates a new Service user with the Admin and
Configurator roles, issues a 90-day API key, and downloads a ZIP containing
AGENTS.md, CLAUDE.md, .moltaro/credentials.env, and a nested
.moltaro/.gitignore. The key appears only in that download.
Extract the kit into the intended project, delete the ZIP, and keep
.moltaro/credentials.env local. Never commit, upload, paste, echo, or print
the credentials file or MOLTARO_API_KEY. Other unattended integrations can
still use Administration > Users to create a narrowly scoped Service user and
API key manually.
An agent receives the prepared guide, URL, and token. If they are missing, it asks the user to complete Agent integration in the Portal or administrative interface. It does not create its own service user, issue a key, change roles, or use administrative Agent integration endpoints to discover an unknown workspace.
A service key is gated by roles only — exactly like a human user. With the runtime roles a key covers the record-centric integration surface; with the Admin or Configurator role it can also use the administrative configuration API, including creating and changing entity definitions. Grant the narrowest role set that covers the integration’s task. The one exception is account self-service (profile, password): service accounts have nothing to manage there, and those endpoints answer them with an explicit error.
The curated administrative surface is documented in the Configuration API reference, starting with entity definition and schema field management.
The generated Markdown files contain the installation’s real base URL, the
discovery flow, secret-handling rules, and links to the installation-local
OpenAPI documents. This is a completed administrative handoff, not an agent
task. The agent-integration routes are administrative endpoints of the
configured Moltaro Application, not the Portal. An authorized agent may call
the read endpoints after it already knows WORKSPACE_API_BASE_URL and has a
key; it must not call the access-kit endpoint to provision or elevate itself.
For retry, concurrency, polling, recovery, and audit behavior, apply Reliable API automation.
Step 1 — Read the workspace context
Section titled “Step 1 — Read the workspace context”GET /api/workspace/context requires no authentication and returns the
installation’s locale, time zone, and authoring mode:
curl -s https://ops.example.com/api/workspace/context{ "Data": { "DisplayName": "Acme Operations", "Locale": "en", "TimeZone": "Etc/UTC", "RegionCode": "us", "EnabledFeatures": [], "GoogleMapsApiKey": null, "BusinessLogicAuthoringMode": "NetOperationProject" }, "Errors": [], "Warnings": [], "Success": true}Step 2 — Authenticate
Section titled “Step 2 — Authenticate”POST /api/workspace/auth/login exchanges credentials for an access token.
(For unattended integrations and agents, prefer a
service-account API key and skip this step —
API keys are sent the same way, as bearer tokens.)
curl -s -X POST https://ops.example.com/api/workspace/auth/login \ -H "Content-Type: application/json" \ -d '{ "UserNameOrEmail": "owner@acme.test", "Password": "<password>" }'{ "Data": { "AccessToken": "<jwt-access-token>", "UserId": "f4ac8709-c8ac-4404-a30a-7ddbcfcc8115", "UserName": "owner", "WorkspaceName": "default", "Locale": "en", "TimeZone": "Etc/UTC", "Roles": ["Admin", "Everyone"], "Modules": { "Boards": { "Enabled": false } } }, "Success": true}Send the token on every following request:
-H "Authorization: Bearer <jwt-access-token>". GET /api/workspace/auth/me
returns the same profile for an existing token.
Step 3 — List entity definitions
Section titled “Step 3 — List entity definitions”GET /api/workspace/entity-definitions
returns every record type the caller may see — the workspace’s data model:
{ "Data": [ { "Id": "I0ub2IyjFcx4", "Name": "SupportTicket", "DisplayNameSingular": "Support ticket", "DisplayNamePlural": "Support tickets", "Description": "Demo record type for the integration quickstart.", "CommentsEnabled": true, "AttachmentsEnabled": true, "TagsEnabled": true, "SearchTargetCount": 0, "AuditTrailEnabled": true, "IsReadOnly": false, "DisabledOperations": 0, "UsageType": 0 } ], "Success": true}Name is the stable internal name. Instance endpoints accept either the Id
or this name as {entityIdOrKey}.
Entity Definition discovery does not imply record access. Every record query
and direct read is constrained by the definition’s current Security configuration.
Missing, invalid, disabled, or stale root View Permission Assignments fail closed.
Step 4 — Read one definition to learn its schema
Section titled “Step 4 — Read one definition to learn its schema”GET /api/workspace/entity-definitions/{entityDefinitionId}
returns the full definition, including tables and fields. This is what tells an agent which
field keys exist, their types, and their rules:
{ "Data": { "Id": "I0ub2IyjFcx4", "Name": "SupportTicket", "Tables": [ { "Id": "oGp2IzJVF8Sn", "IsPrimary": true, "Fields": [ { "Key": "Title", "FieldType": 10, "RequiredWhen": "TRUE", "StringMaxLength": 200 }, { "Key": "DueDate", "FieldType": 1 }, { "Key": "Priority", "FieldType": 9, "Options": [ { "Key": "low", "Value": "Low" }, { "Key": "medium", "Value": "Medium" }, { "Key": "high", "Value": "High" } ] }, { "Key": "EstimatedHours", "FieldType": 6, "DecimalPrecision": 6, "DecimalScale": 2 } ] } ] }, "Success": true}FieldType is the numeric EntityFieldTypeEnum (10 = String, 1 = DateOnly,
2 = TimeOnly, 9 = Select, 6 = Decimal, 13 = Reference, 17 = DateTimeOffset,
…). The full list with
semantics is in
Fields and schema.
RequiredWhen: "TRUE" means always required; other values are conditional
expressions.
DateTimeOffset values are ISO 8601 timestamps with an explicit Z or numeric
offset. For example, "2026-07-27T10:15:42.123456+02:00" is returned as the
same instant in UTC: "2026-07-27T08:15:42.123456Z". The API and database
preserve microseconds, but not the sender’s original offset. Never send a local
timestamp without an offset.
Step 5 — Create a record
Section titled “Step 5 — Create a record”POST /api/workspace/entity/{entityIdOrKey}/instances uses a caller-generated
OperationKey, the definition identity and current definition row version,
plus a Values map keyed by field key:
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": [] }'{ "Data": { "Kind": "AuthorizedEntityDetail", "Data": { "EntityDefinitionId": "pYZqCLZWb0K1", "EntityInstanceId": "c24878e1ff334648aeb4022e19f6cd96", "RowVersion": "53cedb7e-dad6-425c-86c1-edd23dacfd59", "AuthorizedFields": { "Title": "Printer in hall B is jammed", "DueDate": "2026-07-25", "Priority": "high", "EstimatedHours": 1.5 } }, "IsIdempotentReplay": false, "CanViewResult": true }, "Success": true}A successful create always has Data.Kind: "AuthorizedEntityDetail". Its
nested Data.EntityInstanceId and Data.RowVersion are the stable values for
the next read or write. If the caller cannot read the final record, creation is
rolled back and the endpoint returns 403; POST does not return a safe receipt.
Preserve OperationKey only for an exact retry after an unknown outcome. An
exact retry by the same caller with the same OperationKey and body, within
the seven-day receipt retention, returns the original result with
IsIdempotentReplay: true instead of creating a second record. The replay
still answers 201 Created, so check the flag, not the status code. It
returns the snapshot taken at creation, whose RowVersion may be out of
date. A caller that can no longer read the created record, or one of its
stored fields, gets 403 with moltaro.instances.replay.resultNotViewable
and no record id: the record was created, so do not retry with a new key,
which would create a second record. Reusing the key with a different body
fails with 409 and moltaro.security.idempotency.keyReuse.
Step 6 — Query records
Section titled “Step 6 — Query records”POST /api/workspace/entity/{entityIdOrKey}/instances/query supports paging,
a nested filter tree, sorting, and reference includes:
For filters and sorts that traverse references, use Target.Path instead of
the direct Field property. The
Entity Instance Query guide gives
complete payloads, limits, reference-ID operand rules, access semantics, and
saved/shared-view behavior.
{ "Page": 1, "PageSize": 25, "Filter": { "Operator": 0, "Conditions": [ { "Field": { "FieldKey": "Priority" }, "Operator": 0, "Values": [ { "Value": "high" } ] } ] }, "Sort": [ { "Field": { "FieldKey": "DueDate" }, "Direction": 0 } ]}The response is a page: { "Items": [...], "TotalCount": 1, "Page": 1, "PageSize": 25, "Permissions": { "CanCreate": true, … } }. Condition
Operator is numeric (0 = Eq, 7 = In, 9 = Contains, 13 = IsNull, …);
the full operator list is in the
query operation
schema. A single record is read with
GET .../instances/{instanceId}.
Step 7 — Update with optimistic concurrency
Section titled “Step 7 — Update with optimistic concurrency”PATCH /api/workspace/entity/{entityIdOrKey}/instances/{instanceId} sends only
the fields to change in Patch, the RowVersion you last saw, and the stable
identities for this logical operation. Omitted keys stay unchanged; an explicit
null clears a value:
curl -s -X PATCH https://ops.example.com/api/workspace/entity/SupportTicket/instances/c24878e1ff334648aeb4022e19f6cd96 \ -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \ -d '{ "OperationKey": "138f4d41-a572-4bd4-86b6-af8c41ca301c", "EntityDefinitionKey": "SupportTicket", "EntityInstanceId": "c24878e1ff334648aeb4022e19f6cd96", "Patch": { "Priority": "medium" }, "RowVersion": "53cedb7e-dad6-425c-86c1-edd23dacfd59" }'A successful PATCH returns Data.Kind: "AuthorizedEntityDetail" and a new
Data.Data.RowVersion when the caller may read the final state. If the write
commits but the final state is no longer readable, the endpoint instead returns
Data.Kind: "Receipt" with OperationKey, CompletedAt,
CallerKnownIdentity (EntityDefinitionId and EntityInstanceId),
AuditCorrelationId, IsIdempotentReplay, Success: true, and
CanViewResult: false. A receipt deliberately contains no RowVersion or
record values. Treat it as proof that the operation committed; obtain a fresh
authorized record before another write. The same two HTTP result variants
apply to archive and restore. A stale RowVersion fails the request instead
of silently overwriting someone else’s change.
A successful PATCH that sends at least one writable stored field (a scalar or
a table field; an inverse reference whose membership is unchanged does not
count) creates a new record version even when every sent value equals the
stored one; unchanged values are written, not skipped. The record gets a new
RowVersion, its modified and last-activity stamps advance, and other clients
that still hold the previous RowVersion get a conflict on their next write.
The save also runs as a real update: save functions and the before-save changes
that use a sent field as a trigger run again,
update triggers
fire, and the record history gets an update entry when the audit trail is
enabled. Read-only fields are skipped with a warning and calculated fields are
ignored. On a record type without calculated or line-number fields, a PATCH
that sends only such fields (or an empty Patch) creates no version, but it is
still an accepted update and update triggers run. On a record type with
calculated or line-number fields, every accepted PATCH, even an empty one,
creates a new version because those values are written again. An exact retry
with the same OperationKey and body returns the original result, marked
IsIdempotentReplay: true, without writing again; current visibility is
rechecked. When the caller can no longer read the record, or it was deleted,
the retry answers a Receipt without record values instead. The same holds
for archive, restore and delete. Send only the fields that actually change,
and send no request when nothing changed.
Error handling
Section titled “Error handling”Failures return the same envelope with Success: false and structured errors.
This 400 came from creating a record without the required Title:
{ "Data": null, "Errors": [ { "Code": "moltaro.instances.validation.requiredFields", "Message": "Fill in all required fields and try again.", "Field": null, "Type": 1, "Severity": 2 }, { "Code": "moltaro.instances.field.required", "Message": "This field is required.", "Field": "Title", "Type": 1, "Severity": 2 } ], "Success": false}Code is stable and machine-readable; Field names the offending field key or
PascalCase request property; Message is localized for display. Agents should
branch on Code and Field, not on Message. A malformed or incomplete
create, update, or lifecycle request answers one error in a fixed order; see
request errors. The error shape is documented
in Errors and responses, and a curated catalog of
core entity and record-write codes is in the
error code reference. For create, update,
archive, restore, delete and the composite mutation, read
GET /api/workspace/error-catalog from the
installation you call: it gives each code’s HTTP status, Field and safe
reaction for that build, and says how to treat a code it does not list.
Field value shapes when writing
Section titled “Field value shapes when writing”| Field type | Write value |
|---|---|
| Yes/No | true / false |
| Date / Time | "2026-07-25" / "14:30:00" |
| String / Text | JSON string |
| Whole number | JSON number |
| Decimal | JSON number or invariant decimal string on write; canonical invariant string padded to configured scale in Entity Instance field envelopes and form-evaluation patches |
| Select | option key string; array of keys when the field allows multiple |
| Classifier | Catalog node id string from the Classifier Catalog bound to the field |
| User / Role / File / Reference | id string of the target |
| Inverse reference | array of source record ids |
| Money | { "Amount": 10.5, "CurrencyCode": "EUR" } — amounts come back as strings |
| Address | object with FullAddress, street/locality/region/postal/country parts, optional Latitude/Longitude |
| Table | managed through table-row payloads, not a scalar value |
Details per type: Fields and schema.
Invoke business logic
Section titled “Invoke business logic”Workspaces can publish their own C# business logic as callable functions.
Which keys exist is workspace-specific — publications are part of the
workspace configuration, so discover them per installation. Accounts with the
Admin role can list every function and its publications through the
Function Catalog in the
Configuration API
(GET /api/workspace/admin/function-catalog); other integrations receive
their callable keys from the workspace administrator.
A command runs synchronously and returns its result in the response.
POST /api/workspace/commands/{functionKey} takes optional Args (JSON
matching the command’s argument type) and an optional CorrelationId; an
empty body is allowed for commands without arguments:
curl -s -X POST https://ops.example.com/api/workspace/commands/close-overdue-tickets \ -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \ -d '{ "Args": { "GraceDays": 3 }, "CorrelationId": "req-8842" }'{ "Data": { "RunId": "b6f3e0d8a3c14f7f9a1de2c07f6b4a51", "Message": "Closed 4 overdue tickets.", "Result": { "ClosedCount": 4 }, "DurationMs": 118, "CorrelationId": "req-8842" }, "Success": true}Each publication either allows any authenticated user or requires a specific
permission; callers without it get 403. Unknown, disabled, and retired keys
all return the same 404 (moltaro.functionCommands.notFound), so command
keys cannot be probed.
Long-running logic is published for asynchronous execution instead.
POST /api/workspace/functions/{functionKey}/enqueue accepts optional Args,
a DeduplicationKey (reuses an active queued job), RunAfter (earliest
execution time), and CorrelationId, and returns the queued job:
{ "Data": { "JobId": "0d2f6c1e58b747a2a9c3f1b4e6d80a97", "FunctionKey": "rebuild-reporting-snapshots", "Status": 0, "CreatedAt": "2026-07-22T09:14:03+00:00", "NextRunAt": null, "AttemptCount": 0, "DeduplicationKey": null, "CorrelationId": null, "LastError": null, "CanCancel": true, "Run": null }, "Success": true}Poll GET /api/workspace/functions/jobs/{jobId} until Status reaches a
terminal value (0 = Queued, 1 = Leased, 2 = Completed, 3 = Failed,
4 = Cancelled). A finished job carries its latest execution under Run,
including the structured ResultData payload and a bounded ErrorSummary on
failure. While CanCancel is true,
POST /api/workspace/functions/jobs/{jobId}/cancel cancels the queued job.
Job visibility matches invocability: the initiator and every holder of the
publication permission can read and cancel the job.
The full contract — publication and permission model, request options, job lifecycle, and error codes — is in Commands and API functions.
Where to go next
Section titled “Where to go next”- Entity UI returns the configured list, details, drawer, card, and form payloads when an integration should render records the way the product does.
- Entity Data Transfer moves record data in bulk through admin-defined profiles.
- The Configuration quickstart
creates the entity definition this page discovers — schema authoring through
the Configuration API
(raw contract:
moltaro-config-v1.json). - The Boards walkthroughs cover process automation end to end: board configuration and items and moves.
- The Entitlement Operations walkthrough goes from module setup to grant, consume, and the ledger.
- Record history and audit reads the change feed this page’s create and update produced.
- Composite Entity mutations create and update several records in one transaction when a failed second write must not leave the first one saved.
- AI agents can start from
/llms.txtfor a machine-oriented map of this documentation, or fetch the complete documentation as one Markdown file at/docs/llms-full.txt(abridged variant:/docs/llms-small.txt).