Skip to main content

Entities & Custom Fields

Explore the API
Launch the OpenAPI Explorer to browse the live REST specs, inspect request and response schemas, and execute calls against your environment with an API key.

Entity extensibility is central to Open Mercato. Every endpoint published by the module lives under /api/entities/*; the tables below list the suffix you append to your API origin.

export BASE_URL="http://localhost:3000/api"
export API_KEY="<paste your API key secret here>"
export ENTITY_ID="example.todo" # generated or custom entity id
export CUSTOM_ENTITY_ID="<uuid from POST /entities/entities>"
export RECORD_ID="<record uuid>"

Provision an API key with the required entities.* features as described in Managing API keys. Feature flags are defined in packages/core/src/modules/entities/acl.ts:1.

Heads-up: The dispatcher checks feature requirements exported from each route’s metadata. If a call returns 403, confirm the feature listed in the section header is granted to the caller.

Custom entity catalogue — /entities/entities

List entities — GET /entities/entities

  • Auth-only guard; no feature requirement (packages/core/src/modules/entities/api/entities.ts:8).
  • Returns generated MikroORM entities plus tenant/org scoped overrides with field counts.
curl -X GET "$BASE_URL/entities/entities" \
-H "X-Api-Key: $API_KEY" \
-H "Accept: application/json"

Upsert custom entity — POST /entities/entities

  • Feature: entities.definitions.manage.
  • Body matches upsertCustomEntitySchema (packages/core/src/modules/entities/data/validators.ts:72):
    • entityId (string) identifies the logical entity (e.g. custom.ticket).
    • Display metadata: label, optional description, labelField, defaultEditor, showInSidebar.
    • Flags: isActive (default true).
curl -X POST "$BASE_URL/entities/entities" \
-H "X-Api-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"entityId": "custom.ticket",
"label": "Support Ticket",
"description": "Tenant defined issue tracker",
"labelField": "title",
"defaultEditor": "markdown",
"showInSidebar": true
}'

Soft delete custom entity — DELETE /entities/entities

  • Feature: entities.definitions.manage.
  • Request accepts JSON { "entityId": "<id>" }; the implementation scopes deletion to the caller’s tenant/org (packages/core/src/modules/entities/api/entities.ts:121).
curl -X DELETE "$BASE_URL/entities/entities" \
-H "X-Api-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "entityId": "custom.ticket" }'
  • Auth-only guard (packages/core/src/modules/entities/api/sidebar-entities.ts:7).
  • Returns custom entities flagged with showInSidebar so you can build scoped navigation.
curl -X GET "$BASE_URL/entities/sidebar-entities" \
-H "X-Api-Key: $API_KEY" \
-H "Accept: application/json"

Field definitions — /entities/definitions*

Load active definitions — GET /entities/definitions

  • Auth-only guard; no feature gate so record forms can load fields (packages/core/src/modules/entities/api/definitions.ts:8).
  • Accepts repeated entityId=<id> parameters or a comma-delimited entityIds query.
  • Response merges global, org, and tenant scopes, omitting tombstoned keys and preserving priority metadata (packages/core/src/modules/entities/api/definitions.ts:120).
curl -X GET "$BASE_URL/entities/definitions?entityId=$ENTITY_ID" \
-H "X-Api-Key: $API_KEY" \
-H "Accept: application/json"

Management snapshot — GET /entities/definitions.manage

  • Feature: entities.definitions.manage.
  • Returns { items, deletedKeys }, showing winners per scope plus keys tombstoned in the current tenant/org (packages/core/src/modules/entities/api/definitions.manage.ts:15).
curl -X GET "$BASE_URL/entities/definitions.manage?entityId=$ENTITY_ID" \
-H "X-Api-Key: $API_KEY" \
-H "Accept: application/json"

Upsert definition — POST /entities/definitions

  • Feature: entities.definitions.manage.
  • Body matches upsertCustomFieldDefSchema (packages/core/src/modules/entities/data/validators.ts:97):
    • Required: entityId, key, kind.
    • configJson lets you set label, options, filterable, listVisible, formEditable, dictionaries, validation rules, and editor hints.
    • Optional isActive soft disables fields without deleting history.
curl -X POST "$BASE_URL/entities/definitions" \
-H "X-Api-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"entityId\": \"$ENTITY_ID\",
\"key\": \"priority\",
\"kind\": \"enum\",
\"configJson\": {
\"label\": \"Priority\",
\"options\": [\"low\", \"medium\", \"high\"],
\"filterable\": true,
\"listVisible\": true
},
\"isActive\": true
}"

Batch upsert — POST /entities/definitions.batch

  • Feature: entities.definitions.manage.
  • Body { entityId, definitions: [ ... ] } lets you send an ordered array of definition payloads (packages/core/src/modules/entities/api/definitions.batch.ts:19).
  • Optional fieldsets array (matching customFieldsetSchema) and singleFieldsetPerRecord flag let you manage the drop-down metadata programmatically. Each field definition can also set configJson.fieldset and configJson.group to map the field into the new layout.
curl -X POST "$BASE_URL/entities/definitions.batch" \
-H "X-Api-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"entityId\": \"$ENTITY_ID\",
\"definitions\": [
{ \"key\": \"status\", \"kind\": \"enum\", \"configJson\": { \"label\": \"Status\", \"options\": [\"open\",\"won\",\"lost\"], \"priority\": 0 } },
{ \"key\": \"assignee\", \"kind\": \"string\", \"configJson\": { \"label\": \"Assignee\", \"filterable\": true, \"priority\": 1 } }
],
\"fieldsets\": [
{
\"code\": \"service_booking\",
\"label\": \"Services · Booking\",
\"icon\": \"solar:calendar-linear\",
\"groups\": [
{ \"code\": \"timing\", \"title\": \"Timing\" },
{ \"code\": \"resources\", \"title\": \"Resources\" }
]
}
],
\"singleFieldsetPerRecord\": true
}"

Soft delete definition — DELETE /entities/definitions

  • Feature: entities.definitions.manage.
  • Accepts JSON { "entityId": "...", "key": "..." } and marks the definition inactive (packages/core/src/modules/entities/api/definitions.ts:189).
curl -X DELETE "$BASE_URL/entities/definitions" \
-H "X-Api-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"entityId\": \"$ENTITY_ID\",
\"key\": \"priority\"
}"

Restore definition — POST /entities/definitions.restore

  • Feature: entities.definitions.manage.
  • Body { entityId, key } reactivates a tombstoned definition and invalidates the cache (packages/core/src/modules/entities/api/definitions.restore.ts:9).
curl -X POST "$BASE_URL/entities/definitions.restore" \
-H "X-Api-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"entityId\": \"$ENTITY_ID\",
\"key\": \"priority\"
}"

Records — /entities/records

List records — GET /entities/records

  • Feature: entities.records.view (packages/core/src/modules/entities/api/records.ts:18).
  • Required query: entityId. Optional filters include:
    • Paging: page, pageSize (max 100), withDeleted.
    • Sorting: sortField, sortDir.
    • Export: format=csv|json|xml|markdown, exportScope=full, all=true, full=true.
    • Arbitrary field filters; prefix custom fields with cf_.
curl -X GET "$BASE_URL/entities/records?entityId=$ENTITY_ID&page=1&pageSize=25&sortField=created_at&sortDir=desc" \
-H "X-Api-Key: $API_KEY" \
-H "Accept: application/json"

Create record — POST /entities/records

  • Feature: entities.records.manage.
  • Body { entityId, recordId?, values } where values is a flat object; prefixing cf_ is optional because the handler normalises keys (packages/core/src/modules/entities/api/records.ts:204).
  • Supplying a non-UUID recordId triggers auto-generation; use this to support optimistic UI drafts.
curl -X POST "$BASE_URL/entities/records" \
-H "X-Api-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"entityId\": \"$ENTITY_ID\",
\"values\": {
\"title\": \"Onboard new merchant\",
\"description\": \"Kick off data import\",
\"cf_priority\": \"high\"
}
}"

Update record — PUT /entities/records

  • Feature: entities.records.manage.
  • Body { entityId, recordId, values } with the same shape as the create payload (packages/core/src/modules/entities/api/records.ts:246).
  • The handler re-validates values against active custom fields and, if the provided recordId is not a UUID, falls back to create semantics (useful for spreadsheet imports).
curl -X PUT "$BASE_URL/entities/records" \
-H "X-Api-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"entityId\": \"$ENTITY_ID\",
\"recordId\": \"$RECORD_ID\",
\"values\": {
\"title\": \"Onboard new merchant (updated)\",
\"cf_priority\": \"medium\"
}
}"

Delete record — DELETE /entities/records

  • Feature: entities.records.manage.
  • Accepts query parameters (?entityId=...&recordId=...) or JSON body; deletion is soft and scoped to the active tenant/org (packages/core/src/modules/entities/api/records.ts:337).
curl -X DELETE "$BASE_URL/entities/records?entityId=$ENTITY_ID&recordId=$RECORD_ID" \
-H "X-Api-Key: $API_KEY"

Relation options — GET /entities/relations/options

  • Feature: entities.definitions.view.
  • Parameters (packages/core/src/modules/entities/api/relations/options.ts:12):
    • entityId (target entity providing options).
    • labelField (field used for display text).
    • Optional q (search), pageSize, cursor.
curl -X GET "$BASE_URL/entities/relations/options?entityId=$ENTITY_ID&labelField=title&q=demo" \
-H "X-Api-Key: $API_KEY" \
-H "Accept: application/json"

Response example:

{
"items": [
{ "value": "6d44ce18-4bc7-49d6-8b25-a94288363f25", "label": "Demo Ticket" }
],
"nextCursor": null
}

Implementation helpers

  • The module also exports createDefinitionsCacheKey and invalidateDefinitionsCache in packages/core/src/modules/entities/api/definitions.cache.ts:6. These utilities are meant for internal cache management when definitions change; there is no public HTTP route at /entities/definitions.cache, even though the generator lists the file for completeness.
  • If you mirror the caching strategy, tag invalidations with entities:definitions:<tenant> so UI fragments and API consumers observe new fields immediately.

Pair these endpoints with the DI queryEngine for server-side filtering or plug them into shared admin components (packages/shared/src/lib/crud/factory.ts:23) to deliver tenant-aware experiences without rebuilding CRUD plumbing.