Objects
Create, read, update, and delete individual objects. Type names in the path are always the plural REST name — see TypeFQN to URL mapping.
Create
POST /data/{tenant}/{typeName}
Creates a new object. Returns 201 Created with a Location header pointing at the new object's URI and no response body. To retrieve the created object, issue a follow-up Get one against the Location URI.
Interview needed. Document: request body shape (which fields are required vs. optional, how relations and files are supplied), what fields are computed server-side (formulas, id, name), and validation/error behavior.
Create related
POST /data/{tenant}/{referenceTypeName}/{referenceId}/{typeName}
Creates a new object of typeName linked to the existing referenceTypeName/referenceId object. An optional ?type= query parameter selects a concrete subtype to instantiate.
Returns 201 Created with a Location header pointing at the new object's URI and no response body. To retrieve the created object, issue a follow-up Get one against the Location URI.
Interview needed. Document: which relation the new object is attached through (how the reference is chosen when multiple relations connect the two types), the role of the type (subtype) parameter, and how it differs from creating then patching.
Get one
GET /data/{tenant}/{typeName}/{id}
Returns a single object as a DataObjectPresentation.
Interview needed. Document the DataObjectPresentation shape: object fields, relation links, formulas, presentation metadata, and how it differs from the list-row shape returned by querying.
Get own
GET /data/{tenant}/{typeName}/own
Returns the object of this type that belongs to the authenticated caller.
Interview needed. Explain the "own" concept: which type(s) support it (user profile?), how ownership is determined from the authenticated identity, and the response when the caller has no such object.
Patch
PATCH /data/{tenant}/{typeName}/{id}
Partially updates an object. Only the fields present in the body are changed.
Returns 204 No Content with a Location header pointing at the patched object's URI and no response body. To retrieve the updated object, issue a follow-up Get one against the Location URI.
Interview needed. Document: merge semantics (how to clear a field vs. leave it untouched), how relations are added/removed/replaced, whether formulas re-run, and concurrency/versioning if any.
Delete
DELETE /data/{tenant}/{typeName}/{id}
Deletes a single object.
Interview needed. Document cascade behavior (what happens to related objects and inverse relations), soft vs. hard delete, and the success/error responses.
Merge
POST /data/{tenant}/{typeName}/{id}/merge
Merges another object of the same BASIC class — the loser — into this object — the winner, identified by typeName/id — then deletes the loser.
{
"loser": "550e8400-e29b-41d4-a716-446655440000",
"patch": {
"email": "winner@example.com"
}
}
loser— the id of the object to merge away, and delete, into the winner. Accepts a bare UUID or a full resource URI (e.g. anhrefcopied straight out of a_linksentry) — only the last 36 characters are read, so a truncated or malformed value is not rejected the way an invalidpatchfield would be.patch— optional, same shape as the body of Patch. Applied to the winner exactly like an ordinary patch: fields and relations not mentioned keep the winner's current value. The server never compares the winner's and loser's field values or detects conflicts — resolving which value wins per field is entirely up to the caller, expressed by what is (and isn't) included inpatch.
Returns 204 No Content with a Location header pointing at the winner's URI and no response body. To see the winner's state after the merge — including any repointed relations — issue a follow-up Get one against the Location URI.
What happens, in order:
patchis applied to the winner as an ordinary patch.- Any classes the loser has that the winner doesn't are added to the winner (a union), regardless of
patch. This keeps working scripts and gateways bound to the loser's types active on the merged object. - Every relation that points at the loser from other objects is repointed to the winner: single-valued relations are overwritten, multi-valued relations are deduplicated (if the winner is already linked, the loser's link is dropped rather than duplicated). The winner's own relations only change through step 1.
- The winner is saved.
- The loser is deleted through the normal delete pipeline: a hard delete, with no tombstone and no pointer left behind from the loser to the winner. By this point nothing should still reference the loser, so its delete constraints normally pass — but see the
409case below.
History-enabled fields are treated like any other field for conflict purposes: the entire history list follows whichever side won that field (winner's or loser's), never interleaved.
Errors:
| Status | When |
|---|---|
400 Bad Request | The loser's BASIC class differs from the winner's. |
404 Not Found | typeName, the winner id, or the loser could not be resolved. |
409 Conflict | Deleting the loser violates a delete constraint that relation repointing didn't clear — the same conflict a direct DELETE on the loser would raise. |
Notes:
- The merge is best-effort, not transactional across the platform's stores, consistent with
save/deleteelsewhere in this API. - Passing the winner's own
idasloseris not guarded against: the object is patched, then immediately deleted by step 5. Never pass the same id for both sides. - There is no server-side conflict UI or SDK helper for this endpoint yet — callers build the
patchpayload themselves.