Property changes
List Property Changes
Section titled “List Property Changes”Retrieves the property change history of one or more layer features. Every edit to a layer feature property is appended to this log, so it is the record of what changed, when, and by whom.
Endpoint: GET /propertyChanges
Authentication: Required (API Key)
Query Parameters
Section titled “Query Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
layer_feature_id |
integer[] | Yes | Filter changes by one or more layer feature IDs (the id field of List Layer Features). At least one, at most 200. |
property_key |
string[] | No | Narrow the result to one or more property keys: a built-in property key (e.g. STATUS, ASSIGNEE) or a custom property definition UUID. Only narrows within layer_feature_id; cannot be used on its own. At most 200. |
_page |
integer | No | Page number (positive integer, defaults to 1) |
_pageSize |
integer | No | Number of changes per page (positive integer, defaults to 10, max 10,000) |
_sortBy |
string | No | Sort order. Format: +field (ascending) or -field (descending); repeat the parameter for tie-breaks. Sortable fields: created_at, id. Default: +created_at then +id (oldest first, see the ordering note below) |
There is deliberately no dataset_id filter: change history is always read per layer feature.
Note: in a URL a literal + decodes to a space, so an ascending _sortBy must be percent-encoded as %2B (_sortBy=%2Bcreated_at), or simply omitted since ascending is the default here.
Response Format
Section titled “Response Format”Returns a paginated response with the following structure:
{ "data": [ { "id": "uuid", "layer_feature_id": "integer", "property_key": "string", "new_value": "any | null", "system": "boolean", "user_id": "integer | null", "caused_by_change_id": "uuid | null", "created_at": "timestamp" } ], "pagination": { "rowCount": "integer", "pageCount": "integer", "page": "integer", "pageSize": "integer" }}Response Fields
Section titled “Response Fields”- id: UUID of the change
- layer_feature_id: Integer ID of the layer feature the change was made on
- property_key: The property that was changed. Either a built-in property key (e.g.
STATUS,ASSIGNEE,VOLUME), which is its own name and is not listed by List Property Definitions, or the UUID of a custom property definition, which you can resolve to a name via that endpoint. - new_value: The value the property was set to. Its type follows the property definition (string, number, boolean, array, …) and is
nullwhen the property was cleared. - system:
truewhen the change was made by Sitemark itself, for example by an automation, rather than by a person. A system change never has auser_id. - user_id: Integer ID of the user who made the change. Resolve it to a name via List Users. It is
nullfor system changes (system: true), andnullwithsystem: falsewhen the change isn’t linked to a user, for example a change made with an API key. - caused_by_change_id: UUID of the change that caused this one, when this change is a knock-on effect of another.
nullfor changes that were made directly. - created_at: ISO 8601 timestamp of when the change was made. Changes made in the web or mobile app carry the time of the device, so that edits made offline keep their real time. For changes made through the external API (Update Ticket Fields), it is the time Sitemark received them.
- pagination: The standard pagination object.
rowCountcounts only the changes you are allowed to see.
Ordering: by default changes are returned oldest first, by created_at and then by id for changes that share a timestamp (a bulk edit stamps all of its changes with the same time). Because created_at can come from the client, a change recorded later can carry an earlier timestamp; it is sorted by that timestamp, not by when it reached the server. Because of this, a row that arrives while you are paging through +created_at can land in the middle of the order and shift the following pages, so a page walk may repeat or skip a row. To sweep a complete history reliably, page with _sortBy=%2Bid: id is assigned at insert time and increases with insertion order, so new rows only ever append. Use +created_at for display order.
Example Requests
Section titled “Example Requests”List the change history of a single layer feature:
curl -X GET "https://api.sitemark.com/propertyChanges?layer_feature_id=123&_pageSize=100" \ -H "Authorization: ApiKey your-api-key-here"List the change history of several layer features (with brackets):
curl -g -X GET "https://api.sitemark.com/propertyChanges?layer_feature_id[]=123&layer_feature_id[]=456" \ -H "Authorization: ApiKey your-api-key-here"Narrow to a single property:
curl -X GET "https://api.sitemark.com/propertyChanges?layer_feature_id=123&property_key=6f2f0e1a-2c4a-4f3e-9b62-0d1d0d0f9a11" \ -H "Authorization: ApiKey your-api-key-here"Newest first, second page:
curl -X GET "https://api.sitemark.com/propertyChanges?layer_feature_id=123&_sortBy=-created_at&_page=2&_pageSize=100" \ -H "Authorization: ApiKey your-api-key-here"Example Response
Section titled “Example Response”{ "data": [ { "id": "0193c0b6-1f3b-7a0e-9f2e-9c9b7b7a1c21", "layer_feature_id": 123, "property_key": "6f2f0e1a-2c4a-4f3e-9b62-0d1d0d0f9a11", "new_value": "IN_PROGRESS", "system": false, "user_id": 42, "caused_by_change_id": null, "created_at": "2026-01-15T09:12:44.000Z" }, { "id": "0193c0b6-1f3b-7a0e-9f2e-9c9b7b7a1c22", "layer_feature_id": 123, "property_key": "b1c2d3e4-5f60-4711-8899-aabbccddeeff", "new_value": null, "system": true, "user_id": null, "caused_by_change_id": "0193c0b6-1f3b-7a0e-9f2e-9c9b7b7a1c21", "created_at": "2026-01-15T09:12:44.000Z" } ], "pagination": { "rowCount": 2, "pageCount": 1, "page": 1, "pageSize": 10 }}Rate Limits
Section titled “Rate Limits”This endpoint has its own limits, on top of the company limits. They are counted separately from the limits of GET /layerFeatures:
- 10 requests per 10 seconds
- 50 requests per minute
- 5 concurrent requests
Error Responses
Section titled “Error Responses”| Status Code | Description |
|---|---|
| 401 | Unauthorized - Invalid or missing API key |
| 400 | Bad Request - Missing layer_feature_id, more than 200 ids, an unsortable _sortBy field, or an unsupported parameter |
| 429 | Too Many Requests - A rate limit was exceeded |
| 500 | Internal Server Error |
Note: Changes on layer features you do not have access to are left out of the response; they do not cause an error. Naming such a layer feature in layer_feature_id simply yields no rows for it.
Update Ticket Fields
Section titled “Update Ticket Fields”Changes one or more fields on one or more tickets, the same way as an edit in the web app: notifications go out and automations run. Changes to fields are recorded in the ticket history, which you can read back with List property changes.
Endpoint: POST /propertyChanges
Authentication: Required (API key).
{ "changes": [ { "id": "uuid", "layer_feature_id": "integer", "property_key": "string", "new_value": "any | null" } ]}| Field | Type | Required | Description |
|---|---|---|---|
id |
uuid | Yes | A UUID you generate for this change (UUIDv7 recommended). Sitemark uses it to recognise a resent change; see Retries. Use a new id for every new change. |
layer_feature_id |
integer | Yes | The ticket’s id from List layer features, not the ticket number. |
property_key |
string | Yes | A built-in field (see Fields and values) or a custom property definition UUID. |
new_value |
any | Yes | The new value itself, not wrapped in { "value": … }. null clears the field, where the field allows it. |
Example
Section titled “Example”Resolve a ticket and change its assignee:
curl -X POST "https://api.sitemark.com/propertyChanges" \ -H "Authorization: ApiKey <your-api-key>" \ -H "Content-Type: application/json" \ -d '{ "changes": [ { "id": "0193f1a2-7c10-7a51-9a3e-1b2c3d4e5f60", "layer_feature_id": 482107, "property_key": "STATUS", "new_value": "RESOLVED" }, { "id": "0193f1a2-7c10-7a51-9a3e-1b2c3d4e5f61", "layer_feature_id": 482107, "property_key": "ASSIGNEE", "new_value": 3315 } ] }'Response
Section titled “Response”204 No Content. Read the result back with List property changes or
List layer features.
Error Responses
Section titled “Error Responses”| Status Code | Description |
|---|---|
| 400 | Invalid body or value (see Fields and values and Rules), a layer feature that isn’t a ticket on a site’s ticket form, or a change to a ticket linked to Procore or on a Procore-linked form |
| 401 | Missing, invalid, revoked or expired API key |
| 403 | The key may not make this change |
| 404 | A ticket does not exist, is not visible to the key, or is outside the key’s company or sites |
| 429 | Too Many Requests - A rate limit was exceeded |
| 500 | Internal Server Error |
Errors return a JSON body with a message. Other fields can be present. A body that doesn’t match the
shape above (for example more than 100 changes, or the same field twice) returns only Bad Request.
When field values are refused, issues lists each problem; path points at the change in your
request, and code is one of FIELD_NOT_WRITABLE, UNSUPPORTED_TYPE, INVALID_VALUE,
UNREADABLE_USER or REQUIRED (see
Create ticket):
{ "message": "Some fields cannot be written.", "public": true, "issues": [ { "path": ["changes", 1, "new_value"], "code": "INVALID_VALUE", "message": "PRIORITY must be one of LOW, MEDIUM, HIGH." } ]}Fields and Values
Section titled “Fields and Values”Built-in fields (not listed by List property definitions):
| Field | Values |
|---|---|
STATUS |
TO_DO, PLANNED, IN_PROGRESS, RESOLVED, WONT_DO, CLOSED |
PRIORITY |
LOW, MEDIUM, HIGH |
ASSIGNEE |
The id of a user or group of the ticket’s company, from List users |
TICKET_DUE_DATE |
A plain date YYYY-MM-DD, for example "2026-11-05" |
TICKET_PLANNED_DATE |
One ISO 8601 interval "<start>/<end>" of two timestamps with offset, start before end, for example "2026-11-03T08:00:00+01:00/2026-11-03T12:00:00+01:00" |
MODULE_SERIAL_NUMBER |
A string of at most 2000 characters |
TAGS |
A list of 1 to 100 distinct, non-empty strings of at most 2000 characters. [] is refused; null removes all tags. A list containing internal returns 400 for keys whose company uses internal tickets and that can’t see them. |
DESCRIPTION |
A string of at most 2000 characters; it updates the ticket’s description (see Rules) |
Custom fields, by their type in List property definitions.
Fields of other types, and fields Sitemark sets itself, are refused with 400.
Custom field type |
Value to send |
|---|---|
STRING |
A string of at most 2000 characters |
URL |
A string of at most 2000 characters |
BOOLEAN |
A yes/no field: "YES" or "NO" (strings, not true or false) |
FLOAT |
A number |
DATE |
A plain date "YYYY-MM-DD". If the field’s config.includeTime is true, an ISO 8601 date and time with offset, for example "2026-11-05T08:00:00+01:00". |
OPTION |
The value of one of the field’s config.options. This is a UUID, not the label. |
MULTI_OPTION |
An array of distinct option values |
USER |
One user or group id of the ticket’s company, from List users |
User fields and ASSIGNEE only accept a user or group of the ticket’s company that the key can see in
List users. Fields shown only under conditions are accepted whether
or not the conditions hold, and the value is kept.
- Size: at most 100 changes per request, and each field of a ticket at most once.
- All or nothing: if one change is refused, none of the changes is applied. Fix the request and send it again.
- Only changes: a value equal to the current one is ignored and creates no history, even when the
key has no right to change that field or ticket, or the ticket is linked to Procore. The current
value is the field’s
valuein List layer features, or itsdefaultValuewhenvalueis missing ornull. A ticket without a status counts as To Do. Sendingnullfor a field the ticket holds without avalueis ignored too. You can send a ticket’s full state as List layer features returns it. - Time of the change: for changes sent with this endpoint,
created_atin List property changes is the time Sitemark received the request. Acreated_atyou send is ignored. - Required fields: a field can be required from a status onwards (see
List property definitions). Moving a ticket to a status where a
required field is empty is refused, as in the web app, and so is clearing a field that is required
at the ticket’s status. A field shown only under conditions is required only when its conditions
hold after the changes. Each empty field is a
REQUIREDissue; itspathpoints at the change of that field, or at the ticket’s first change that is applied. - Assignee: changing
ASSIGNEEnotifies the new assignee (or the group’s members). - Description:
DESCRIPTIONupdates the ticket’sdescription. Description changes are not recorded in the history, and replaying an old change can overwrite a newer description. - Concurrent changes: if your system and a Sitemark user change the same field, the latest change wins.
Retries
Section titled “Retries”See Retries. A resent request is checked again, and an old change can overwrite a value that was changed since, so only resend a request to retry one that failed or timed out.