Skip to content

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)

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.

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"
}
}
  • 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 null when the property was cleared.
  • system: true when the change was made by Sitemark itself, for example by an automation, rather than by a person. A system change never has a user_id.
  • user_id: Integer ID of the user who made the change. Resolve it to a name via List Users. It is null for system changes (system: true), and null with system: false when 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. null for 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. rowCount counts 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.

List the change history of a single layer feature:

Terminal window
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):

Terminal window
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:

Terminal window
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:

Terminal window
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"
{
"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
}
}

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
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.

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.

Resolve a ticket and change its assignee:

Terminal window
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
}
]
}'

204 No Content. Read the result back with List property changes or List layer features.

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."
}
]
}

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 value in List layer features, or its defaultValue when value is missing or null. A ticket without a status counts as To Do. Sending null for a field the ticket holds without a value is 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_at in List property changes is the time Sitemark received the request. A created_at you 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 REQUIRED issue; its path points at the change of that field, or at the ticket’s first change that is applied.
  • Assignee: changing ASSIGNEE notifies the new assignee (or the group’s members).
  • Description: DESCRIPTION updates the ticket’s description. 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.

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.