Skip to content

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

  • WORKSPACE_API_BASE_URL is the API host of the deployed installation, for example https://ops.example.com; all endpoints live under its /api/workspace/ path. It is not https://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 }. Check Success, then read Data. 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 as x-enum-descriptions).
  • User docs say record type and record; the API says entity definition and entity instance. See the terminology bridge.

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.

GET /api/workspace/context requires no authentication and returns the installation’s locale, time zone, and authoring mode:

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

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

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

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.

POST /api/workspace/entity/{entityIdOrKey}/instances takes a Fields map keyed by field key:

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 '{ "Fields": { "Title": "Printer in hall B is jammed", "DueDate": "2026-07-25",
"Priority": "high", "EstimatedHours": 1.5 } }'
{
"Data": {
"InstanceId": "c24878e1ff334648aeb4022e19f6cd96",
"RowVersion": "53cedb7e-dad6-425c-86c1-edd23dacfd59",
"ReadableAfterMutation": true,
"Instance": {
"Id": "c24878e1ff334648aeb4022e19f6cd96",
"Number": "1",
"DisplayName": "1",
"Fields": {
"Title": { "State": 0, "Value": "Printer in hall B is jammed" },
"DueDate": { "State": 0, "Value": "2026-07-25" },
"Priority": { "State": 0, "Value": "high" },
"EstimatedHours": { "State": 0, "Value": 1.5 }
},
"Permissions": { "CanRead": true, "CanUpdate": true, "CanArchive": true, "CanDelete": false }
}
},
"Success": true
}

Note the read shape: each field comes back as an envelope with a State (0 = value present, 1 = restricted — the caller may not read this field) and the Value. Permissions are server-computed for the calling user — trust them instead of guessing.

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 plus the RowVersion you last saw. Omitted keys stay unchanged; an explicit null clears a value:

Terminal window
curl -s -X PATCH https://ops.example.com/api/workspace/entity/SupportTicket/instances/c24878e1ff334648aeb4022e19f6cd96 \
-H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
-d '{ "Fields": { "Priority": "medium" }, "RowVersion": "53cedb7e-dad6-425c-86c1-edd23dacfd59" }'

A successful mutation returns the new RowVersion ("75ee761a-0dd1-44f5-ab36-33089bd37a6e" here) — keep it for the next write. A stale RowVersion fails the request instead of silently overwriting someone else’s change.

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

Field typeWrite value
Yes/Notrue / false
Date / Time"2026-07-25" / "14:30:00"
String / TextJSON string
Whole numberJSON number
DecimalJSON number or invariant decimal string on write; canonical invariant string padded to configured scale in Entity Instance field envelopes and form-evaluation patches
Selectoption key string; array of keys when the field allows multiple
ClassifierCatalog node id string from the Classifier Catalog bound to the field
User / Role / File / Referenceid string of the target
Inverse referencearray of source record ids
Money{ "Amount": 10.5, "CurrencyCode": "EUR" } — amounts come back as strings
Addressobject with FullAddress, street/locality/region/postal/country parts, optional Latitude/Longitude
Tablemanaged through table-row payloads, not a scalar value

Details per type: Fields and schema.

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:

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