DataSync data operations
Every DataSync resource, entities, relationships, users, channels, and memberships, supports the same operation set: list, get, create, replace, partial update, and delete. All of these operations require Access Manager authorization. Refer to DataSync access control for how that works.
Every operation maps to a Core REST API endpoint:
| Entity | Relationship | User | Channel | Membership | |
|---|---|---|---|---|---|
| List | Get entities | Get relationships | Get users | Get channels | Get memberships |
| Get | Get entity | Get relationship | Get user | Get channel | Get membership |
| Create | Create entity | Create relationship | Create user | Create channel | Create membership |
| Replace | Update entity | Update relationship | Update user | Update channel | Update membership |
| Partial update | Patch entity | Patch relationship | Patch user | Patch channel | Patch membership |
| Delete | Delete entity | Delete relationship | Delete user | Delete channel | Delete membership |
For code examples, refer to Create entities and relationships and Query DataSync.
Creating objects
Every create names the class the new object is an instance of. What else you must supply depends on the object kind:
| Creating | You must supply |
|---|---|
| An entity | The class name and class version |
| A relationship | Both linked entity IDs, the class name, and the class version |
| A user | The class version. The class name defaults to User |
| A channel | The class version. The class name defaults to Channel |
| A membership | channelId, userId, and the class version |
You can optionally supply your own id, a status, and the payload. If you don't supply an id, the server generates one. Supplying an id that already exists is rejected with a 409 Conflict.
Reading objects
Getting an object by ID returns its system fields and its payload. Projection filtering applies to payload and status only: the remaining system fields are always returned in full. Refer to Projections.
Expired objects are not returned by reads. Eventually consistent search (filter) can return an object whose expiresAt has passed, so check expiresAt on results from that tier.
Updating objects
DataSync supports two ways to update an object: replace and partial update.
Replace
Replace is a PUT. It sends the new state for every payload field visible within the token's resolved projection, including the class version. Payload fields outside that projection are left untouched. For a class with no custom projections, replace behaves exactly like sending the full object.
Two things to watch in a replace body:
- The class version isn't a passive echo. Sending a different existing version of the same class re-points the object at that version. A version whose base Global class differs from the object's own is rejected with a
400. statusis protected by its declared projections. If the resolved projection includes/status, omittingstatusfrom a replace body sets it to null. If the resolved projection excludes/status, changing it is rejected with a403.
Partial update
Partial update is a PATCH. It uses JSON Patch (RFC 6902) with add, remove, replace, move, copy, and test operations, addressed by JSON Pointer paths into the object. A pointer starts at the object, where payload is one field alongside the system fields, so the price on a product is /payload/price.
A partial update can only address three paths on the object:
/status/entityClassVersion, or/relationshipClassVersionfor relationships and memberships/payload, or anything beneath it
Any other pointer is rejected with a 400 Bad Request. The test operation is the exception: it can target any pointer without modifying the document, which makes it a way to assert /eTag inline as part of the patch.
Optimistic concurrency with ETags
Every entity and relationship carries an eTag that changes on every write. If-Match is an optional request header on every write endpoint. Omit it and the write applies unconditionally. Send the eTag you last read as If-Match to make the write conditional, or If-Match: * to require only that the object still exists. If the value no longer matches the object's current eTag, the write is rejected with a 412 Precondition Failed.
The pattern for safe concurrent writes:
- Read the object and note its
eTag. - Modify the fields you want to change.
- Write with
If-Match: <eTag>. If theeTagstill matches, the write succeeds with200 OKand returns the neweTag. - If the response is
412, re-read and retry.
If you're also subscribed to an object's events, use an incoming event as a signal to refresh your copy and its eTag before writing, rather than writing blindly.
Deleting objects
Delete removes an object immediately. Like other writes, it respects If-Match, so a delete against a stale eTag is rejected with a 412. Deleting an entity also deletes every relationship linked to it, and each cascaded relationship delete publishes an event of its own.
A successful delete returns 200 OK with an empty body. Deleting an ID that doesn't exist returns a 404, so delete isn't idempotent.
Objects also expire automatically based on their class TTL. expiresAt is set once at creation and is not refreshed by updates. Refer to Schemas: data expiry (TTL).
Filtering
DataSync offers two mutually exclusive filtering parameters: filter_fast for strongly consistent queries and filter for eventually consistent queries. Supplying both in one request is rejected with a 400.
Strongly consistent (filter_fast) | Eventually consistent (filter) | |
|---|---|---|
| Eligible properties | Declared with filtering mode simple or full | Declared with filtering mode full |
| Storage | Same store used for writes | A separate search store |
| Consistency | Strongly consistent; reflects completed writes | Eventually consistent; can briefly lag recent writes |
| Predicate count | Maximum 10 by default, configurable per keyset | No predicate limit is enforced |
Both parameters share the same expression language. Filter expressions use comparison operators (==, !=, <, >, <=, >=), pattern operators (LIKE, SLIKE, ILIKE), and logical operators (&&, ||, !). In filter and sort expressions, you always use a property's name, not its path.
Four fields every object carries natively are filterable and sortable without being declared on the class:
| Field | Filters as |
|---|---|
id | string |
createdAt | datetime |
updatedAt | datetime |
status | string |
Declare filterable properties first
Beyond the four built-in fields, filtering only works on properties you've declared on the class, and only for data written after the property was declared. Data written earlier isn't retroactively indexed.
Listing generic entities requires an entity_class, and listing generic relationships requires a relationship_class. There's no way to list every entity on a keyset regardless of class.
Sorting and pagination
List endpoints accept a sort parameter: a comma-separated list of property names, each optionally followed by :desc for descending order. Only declared properties are sortable, and which properties are sortable depends on the filtering tier: filter_fast supports simple and full properties, while filter supports full only.
List endpoints use cursor-based pagination. Page size ranges from 1 to 100, with a default of 20. Pagination is forward-only. To page forward, pass the response's next_cursor back as cursor on the next call, and stop when has_next is false.
Refer to Query DataSync for code examples.