crud — entity lifecycle API
cyoda-go version 0.6.2
crud — entity create, read, update, delete, and transition REST API.
SYNOPSIS
Section titled “SYNOPSIS”POST /api/entity/{format}/{entityName}/{modelVersion}POST /api/entity/{format}GET /api/entity/{entityId}PUT /api/entity/{format}/{entityId}PUT /api/entity/{format}/{entityId}/{transition}PUT /api/entity/{format}DELETE /api/entity/{entityId}DELETE /api/entity/{entityName}/{modelVersion}GET /api/entity/{entityName}/{modelVersion}GET /api/entity/{entityId}/changesGET /api/entity/{entityId}/transitionsGET /api/entity/statsGET /api/entity/stats/statesGET /api/entity/stats/{entityName}/{modelVersion}GET /api/entity/stats/states/{entityName}/{modelVersion}GET /api/platform-api/entity/fetch/transitionsContext path prefix is CYODA_CONTEXT_PATH (default /api). All endpoints require Authorization: Bearer <token> except when CYODA_IAM_MODE=mock.
DESCRIPTION
Section titled “DESCRIPTION”Entities are instances of models. Each entity has a UUID, a model reference (entityName, modelVersion), and a lifecycle state managed by the workflow engine. Creating an entity requires the referenced model to be in LOCKED state. All write operations run within a Cyoda transaction and return a transactionId alongside the affected entity IDs.
Body size limit on all write endpoints: 10 MiB.
ENDPOINTS
Section titled “ENDPOINTS”POST /api/entity/{format}/{entityName}/{modelVersion} — Create a single entity
format(path):JSONorXMLentityName(path): string — model namemodelVersion(path): int32waitForConsistencyAfter(query, optional): boolean, defaultfalsetransactionTimeoutMillis(query, optional): int64, default10000
If the request body is a JSON array, the handler delegates to the collection-create path: each element is treated as a separate entity of the same model.
Response: 200 OK, application/json:
[{ "transactionId": "cb91fa80-d4a8-11ee-a357-ae468cd3ed16", "entityIds": ["74807f00-ed0d-11ee-a357-ae468cd3ed16"]}]POST /api/entity/{format} — Create a collection (mixed models)
format(path):JSONorXML
IMPORTANT — payload is a JSON-encoded string, not an object.
The payload field must be a string containing the JSON-encoded entity body, not a nested JSON object. This is a deliberate API contract — it preserves the payload as an opaque blob through the pipeline.
Correct: "payload": "{\"category\":\"physics\"}"
Wrong: "payload": {"category":"physics"} (will be rejected with errors.BAD_REQUEST)
Request body: JSON array of CreatePayload objects:
[ { "model": { "name": "nobel-prize", "version": 1 }, "payload": "{\"category\":\"physics\",\"year\":\"2024\"}" }]Each item may reference a different model. All items are created in a single transaction.
Response: 200 OK, application/json:
[{ "transactionId": "cb91fa80-d4a8-11ee-a357-ae468cd3ed16", "entityIds": [ "74807f00-ed0d-11ee-a357-ae468cd3ed16", "72428380-0704-11ef-a357-ae468cd3ed16" ]}]GET /api/entity/{entityId} — Read a single entity by UUID
entityId(path): UUID stringpointInTime(query, optional): RFC 3339 date-time — load entity state at this instanttransactionId(query, optional): UUID — load entity state as of the end of this transaction
pointInTime and transactionId are mutually exclusive; supplying both returns 400 BAD_REQUEST.
Response: 200 OK, application/json:
{ "type": "ENTITY", "data": { "category": "physics", "year": "2024" }, "meta": { "id": "74807f00-ed0d-11ee-a357-ae468cd3ed16", "state": "NEW", "creationDate": "2025-08-01T10:00:00Z", "lastUpdateTime": "2025-08-01T10:00:00Z", "transactionId": "cb91fa80-d4a8-11ee-a357-ae468cd3ed16", "transitionForLatestSave": "loopback" }}PUT /api/entity/{format}/{entityId} — Update a single entity (loopback transition)
format(path):JSONorXMLentityId(path): UUIDIf-Match(header, optional): transaction ID of last read — optimistic concurrency; if the entity was modified since, returns412 Precondition FailedtransactionTimeoutMillis(query, optional): int64, default10000waitForConsistencyAfter(query, optional): boolean, defaultfalse
Request body: updated entity JSON/XML payload.
Response: 200 OK, application/json:
{ "transactionId": "733e7180-c055-11ef-a357-ae468cd3ed16", "entityIds": ["cdcff600-bab1-11ee-a357-ae468cd3ed16"]}PUT /api/entity/{format}/{entityId}/{transition} — Update a single entity with a named transition
format(path):JSONorXMLentityId(path): UUIDtransition(path): string — transition name defined in the model’s workflowIf-Match(header, optional): transaction IDtransactionTimeoutMillis(query, optional): int64, default10000waitForConsistencyAfter(query, optional): boolean, defaultfalse
Response: 200 OK, same shape as loopback update.
PUT /api/entity/{format} — Update a collection (mixed entities)
format(path):JSON(only supported format today; single-item PUT endpoints still accept XML)transactionWindow(query, optional): int32, default100, max1000— max items accepted in one batch; batches over the window are rejected with400 BAD_REQUESTtransactionTimeoutMillis(query, optional): int64, default10000waitForConsistencyAfter(query, optional): boolean, defaultfalse
IMPORTANT — payload is a JSON-encoded string, not an object.
The payload field in each update item must be a string containing the JSON-encoded entity body, not a nested JSON object (same contract as collection create).
Correct: "payload": "{\"category\":\"physics\"}"
Wrong: "payload": {"category":"physics"} (will be rejected with errors.BAD_REQUEST)
Request body: JSON array of update items:
[ { "id": "8824c480-c166-11ee-9e63-ae468cd3ed16", "payload": "{\"category\":\"physics\",\"year\":\"2024\"}", "transition": "UPDATE" }]If any entity in the collection is not found, the entire operation fails and no entities are updated (all-or-nothing).
Response: 200 OK, application/json, EntityTransactionResponse array (one element — the whole collection runs in a single transaction):
[ { "transactionId": "733e7180-c055-11ef-a357-ae468cd3ed16", "entityIds": ["8824c480-c166-11ee-9e63-ae468cd3ed16"] }]DELETE /api/entity/{entityId} — Delete a single entity by UUID
entityId(path): UUID
Response: 200 OK, application/json:
{ "id": "a2242880-8d30-11ef-9e63-ae468cd3ed16", "modelKey": { "name": "nobel-prize", "version": 4 }, "transactionId": "9fe62d00-a727-11ef-9e63-ae468cd3ed16"}DELETE /api/entity/{entityName}/{modelVersion} — Delete all entities for a model
entityName(path): stringmodelVersion(path): int32
Response: 200 OK, application/json:
[{ "deleteResult": { "idToError": {}, "numberOfEntitites": 42, "numberOfEntititesRemoved": 42 }, "entityModelClassId": "31134900-d9cb-11ee-b913-ae468cd3ed16"}]GET /api/entity/{entityName}/{modelVersion} — List all entities for a model (paginated)
entityName(path): stringmodelVersion(path): int32pageSize(query, optional): int32, default20pageNumber(query, optional): int32, default0
Response: 200 OK, application/json, array of entity envelopes (same shape as single-entity GET).
GET /api/entity/{entityId}/changes — Get entity change history metadata
entityId(path): UUIDpointInTime(query, optional): RFC 3339 — view history as it existed at this time
Response: 200 OK, application/json, array of change entries:
[ { "changeType": "CREATED", "timeOfChange": "2025-08-01T10:00:00Z", "user": "admin", "transactionId": "cb91fa80-d4a8-11ee-a357-ae468cd3ed16" }, { "changeType": "UPDATED", "timeOfChange": "2025-08-02T09:00:00Z", "user": "admin", "transactionId": "733e7180-c055-11ef-a357-ae468cd3ed16" }]changeType:CREATED,UPDATED, orDELETEDtransactionId: present only whenhasEntityis true (i.e., entity payload exists at that version)
GET /api/entity/{entityId}/transitions — List available transitions for an entity
entityId(path): UUIDpointInTime(query, optional): RFC 3339transactionId(query, optional): UUID — derive point-in-time from transaction submit time
pointInTime and transactionId are mutually exclusive; supplying both returns 400 BAD_REQUEST. When neither is provided, the current time is used.
Response: 200 OK, application/json, array of available transition names (as returned by the workflow engine).
GET /api/platform-api/entity/fetch/transitions — List available transitions (platform-api format)
entityClass(query, required): string inName.Versionformat, e.g.,Offer.1entityId(query, required): UUID string
Response: 200 OK, application/json, array of available transition names.
GET /api/entity/stats — Entity count statistics across all models
Response: 200 OK, application/json:
[ { "modelName": "nobel-prize", "modelVersion": 1, "count": 42 }, { "modelName": "family-member", "modelVersion": 3, "count": 7 }]GET /api/entity/stats/states — Entity count by state across all models
states(query, optional): comma-separated list of state names to filter by; maximum 1000 entries
Response: 200 OK, application/json:
[ { "modelName": "nobel-prize", "modelVersion": 1, "state": "NEW", "count": 10 }, { "modelName": "nobel-prize", "modelVersion": 1, "state": "APPROVED", "count": 32 }]GET /api/entity/stats/{entityName}/{modelVersion} — Entity count for a specific model
entityName(path): stringmodelVersion(path): int32
Response: 200 OK, application/json, single ModelStatsDto.
GET /api/entity/stats/states/{entityName}/{modelVersion} — Entity count by state for a specific model
entityName(path): stringmodelVersion(path): int32states(query, optional): list of state names to filter by; maximum 1000 entries
Response: 200 OK, application/json, array of ModelStateStatsDto.
ENTITY ENVELOPE
Section titled “ENTITY ENVELOPE”All entity read operations return entities in the standard envelope:
{ "type": "ENTITY", "data": { ... }, "meta": { "id": "74807f00-ed0d-11ee-a357-ae468cd3ed16", "modelKey": { "name": "nobel-prize", "version": 1 }, "state": "NEW", "creationDate": "2025-08-01T10:00:00.000000000Z", "lastUpdateTime": "2025-08-01T10:00:00.000000000Z", "transactionId": "cb91fa80-d4a8-11ee-a357-ae468cd3ed16", "transitionForLatestSave": "UPDATE" }}type— always"ENTITY"data— the entity’s JSON payload (decoded withjson.Numberfor numeric precision)meta.id— UUID stringmeta.modelKey— object withname(string) andversion(int32) identifying the model; present in single-entityGET /entity/{id}responses. Omitted from list/search results because the model is already part of the request path (/api/entity/{entityName}/{modelVersion}).meta.state— current workflow state stringmeta.creationDate— RFC 3339 with nanosecondsmeta.lastUpdateTime— RFC 3339 with nanosecondsmeta.transactionId— present when a transaction ID existsmeta.transitionForLatestSave— transition name that produced the latest save. Valid values:"loopback"(loopback update with no transition supplied by the client) or the named transition string. Known bug (#94): the server currently stores the literal"workflow"for engine-driven initial-state writes; there is no valid"workflow"value and this is tracked for fix.
OPTIMISTIC CONCURRENCY
Section titled “OPTIMISTIC CONCURRENCY”The If-Match header on update endpoints accepts a transaction ID. If the entity’s meta.transactionId does not match the value in If-Match, the server returns 412 Precondition Failed. This provides optimistic concurrency without distributed locking.
To use: read the entity (GET /entity/{id}), note meta.transactionId, include it in If-Match on the subsequent update.
ERRORS
Section titled “ERRORS”errors.ENTITY_NOT_FOUND—404— entity UUID does not existerrors.MODEL_NOT_FOUND—404— model referenced during create does not existerrors.MODEL_NOT_LOCKED—409— model exists but is not inLOCKEDstate; entities cannot be created until the model is lockederrors.VALIDATION_FAILED—400— payload fails schema validation against the modelerrors.CONFLICT—409— transaction conflict (retryable)errors.IDEMPOTENCY_CONFLICT—409— reserved; not yet implemented (#91). Future contract: returned on collection create/update when theIdempotency-Keyheader is re-used with a different payload bodyerrors.TRANSITION_NOT_FOUND—404— named transition does not exist in the workflowerrors.BAD_REQUEST—400— malformed request, invalid UUID, conflicting query parameters, states filter exceeds 1000 entries
EXAMPLES
Section titled “EXAMPLES”Create a single entity:
curl -s -X POST \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"category":"physics","year":"2024"}' \ "http://localhost:8080/api/entity/JSON/nobel-prize/1"Read an entity:
curl -s -H "Authorization: Bearer $TOKEN" \ "http://localhost:8080/api/entity/74807f00-ed0d-11ee-a357-ae468cd3ed16"Read an entity at a point in time:
curl -s -H "Authorization: Bearer $TOKEN" \ "http://localhost:8080/api/entity/74807f00-ed0d-11ee-a357-ae468cd3ed16?pointInTime=2025-08-01T10:00:00Z"Update an entity with loopback transition:
curl -s -X PUT \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -H "If-Match: cb91fa80-d4a8-11ee-a357-ae468cd3ed16" \ -d '{"category":"chemistry","year":"2024"}' \ "http://localhost:8080/api/entity/JSON/74807f00-ed0d-11ee-a357-ae468cd3ed16"Update an entity with a named transition:
curl -s -X PUT \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"category":"chemistry","year":"2024"}' \ "http://localhost:8080/api/entity/JSON/74807f00-ed0d-11ee-a357-ae468cd3ed16/APPROVE"Delete a single entity:
curl -s -X DELETE \ -H "Authorization: Bearer $TOKEN" \ "http://localhost:8080/api/entity/74807f00-ed0d-11ee-a357-ae468cd3ed16"Delete all entities for a model:
curl -s -X DELETE \ -H "Authorization: Bearer $TOKEN" \ "http://localhost:8080/api/entity/nobel-prize/1"List all entities for a model (page 0, size 20):
curl -s -H "Authorization: Bearer $TOKEN" \ "http://localhost:8080/api/entity/nobel-prize/1?pageSize=20&pageNumber=0"Get entity change history:
curl -s -H "Authorization: Bearer $TOKEN" \ "http://localhost:8080/api/entity/74807f00-ed0d-11ee-a357-ae468cd3ed16/changes"Get available transitions:
curl -s -H "Authorization: Bearer $TOKEN" \ "http://localhost:8080/api/entity/74807f00-ed0d-11ee-a357-ae468cd3ed16/transitions"Create a multi-model collection:
curl -s -X POST \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '[{"model":{"name":"nobel-prize","version":1},"payload":"{\"category\":\"physics\",\"year\":\"2024\"}"}]' \ "http://localhost:8080/api/entity/JSON"Get statistics by state for a model:
curl -s -H "Authorization: Bearer $TOKEN" \ "http://localhost:8080/api/entity/stats/states/nobel-prize/1"SEE ALSO
Section titled “SEE ALSO”- models
- search
- workflows
- errors.ENTITY_NOT_FOUND
- errors.MODEL_NOT_FOUND
- errors.MODEL_NOT_LOCKED
- errors.VALIDATION_FAILED
- errors.CONFLICT
- errors.TRANSITION_NOT_FOUND
- openapi
See also
Section titled “See also”cyoda help models— A model is a named, versioned schema registered per tenant. Every entity in the system is an instance of exactly one model. Models are identified by(entityName, modelVersion). The model ID is a deterministic UUID v5 derived from that key:UUID.newSHA1(NameSpaceURL, "{entityName}.{modelVersion}").cyoda help search— Search operates against a specific entity model(entityName, modelVersion). Two modes are supported:cyoda help workflows— A workflow definition is a named finite state machine attached to an entity model. Workflows are stored per model reference(entityName, modelVersion). A model may have multiple workflow definitions; the engine selects the matching one per entity using the workflow-levelcriterionfield evaluated at entity creation time. When nocriterionmatches, the engine uses the default built-in workflow.cyoda help errors ENTITY_NOT_FOUND— No entity with the given ID exists in the tenant’s data store, or the entity existed at a point-in-time that precedes the requested snapshot. Also returned for audit log lookups when the specified event or message cannot be found.cyoda help errors MODEL_NOT_FOUND— The entity type or model name specified in the request does not exist in the tenant’s model registry. Occurs when creating entities with an unknown type, importing data that references a missing model, or performing model lifecycle transitions on a model ID that does not exist.cyoda help errors MODEL_NOT_LOCKED— Entity creation and bulk write operations require the model to be in theLOCKEDlifecycle state. Models inDRAFTor unlocked-for-editing state reject writes to prevent schema changes from affecting in-flight data.cyoda help errors VALIDATION_FAILED— UnlikeBAD_REQUEST(which covers parse failures), this error is returned when the payload is parseable but violates the registered model schema — for example, a required field is missing, a value is out of the allowed range, or a workflow guard condition is not satisfied. The error detail includes the specific validation failure.cyoda help errors CONFLICT— The server detected that the entity was modified by another writer between the time it was read and the time the current write was committed. Normal outcome under concurrent load.cyoda help errors IDEMPOTENCY_CONFLICT— The idempotency key is supplied via theIdempotency-KeyHTTP header on collection create and update requests. Seecrudfor the request shape.cyoda help errors TRANSITION_NOT_FOUND— Entity workflow state machines define explicit transitions between states. This error fires when a transition is triggered that does not exist in the model’s workflow definition for the entity’s current state. Also occurs when the transition name is misspelled or when the entity is in a terminal state that allows no further transitions.cyoda help openapi— cyoda-go generates its OpenAPI 3.1 specification from the embeddedapi/openapi.yamlfile compiled into the binary at build time. The spec is served at/openapi.jsonwith runtime-patched server URLs. The Scalar API Reference UI is served at/docsand loads the spec from/openapi.json.
Raw formats
Section titled “Raw formats”/help/crud.json— full descriptor (matchesGET /help/{topic}envelope)/help/crud.md— body only