> For the complete documentation index, see [llms.txt](https://help.security.gallagher.com/knowledge-hub/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.security.gallagher.com/knowledge-hub/developer/rest-api/cardholder-change-tracking.md).

# Cardholder change tracking

This feature keeps you up to date with cardholders by telling you the changes that happened since the last time you called. It works for creations, modifications, and deletions coming from all sources, including Gallagher's clients, EDI, Active Directory, remote servers, and the other cardholder APIs.

It first appeared in version 8.30.

Because it also reports changes made through the REST API, it will tell you about your own changes. If you are using this API both for managing cardholders and as a source of truth, you should be careful not to get yourself into a loop.

This API was built to synchronise a second system with Command Centre's cardholder directory, not for auditing or reporting. You will find the events API quicker and more flexible for those tasks.

### Recommended use

1. First, get a bookmark at the end of Command Centre's list of changes as follows:

   1a. `GET /api`

   1b. Follow the link at `features.cardholders.changes` ↪.

   1c. Take note of the href in the `next` block. This is your bookmark. Later on you will use it to ask for all the changes that happen after this point.
2. If you are filling a user directory from Command Centre, download all cardholders using this advice. Here is a reminder:

   2a. Using the results of `GET /api` again, follow the link at `features.cardholders.cardholders` ↪, adding query parameters `top=1000` and `sort=id`.

   2b. Process those cardholders (probably 1000) and follow the `next.href` link in a loop until you have extracted all of them.

Now you are up to date, or very close to it, so you can drop into a loop handling deltas as they occur. The first time through it you will catch changes that came in during the big download you did in step two. Recall the href you got in step one above...

1. ...and GET it.
2. If the `results` array you get back is empty you are up to date and you have nothing to do. Sleep for a time before your next poll, to avoid a tight loop and ease the load on your database. When it comes time to poll again, do not be tempted to resubmit the same request: the URL to use sometimes changes even when there are no results, so always get it from `next.href` in the results of your previous GET, even if its `results` array was empty.
3. If the `results` array is not empty it will contain the first changes that happened after your previous call. The sections below will help you process them.
4. Using the link in `next.href`, go to step one...

### Use case: detecting when cardholders are created.

The first thing you need to do is get a link from the changes API call *before* the cardholders you are interested in are created in Command Centre. If you didn't do that, there is a workaround described in the cardholder use cases, but it is not suitable for production.

When you call this API the second time you will receive all changes made to cardholders since the previous call. To find the new cardholders, look at the results that do not have `type=add`.

Since you are only interested in new data, you can optimise the call a little with `fields=newValues` which will remove the fields you do not need.

### Notes

* The API will not notify changes to any fields that you cannot see in the cardholders API, such as user codes, passwords, card PINs, car park assignments, biometric credentials, and (in versions before 8.50) operator settings.
* There is a known issue in 8.50 preventing operator group membership changes coming out of this API. It was fixed in 8.60. Changes to the other five operator settings work correctly in 8.50.
* The API returns changes to configuration, not changes in status. For example, when the time passes into or out of the activity period of a card or competency, the status of that card or competency changes but that does not prompt anything to come out of this API. To monitor for that, watch the configuration fields that define the card's or competency's validity period.

  Similarly, a cardholder moving between access zones changes two fields beginning with `lastSuccessfulAccess` but does not count as a cardholder change.
* The API will return the pre-change values of some fields, but not those that require a lot of storage such as image PDFs and PIV certificates. Command Centre does not hang on to them after they change.

## Get changes

> This returns cardholder changes matching your search criteria and a link for the next batch.\
> \
> The first time you call this it will return a link back to this call with a parameter\
> marking the head of the change list.  \*\*\*There will not be any changes in the result set for\
> your first call\*\*\*.  When you later GET the link the server sent you it will return the\
> changes that occurred since, if there were any, and a new link.\
> \
> There will be no more than 1000 changes, or as many as you asked for using the \`top\` query\
> parameter.\
> \
> When you are up to date with all the cardholder changes, the \`results\` array will be empty.\
> When it comes time to check again, do not just re-use the same URL:  get a new one from the\
> \`next\` block.  It can change even when there are no results.\
> \
> You can monitor changes to card data using this call, including serial numbers (MIFARE\
> UIDs).  However you cannot monitor changes in the \`lastSuccessfulAccessTime\` or\
> \`lastSuccessfulAccessZone\` fields because they are not attributes of a cardholder object:\
> they are derivatives of his or her activity.  If you want to monitor a person's movements we\
> advise subscribing to events.\
> \
> This is a polled interface:  it will return immediately, whether or not there are results.\
> Therefore you should wait for a time between calls if the \`results\` array was empty.\
> \
> \[Do not code this URL]\(forward-compatibility-hateoas) into your application.  Take\
> it from the href field in the \`features.cardholders.changes\` section of \`/api\`.\
> \
> \## Efficiency tips when collecting cardholder changes\
> \
> \- Filter for the kinds of changes you are after using the \`filter\` parameter.  If you are\
> &#x20; only interested in people's access group memberships, for example, add\
> &#x20; \`filter=accessGroups\` to your GET, and you will not be troubled with all the other kinds\
> &#x20; of changes that cardholders go through.\
> \
> \- The default set of fields is large and expensive to compute.  Ease the load on the server,\
> &#x20; the network, and your client by asking for a smaller\
> &#x20; response.  For example if you are only interested in the current state of a cardholder's\
> &#x20; group memberships and do not care who made the change, or when, or what state the\
> &#x20; cardholder was in before, use \`fields=item,cardholder.accessGroups\`.  You need \`item\` so\
> &#x20; you can tell which cardholder changed.  If you store your own identifier in a PDF you\
> &#x20; could drop \`item\` and add \`cardholder.pdf\_XXX\`, where 'XXX' is the ID of your PDF.\
> \
> \- Sleep for as long as you can between calls.\
> \
> \- If you are only interested in changes to cardholders in certain divisions, only give your\
> &#x20; REST operator access to those divisions.  It will not see changes outside them.<br>

```json
{"openapi":"3.2.0","info":{"title":"Command Centre REST API","version":"2.0"},"tags":[{"name":"Cardholder change tracking","description":"This feature keeps you up to date with cardholders by telling you the changes that happened\nsince the last time you called.  It works for creations, modifications, and deletions coming\nfrom all\nsources, including Gallagher's clients, EDI, Active Directory, remote servers, and the other\ncardholder APIs.\n\nIt first appeared in version 8.30.\n\nBecause it also reports changes made through the REST API, it will tell you about your own\nchanges.  If you are using this API both for managing cardholders and as a source of truth, you\nshould be careful not to get yourself into a loop.\n\nThis API was built to synchronise a second system with Command Centre's cardholder directory,\nnot for auditing or reporting.  You will find the [events API](events) quicker and more\nflexible for those tasks.\n\n## Recommended use\n\n1. First, get a bookmark at the end of Command Centre's list of changes as follows:\n  \n    1a. `GET /api`\n\n    1b. Follow the link at `features.cardholders.changes`\n    [↪](cardholders#get-api-cardholders-changes).\n\n    1c. Take note of the href in the `next` block.  This is your bookmark.  Later on you will\n    use it to ask for all the changes that happen after this point.\n\n1. If you are filling a user directory from Command Centre, download all cardholders using [this\n   advice](cardholders#downloading-all-cardholders).  Here is a reminder:\n\n    2a. Using the results of `GET /api` again, follow the link at\n      `features.cardholders.cardholders` [↪](cardholders#get-api-cardholders), adding query\n      parameters `top=1000` and `sort=id`.\n\n    2b. Process those cardholders (probably 1000) and follow the `next.href` link in\n      a loop until you have extracted all of them.\n\nNow you are up to date, or very close to it, so you can drop into a loop handling deltas as they\noccur.  The first time through it you will catch changes that came in during the big download\nyou did in step two.  Recall the href you got in step one above...\n\n1. ...and GET it.\n\n1. If the `results` array you get back is empty you are up to date and you have nothing to do.\n   Sleep for a time before your next poll, to avoid a tight loop and ease the load on your\n   database.  When it comes time to poll again, do not be tempted to resubmit the same request:\n   the URL to use sometimes changes even when there are no results, so always get it from\n   `next.href` in the results of your previous GET, even if its `results` array was empty.\n\n1. If the `results` array is not empty it will contain the first changes that happened after\nyour previous call.  The sections below will help you process them.\n\n1. Using the link in `next.href`, go to step one...\n\n## Use case:  detecting when cardholders are created.\n\nThe first thing you need to do is get a link from the changes API call _before_ the cardholders\nyou are interested in are created in Command Centre.  If you didn't do that, there is a\nworkaround described in the cardholder use cases, but it is not suitable for production.\n\nWhen you call this API the second time you will receive all changes made to cardholders since\nthe previous call.  To find the new cardholders, look at the results that do not have\n`type=add`.\n\nSince you are only interested in new data, you can optimise the call a little with\n`fields=newValues` which will remove the fields you do not need.\n\n## Notes\n\n- The API will not notify changes to any fields that you cannot see in the cardholders API, such\n  as user codes, passwords, card PINs, car park assignments, biometric credentials, and (in\n  versions before 8.50) operator settings.\n\n- There is a known issue in 8.50 preventing operator group membership changes coming out of this\n  API.  It was fixed in 8.60.  Changes to the other five operator settings work correctly in\n  8.50.\n\n- The API returns changes to configuration, not changes in status.  For example, when\n  the time passes into or out of the activity period of a card or competency, the status of\n  that card or competency changes but that does not prompt anything to come out of this\n  API.  To monitor for that, watch the configuration fields that define the card's or\n  competency's validity period.\n\n  Similarly, a cardholder moving between access zones changes two fields beginning with\n  `lastSuccessfulAccess` but does not count as a cardholder change.\n\n- The API will return the pre-change values of some fields, but not those that require a lot of\n  storage such as image PDFs and PIV certificates.  Command Centre does not hang on to them\n  after they change.\n"}],"servers":[{"url":"https://127.0.0.1:8904"}],"security":[{"API_key":[]},{"basic":[]}],"components":{"securitySchemes":{"API_key":{"type":"apiKey","name":"Authorization","in":"header","description":"\nClients authenticate by including a pre-shared API key in the `Authorization` header of each\nrequest after an authorisation method of `GGL-API-KEY`:\n\n`Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB`\n\nFor a fuller description see [API authentication](api-authentication).\n"},"basic":{"type":"http","scheme":"basic","description":"This is HTTP Basic authentication using a blank username and the API key as the password.\n\nExample:  `Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==`\n\nThat example is equivalent to the example in the description of the api_Key method:\nprepending a colon to the API key beginning with `C774` then encoding it into Base64\nproduces the 56 characters beginning with `OkM3`.\n\nThe colon is a separator between a username, which is blank in our case, and the password,\nwhich is our API key.\n\nFor a fuller description see [API authentication](api-authentication)\n"}},"parameters":{"top":{"name":"top","in":"query","required":false,"schema":{"type":"integer","minimum":1},"description":"Limits the results to no more than this many items per page.\n\nOlder versions of Command Centre returned 100 items per page in the absence of this parameter.  That is acceptable for GUI\napplications that will only display the first page, but for integrations that intend to\nproceed through the entire database it causes a lot of chatter.\n\n8.70 and later versions will default to 1000 items per request.  This is about where a graph\nof throughput versus page size begins to level out.\n"},"pos":{"name":"pos","in":"query","required":false,"schema":{"type":"integer"},"description":"Reserved for internal use.  You may see it in URLs you receive from the server, but you must never add it yourself."}},"schemas":{"CardholderChange":{"title":"Cardholder change","description":"`/api/cardholders/changes` returns an array of these.  Each contains a description of a change\nmade to a cardholder.\n\nIn this example a cardholder has had his name changed from Craig to Gavin, has had an\nenablement date set on a competency, and has had a card and access group membership added.\n\n## Notes\n\n- Changes to an access group membership will have a different href in the `oldValues` and\n  `newValues` blocks, because modifying a group membership changes its ID.\n\n- The API will not notify changes to a competency's `credit` integer or `limitedCredit`\n  boolean.  These fields are part of a separate Command Centre feature and are not supported\n  by the changes API.\n\n- The API will report a spurious change to a PDF value when a cardholder rejoins an access\n  group he or she was previously a member of, provided that access group carried a PDF with no\n  default and the cardholder did not have a value for it.\n\n- This API will not notify changes to `lastSuccessfulAccessTime` or\n  `lastSuccessfulAccessZone`.\n","properties":{"href":{"type":"string","format":"uri-reference","description":"Command Centre's identifier for this change.  This has no use in the API:  you cannot use\nit as a URL, but you may like to use it to track the changes you have seen.\n"},"time":{"type":"string","format":"date-time","description":"The time that this change occurred."},"type":{"description":"`add` if this change added a cardholder, `update` if it modified a cardholder, or `remove`\nif it deleted a cardholder.\n","type":"string","enum":["add","update","remove"]},"item":{"description":"A block containing the href of the changed cardholder.  You can GET this URL to find the\ncardholder's current state, or you could add the cardholder block to the change using\n`fields=cardholder` in the query.  Note that you can add individual cardholder fields such\nas PDFs using `fields=defaults,cardholder.personalDataFields`.\n","type":"object","properties":{"href":{"type":"string","format":"uri-reference"}}},"operator":{"description":"A block containing the href and current name of the operator who made this change.\n\nBecause building this block requires more work from the server it is not in the default\nfield set.  If you need it you must ask for it using the fields parameter:\n`fields=defaults,operator`.\n","type":"object","properties":{"name":{"type":"string","readOnly":true},"href":{"type":"string","format":"uri-reference","description":"The API's identifier for an item."}}},"oldValues":{"type":"object","description":"A block containing the values of the changed fields before the change, if Command Centre\nstill has them, in the same format as a [cardholder\ndetail](models#cardholderdetail).  If a value is blank, it means that the value was\nnull before the change or the server no longer has it.\n\nBecause this requires extra effort from the server, you may like to omit this block using\nsomething like `fields=time,type,item,operator` unless you are particularly interested in\nhistorical data.\n"},"newValues":{"type":"object","description":"A similar block containing the values of the changed fields after the change, if the\nserver has them.\n\nGenerally, this block is less useful than the current state of the cardholder, described\nnext.  It also requires extra effort from the server, so you may like to omit this block\nusing `fields`.\n"},"cardholder":{"description":"A block containing the current fields on the cardholder, provided the cardholder has not\nbeen deleted.  This is in the same format as a [cardholder\ndetail](models#cardholderdetail).\n\nBecause building this requires more work from the server it is not in the default result\nset.  If you need it you must ask for it using the `fields` parameter.  While the server\nhas a default field set for cardholders, your query will be more efficient if you ask for\njust the fields you need:\n`fields=defaults,cardholder.firstName,cardholder.lastName,cardholder.cards`, etc.  Note\nhow you must prefix each field with `cardholder` since they are all inside a block with\nthat name.\n","type":"object"}}}}},"paths":{"/api/cardholders/changes":{"get":{"tags":["Cardholder change tracking"],"summary":"Get changes","description":"This returns cardholder changes matching your search criteria and a link for the next batch.\n\nThe first time you call this it will return a link back to this call with a parameter\nmarking the head of the change list.  ***There will not be any changes in the result set for\nyour first call***.  When you later GET the link the server sent you it will return the\nchanges that occurred since, if there were any, and a new link.\n\nThere will be no more than 1000 changes, or as many as you asked for using the `top` query\nparameter.\n\nWhen you are up to date with all the cardholder changes, the `results` array will be empty.\nWhen it comes time to check again, do not just re-use the same URL:  get a new one from the\n`next` block.  It can change even when there are no results.\n\nYou can monitor changes to card data using this call, including serial numbers (MIFARE\nUIDs).  However you cannot monitor changes in the `lastSuccessfulAccessTime` or\n`lastSuccessfulAccessZone` fields because they are not attributes of a cardholder object:\nthey are derivatives of his or her activity.  If you want to monitor a person's movements we\nadvise subscribing to events.\n\nThis is a polled interface:  it will return immediately, whether or not there are results.\nTherefore you should wait for a time between calls if the `results` array was empty.\n\n[Do not code this URL](forward-compatibility-hateoas) into your application.  Take\nit from the href field in the `features.cardholders.changes` section of `/api`.\n\n## Efficiency tips when collecting cardholder changes\n\n- Filter for the kinds of changes you are after using the `filter` parameter.  If you are\n  only interested in people's access group memberships, for example, add\n  `filter=accessGroups` to your GET, and you will not be troubled with all the other kinds\n  of changes that cardholders go through.\n\n- The default set of fields is large and expensive to compute.  Ease the load on the server,\n  the network, and your client by asking for a smaller\n  response.  For example if you are only interested in the current state of a cardholder's\n  group memberships and do not care who made the change, or when, or what state the\n  cardholder was in before, use `fields=item,cardholder.accessGroups`.  You need `item` so\n  you can tell which cardholder changed.  If you store your own identifier in a PDF you\n  could drop `item` and add `cardholder.pdf_XXX`, where 'XXX' is the ID of your PDF.\n\n- Sleep for as long as you can between calls.\n\n- If you are only interested in changes to cardholders in certain divisions, only give your\n  REST operator access to those divisions.  It will not see changes outside them.\n","parameters":[{"$ref":"#/components/parameters/top"},{"$ref":"#/components/parameters/pos","description":"Reserved for internal use.  You may see it in URLs you receive from the server, but you must never add it yourself."},{"name":"filter","in":"query","required":false,"schema":{"type":"array","default":["defaults"],"items":{"type":"string","enum":["href","id","firstName","lastName","shortName","description","authorised","lastSuccessfulAccessZone","division","notes","personalDataFields","operatorLoginEnabled","operatorUsername","operatorPassword","operatorPasswordExpired","windowsLoginEnabled","windowsUsername","cards","accessGroups","competencies","notifications","relationships","lockers","defaults"]}},"description":"Limits the search results to the changes that affected these fields, and limits the\n`oldValues` and `newValues` blocks to these fields.  You can specify practically any of\nthe fields in the [cardholder detail](models#cardholderdetail).  You can also go\ninto more detail; for example you can monitor a cardholder's cards' validity dates using\n`filter=cards.from,cards.until`.\n\n`filter` reduces the number of results; if you want to choose the blocks you receive in\neach result, use `fields`.\n\nIf you do not supply a filter parameter it will use the same fields you get in a\ncardholder details page.  That is nearly everything the API has for a cardholder, but\nomits some seldom-used or expensive features such as card tracing and the large PIV\nfields.  If you want to monitor them you must list them here.  For example,\n`filter=defaults,cards.trace` will add the card trace flag to the usual filter.\n\n`personalDataFields` will filter for PDF changes, and will give you the\n`personalDataDefinitions` block plus the PDF values that changed.  You cannot filter for\nchanges to a particular PDF:  you will need to do that in your client.\n\nBeing able to monitor operator settings arrived in 8.50 and operator group memberships in\n8.60.\n\nNote that you cannot monitor changes in the `lastSuccessfulAccessTime` or\n`lastSuccessfulAccessZone` fields because they are not attributes of a cardholder object:\nthey are derivatives of his or her activity.  If you want to monitor a person's movements\nwe advise subscribing to events.\n\nIf card changes are all you are interested in, and you do not want to hear about all the\nother changes that cardholders can experience, use `filter=cards`.\n"},{"name":"fields","in":"query","required":false,"schema":{"type":"array","default":["defaults"],"items":{"type":"string","enum":["href","operator","operator.href","operator.name","time","type","item","oldValues","newValues","cardholder","cardholder.*","defaults"]}},"description":"Limits the blocks in the results.\n\n`fields` affects the blocks in each result; if you want to reduce the number of results,\nuse `filter`.\n\nYou can have finer-grained control of fields inside those blocks by listing their JSON\npaths.  For example, to see what the operator and affected cardholder's names are now, you\ncould use `fields=operator.name,cardholder.firstName,cardholder.lastName`.\n\nThe values you can list for the cardholder block are very similar to [these field\nnames](cardholders#field-names-in-query-parameters).  Prefix each with `cardholder.` (since in this\nAPI they are all inside a block called `cardholder`).\n\nSeparate values with commas and treat the strings as case sensitive.\n\nIf you do not send this parameter the API will return all cardholder changes to a default\nset of fields.\n\nUse `personalDataFields` for changes to PDF values.\n"},{"name":"deadline","in":"query","required":false,"schema":{"default":50,"type":"integer","minimum":0},"description":"Sets the number of seconds after which the server will abort the query and return a 500.\nIf that happens, you should try again later when the server (particularly the database\nserver) is not so busy.\n\nUsing this can be dangerous.  When the server aborts a query it discards all the work it\ndid up to that point.  If you send the same query again later, with the same deadline, you\nmay end up stuck in a loop, never making progress, and effectively DoSing your server.\n\nIf your server is struggling, try reducing the size of each result set using `top`.\n\nAdded in 8.80.\n"}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"description":"An array of cardholder changes, and a `next` link for more.\n","properties":{"results":{"type":"array","description":"An array of cardholder changes.","items":{"$ref":"#/components/schemas/CardholderChange"}},"next":{"type":"object","properties":{"href":{"type":"string","format":"uri-reference","description":"The link to the next page of changes.  This will always be present, because (unlike items)\nchanges never run out.\n\nBecause the `next.href` link is a pointer to the head of a queue of changes, and new changes\nare being added to that queue which will not suit your filter or privileges, it will\nchange even when there are no results.\n\nTherefore you should always use this link for your next query.  Do not be tempted to\nre-use a URL after `results` comes back empty, thinking you merely need to ask the same\nquestion again.  Doing that will cause the server unnecessary work, skipping over changes\nthat did not pass your filter or privilege checks on the previous call.\n"}}}}}}}},"403":{"description":"The site does not have the RESTCardholders licence.\n"},"500":{"description":"The server was too busy to complete the request before the deadline.\n"}}}}}}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://help.security.gallagher.com/knowledge-hub/developer/rest-api/cardholder-change-tracking.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
